Internal API Structure¶
Here we describe how the code is organized internally. This is only really relevant for advanced users and developers.
Fitting 101¶
Fitting a model to data is, at it’s most basic, a parameter optimisation, and
depending on whether you do a least-squares fit or a loglikelihood fit your
objective function changes. This means we can split the process of fitting in
three distinct, isolated parts: the Model
, the
Objective and the Minimizer.
In practice, Fit
will choose an appropriate objective
and minimizer on the basis of the model and the data, but you can also give it
specific instances and classes; just in case you know better.
For both the minimizers and objectives there are abstract base classes, which
describe the minimal API required. If a minimizer is more specific, e.g. it
supports constraints, then there are corresponding abstract classes for that,
e.g. ConstrainedMinimizer
.
Models¶
Models house the mathematical definition of the model we want to use to fit.
For the typical usecase in symfit
these are fully symbolical, and
therefore a lot of their properties can be inspected automatically.
As a basic quality, all models are callable, i.e. they have implemented
__call__
. This is used to numerically evaluate the model given the
parameters and independent variables. In order to make sure you get all the
basic functionality, always inherit from BaseModel
.
Next level up, if they inherit from GradientModel
then they will have eval_jacobian
, which will numerically evaluate the
jacobian of the model. Lastly, if they inherit from
HessianModel
, they will also have eval_hessian
to evaluate the hessian of the model. The standard
Model
is all of the above.
Odd ones out from the current library are
CallableNumericalModel
and
ODEModel
. They only inherit from
BaseModel
and are therefore callable,
but their other behaviors are custom build.
Since symfit
0.5.0
, the core of the model has been improved
significantly. At the center of these improvements is
connectivity_mapping
. This mapping
represent the connectivity matrix of the variables and parameters, and therefore
encodes which variable depends on which. This is used in __call__
to
evaluate the components in order. To help with this, models have
ordered_symbols
. This property is the
topologically sorted connectivity_mapping
, and dictates the order in which
variables have to be evaluated.
Objectives¶
Objectives wrap both the Model and the data supplied, and expose only the free
parameters of the model to the outside world.
When called they must return a scalar. This scalar will be minimized, so when
you need something maximized, be sure to add a negation in the right place(s).
They can be called by using the parameter names as keyword arguments, or with a
list of parameter values in the same order as
free_params
(alphabetical).
The latter is there because this is how scipy
likes it.
Be sure to inherit from the abstract base class(es) so you’re sure you define
all the methods that are expected of an objective. Similar to the models, they
come in three types: BaseObjective
,
GradientObjective
and
HessianObjective
. These must implement
__call__
, eval_jacobian
and eval_hessian
respectively.
When defining a new objective, it is best to inherit from
HessianObjective
and to define all three if
possible. When feeding a model that does not implement eval_hessian
to a
HessianObjective
no puppies die,
Fit
is clever enough to prevent this.
Minimizers¶
Last in the chain are the minimizers. They are provided with a function to
minimize (the objective) and the Parameter
s as
a function of which the objective should be minimized. Note that once again
there are different base classes for minimizers that take e.g. bounds or
support gradients. Their execute()
method takes the metaparameters for the minimization.
Again, be sure to inherit from the appropriate base class(es) if you’re
implementing your own minimizer to make sure all the expected methods are there.
Fit
depends on this to make its decisions.
And if you’re wrapping Scipy style minimizers, have a look at
ScipyMinimize
to avoid a duplication of efforts.
Minimizers must always implement a method execute
, which will return an
instance of FitResults
. Any *args
and
**kwargs
given to execute must
be passed to the underlying minimizer.