Curve fitting with lmfit#

In this section, we will cover basic curve fitting using lmfit for reference purposes. For detailed information, please refer to the lmfit documentation.

If you are already familiar with lmfit, you can skip to the next section.

import matplotlib.pyplot as plt
import numpy as np
import xarray as xr

Let’s start by defining a model function and the data to fit.

def poly1(x, a, b):
    return a * x + b


# Generate some toy data
x = np.linspace(0, 10, 20)
y = poly1(x, 1, 2)

# Add some noise with fixed seed for reproducibility
rng = np.random.default_rng(1)
yerr = np.full_like(x, 0.5)
y = rng.normal(y, yerr)

Models#

A lmfit model can be created by calling lmfit.Model with the model function and the independent variable(s) as arguments.

import lmfit

model = lmfit.Model(poly1)
params = model.make_params(a=1.0, b=2.0)
result = model.fit(y, x=x, params=params, weights=1 / yerr)

result.plot()
result

Fit Result

Model: Model(poly1)

../_images/a52b1a76851ba7a33ae2915bf8631c3986983ec318dc202d563be490b96f19c3.svg

By passing dictionaries to make_params, we can set the initial values of the parameters and also set the bounds for the parameters.

model = lmfit.Model(poly1)
params = model.make_params(
    a={"value": 1.0, "min": 0.0},
    b={"value": 2.0, "vary": False},
)
result = model.fit(y, x=x, params=params, weights=1 / yerr)
_ = result.plot()
../_images/b3473a0ece14d05edc441e5b10f6ef75f86693bc4bc9a980785cd27e67937fb3.svg

result is a lmfit.model.ModelResult object that contains the results of the fit. The best-fit parameters can be accessed through the result.params attribute.

Note

Since all weights are the same in this case, it has little effect on the fit results. However, if we are confident that we have a good estimate of yerr, we can pass scale_covar=True to the fit method to obtain accurate uncertainties.

result.params
result.params["a"].value, result.params["a"].stderr
(0.9938903037183116, 0.011256084507121714)

The parameters can also be retrieved in a form that allows easy error propagation calculation, enabled by the uncertainties package.

a_uvar = result.uvars["a"]
print(a_uvar)
print(a_uvar**2)
0.994+/-0.011
0.988+/-0.022

Composite models#

Before fitting, let us generate a Gaussian peak on a linear background.

# Generate toy data
x = np.linspace(0, 10, 50)
y = -0.1 * x + 2 + 3 * np.exp(-((x - 5) ** 2) / (2 * 1**2))

# Add some noise with fixed seed for reproducibility
rng = np.random.default_rng(5)
yerr = np.full_like(x, 0.3)
y = rng.normal(y, yerr)

# Plot the data
plt.errorbar(x, y, yerr, fmt="o")
<ErrorbarContainer object of 3 artists>
../_images/7b6a9620a8a45879d2bbf4d1ebadb4d3bdbdb85d45d6c427669ba55a8feb44ce.svg

A composite model can be created by adding multiple models together.

from lmfit.models import GaussianModel, LinearModel

model = GaussianModel() + LinearModel()
params = model.make_params(slope=-0.1, center=5.0, sigma={"value": 0.1, "min": 0})
params
result = model.fit(y, x=x, params=params, weights=1 / yerr)
result.plot()
result

Fit Result

Model: (Model(gaussian) + Model(linear))

../_images/4da675177d6a5f42b8d1509da7bdf67aab94878af537893e551c3fbd985a425e.svg

How about multiple gaussian peaks? Since the parameter names overlap between the models, we must use the prefix argument to distinguish between them.

model = GaussianModel(prefix="p0_") + GaussianModel(prefix="p1_") + LinearModel()
model.make_params()

For more information, see the lmfit documentation.