scipp.optimize.curve_fit(f, da, *, p0=None, **kwargs)

Use non-linear least squares to fit a function, f, to data.

This is a wrapper around scipy.optimize.curve_fit(). See there for a complete description of parameters. The differences are:

  • Instead of separate xdata, ydata, and sigma arguments, the input data array defines provides these, with sigma defined as the square root of the variances, if present, i.e., the standard deviations.

  • The fit function f must work with scipp objects. This provides additional safety over the underlying scipy function by ensuring units are consistent.

  • The fit function f must only take a single positional argument, x. All other arguments mapping to fit parameters must be keyword-only arguments.

  • The inital guess in p0 must be provided as a dict, mapping from fit-function parameter names to initial guesses.

  • The fit parameters may be scalar scipp variables. In that case an initial guess p0 with the correct units must be provided.

  • The returned optimal parameter values popt and the coverance matrix pcov will have units provided that the initial parameters have units. popt and pcov are a dict and a dict of dict, respectively. They are indexed using the fit parameter names. The variance of the returned optimal parameter values is set to the corresponding diagonal value of the covariance matrix.

  • f (Callable) – The model function, f(x, …). It must take the independent variable (coordinate of the data array da) as the first argument and the parameters to fit as keyword arguments.

  • da (scipp._scipp.core.DataArray) – One-dimensional data array. The dimension coordinate for the only dimension defines the independent variable where the data is measured. The values of the data array provide the dependent data. If the data array stores variances then the standard deviations (square root of the variances) are taken into account when fitting.

  • p0 (Optional[Dict[str, scipp._scipp.core.Variable]]) – An optional dict of optional initial guesses for the parameters. If None, then the initial values will all be 1 (if the parameter names for the function can be determined using introspection, otherwise a ValueError is raised). If the fit function cannot handle initial values of 1, in particular for parameters that are not dimensionless, then typically a scipp.UnitError is raised, but details will depend on the function.

Return type

Tuple[Dict[str, Union[scipp._scipp.core.Variable, numbers.Real]], Dict[str, Dict[str, Union[scipp._scipp.core.Variable, numbers.Real]]]]


>>> def func(x, *, a, b):
...     return a * sc.exp(-b * x)
>>> x = sc.linspace(dim='x', start=0.0, stop=0.4, num=50, unit='m')
>>> y = func(x, a=5, b=17/sc.Unit('m'))
>>> y.values += 0.01 * np.random.default_rng().normal(size=50)
>>> da = sc.DataArray(y, coords={'x': x})
>>> from scipp.optimize import curve_fit
>>> popt, _ = curve_fit(func, da, p0 = {'b': 1.0 / sc.Unit('m')})
>>> sc.round(sc.values(popt['a']))
<scipp.Variable> ()    float64            [dimensionless]  [5]
>>> sc.round(sc.values(popt['b']))
<scipp.Variable> ()    float64            [1/m]  [17]

Fit-function parameters that have a default value do not participate in the fit unless an initial guess is provided via the p0 parameters:

>>> from functools import partial
>>> func2 = partial(func, a=5)
>>> popt, _ = curve_fit(func2, da, p0 = {'b': 1.0 / sc.Unit('m')})
>>> 'a' in popt
>>> popt, _ = curve_fit(func2, da, p0 = {'a':2, 'b': 1.0 / sc.Unit('m')})
>>> 'a' in popt