lmfit / lmfit-py

Non-Linear Least Squares Minimization, with flexible Parameter settings, based on scipy.optimize, and with many additional classes and methods for curve fitting.
https://lmfit.github.io/lmfit-py/
Other
1.06k stars 274 forks source link

Retain default values for keyword arguments when manually choosing param_names #918

Closed schtandard closed 11 months ago

schtandard commented 11 months ago

Description

I assume that the current behavior is intended, so I consider this a feature request.

By default, all model function arguments are turned into model parameters unless they are an independent variable or they have a non-numeric default value. Those arguments with a default value get turned into parameters with that as their initial value. However, when explicitly choosing the parameters using param_names, this stops being true and one has to set all initial values manually which feels redundant.

It would be really nice if the initial values were derived from the default argument values even when using param_names.

A Minimal, Complete, and Verifiable example
import lmfit

def foo(x, a=3, b=5, s=1):
    return a + s * b * x**2

print("\nDefault behavior:")
mdl = lmfit.Model(foo)
params = mdl.make_params()
for pname, par in params.items():
    print(pname, par)

print("\nExplicitly choosing parameter names:")
mdl = lmfit.Model(foo, param_names=['a', 'b'])
params = mdl.make_params()
for pname, par in params.items():
    print(pname, par)

print("\nWe have to explicitly set the initial values:")
mdl = lmfit.Model(foo, param_names=['a', 'b'])
mdl.set_param_hint('a', value=3)
mdl.set_param_hint('b', value=5)
params = mdl.make_params()
for pname, par in params.items():
    print(pname, par)

print("\nIn this particular case, we could cheat by using True instead of 1, but that's very hacky:")
def foo(x, a=3, b=5, s=True):
    return a + s * b * x**2
mdl = lmfit.Model(foo)
params = mdl.make_params()
for pname, par in params.items():
    print(pname, par)

Output:

Default behavior:
a <Parameter 'a', value=3, bounds=[-inf:inf]>
b <Parameter 'b', value=5, bounds=[-inf:inf]>
s <Parameter 's', value=1, bounds=[-inf:inf]>

Explicitly choosing parameter names:
a <Parameter 'a', value=-inf, bounds=[-inf:inf]>
b <Parameter 'b', value=-inf, bounds=[-inf:inf]>

We have to explicitly set the initial values:
a <Parameter 'a', value=3, bounds=[-inf:inf]>
b <Parameter 'b', value=5, bounds=[-inf:inf]>

In this particular case, we could cheat by using True instead of 1, but that's very hacky:
a <Parameter 'a', value=3, bounds=[-inf:inf]>
b <Parameter 'b', value=5, bounds=[-inf:inf]>
Version information
Python: 3.9.18 | packaged by conda-forge | (main, Aug 30 2023, 03:40:31) [MSC v.1929 64 bit (AMD64)]

lmfit: 1.2.2, scipy: 1.11.3, numpy: 1.26.0,asteval: 0.9.31, uncertainties: 3.1.7