I am trying to write a curve fitting function which returns the optimal parameters a, b and c, here is a simplified example:
import numpy
import scipy
from scipy.optimize import curve_fit
def f(x, a, b, c):
return x * 2*a + 4*b - 5*c
xdata = numpy.array([1,3,6,8,10])
ydata = numpy.array([ 0.91589774, 4.91589774, 10.91589774, 14.91589774, 18.91589774])
popt, pcov = scipy.optimize.curve_fit(f, xdata, ydata)
This works fine, but I want to give the user a chance to supply some (or none) of the parameters a, b or c, in which case they should be treated as constants and not estimated. How can I write f
so that it fits only the parameters not supplied by the user?
Basically, I need to define f
dynamically with the correct arguments. For instance if a
was known by the user, f
becomes:
def f(x, b, c):
a = global_version_of_a
return x * 2*a + 4*b - 5*c
Method 1: exec() It's the perfect way to dynamically create a function in Python! ? Python's built-in exec() executes the Python code you pass as a string or executable object argument. This is called dynamic execution because, in contrast to normal static Python code, you can generate code and execute it at runtime.
Dynamic function is a way of dynamically invoking a function call. The compiler will have limited knowledge of what you are up to so you will get run time errors if you don't use correct inputs and outputs. One example that runs different functions depending on user input: DEFINE VARIABLE iFunc AS INTEGER NO-UNDO.
The dynamic nature of JavaScript means that a function is able to not only call itself, but define itself, and even redefine itself. This is done by assigning an anonymous function to a variable that has the same name as the function.
Taking a page from the collections.namedtuple playbook, you can use exec to "dynamically" define func
:
import numpy as np
import scipy.optimize as optimize
import textwrap
funcstr=textwrap.dedent('''\
def func(x, {p}):
return x * 2*a + 4*b - 5*c
''')
def make_model(**kwargs):
params=set(('a','b','c')).difference(kwargs.keys())
exec funcstr.format(p=','.join(params)) in kwargs
return kwargs['func']
func=make_model(a=3, b=1)
xdata = np.array([1,3,6,8,10])
ydata = np.array([ 0.91589774, 4.91589774, 10.91589774, 14.91589774, 18.91589774])
popt, pcov = optimize.curve_fit(func, xdata, ydata)
print(popt)
# [ 5.49682045]
Note the line
func=make_model(a=3, b=1)
You can pass whatever parameters you like to make_model. The parameters you pass to make_model
become fixed constants in func
. Whatever parameters remain become free parameters that optimize.curve_fit
will try to fit.
For example, above, a=3 and b=1 become fixed constants in func
. Actually, the exec
statement places them in func
's global namespace. func
is thus defined as a function of x
and the single parameter c
. Note the return value for popt
is an array of length 1 corresponding to the remaining free parameter c
.
Regarding textwrap.dedent
: In the above example, the call to textwrap.dedent
is unnecessary. But in a "real-life" script, where funcstr
is defined inside a function or at a deeper indentation level, textwrap.dedent
allows you to write
def foo():
funcstr=textwrap.dedent('''\
def func(x, {p}):
return x * 2*a + 4*b - 5*c
''')
instead of the visually unappealing
def foo():
funcstr='''\
def func(x, {p}):
return x * 2*a + 4*b - 5*c
'''
Some people prefer
def foo():
funcstr=(
'def func(x, {p}):\n'
' return x * 2*a + 4*b - 5*c'
)
but I find quoting each line separately and adding explicit EOL characters a bit onerous. It does save you a function call however.
I usually use a lambda for this purpose.
user_b, user_c = get_user_vals()
opt_fun = lambda x, a: f(x, a, user_b, user_c)
popt, pcov = scipy.optimize.curve_fit(opt_fun, xdata, ydata)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With