| Larch: Data Analysis Tools for X-ray Spectroscopy

# 12.2. Parameters

The parameters used in the fitting model are all meant to be continuous variables – floating point numbers. In general, the fitting procedure may assign any value to any parameter. In many cases, however, you may want to place some restrictions on the value a parameter can take. For example, if you’re fitting data to a line, you may want to ensure that the slope of the line is positive. For more complex cases, you might want to write a a general model describing the data, but keep some of the parameters in the model fixed.

In Larch, a Parameter is a fundamental data type designed to hold variables for fits that can be restricted in the values it can take. A Parameter has several attributes, the most important of which is a `value` – the current value. A Parameter’s value can be changed of course – this is what will happen during a fit – but other attributes can be set to help determine its value too. In most cases, a Parameter can be used as a floating point number, and its value attribute will be used. Thus:

    larch> x = param(10)
    larch> print x
    param(10, vary=False)
    larch> print x+2
    12

To create a Parameter, use the `param()` function, which takes a value as its first argument, and a few optional keyword arguments to control whether the value is to be varied in a fit or kept fixed, to set optional upper and lower bounds for the Parameter value, or to set an algebraic expression to use to evaluate its value as a constrained Parameter.

param(value, vary=False, min=None, max=None, expr=None)

  define a Parameter, setting some of it principle attributes

  Parameters

    - value – floating point value. This value may be adjusted during a fit.

    - vary – flag telling whether Parameter is to be varied during a fit (`True`, `False`) [`False`]

    - min – minimum value the Parameter can take.

    - max – maximum value the Parameter can take.

    - expr – algebraic expression for a constrained Parameter. See using algebraic constraints for details.

  Returns

    a new Parameter defined according to input.

A Parameter may have the following attributes to either control its value or give additional information about its value:

> attribute | meaning | default | set by which functions:
> --- | --- | --- | ---
> value | value |  | `param()`
> vary | value can change in fit | `False` | `param()`
> min | lower bound | `None` | `param()`
> max | upper bound | `None` | `param()`
> name | optional name | `None` | `param()`
> expr | algebraic constraint | `None` | `param()`
> stderr | standard error |  | `minimize()`
> correl | correlations |  | `minimize()`
> uvalue | value with uncertainty |  | `minimize()`

guess(value, min=None, max=None, expr=None)

  define a variable Parameter, setting some of it principle attributes. The arguments here are identical to `param()`, except that `vary=True` is set.

An example of creating some parameters, and creating a group of parameters would be:

    # create some Parameters
    c1 = param(0.75)              # a constant (non-varying) parameter
    a1 = param(1.0, min=0, max=5, vary=True)     # a bounded variable parameter
    a2 = guess(10., min=0)        # a semi-bounded variable parameter

    # create a group of parameters, either from existing parameters
    # or ones created right here
    params = group(a1 = a1, a2 = a2,
                   centroid = param(99, vary=False) )

    # add more parameters to the group:
    params.c1 = c1

    # add a constrained parameter: dependent on other parameters in the group
    params.e1 = param(expr='a1 - c1*sqrt(a2)')

## 12.2.1. setting bounds

Upper and lower bounds can be set on a Parameters value using the min and max arguments to `param()` or by setting the min and max attribute of an existing Parameter. To remove a bound, set the corresponding attribute to `None`.

During a fit, a Parameter’s value may approach or even equal one of the bounds, but will never violate the boundary. It should be kept in mind that a Parameter with a best-fit value at or very close to a boundary may not have an accurate estimate of its uncertainty. In some cases, it may even be that a best-fit value at a boundary will prevent a reasonable estimate of the uncertainty in any of the other Parameters in the fit.

## 12.2.2. using algebraic constraints

It is often useful to be able to build a fitting model in which Parameters in the model are related to one another. As a simple example, it might be useful to fit a spectrum with a sum of two lineshapes that have different centroids, but the same width. As a second example, it might be useful to fit a spectrum to a sum of two model spectra where the relative weight of the model spectra must add to 1.

For each of these cases, one could write a model function that implemented such constraints. Rather than trying to capture and encourage such special cases, Larch takes a more general approach, allowing Parameters to get their value from an algebraic expression. Thus, one might define an objective function for a sum of two Gaussian functions (discussed in more detail in Some Builtin Line-shape Functions), as:

    def fit_2gauss(params, data):
        model = params.amp1 * gaussian(data.x, params.cen1, params.wid1) + \
                params.amp2 * gaussian(data.x, params.cen2, params.wid2)
        return (data.y - model)
    enddef

This is general and does not impose any relations between the parameter values within the objective function. But one can place such relations in the definitions of the parameters and have them obeyed within the fit. That is, one could constrain the two widths of the Gaussians to be the same value with:

    params.wid1 = guess(1, min=0)
    params.wid2 = param(expr='wid1')

and the value of params.wid2 will have the same value as params.wid1 every time the objective is called, and will not be an independent variable in the fit. For the second example, one could constrain the two amplitude parameters to add to 1 and each be between 0 and 1 as:

    params.amp1 = guess(0.5, min=0, max=1)
    params.amp2 = param(expr='1 - amp1')

One can use more complex expressions, and also access built-in values and common mathematical functions, like pi, sin, and log. Essentially any valid Python/Larch expression is allowed, including slicing of arrays and array methods. Some additional details are discussed below (fiteval and details about algebraic constraints),

Changed in version 0.9.34: _sys.paramGroup is no longer used, and _sys.fiteval is used instead.

## 12.2.3. `param_group()`: creating a Parameter Group for fitting constraints

While the examples of constraint expressions above will work during a fit, the constraint values will not be updated immediately in the main Larch interpreter. That is, doing:

    larch> params = group(amp1=guess(0.6, min=0, max=1),
                          amp2=param(expr='1 - amp1'))
    larch> print(params.amp1, params.amp2)
    (<Parameter 0.6, bounds=[0:1]>, <Parameter -inf, bounds=[-inf:inf], expr='1-amp1'>)

That is, the value of constrained parameter amp2 is not properly set yet. To be clear, a fit with this group of parameters will work, but it’s sometimes useful to see the values for the constrained parameters.

The function `param_group()` will create a “live, working” group of parameters:

    larch> params = param_group(amp1=guess(0.6, min=0, max=1),
                                amp2=param(expr='1 - amp1'))
    larch> print(params.amp1, params.amp2)
    (<Parameter 'amp1', 0.6, bounds=[0:1]>, <Parameter 'amp2', 0.4, bounds=[-inf:inf], expr='1-amp1'>)

In addition, you can change the value of params.amp1, with the value of params.amp2 being automatically updated:

> larch> params.amp1.value = 0.2 larch> print(params.amp1, params.amp2) (<Parameter ‘amp1’, 0.2, bounds=[0:1]>, <Parameter ‘amp2’, 0.8, bounds=[-inf:inf], expr=’1-amp1’>)

param_group(**kws)

  create and return a Parameter Group that uses _sys.fiteval for constraint expression.

  Parameters

    kws – optional keyword/argument values for parameters

  Returns

    a new Parameter Group with working constraint expressions.

A Parameter Group can contain non-Parameter values as well as fitting Parameters. For backward compatibility, a simple group containing parameters will work with fitting, but a `param_group()` is recommended for many cases.

## 12.2.4. fiteval and details about algebraic constraints

Beginning with version 0.9.34, Larch uses the lmfit python library to do all the fitting. This project is related to and very similar to Larch itself, but is maintained and developed separately. Lmfit supports algebraic constraints like those discussed in the previous section, using an isolated, embedded mini-interpreter (very similar to Larch itself, based on asteval). Within Larch, this embedded expression interpreter for fitting constraints is held in the Larch system variable _sys.fiteval. The set of available functions and variables is in its symbol table, _sys.fiteval.symtable, which has more than 400 named functions and variables available, most of them from numpy.

During a fit, all the components of the paramgroup given to `minimize()` will be put put into the _sys.fiteval symbol table. Any of these variables can be used in the constraint expressions. In addition, all the true parameters in the paramgroup will be converted into lmfit.Parameters. After the fit is complete, the updated parameter values will be put back into the

The `param_group()` function discussed above keeps an internal link to _sys.fiteval and uses that for evaluating constraint expressions. That is, following the above example, one can see the current values for params.amp1 and params.amp2 within the _sys.fiteval symbol table:

    larch> params = param_group(amp1=guess(0.6, min=0, max=1),
                                amp2=param(expr='1 - amp1'))
    larch> print(params.amp1, params.amp2)
    (<Parameter 'amp1', 0.6, bounds=[0:1]>, <Parameter 'amp2', 0.4, bounds=[-inf:inf], expr='1-amp1'>)
    larch> print(_sys.fiteval.symtable.amp2)
    0.4
    larch> params1.amp.value = 0.1
    larch> print(params.amp1, params.amp2)
    (<Parameter 'amp1', 0.1, bounds=[0:1]>, <Parameter 'amp2', 0.9, bounds=[-inf:inf], expr='1-amp1'>)
    larch> print(_sys.fiteval.symtable.amp2)
    0.9

Because _sys.fiteval is used for all fits with `minimize()` (and for XAFS, with `feffit()`), you may find yourself wanting to clear or reset the fitting symbol table for a new fit. This should not be necessary, but it is available with the function `reset_fiteval()`:

reset_fiteval()

  clear and reset _sys.fiteval for a new fit. This function takes no arguments.

## 12.2.5. working with uncertainties

After a fit, each Parameter that was actually varied in the fit should be assigned information about the uncertainty in the fitted value as well as its best fit value. On rare occasions (such as when a best-fit value is very close to a bound) the setting of uncertainties is not possible. The primary way the uncertainty for a Parameter is expressed is with the `stderr` attribute, which holds the estimated standard error for the Parameter’s value. The correlation with all other Parameters is held in the `correl` attribute – a dictionary with keys of variable names and values of correlation with that variable. In addition, the two-dimensional covariance matrix will be held in the `covar` attribute of the parameter group for each fit.

Note that the uncertainties calculated for constrained parameters involving more than one variable will encapsulate not only the simple propogation of errors for the independent variables, but also their correlation. This can have a significant impact on the uncertainties for constrained parameters.

Finally, each Parameter will have a `uvalue` attribute which is a special object from the uncertainties package that holds both the best-fit value and standard error. A key feature of these `uvalue` attributes is that they can be used in simple mathematical expressions (addition, subtraction, multiplication, division, exponentiation) and propogate the uncertainties to the result (ignoring correlations).