Parameter constraints

Models are characterized by parameters (see Basic concepts and data types) whose values are modified during fitting until a convergence criterion is met, and the best fit values are identified. In many cases, however, the parameters can not vary arbitrarily but should satisfy some constraints for their values to be meaningful. GModelFit.jl supports the definition of constraints by fixing the parameter to a specific value, limiting the value in a user defined range, or by dynamically calculating its value using a mathematical expression involving other parameter values. In the latter case the parameter is not free to vary in the fit since its actual value is determined by the patch constraint, hence it is dubbed a patched parameter. Such unused parameter can optionally be repurposed as a new free parameter in a parametrized patch expression (see example below).

An important concept to bear in mind is that the GModelFit.Parameter structure provides two field for the associated numerical value:

  • val: is the parameter value which is being varied by the minimizer during fitting. The value set before the fitting is the guess value. The value after fitting is the best fit one;
  • actual: is the result of the patch expression evaluation, and the actual value used when evaluating a component via its evaluate! method. Note that this value will be overwitten at each model evaluation, hence setting this field has no effect. The val and actual values are identical if no patch constraint has been defined.

A parameter constraint is defined by explicitly modifiying the fields of the corresponding GModelFit.Parameter structure. More specifically:

  1. to set a parameter to a specific value: set the val field to the numeric value and set the fixed field to true;
  2. to set a parameter value range: set one or both the low and high fields (default values are -Inf and +Inf respectively);
  3. to constraint a parameter to have the same numerical value as another one with the same name (but in another component): set the patch value to the component name (it must be a Symbol);
  4. to dynamically calculate an actual value using a mathematical expression depending on other parameter values: set the patch field to an anonymous function generated with the @fd macro. The function must accept a single argument (actually a dictionary of components) and return a scalar numberl;
  5. to define a parametrized patch expression: create an anonymous function with the @fd macro with two arguments, the first has the same meaning as in the previous case, and the second is the free parameter value. Note that patched parameter loses its original meaning, and becomes the parameter of the patch expression;
  6. to define a patch constraint involving parameters from other models in a Multi-dataset fitting scenario: simply use mpatch in place of patch, and the first argument to the λ-function will be a vector with as many elements as the number of models in the Vector{Model} object.

The following examples show how to define constraints for each of the afore-mentioned cases.

Example

We will consider a model for a 1D domain consisting of the sum of a linear background component (named bkg) and two Gaussian-shaped features (l1 and l2):

using GModelFit

model = Model(:bkg => GModelFit.OffsetSlope(1, 1, 0.1),
              :l1 => GModelFit.Gaussian(1, 2, 0.2),
              :l2 => GModelFit.Gaussian(1, 3, 0.4),
              :main => SumReducer(:bkg, :l1, :l2))

Assume that, for the model to be meaningful, the parameters should satisfy the following constraints:

  • the bkg should have a fixed value of 1 at x=1, and a slope which is in the range [0:0.2]:
model[:bkg].offset.val = 1
model[:bkg].offset.fixed = true

model[:bkg].slope.low  = 0
model[:bkg].slope.high = 0.2
  • the normalization of l1 and l2 must be the same:
model[:l2].norm.patch = :l1
  • the width of l2 must be twice that of l1 (patched parameter):
model[:l2].sigma.patch = @fd m -> 2 * m[:l1].sigma
  • the center of l2 must be at a larger coordinate with respect to the center of l1. In this case we re-interpret the model[:l2].center parameter as the distance between the two centers, and create a parametrized patch expression to calculate the actual center value of l2:
model[:l2].center.patch = @fd (m, v) -> v + m[:l1].center
model[:l2].center.val = 1   # guess value for the distance between the centers
model[:l2].center.low = 0   # ensure [l2].center > [l1].center

We can fit the model against a mock dataset (see Generate mock datasets):

dom = Domain(0:0.1:5)
data = GModelFit.mock(Measures, model, dom)
bestfit, stats = fit(model, data)
(Components:
───────────┬────────────────┬───────┬─────────────┬───────────┬───────────┬───────────┬─────────╮
 Component  Type            #Free  Eval. count  Min        Max        Mean       NaN/Inf 
├───────────┼────────────────┼───────┼─────────────┼───────────┼───────────┼───────────┼─────────┤
 main       SumReducer             124             0.9026      3.281      1.553  0       
 ├─╴bkg     OffsetSlope_1D  1      40              0.9026       1.39      1.146  0       
 ├─╴l1      Gaussian_1D     3      80           4.385e-53      2.153     0.2034  0       
 └─╴l2      Gaussian_1D     1      91           3.547e-14      1.077     0.2034  0       
╰───────────┴────────────────┴───────┴─────────────┴───────────┴───────────┴───────────┴─────────╯

Parameters:
───────────┬────────────────┬────────┬──────────┬───────────┬───────────┬───────────┬─────────────────────────────╮
 Component  Type            Param.  Range     Value      Uncert.    Actual     Patch                       
├───────────┼────────────────┼────────┼──────────┼───────────┼───────────┼───────────┼─────────────────────────────┤
 bkg        OffsetSlope_1D  offset  -Inf:Inf          1   (fixed)                                          
                            x0      -Inf:Inf          1   (fixed)                                          
                            slope   0:0.2       0.09741     0.0126                                         
├───────────┼────────────────┼────────┼──────────┼───────────┼───────────┼───────────┼─────────────────────────────┤
 l1         Gaussian_1D     norm    0:Inf         1.037    0.04602                                         
                            center  -Inf:Inf      2.012    0.01213                                         
                            sigma   0:Inf        0.1918    0.00889                                         
├───────────┼────────────────┼────────┼──────────┼───────────┼───────────┼───────────┼─────────────────────────────┤
 l2         Gaussian_1D     norm    0:Inf             1   (fixed)       1.037  l1                          
                            center  0:Inf         1.011    0.03436      3.023  (m, v)->v + (m[:l1]).center 
                            sigma   0:Inf           0.4   (fixed)      0.3837  m->2 * (m[:l1]).sigma       
╰───────────┴────────────────┴────────┴──────────┴───────────┴───────────┴───────────┴─────────────────────────────╯
, Fit results: #data: 51, #free pars: 5, red. fit stat.:     1.6411, Status: OK      
)

and plot the results with Gnuplot.jl:

using Gnuplot
@gp    coords(dom) values(data) uncerts(data) "w yerr t 'Data'" :-
@gp :- coords(dom) model(dom) "w l t 'Model'"

See Multi-dataset fitting for an example on how to create a patch epression involving multiple models.