NcmModel tutorial

Curve fitting tutorial

Author

NumCosmo developers

Overview

NumCosmo: Defining Models in an Object-Oriented Framework

One of the key objectives of NumCosmo is to provide a comprehensive framework for model fitting. In this tutorial, we will explore the process of defining models within this framework. When modeling a phenomenon, the first step is to identify the key quantities associated with the model and determine how to describe them. In the NumCosmo framework, these aspects are expressed using an Object-Oriented (OO) approach.

The framework encourages the creation of an abstract model that defines only the fundamental quantities. The specific details of these quantities are left to the implementations of the abstract model. Although this separation might initially seem complex, it offers several long-term advantages, including reduced code complexity. Key benefits include:

  • Abstract Interface Consistency: Functions and objects that rely on the abstract model can be developed using a standard interface, without concern for specific implementations.
  • Clear Model Definition: The approach compels programmers and scientists to clearly distinguish between fundamental quantities of the model and the choices made during implementation.
  • Modularity: The framework naturally supports modularity, allowing specific implementations to be modified or replaced without affecting the broader codebase.
  • Facilitated Collaboration: Once the abstract model is defined, different groups can independently develop their implementations, simplifying collaborative efforts.

In this tutorial, we will guide you through the process of writing an abstract model in C. While this tutorial focuses on C, writing models in Python is also possible and will be covered in a separate tutorial.

Our abstract model

In this tutorial, we consider the case where we are modeling a phenomenon described by a curve \(f(x)\). In this simple example, it is clear that the only fundamental quantities are the curve \(f(x)\) itself and its domain \([x_l,\; x_u]\). Note that we are not including any specific functional form for \(f(x)\) or any parametrization; these choices are left to the specific implementations of our model.

If you are not familiar with the GObject framework, take a look at this tutorial.

To translate this to an OO framework, we will define an abstract object NcCurve (subclass of NcmModel to represent the fundamental quantities. The function \(f(x)\) is represented by the virtual method called nc_curve_f, and the interval by two properties called xl and xu, which represent \(x_l\) and \(x_u\), respectively.

Abstract model header

We include the full header below; however, first, we will highlight the main steps:

Virtual method type

This line determines the prototype of our virtual function. While this step is not strictly necessary – since one could use this prototype directly in the class structure – defining a type improves readability.

typedef gdouble (*NcCurveF) (NcCurve *curve, const gdouble x);

Object class

In the GObject framework, virtual functions are defined as function pointers in the class structure. In this simple example, there is a single virtual function, which we call f using the type defined above. The class structure is defined as follows:

struct _NcCurveClass
{
  /*< private >*/
  NcmModelClass parent_class;
  NcCurveF f;
}; 

Implementation enumerator

The NcmModel framework is designed to support complex models that may involve several virtual methods. Some of these methods may not be required by every implementation. For this reason in introduced the implementation enumerator, this way, the object that use the model may ask if that model implements a specific virtual method. In our example we have a single virtual method, so the enumerator is given by:

typedef enum _NcCurveImpl
{
  NC_CURVE_IMPL_f = 0,
} NcCurveImpl; 

The enumerator above states that the first bit in the implementation enumerator controls if the child implements this method.

Defining the class id prototype

In order to be able to analyze different models, we need an object to aggregate all necessary models. In NumCosmo this task is performed by the NcmMSet (model set) object. We use the NCM_MSET_MODEL_DECLARE_ID macro to declare the id function nc_curve_id in the NcmMSet framework.

NCM_MSET_MODEL_DECLARE_ID (nc_curve);

Method prototypes

Finally we declare the two methods necessary to close the implementation (apart from the default ones).

void nc_curve_set_f_impl (NcCurveClass *curve_class, NcCurveF f);
gdouble nc_curve_f (NcCurve *curve, const gdouble x);

The first one just declare the class method that will be used to set the virtual method f. It is necessary because it has two jobs, it sets the function pointer to the member f of the class structure and updates the implementation flags to assert that child implements this method.

Abstract model source

We include the full C file below, however, first we are going to highlight the main steps:

Private properties

The first declaration in our code is the definition of the private struct

struct _NcCurvePrivate
{
  gdouble xl;
  gdouble xu;
};

In principle we could have added these members directly to the NcCurve struct, however, this would allow users to modify it directly. By hiding it in the compilation unit we avoid this, the only way to modify these values will be through the exported methods. This is important since these methods will perform consistency checks and call all necessary hooks that update the model.

As a rule of thumb, if the access to the struct member is not part of computationally heavy part of the code (here these members are likely to be set just once in the beginning and stay constant throughout all computations), then it just me hidden in the private struct for the reasons described above. Nevertheless, if the member will be accessed constantly during the computations we would attribute it to the object struct and include a inline method to access it, this technique will be discussed in detail in a future tutorial.

Property enumerator

The property enumerator is the part of the GObject framework used to define properties. Here the PROP_0 member is required by the GObject implementation and do not correspond to any actual property. The following members PROP_XL and PROP_XU correspond respectively to \(x_l\) and \(x_u\). Finally, PROP_SIZE* is a convenience member (that we use by convention in NumCosmo) used to count the number of properties, in this case PROP_SIZE == 3 (an enumerator always assign 0 to its first member and +1 to the subsequent, this can be changes by explicitly assigning values).

enum
{
  PROP_0,
  PROP_XL,
  PROP_XU,
  PROP_SIZE,
};

Object defining macro

The first step in creating the object is to call the GObject macro G_DEFINE_ABSTRACT_TYPE. This macro creates an abstract object (that cannot be instantiated) that is a child of NcmModel.

G_DEFINE_ABSTRACT_TYPE (NcCurve, nc_curve, NCM_TYPE_MODEL);

This macro assumes that two functions will be implemented: - nc_curve_init: this function should make all necessary initialization to the object that happens before any property is set. This usually consists of assign null or zero value for all struct members. - nc_curve_class_init: the class method must define all object’s properties and virtual methods.

Object initialization

In this example, the initialization function nc_curve_init has two tasks, getting the pointer to its private struct and assign the initial values for the properties. Our convention is that if both xl and xu are equal to zero then the object is in the initial state.

static void
nc_curve_init (NcCurve *curve)
{
  curve->priv = G_TYPE_INSTANCE_GET_PRIVATE (curve, NC_TYPE_CURVE, NcCurvePrivate);
  curve->priv->xl = 0.0;
  curve->priv->xu = 0.0;
}

Set/Get hooks

In the GObject framework all properties are modified through the set/get hooks. Here we have a two properties, for both we delegate the set/get job to methods implemented below. We could implement it directly inside the enumerators below, however, implementing it in separated functions improve readability.

static void
_nc_curve_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
  NcCurve *curve = NC_CURVE (object);
  g_return_if_fail (NC_IS_CURVE (object));

  switch (prop_id)
  {
    case PROP_XL:
      nc_curve_set_xl (curve, g_value_get_double (value));
      break;
    case PROP_XU:
      nc_curve_set_xu (curve, g_value_get_double (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
_nc_curve_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
  NcCurve *curve = NC_CURVE (object);
  g_return_if_fail (NC_IS_CURVE (object));

  switch (prop_id)
  {
    case PROP_XL:
      g_value_set_double (value, nc_curve_get_xl (curve));
      break;
    case PROP_XU:
      g_value_set_double (value, nc_curve_get_xu (curve));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

Defining the id function

The NcmMSet framework provides the macro NCM_MSET_MODEL_REGISTER_ID that should be used in the object code to create the nc_curve_id function. This function will return the unique ID defined in the NcmMSet type list.

NCM_MSET_MODEL_REGISTER_ID (nc_curve, NC_TYPE_CURVE);

The class initialization hook

Attributing the Set/Get hooks

In the class initialization hook one must define the object related hooks, as the set/get functions. Since we are using the NcmModel framework, we need to attribute the set/get hooks to the NcmModelClass struct. This is necessary since the framework takes care in organizing all model parameters and only the additional properties are handled by the set/get hooks defined here.

  GObjectClass* object_class = G_OBJECT_CLASS (klass);
  NcmModelClass *model_class = NCM_MODEL_CLASS (klass);

  ...
  
  model_class->set_property = &_nc_curve_set_property;
  model_class->get_property = &_nc_curve_get_property;

The next step is to initialize the model parameters.

ncm_model_class_set_name_nick (model_class, "Curve-f", "NcCurve");
ncm_model_class_add_params (model_class, 0, 0, PROP_SIZE);

These functions set the model name and nick and tells the framework that it requires 0 scalar parameters and 0 vector parameters and it has additionally PROP_SIZE properties. It is usually the case that the abstract model has no parameters, this happens because the parameters are usually connected to the specific implementations and not to the abstract definitions.

Registering the ID in NcmMSet

  ncm_mset_model_register_id (model_class,
                              "NcCurve",
                              "Curve model.",
                              NULL,
                              FALSE,
                              NCM_MSET_MODEL_MAIN);

This functions call takes care in registering the NcCurve type in the NcmMSet type list. This is necessary to set/get objects of this type from a NcmMSet.

Registering the properties

As discussed above, the properties \(x_l\) and \(x_u\) will not be part of the model parameters and will be taken as fixed numbers, in this case, we include them as usual GObject properties.

  g_object_class_install_property (object_class,
                                   PROP_XL,
                                   g_param_spec_double ("xl",
                                                        NULL,
                                                        "x lower bound",
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB));
  g_object_class_install_property (object_class,
                                   PROP_XU,
                                   g_param_spec_double ("xu",
                                                        NULL,
                                                        "x upper bound",
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, 1.0,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB));

Checking parameters implementation

The function call below asserts that all required scalar and vector parameters were implemented correctly. In this case, none were required.

  ncm_model_class_check_params_info (model_class);

Setting a default implementation for the virtual function

This line sets the default implementation for the virtual function f.

  curve_class = &_nc_curve_f;

Methods

Our implementation of the virtual function f here is just a placeholder that raises an error if called.

static gdouble 
_nc_curve_f (NcCurve *curve, const gdouble x)
{
  g_error ("nc_curve_f: model `%s' does not implement this function.", 
           G_OBJECT_TYPE_NAME (curve)); 
  return 0.0;
}

We use the NCM_MODEL_SET_IMPL_FUNC to generate the code for the implementation function nc_curve_set_f_impl. This class method, when called by the child implementations will attribute the function to the class struct and enable the implementation flag for this method.

NCM_MODEL_SET_IMPL_FUNC (NC_CURVE, NcCurve, nc_curve, NcCurveF, f)

Example sources

Source

/***************************************************************************
 *            nc_curve.c
 *
 *  Tue July 11 16:30:26 2017
 *  Copyright  2017  Sandro Dias Pinto Vitenti
 *  <vitenti@uel.br>
 ****************************************************************************/
/*
 * nc_curve.c
 * Copyright (C) 2017 Sandro Dias Pinto Vitenti <vitenti@uel.br>
 *
 * numcosmo is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * numcosmo is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * SECTION:nc_curve
 * @title: NcCurve
 * @short_description: Abstract class for curves!
 *
 * NcCurve is the abstract class designed to include the functions
 * that any simple curve should implement, see NcCurveImpl.
 * Its parent_class is NcmModel.
 *
 * (This is a gtk-doc comment, which always start with two **)
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif /* HAVE_CONFIG_H */
#include "build_cfg.h"

#include "nc_curve.h"

/*
 * All implementations of NcCurve will required the function interval.
 * We could have let the interval itself as model parameters in order
 * to allow it to be fit too, this approach will be presented in a more
 * advanced example.
 *
 */
struct _NcCurvePrivate
{
  gdouble xl;
  gdouble xu;
};

/*
 * Properties enumerator, note that we added a last item PROP_SIZE,
 * this is useful to obtain the actual number of properties in the
 * object without hard coding it. PROP_0 is a special property of
 * the GObject system we should always be there.
 *
 */
enum
{
  PROP_0,
  PROP_XL,
  PROP_XU,
  PROP_SIZE,
};

/*
 * Here we define the object GType using the macro G_DEFINE_ABSTRACT_TYPE.
 * This macro basically defines the function nc_curve_get_type (void) and
 * everything necessary to define an object in the GLib type system. The
 * ABSTRACT version of the macro creates a GType that cannot be instantiated
 * this means that to use this object we *must* define a child.
 *
 * The last argument provides the GType of the parent object.
 *
 */
G_DEFINE_ABSTRACT_TYPE (NcCurve, nc_curve, NCM_TYPE_MODEL);

static void
nc_curve_init (NcCurve *curve)
{
  /*
   * The first step is the creation of the private structure, all allocation and
   * de-allocation is automatically performed by the GObject framework.
   */
  curve->priv = G_TYPE_INSTANCE_GET_PRIVATE (curve, NC_TYPE_CURVE, NcCurvePrivate);

  /*
   * Here we initialize all structure member to null/zero.
   *
   */
  curve->priv->xl = 0.0;
  curve->priv->xu = 0.0;
}

/*
 * Here we call all property accessors in order to set property values.
 *
 */
static void
_nc_curve_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
  NcCurve *curve = NC_CURVE (object);

  g_return_if_fail (NC_IS_CURVE (object));

  switch (prop_id)
  {
    case PROP_XL:
      nc_curve_set_xl (curve, g_value_get_double (value));
      break;
    case PROP_XU:
      nc_curve_set_xu (curve, g_value_get_double (value));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

/*
 * Here we call all property accessors in order to get property values.
 *
 */
static void
_nc_curve_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
  NcCurve *curve = NC_CURVE (object);

  g_return_if_fail (NC_IS_CURVE (object));

  switch (prop_id)
  {
    case PROP_XL:
      g_value_set_double (value, nc_curve_get_xl (curve));
      break;
    case PROP_XU:
      g_value_set_double (value, nc_curve_get_xu (curve));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

/*
 * Here we must de-allocate any memory allocated *inside* gobject framework,
 * i.e., we must unref any outside object contained in our object.
 *
 * Nothing to do!
 *
 */
static void
_nc_curve_dispose (GObject *object)
{
  /*
   * The following comment is always included to remark that at this point the parent
   * method must be called, chaining down the function call, i.e., first we finalize
   * the properties of the child, if any, then the parent and parent's parent, etc.
   *
   */
  /* Chain up : end */
  G_OBJECT_CLASS (nc_curve_parent_class)->dispose (object);
}

/*
 * Here we must de-allocate any memory allocated *outside* gobject framework.
 * Nothing to do!
 *
 */
static void
_nc_curve_finalize (GObject *object)
{
  /*
   * The following comment is always included to remark that at this point the parent
   * method must be called, chaining down the function call, i.e., first we finalize
   * the properties of the child, if any, then the parent and parent's parent, etc.
   *
   */
  /* Chain up : end */
  G_OBJECT_CLASS (nc_curve_parent_class)->finalize (object);
}

/*
 * Registry the ID in NumCosmo model system.
 *
 */
NCM_MSET_MODEL_REGISTER_ID (nc_curve, NC_TYPE_CURVE);

/*
 * Prototype of the default implementation.
 *
 */
static gdouble _nc_curve_f (NcCurve *curve, const gdouble x);

/*
 * At _class_init we will define all properties and parameters we should
 * also include a default implementation for our virtual function `f'.
 *
 */
static void
nc_curve_class_init (NcCurveClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  NcmModelClass *model_class = NCM_MODEL_CLASS (klass);

  /*
   * Tells the type system that we have a private struct
   *
   */
  g_type_class_add_private (klass, sizeof (NcCurvePrivate));

  /*
   * The NcmModel class takes cares of the parameters, thus,
   * the set/get functions above must be set in NcmModelClass
   * structure.
   *
   */
  model_class->set_property = &_nc_curve_set_property;
  model_class->get_property = &_nc_curve_get_property;

  /*
   * The other functions are the usual GObject hooks and
   * must be assigned to GObjectClass.
   *
   */
  object_class->dispose  = &_nc_curve_dispose;
  object_class->finalize = &_nc_curve_finalize;

  /*
   * First we set the model nick `Curve-f' and its name
   *
   */
  ncm_model_class_set_name_nick (model_class, "Curve-f", "NcCurve");

  /*
   * Now we inform that we have 0 scalar parameters and 0 vector parameters.
   * Note that here we should include parameters that *all* implementations
   * would share, here we have none. Finallt, with the last argument we assert
   * that we have PROP_SIZE == 2 additional properties.
   *
   */
  ncm_model_class_add_params (model_class, 0, 0, PROP_SIZE);

  /*
   * Next step is to register the object in NumCosmo's model set object class.
   * We don't include a long description (first NULL below). The last arguments
   * FALSE and NCM_MSET_MODEL_MAIN determines that this object in non-stackable
   * and it is a MAIN model, both concepts will be discussed in an advanced example.
   *
   */
  ncm_mset_model_register_id (model_class,
                              "NcCurve",
                              "Curve model.",
                              NULL,
                              FALSE,
                              NCM_MSET_MODEL_MAIN);


  /*
   * The property PROP_XL is allowed in range [-G_MAXDOUBLE, G_MAXDOUBLE]
   * and its default value is 0.0. This property can be set only during
   * the object construction.
   *
   */
  g_object_class_install_property (object_class,
                                   PROP_XL,
                                   g_param_spec_double ("xl",
                                                        NULL,
                                                        "x lower bound",
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB));

  /*
   * The property PROP_XU is allowed in range [-G_MAXDOUBLE, G_MAXDOUBLE]
   * and its default value is 1.0. This property can be set only during
   * the object construction.
   *
   */
  g_object_class_install_property (object_class,
                                   PROP_XU,
                                   g_param_spec_double ("xu",
                                                        NULL,
                                                        "x upper bound",
                                                        -G_MAXDOUBLE, G_MAXDOUBLE, 1.0,
                                                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME | G_PARAM_STATIC_BLURB));

  /*
   * Check for errors in parameters initialization.
   *
   */
  ncm_model_class_check_params_info (model_class);

  curve_class = &_nc_curve_f;
}

/*
 * Default implementation. It raises an error if the method is called
 * and the child didn't implement it.
 *
 */
static gdouble
_nc_curve_f (NcCurve *curve, const gdouble x)
{
  g_error ("nc_curve_f: model `%s' does not implement this function.",
           G_OBJECT_TYPE_NAME (curve));

  return 0.0;
}

/*
 * This creates a function to be used by the children to
 * implement this method.
 *
 */
/**
 * nc_curve_set_f_impl: (skip)
 * @curve_class: a #NcCurveClass
 * @f: function $f(x)$
 *
 * Sets the implementation of the curve $f(x)$.
 *
 */
NCM_MODEL_SET_IMPL_FUNC (NC_CURVE, NcCurve, nc_curve, NcCurveF, f)

/*
 * This defines a generic constructor for the subclasses
 *
 */
/**
 * nc_curve_new_from_name:
 * @curve_name: #NcCurve child type name
 *
 * Creates a new #NcCurve of the type described by @curve_name.
 *
 * Returns: (transfer full): a new #NcCurve
 */
NcCurve *
nc_curve_new_from_name (const gchar *curve_name)
{
  /*
   * We use the serialization object to transform a string into an instance.
   * This also allows us to chose the parameters through the string.
   *
   */
  GObject *obj = ncm_serialize_global_from_string (curve_name);

  /*
   * This gets the GType of this new instance.
   *
   */
  GType curve_type = G_OBJECT_TYPE (obj);

  /*
   * Check if the string represent an actual child of #NcCurve.
   *
   */
  if (!g_type_is_a (curve_type, NC_TYPE_CLUSTER_MASS))
    g_error ("nc_curve_new_from_name: NcCurve `%s' do not descend from `%s'.",
             curve_name, g_type_name (NC_TYPE_CURVE));

  /*
   * Returns the correct cast.
   *
   */
  return NC_CURVE (obj);
}

/*
 * The reference increasing function
 *
 */

/**
 * nc_curve_ref:
 * @curve: a #NcCurve
 *
 * Increase reference count by one.
 *
 * Returns: (transfer full): @curve.
 */
NcCurve *
nc_curve_ref (NcCurve *curve)
{
  return g_object_ref (curve);
}

/*
 * The reference decreasing function
 *
 */

/**
 * nc_curve_free:
 * @curve: a #NcCurve
 *
 * Decrease reference count by one.
 *
 */
void
nc_curve_free (NcCurve *curve)
{
  g_object_unref (curve);
}

/*
 * This function decreases the reference count
 * by one only if *curve != NULL, and in that case
 * it sets *curve to NULL after decreasing the
 * reference count. It is useful to use these
 * functions in the dispose hooks.
 *
 *
 */

/**
 * nc_curve_clear:
 * @curve: a #NcCurve
 *
 * Decrease reference count by one if *@curve != NULL
 * and sets @curve to NULL.
 *
 */
void
nc_curve_clear (NcCurve **curve)
{
  g_clear_object (curve);
}

/*
 * Below we implement the accessor functions.
 *
 */

/**
 * nc_curve_set_xl:
 * @curve: a #NcCurve
 * @xl: new $x$ lower bound
 *
 * Sets $x$ lower bound to @xl.
 *
 */
void
nc_curve_set_xl (NcCurve *curve, const gdouble xl)
{
  /*
   * Checking if the object is still in the unintitalized state,
   * if that's the case just assign the value to the private
   * struct.
   *
   */
  if ((curve->priv->xl == 0.0) && (curve->priv->xl == curve->priv->xu))
  {
    curve->priv->xl = xl;
  }
  else
  {
    /*
     * Otherwise assert that xl will be less than xu.
     *
     */
    g_assert_cmpfloat (xl, <, curve->priv->xu);
    curve->priv->xl = xl;
  }
}

/**
 * nc_curve_set_xu:
 * @curve: a #NcCurve
 * @xu: new $x$ upper bound
 *
 * Sets $x$ upper bound to @xu.
 *
 */
void
nc_curve_set_xu (NcCurve *curve, const gdouble xu)
{
  /*
   * Checking if the object is still in the unintitalized state,
   * if that's the case just assign the value to the private
   * struct.
   *
   */
  if ((curve->priv->xl == 0.0) && (curve->priv->xl == curve->priv->xu))
  {
    curve->priv->xu = xu;
  }
  else
  {
    /*
     * Otherwise assert that xl will be less than xu.
     *
     */
    g_assert_cmpfloat (curve->priv->xl, <, xu);
    curve->priv->xu = xu;
  }
}

/**
 * nc_curve_get_xl:
 * @curve: a #NcCurve
 *
 * Sets $x$ lower bound.
 *
 * Returns: $x_l$.
 */
gdouble
nc_curve_get_xl (NcCurve *curve)
{
  return curve->priv->xl;
}

/**
 * nc_curve_get_xu:
 * @curve: a #NcCurve
 *
 * Sets $x$ lower bound.
 *
 * Returns: $x_l$.
 */
gdouble
nc_curve_get_xu (NcCurve *curve)
{
  return curve->priv->xu;
}

/*
 * Finally, we implement the generic caller for the
 * virtual function `f'.
 *
 */

/**
 * nc_curve_f: (virtual f)
 * @curve: a #NcCurve
 * @x: $x$
 *
 * Computes $f(x)$.
 *
 * Returns: the value of $f(x)$.
 */
gdouble
nc_curve_f (NcCurve *curve, const gdouble x)
{
  /*
   * This function call by pointer guarantees that
   * the correct virtual function will be called.
   *
   */
  return NC_CURVE_GET_CLASS (curve)->f (curve, x);
}