Multidimensional models#

Fitting is not limited to 1D models. The following example demonstrates how to fit a 2D Gaussian peak to a 2D DataArray.

import numpy as np
import xarray as xr
import matplotlib.pyplot as plt
import lmfit
import xarray_lmfit
# Generate synthetic 2D data
x = np.linspace(-10, 10, 50)
y = np.linspace(-10, 10, 50)
x_arr = xr.DataArray(x, dims=("x",), coords={"x": x})
y_arr = xr.DataArray(y, dims=("y",), coords={"y": y})
z_arr = lmfit.lineshapes.gaussian2d(
    x_arr,
    y_arr,
    amplitude=4.0,
    centerx=0.0,
    centery=0.0,
    sigmax=1.0,
    sigmay=2.0,
).rename("z")

# Add some noise with fixed seed for reproducibility
rng = np.random.default_rng(5)
z_arr = z_arr.copy(data=rng.normal(z_arr, 0.01))

Fitting a 2D model is as simple as providing multiple coordinate names for different independent variables:

model = lmfit.models.Gaussian2dModel()

result_ds = z_arr.xlm.modelfit(
    ("x", "y"),
    model=model,
    params=model.make_params(
        amplitude=2.0, centerx=0.0, centery=0.0, sigmax=1.0, sigmay=2.0
    ),
)
result_ds
<xarray.Dataset> Size: 43kB
Dimensions:                (param: 8, cov_i: 8, cov_j: 8, fit_stat: 9, x: 50,
                            y: 50)
Coordinates:
  * x                      (x) float64 400B -10.0 -9.592 -9.184 ... 9.592 10.0
  * y                      (y) float64 400B -10.0 -9.592 -9.184 ... 9.592 10.0
  * param                  (param) <U9 288B 'amplitude' 'centerx' ... 'height'
  * fit_stat               (fit_stat) <U8 288B 'nfev' 'nvarys' ... 'rsquared'
  * cov_i                  (cov_i) <U9 288B 'amplitude' 'centerx' ... 'height'
  * cov_j                  (cov_j) <U9 288B 'amplitude' 'centerx' ... 'height'
Data variables:
    modelfit_results       object 8B <lmfit.model.ModelResult object at 0x73d...
    modelfit_coefficients  (param) float64 64B 4.065 -0.006079 ... 4.778 0.3158
    modelfit_stderr        (param) float64 64B 0.02927 0.00727 ... 0.002274
    modelfit_covariance    (cov_i, cov_j) float64 512B 0.0008566 ... nan
    modelfit_stats         (fit_stat) float64 72B 25.0 5.0 ... -2.3e+04 0.9355
    modelfit_data          (x, y) float64 20kB -0.008019 -0.01324 ... 0.001742
    modelfit_best_fit      (x, y) float64 20kB 9.16e-28 2.416e-27 ... 7.671e-28

Let’s take a look at the best fit and residuals:

fig, axs = plt.subplots(1, 3, figsize=(12, 3), layout="compressed")

z_arr.plot(ax=axs[0], center=False)
axs[0].set_title("Data")

result_ds.modelfit_best_fit.plot(ax=axs[1])
axs[1].set_title("Fit")

(z_arr - result_ds.modelfit_best_fit).plot(ax=axs[2])
axs[2].set_title("Data $-$ Fit")

for ax in axs:
    ax.set_aspect("equal")
../_images/d06c440bbf8afd0ba3e99583bde6684ab176e2eb93915b885e920e590a7fa938.png