Search code examples
pythonpython-3.xcurve-fittinglmfit

NameError when using composite PseudoVoigt model


I get a NameError when using a composite PseudoVoigt model in combination with prefixes for parameter naming.

I pretty much copied the example for a composite model from a previous question, using a Lorentz profile (Fitting a multi-peak function to a DataSet using LMFIT). This works fine for me, but the Lorentz line shape is just not the function I want to fit.

When I use the PseudoVoigtModel for a single peak I do not have any problems. Also, the LorentzModel works fine with the code below (I also included it in the code so you can double-check/confirm yourself).

from lmfit.models import LorentzianModel, PseudoVoigtModel
import numpy as np
import matplotlib.pyplot as plt

def make_model_L(num):
    pref = "f{0}_".format(num)
    model = LorentzianModel(prefix = pref)
    model.set_param_hint(pref+'amplitude', value=amplitude[num], min=0, max=5*amplitude[num])
    model.set_param_hint(pref+'center', value=center[num], min=center[num]-0.5, max=center[num]+0.5)
    model.set_param_hint(pref+'sigma', value=width[num], min=0, max=2)
    return model


def make_model_V(num):
    pref = "f{0}_".format(num)
    model = PseudoVoigtModel(prefix = pref)
    print('before',model.param_names)
    model.set_param_hint(pref+'fraction',value = 0.7, vary = False)
    model.set_param_hint(pref+'amplitude', value=amplitude[num], min=0, max=5*amplitude[num])
    model.set_param_hint(pref+'center', value=center[num], min=center[num]-0.5, max=center[num]+0.5)
    model.set_param_hint(pref+'fwhm', value=3, min=3/5, max=3*5)
    model.set_param_hint(pref+'sigma', value=1, min=0, max=2)
    model.set_param_hint(pref+'height', value=1, min=-np.inf, max=np.inf, expr='(((1-fraction)*amplitude)/(sigma*sqrt(pi/log(2)))+(fraction*amplitude)/(pi*sigma))')
    print(model.param_names)
    return model

# Some really coarse "data"
x = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29]
y = [1,1,1,1,3,4,5,6,5,4,3,1,1,1,1,1,1,1,1,3,4,5,6,5,4,3,1,1,1,1]

peaks_in_interval = np.array([43, 159, 191, 296, 435, 544])
amplitude = [3,3]
width = [1,1]
center = [7,21]

mod = None
for i in range(len(center)):
    #this_mod = make_model_L(i)
    this_mod = make_model_V(i)
    if mod is None:
        mod = this_mod
    else:
        mod = mod + this_mod

out=mod.fit(y, x=x, method='leastsq')
plt.interactive(True)
print(out.fit_report())
plt.plot(x, y)
plt.plot(x, out.best_fit, label='best fit')
plt.plot(x, out.init_fit, 'r--', label='fit with initial values')
plt.show()

The error message I get:

NameError <_ast.Module object at 0x7f562524dbe0> ^^^ name 'fraction' is not defined

NameError: at expr='<_ast.Module object at 0x7f562524dbe0>'

I did not include the TraceBack. It begins at "out=mod.fit(y, x=x, method='leastsq')" and ends in "~/anaconda3/lib/python3.6/site-packages/asteval/asteval.py in raise_exception(self, node, exc, msg, expr, lineno)"

As mentioned before, with the LorentzianModel everything works fine, I get a fit (not a nice one, but that is due to the test data).

I am not very well versed in python so I cannot really give well-informed hints on what the problem might be. However, I suspect it is connected to the naming of fraction and how it is passed on in the lmfit.fit() - function.

Best, Jan


Solution

  • It is always best to find and post a minimal example that shows the problem and it is always best to include the full output including the traceback.

    For example, you would see the problem you are having with:

    from lmfit.models import PseudoVoigtModel
    
    pref = 'f1_'
    model = PseudoVoigtModel(prefix = pref)
    print('before',model.param_names)
    model.set_param_hint(pref+'fraction',value = 0.7, vary = False)
    model.set_param_hint(pref+'amplitude', value=2, min=0, max=5)
    model.set_param_hint(pref+'center', value=0, min=-0.5, max=0.5)
    model.set_param_hint(pref+'fwhm', value=3, min=3/5, max=3*5)
    model.set_param_hint(pref+'sigma', value=1, min=0, max=2)
    # suspect line:
    model.set_param_hint(pref+'height', value=1, min=-np.inf, max=np.inf,
                         expr='(((1-fraction)*amplitude)/(sigma*sqrt(pi/log(2)))+(fraction*amplitude)/(pi*sigma))')
    
    print(model.param_names)
    params = model.make_params()
    for p in params.values():
        print(p)
    

    The problem comes because there is not a parameter named fraction. As defined just a few lines above, it is named f1_fraction.

    To fix this problem, you should change the expression for pref+'height' to also include your pref prefix string as needed for fraction, amplitude, and sigma.

    Or: you could just remove your hint for height as that would be done automatically anyway, and correctly using the prefix you supply.

    Also:

    a) using parameter hints to supply initial values as you are doing is definitely not encouraged. Hints belong to the Model and should not depend on any particular data set. Make a Model as a general thing, and then make parameters with initial values for each dataset.

    b) do not set bounds too tightly or based on initial values. Bounds (in parameter hints especially) should be used to prevent a parameter from going off to unphysical values, such as "it makes no sense for sigma to be negative", not because the person defining the model thinks "that ought to be close enough". Let the fit do its job. If you do need to set custom bounds, do that per data set.