Lorentzian + background with lmfit

Introduction

The objective of this notebook is to show how to combine the models of the QENSlibrary. Here, we use the Lorentzian profile and a flat background, created from background_polynomials, to perform some fits.

lmfit is used for fitting.

Physical units

For information about unit conversion, please refer to the jupyter notebook called Convert_units.ipynb in the tools folder.

The dictionary of units defined in the cell below specify the units of the refined parameters adapted to the convention used in the experimental datafile.

[1]:
# Units of parameters for selected QENS model and experimental data
dict_physical_units = {'omega': "1/ps",
                       'scale': "unit_of_signal/ps",
                       'center': "1/ps",
                       'hwhm': "1/ps",}

Import libraries

[2]:
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets
import lmfit
import QENSmodels

Plot fitting model

The widget below shows the lorentzian peak shape function with a constant background imported from QENSmodels where the functions’ parameters Scale, Center, FWHM and background can be varied.

[3]:
# Dictionary of initial values
ini_parameters = {'scale': 5, 'center': 0, 'hwhm': 3, 'background': 0.}

def interactive_fct(scale, center, hwhm, background):
    """
    Plot to be updated when ipywidgets sliders are modified
    """
    xs = np.linspace(-10, 10, 100)

    fig1, ax1 = plt.subplots()
    ax1.plot(xs,
             QENSmodels.lorentzian(xs, scale, center, hwhm) +\
             QENSmodels.background_polynomials(xs, background))
    ax1.set_xlabel('x')
    ax1.grid()

# Define sliders for modifiable parameters and their range of variations

scale_slider = ipywidgets.FloatSlider(value=ini_parameters['scale'],
                                      min=0.1, max=10, step=0.1,
                                      description='scale',
                                      continuous_update=False)

center_slider = ipywidgets.IntSlider(value=ini_parameters['center'],
                                     min=-10, max=10, step=1,
                                     description='center',
                                     continuous_update=False)

hwhm_slider = ipywidgets.FloatSlider(value=ini_parameters['hwhm'],
                                     min=0.1, max=10, step=0.1,
                                     description='hwhm',
                                     continuous_update=False)

background_slider = ipywidgets.FloatSlider(value=ini_parameters['background'],
                                       min=0.1, max=10, step=0.1,
                                       description='background',
                                       continuous_update=False)

grid_sliders = ipywidgets.HBox([ipywidgets.VBox([scale_slider, center_slider]),
                                ipywidgets.VBox([hwhm_slider, background_slider])])

# Define function to reset all parameters' values to the initial ones
def reset_values(b):
    """
    Reset the interactive plots to inital values
    """
    scale_slider.value = ini_parameters['scale']
    center_slider.value = ini_parameters['center']
    hwhm_slider.value = ini_parameters['hwhm']
    background_slider.value = ini_parameters['background']


# Define reset button and occurring action when clicking on it
reset_button = ipywidgets.Button(description = "Reset")
reset_button.on_click(reset_values)

# Display the interactive plot
interactive_plot = ipywidgets.interactive_output(interactive_fct,
                                         {'scale': scale_slider,
                                          'center': center_slider,
                                          'hwhm': hwhm_slider,
                                          'background': background_slider})

display(grid_sliders, interactive_plot, reset_button)

Create the reference data

[4]:
# Create array of reference data: noisy lorentzian with background
nb_points = 100
xx = np.linspace(-5, 5, nb_points)
added_noise = np.random.normal(0, 1, nb_points)
lorentzian_noisy = QENSmodels.lorentzian(
    xx,
    scale=0.89,
    center=-0.025,
    hwhm=0.45
) * (1 + 0.1 * added_noise) + 0.5 * (1 + 0.02 * added_noise)

Setting and fitting

[5]:
def flat_background(x, A0):
    """
    Define flat background to be added to fitting model
    """
    return QENSmodels.background_polynomials(x, A0)
[6]:
gmodel = lmfit.Model(QENSmodels.lorentzian) + lmfit.Model(flat_background)
print(f'Names of parameters: {gmodel.param_names}\nIndependent variable(s): {gmodel.independent_vars}')

initial_parameters_values = {'scale': 1, 'center':0.2, 'hwhm': 0.5, 'A0': 0.33}

# Fit
result = gmodel.fit(
    lorentzian_noisy,
    x=xx,
    scale=initial_parameters_values['scale'],
    center=initial_parameters_values['center'],
    hwhm=initial_parameters_values['hwhm'],
    A0=initial_parameters_values['A0']
)
Names of parameters: ['scale', 'center', 'hwhm', 'A0']
Independent variable(s): ['x']
[7]:
# Plot initial model and reference data
fig0 = plt.figure()
plt.plot(xx, lorentzian_noisy, 'b-', label='reference data')
plt.plot(xx, result.init_fit, 'k--', label='model with initial guesses')
plt.xlabel('x')
plt.title('Initial model and reference data')
plt.grid()
plt.legend();
../_images/examples_lmfit_lorentzian_and_backgd_fit_11_0.png

Plot results

using methods implemented in lmfit

[8]:
# display result
print('Result of fit:\n', result.fit_report())

# plot fitting result using lmfit functionality
result.plot()
Result of fit:
 [[Model]]
    (Model(lorentzian) + Model(flat_background))
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 31
    # data points      = 100
    # variables        = 4
    chi-square         = 0.08314541
    reduced chi-square = 8.6610e-04
    Akaike info crit   = -701.233444
    Bayesian info crit = -690.812764
    R-squared          = 0.96564274
[[Variables]]
    scale:   0.91785782 +/- 0.02883869 (3.14%) (init = 1)
    center: -0.02611501 +/- 0.01023971 (39.21%) (init = 0.2)
    hwhm:    0.43150919 +/- 0.01672896 (3.88%) (init = 0.5)
    A0:      0.50059193 +/- 0.00389563 (0.78%) (init = 0.33)
[[Correlations]] (unreported correlations are < 0.100)
    C(scale, hwhm) = 0.790
    C(scale, A0)   = -0.655
    C(hwhm, A0)    = -0.499
[8]:
../_images/examples_lmfit_lorentzian_and_backgd_fit_13_1.png
../_images/examples_lmfit_lorentzian_and_backgd_fit_13_2.png

Other option: plot fitting result and reference data using matplotlib.pyplot

[9]:
fig1 = plt.figure()
plt.plot(xx, lorentzian_noisy, 'b-', label='reference data')
plt.plot(xx, result.best_fit, 'r.', label='fitting result')
plt.legend()
plt.xlabel('x')
plt.title('Fit result and reference data')
plt.grid();
../_images/examples_lmfit_lorentzian_and_backgd_fit_15_0.png

Print values and errors of refined parameters:

[10]:
for item in ['hwhm', 'center', 'scale']:
    print(f"{item}: {result.params[item].value} +/- {result.params[item].stderr} {dict_physical_units[item]}")
hwhm: 0.4315091873010395 +/- 0.016728957918933012 1/ps
center: -0.02611500946307296 +/- 0.010239707149920764 1/ps
scale: 0.9178578224996412 +/- 0.028838688012300575 unit_of_signal/ps
[ ]:


Generated by nbsphinx from a Jupyter notebook.