Search code examples
pythonpython-3.xleast-squareslmfit

python lmfit, non-modifiable array as parameter for Model class?


I am just trying to fit a function which retrieves the correlation Pearson coefficient between two arrays. These two arrays are passed to the function as input parameters but they do not change. For the function, they should be interpreted as constants. I found an option for the parameters where it is possible to fix one parameter, i.e., it can not vary, but it works only for scalar values.

When I call Model.make_params(), the Model Class tries to check of these arrays are lower or grater than the minimum/maximum. This evaluation is not needed as they are constants.

My function:

def __lin_iteration2__(xref, yref_scaled, xobs, yobs, slope, offset, verbose=False, niter=None):

    Acal = 1 + (offset + slope*xref)/xref
    xr_new = xref * Acal
    obs_interp1d = interp1d(xobs, yobs, kind='cubic')
    yobs_new = scale_vector(obs_interp1d(xr_new))
    rho = Pearson(yref_scaled, yobs_new)

    return rho

Where xref, yref_scaled, xobs and yobs are arrays that do not change, i.e., constants. 'interp1d' is the interpolator operator coming from scipy.interpolate, 'scale_vector' scale a vector between -1 and 1, and 'Pearson' calculates the Pearson correlation coefficient.

Who I setup the Model class:

m = Model(corr.__lin_iteration3__)
par = m.make_params(yref_scaled = corr.yref_scaled, \
                    obs_interp1d=corr.obs_interp1d, offset=0, scale=0)
par['yref_scaled'].vary = False
par['obs_interp1d'].vary = False
r = m.fit

The error I got (just in the second line when I call the 'make_params' function of Model Class):

Traceback (most recent call last):

  File "<ipython-input-3-c8f6550e831e>", line 1, in <module>
    runfile('/home/andrey/Noveltis/tests/new_correl_sp/new_correl.py', wdir='/home/andrey/Noveltis/tests/new_correl_sp')

  File "/usr/lib/python3/dist-packages/spyder/utils/site/sitecustomize.py", line 705, in runfile
    execfile(filename, namespace)

  File "/usr/lib/python3/dist-packages/spyder/utils/site/sitecustomize.py", line 102, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

  File "/home/andrey/Noveltis/tests/new_correl_sp/new_correl.py", line 264, in <module>
    obs_interp1d=corr.obs_interp1d, offset=0, scale=0)

  File "/usr/lib/python3/dist-packages/lmfit/model.py", line 401, in make_params
    params.add(par)

  File "/usr/lib/python3/dist-packages/lmfit/parameter.py", line 338, in add
    self.__setitem__(name.name, name)

  File "/usr/lib/python3/dist-packages/lmfit/parameter.py", line 145, in __setitem__
    self._asteval.symtable[key] = par.value

  File "/usr/lib/python3/dist-packages/lmfit/parameter.py", line 801, in value
    return self._getval()

  File "/usr/lib/python3/dist-packages/lmfit/parameter.py", line 786, in _getval
    if self._val > self.max:

ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Solution

  • In lmfit, arguments to the model function are expected to be scalar, floating point parameter values, except for "independent variables" which can be any python objects. By default, the first function argument is assumed to be an independent variable, as is any keyword argument with a non-numeric default value. But, you can specify which arguments are the independent variables (and there can be more than one) when creating your model.

    I think what you want is:

    m = Model(corr.__lin_iteration3__, independent_vars=['xref', 'yref_scaled', 'xobs', 'yobs'])
    

    But also: You could also pass is any Python object, so you could pack your ref and obs data into other structures and do something like

    def lin_iteration(Data, slope, offset, verbose=False, niter=None):
    
        Acal = 1 + (offset + slope*Data['xref'])/Data['xref']
        xr_new = Data['xref'] * Acal
        # or maybe that would be clearer as just
        #   xr_new = offset + (1+slope)* Data['xref'] 
        obs_interp1d = interp1d(Data['xobs'], Data['yobs'], kind='cubic')
        yobs_new = scale_vector(obs_interp1d(xr_new))
        rho = Pearson(Data['yref_scaled'], yobs_new)
    
        return rho
    

    and

    m = Model(lin_iteration)
    par = m.make_params(offset=0, scale=0)
    Data = {'xref': xref, 'yref_scaled': yref_scaled, 'xobs': xobs, 'yobs': yobs}
    result = m.fit(Data, params)
    

    Of course, that's all untested but it might make your life easier...