python: how to call a function with a flexible parameter order? - python

Let us take an example. In certain libraries like "scipy.integrate" calling a function like "odeint" (integrating functions) has to be expressed as "odeint(func, y0, T, ...)" where "func" is a name of a function that has to be predefined with two parameters (y,t0), y vector and t0 a scalar.
The question is how to use "odeint" if the already defined function "func" is specified with two parameters but in the inverse order "(t0, y)".
Best regards.

You can use a lambda function to reorder the arguments like so:
odeint(lambda p, q: func(q, p), y0, T, ...)
Alternatively, you can supposedly swap the orders in the main call if all odeint does is call func on the arguments and does interact directly with the arguments:
odeint(func, T, y0, ...)

You can explicitly call parameters in arbitrary sequence:
def func(a, b, c):
print('a = {}'.format(a))
print('b = {}'.format(b))
print('c = {}'.format(c))
x = 1
y = 2
z = 3
func(a = x, b = y, c = z)
func(c = z, b = y, a = x)
func(b = y, c = z, a = x)
P.S. I'm not on 100% sure, but try odeint(func, y0 = y0, T = T, ...)
or odeint(func, T = T, y0 = y0, ...).

Related

How to get the value of a middle variable in a function that need to use 'fsolve'?

My first py file is the function that I want to find the roots, like this:
def myfun(unknowns,a,b):
x = unknowns[0]
y = unknowns[1]
eq1 = a*y+b
eq2 = x**b
z = x*y + y/x
return eq1, eq2
And my second one is to find the value of x and y from a starting point, given the parameter value of a and b:
a = 3
b = 2
x0 = 1
y0 = 1
x, y = scipy.optimize.fsolve(myfun, (x0,y0), args= (a,b))
My question is: I actually need the value of z after plugging in the result of found x and y, and I don't want to repeat again z = x*y + y/x + ..., which in my real case it's a middle step variable without an explicit expression.
However, I cannot replace the last line of fun with return eq1, eq2, z, since fslove only find the roots of eq1 and eq2.
The only solution now is to rewrite this function and let it return z, and plug in x and y to get z.
Is there a good solution to this problem?
I believe that's the wrong approach. Since you have z as a direct function of x and y, then what you need is to retrieve those two values. In the listed case, it's easy enough: given b you can derive x as the inverse of eqn2; also given a, you can invert eqn1 to get y.
For clarity, I'm changing the names of your return variables:
ret1, ret2 = scipy.optimize.fsolve(myfun, (x0,y0), args= (a,b))
Now, invert the two functions:
# eq2 = x**b
x = ret2**(1/b)
# eq1 = a*y+b
y = (ret1 - b) / a
... and finally ...
z = x*y + y/x
Note that you should remove the z computation from your function, as it serves no purpose.

Passing arguments to a Python function within a function

In the case shown below I have a function titled func1() that resides in func3(), where func3() is solving for the value of an ODE using the Euler method.
def func1(x, y):
return y * np.log(y) / x
def func3(x, y, step_size, func):
dydx = func(x, y)
new_x = x + step_size
new_y = y _ step_size * dydx
return new_x, new_y
step_size - 0.1
lower = 2.0
upper = 3.0
e = 2.7182828284
x_val = [2.0]
y_val = [e]
for i in range(10):
x, y = func3(x_val[i], y_val[i], step_size, func1)
x_val.append(x)
y_val.append(y)
The code is passing func1 to func3 as a decorator and writing the output to a list as it iterates over the range of 0 to 10. However, the code inside func3() is hardcoded to the exact input of func1(), which is x and y. I would like to write func3() to be generic enough that you can pass any function to it as long as its first two inputs are x and y, but it should be cabaple of taking more inputs. So assume that all the code shown above was identical, but instead of passing func1(), I passed func2() to func3(), with the structure whown below.
def func2(x, y, z):
return z * y * np.log(y) / (x*z)
How could I write func3() to be generic enough that it could take either of the two functions and understand that their are more arguments to pass to func1(), which would also have to be passed into func3()?
You can use Python's variable-length argument syntax to await any extra arguments, and pass them on. If there are none, it works anyway: The starred variable will receive the empty list as its value, and will disappear when expanded in the fall to func.
def func1(x, y):
return x+y
def func2(x, y, z):
return (x+y)*z
def caller(func, x, y, other, other2, *args):
return func(x, y, *args)
Use *args and **kwargs to collect arguments (in addition to x and y intended for your callback):
def func3(x, y, step_size, func, *args, **kwargs):
dydx = func(x, y, *args, **kwargs)
...
However, consider if it's really necessary for func3 to call func itself; have the caller do it instead.
def func3(x, y, step_size, dydx):
new_x = x + step_size
new_y = y - step_size * dydx
return new_x, new_y
for old_x, old_y in zip(x_val, y_val):
x, y = func3(old_x, old_y, step_size, func1(x, y))
...
It would be better to use *args and **kargs (**kwargs in function definitions in python is used to pass a keyworded, variable-length argument list. We use the name kwargs with the double star. The reason is because the double star allows us to pass through keyword arguments and any number of them).
For Example:
def myFun(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)
args = ("Hey", "hi", "bye")
myFun(*args)
kwargs = {"arg1" : "Geek", "arg2" : "Nerd", "arg3" : "Noob" }
myFun(**kwargs)
Output:
Hey
hi
bye
Geek
Nerd
Noob
I don’t think that you really need to accept other arguments except x and y or change func3.
Let’s assume you want to pass z to inner function. And you need to pass it to func3 as well. Since z will not change during func3 call you can just do something like func3(x, y, lambda x, y : your_func (x, y , z), step) and use functions with any number of arguments through lambda which accepts x and y.
In your case call will look like:
x, y = func3(x_val[i], y_val[i], step_size, lambda x, y: func2(x, y, 111))

How to pass an array as argument (in Python) when solving ODE by odeint function

My ODE is given as Mx''+Lx'+f(x)=0 where f(x) is a polynomial function. Please look at my FULL CODE where I defined the differential equation in a function namely 'diff'. Then I use 'odeint' which calls 'diff' along with the necessary arguments to solve the differential equaion.
Now I consider f(x)=ax. Here I have to pass three parameters in total (M,L,a) as the argument to the 'diff' function. As a matter of fact the code works if I write: (see full code)
sol = odeint(diff, y0, t, args=(M,L, a))
But when f(x) is a polynomial up to 10 power of 'x', then the parameter list becomes too long. Therefore I want to put all the parameters in an array and then pass that array as argument. I tried in this way:
def diff(y, t, inertia):
M=inertia[0]
L=inertia[1]
a=inertia[2]
x,v = y
dydt = [v, (-L*v - a*x)/M]
return dydt
M=5
L = 0.5
a = 5.0
Inertia=(M,L,a)
sol = odeint(diff, y0, t, args=Inertia)
But this approach doen't work. It says 'TypeError: diff() takes 3 positional arguments but 5 were given'.
How can I can I make this approach work, or how to send a list of parameters as argument?
Full Code:
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
def diff(y, t, M, L, a):
x,v = y
dydt = [v, (-L*v - a*x)/M]
return dydt
M=5
L = 0.5
a = 5.0
#Inertia=(M,L,a)
#But I cant pass the 'Inertia' as an argument
y0 = [- 0.1, 0.0]
t = np.linspace(0, 10, 101)
sol = odeint(diff, y0, t, args=(M,L, a))
plt.plot(t, sol[:, 0], 'b', label='x(t)')
plt.plot(t, sol[:, 1], 'g', label='v(t)')
plt.legend(loc='best')
plt.show()
Inertia in this case is a tuple. odeint expects a tuple of arguments as its args parameter, so Inertia gets unpacked and the arguments to diff become y0, t, M, L, a. To circumvent this, you should pack Inertia in another tuple to make Inertia a single argument, like so:
sol = odeint(diff, y0, t, args=(Inertia,))
Note the , after Inertia. this makes it a tuple ((a) == a, (a,) == tuple(a))
Your approach doesn't work because you have assigned inertia as a tuple instead of an array. Correct is inertia=[a,b,c].
As arguments are passed to functions as well, your "array" gets appended to the other argumenty when passing it to a function an so this function receives 5 arguments.

passing arguments to a function for fitting

I am trying to fit a function which takes as input 2 independent variables x,y and 3 parameters to be found a,b,c. This is my test code:
import numpy as np
from scipy.optimize import curve_fit
def func(x,y, a, b, c):
return a*np.exp(-b*(x+y)) + c
y= x = np.linspace(0,4,50)
z = func(x,y, 2.5, 1.3, 0.5) #works ok
#generate data to be fitted
zn = z + 0.2*np.random.normal(size=len(x))
popt, pcov = curve_fit(func, x,y, zn) #<--------Problem here!!!!!
But i am getting the error: "func() takes exactly 5 arguments (51 given)". How can pass my arguments x,y correctly?
A look at the documentation of scipy.optimize.curve_fit() is all it takes. The prototype is
scipy.optimize.curve_fit(f, xdata, ydata, p0=None, sigma=None, **kw)
The documentation states curve_fit() is called with the target function as the first argument, the independent variable(s) as the second argument, the dependent variable as the third argument ans the start values for the parameters as the forth argument. You tried to call the function in a completely different way, so it's not surprising it does not work. Specifically, you passed zn as the p0 parameter – this is why the function was called with so many parameters.
The documentation also describes how the target function is called:
f: callable
The model function, f(x, ...). It must take the independent variable as the first argument and the parameters to fit as separate remaining arguments.
xdata : An N-length sequence or an (k,N)-shaped array
for functions with k predictors. The independent variable where the data is measured.
You try to uses to separate arguments for the dependent variables, while it should be a single array of arguments. Here's the code fixed:
def func(x, a, b, c):
return a * np.exp(-b * (x[0] + x[1])) + c
N = 50
x = np.linspace(0,4,50)
x = numpy.array([x, x]) # Combine your `x` and `y` to a single
# (2, N)-array
z = func(x, 2.5, 1.3, 0.5)
zn = z + 0.2 * np.random.normal(size=x.shape[1])
popt, pcov = curve_fit(func, x, zn)
Try to pass the first two array parameters to func as a tuple and modify func to accept a tuple of parameters
Normally it is expected the curvefit would accept an x and y parameter func(x) as an input to fit the curve. Strangely in your example as your x parameter is not a single value but two values (not sure why), you have to modify your function so that it accept the x as a single parameter and expands it within.
Generally speaking, three dimensional curve fitting should be handled in a different manner from what you are trying to achieve. You can take a look into the following SO post which tried to fit a three dimensional scatter with a line.
>>> def func((x,y), a, b, c):
return a*np.exp(-b*(x+y)) + c
>>> y= x = np.linspace(0,4,50)
>>> z = func((x,y), 2.5, 1.3, 0.5) #works ok
>>> zn = z + 0.2*np.random.normal(size=len(x))
>>> popt, pcov = curve_fit(func, (x,y), zn)

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
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 want a simple solution based on curve_fit, I'd suggest that you wrap your function in a class. Minimal example:
import numpy
from scipy.optimize import curve_fit
class FitModel(object):
def f(self, x, a, b, c):
return x * 2*a + 4*b - 5*c
def f_a(self, x, b, c):
return self.f(x, self.a, b, c)
# user supplies a = 1.0
fitModel = FitModel()
fitModel.a = 1.0
xdata = numpy.array([1,3,6,8,10])
ydata = numpy.array([ 0.91589774, 4.91589774, 10.91589774, 14.91589774, 18.91589774])
initial = (1.0,2.0)
popt, pconv = curve_fit(fitModel.f_a, xdata, ydata, initial)
There is already a package that does this:
https://lmfit.github.io/lmfit-py/index.html
From the README:
"LMfit-py provides a Least-Squares Minimization routine and class
with a simple, flexible approach to parameterizing a model for
fitting to data. Named Parameters can be held fixed or freely
adjusted in the fit, or held between lower and upper bounds. In
addition, parameters can be constrained as a simple mathematical
expression of other Parameters."
def f(x, a = 10, b = 15, c = 25):
return x * 2*a + 4*b - 5*c
If the user doesn't supply an argument for the parameter in question, whatever you specified on the right-hand side of the = sign will be used:
e.g.:
f(5, b = 20) will evaluate to return 5 * 2*10 + 4*20 - 5*25 and
f(7) will evaluate to return 7 * 2*10 + 4*15 - 5*25

Categories

Resources