This may not be a complete answer, but is too long for a comment.
For sure, providing a more concrete example of what you're trying to do would definitely help clarify your question and lead to better answers.
It's not entirely clear to me what the signature of your function is, but it may be that the basic question is how to have the user select which of the function arguments become variables in the fit while having other arguments not become variables, either remaining at some fixed value or being used as switches for what functional form to actually use (which seems like what your clarifying comment was trying to say).
In either case, you may find lmfit (https://lmfit.github.io/lmfit-py/) useful. It has a Model class that supports curve-fitting based on scipy.optimize but separate from (and somewhat higher level than) curve_fit
. lmfit.Model
can turn any "model function" into a Model that can be used to fit to data, and uses inspection to turn the function arguments into Parameters used in the fit. Any of the Parameters can be variable or fixed (or have bounds or be set by a simple constraint expression), which might be what you're looking for: the user can decide that some of the function arguments would not be fit variables.
Also, if the model function has keyword arguments that don't have numerical defaults, they do not become fit variables, but can be used as optional switches, perhaps controlling the functional form used. That is, if you have a model function like
def func(x, a=1, b=2, c=3, d=0, option=True):
if option:
return a * np.exp(-b*x) + c + d*x
else:
return a * np.exp(-b*x**2) + c + d*x
Then you could create a fitting model and parameters with
from lmfit impor Model
mymodel = Model(func)
params = mymodel.make_params(a=100, b=-1, c=0, d=0)
and then (for example) fix d
to be 0:
params['d'].value = 0
params['d'].vary = False
or apply other starting values, bounds, etc.
To change the value of the option
argument, you would treat it as another independent variable, passing it to mymodel.fit()
, or mymodel.eval()
as a keyword argument, like this:
xdata = <array of data>
ydata = <array of data>
result = mymodel.fit(ydata, params, x=xdata, option=False)
print(result.fit_report())
That would allow the user to select the options used in the model function.
Hope that points you in the right direction....
After updates to question:
In general, supporting **kws
would be very hard: the user can pass in anything to this dict and how it is used in the function is unknown. Fitting variables have to be floating point numbers, for example. But, you might also recognize that
def func(data, **kwargs):
a = kwargs['a'] if a in kwargs else 0
...
can be restated as
def func(data, a=0, **kwargs):
...
and this is supported. That would mean you would have to explicitly make function arguments for all quantities that could potentially be variables, but that should not be too hard. Once that is done, the user can decide which of these are actually varied in a particular fit.
You can definitely use something like
def func(x, a=1, b=2, c=3, d=0):
...
# read from "config_1.ini"
params_to_be_optimized = 'a', 'b', 'c', 'd'
# read from "config_2.ini"
params_to_be_optimized = 'a', 'c', 'd'
and something like
mymodel = Model(func)
params = mymodel.make_params()
for parname, param in params.items():
param.vary = parname in params_to_be_optimized
to control which parameters will be optimized and which will be fixed.