Skip to content

CompositeModel depends on return type of model functions #875

Closed
@Julian-Hochhaus

Description

@Julian-Hochhaus

Description

When using a single model, whether the return type of the model function is a numpy.ndarray or a list does not affect the result. However, when using a CompositeModel that combines two models (e.g. by + operation), fitting fails if both model functions return a list. In contrast, if at least one of the model functions returns a numpy.ndarray, the fit succeeds.

The issue arises due to the _residual() method of the model class, which throws an error message while calculating diff = model - data. Here, model is the output of eval(). The problem appears to be that the length of model is twice that of data and the output of each model function when using two lists as the return type. It is likely that the bug is caused by appending the lists instead of adding them element-wise during Model evaluation.

Using two lists as return:

import numpy as np
from lmfit.model import Model


def lin1(x, k):
    rtrn = [k*x1 for x1 in x]
    return rtrn



def lin2(x, k):
    rtrn = [k * x1 for x1 in x]
    return rtrn



y=np.linspace(0, 100, 100)
x=np.linspace(0, 100, 100)

Model1 = Model(lin1, independent_vars=["x"], prefix="m1_")
Model2 = Model(lin2, independent_vars=["x"], prefix="m2_")


ModelSum = Model1 + Model2
pars = ModelSum.make_params()

pars.add('m1_k', value=0.5)
pars.add('m2_k', value=0.5)
result = ModelSum.fit(y, params=pars, x=x)
print(result.fit_report())

Error message:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-8-ad2e6908430e> in <module>
     27 pars.add('m1_k', value=0.5)
     28 pars.add('m2_k', value=0.5)
---> 29 result = ModelSum.fit(y, params=pars, x=x)
     30 print(result.fit_report())

~/anaconda3/lib/python3.8/site-packages/lmfit/model.py in fit(self, data, params, weights, method, iter_cb, scale_covar, verbose, fit_kws, nan_policy, calc_covar, max_nfev, **kwargs)
   1088                              nan_policy=self.nan_policy, calc_covar=calc_covar,
   1089                              max_nfev=max_nfev, **fit_kws)
-> 1090         output.fit(data=data, weights=weights)
   1091         output.components = self.components
   1092         return output

~/anaconda3/lib/python3.8/site-packages/lmfit/model.py in fit(self, data, params, weights, method, nan_policy, **kwargs)
   1445         self.userkws.update(kwargs)
   1446         self.init_fit = self.model.eval(params=self.params, **self.userkws)
-> 1447         _ret = self.minimize(method=self.method)
   1448 
   1449         for attr in dir(_ret):

~/anaconda3/lib/python3.8/site-packages/lmfit/minimizer.py in minimize(self, method, params, **kws)
   2377                         val.lower().startswith(user_method)):
   2378                     kwargs['method'] = val
-> 2379         return function(**kwargs)
   2380 
   2381 

~/anaconda3/lib/python3.8/site-packages/lmfit/minimizer.py in leastsq(self, params, max_nfev, **kws)
   1701         result.call_kws = lskws
   1702         try:
-> 1703             lsout = scipy_leastsq(self.__residual, variables, **lskws)
   1704         except AbortFitException:
   1705             pass

~/anaconda3/lib/python3.8/site-packages/scipy/optimize/_minpack_py.py in leastsq(func, x0, args, Dfun, full_output, col_deriv, ftol, xtol, gtol, maxfev, epsfcn, factor, diag)
    411     if not isinstance(args, tuple):
    412         args = (args,)
--> 413     shape, dtype = _check_func('leastsq', 'func', func, x0, args, n)
    414     m = shape[0]
    415 

~/anaconda3/lib/python3.8/site-packages/scipy/optimize/_minpack_py.py in _check_func(checker, argname, thefunc, x0, args, numinputs, output_shape)
     24 def _check_func(checker, argname, thefunc, x0, args, numinputs,
     25                 output_shape=None):
---> 26     res = atleast_1d(thefunc(*((x0[:numinputs],) + args)))
     27     if (output_shape is not None) and (shape(res) != output_shape):
     28         if (output_shape[0] != 1):

~/anaconda3/lib/python3.8/site-packages/lmfit/minimizer.py in __residual(self, fvars, apply_bounds_transformation)
    588             raise AbortFitException(f"fit aborted: too many function evaluations {self.max_nfev}")
    589 
--> 590         out = self.userfcn(params, *self.userargs, **self.userkws)
    591 
    592         if callable(self.iter_cb):

~/anaconda3/lib/python3.8/site-packages/lmfit/model.py in _residual(self, params, data, weights, **kwargs)
    798             raise ValueError(msg)
    799 
--> 800         diff = model - data
    801 
    802         if diff.dtype == complex:

ValueError: operands could not be broadcast together with shapes (200,) (100,) 

Using at least one numpy.ndarray as return:

import numpy as np
from lmfit.model import Model


def lin1(x, k):
    rtrn = k*x
    return rtrn



def lin2(x, k):
    rtrn = [k * x1 for x1 in x]
    return rtrn



y=np.linspace(0, 100, 100)
x=np.linspace(0, 100, 100)

Model1 = Model(lin1, independent_vars=["x"], prefix="m1_")
Model2 = Model(lin2, independent_vars=["x"], prefix="m2_")


ModelSum = Model1 + Model2
pars = ModelSum.make_params()

pars.add('m1_k', value=0.5)
pars.add('m2_k', value=0.5)
result = ModelSum.fit(y, params=pars, x=x)
print(result.fit_report())

Fit succeeds!

Version information

Python: 3.8.3 (default, Jul 2 2020, 16:21:59)
[GCC 7.3.0]

lmfit: 1.2.1, scipy: 1.10.1, numpy: 1.23.4,asteval: 0.9.29, uncertainties: 3.1.6

If you need any further information, please let me know,
Kind regards,
Julian

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions