Benjamin Benjamin - 5 days ago 5
Python Question

Dynamically define a function

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

Answer

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.

Comments