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))
Related
I'm developing a scientific library where I would define vector functions in the time and frequency domain (linked by FFT). I created a class for vector formulas in the freq domain, and now I'd want to define an identical class for the time domain.
I want that in the time domain, the class functions - although being identical to their frequency-domain twin - have one parameter named t instead of omega. Is there an easier way of achieving this instead of repeated definition of every single method, while maintaining readibility?
My code:
(Note: my classes are much more complicated, and one can't just use the functions as formula.x_func(...) - some checking and etc are included. Also, there are actually 6 components.)
class VecFormula(object):
pass
class FreqFormula(VecFormula):
def __init__(self, x_func, y_func, z_func):
self.x_func = x_func
self.y_func = y_func
self.z_func = z_func
def x(self, x, y, z, omega, params):
return self.x_func(x, y, z, omega, params)
def y(self, x, y, z, omega, params):
return self.y_func(x, y, z, omega, params)
def z(self, x, y, z, omega, params):
return self.z_func(x, y, z, omega, params)
def component(self, comp, x, y, z, omega, params):
if comp == 'x':
return self.x(x, y, z, omega, params)
elif comp == 'y':
return self.y(x, y, z, omega, params)
elif comp == 'z':
return self.z(x, y, z, omega, params)
else:
raise ValueError(f'invalid component: {comp}')
class TimeFormula(FreqFormula):
"same as FreqFormula, but the omega parameter is renamed to t"
def x(self, x, y, z, t, params):
return super(TimeFormula, self).x(x, y, z, t, params)
def y(self, x, y, z, t, params):
return super(TimeFormula, self).y(x, y, z, t, params)
def z(self, x, y, z, t, params):
return super(TimeFormula, self).z(x, y, z, t, params)
def component(self, comp, x, y, z, t, params):
return super(TimeFormula, self).component(x, y, z, t, params)
It is easy to add methods to a class after creation they are just class attributes. The hard part here, is that you need to dynamically create new functions to clone the methods from the original class to be able to change their signature.
It is not the most clear part of Python, and dynamic function creation is not documented in the official reference documentations but can be found on SO: Python: dynamically create function at runtime
So here is a possible way:
# have the new class derive from the common base
class TimeFormula(VecFormula):
"same as FreqFormula, but the omega parameter is renamed to t"
pass
# loop over methods of origina class
for i,j in inspect.getmembers(FreqFormula, inspect.isfunction):
# copy the __init__ special method
if i == '__init__':
setattr(TimeFormula, i, j)
elif i.startswith('__'): continue # ignore all other special attributes
if not j.__qualname__.endswith('.'.join((FreqFormula.__name__, i))):
continue # ignore methods defined in parent classes
# clone the method from the original class
spec = inspect.getfullargspec(j)
newspec = inspect.FullArgSpec(['t' if i == 'omega' else i
for i in spec.args], *spec[1:])
f = types.FunctionType(j.__code__, j.__globals__, i, newspec, j.__closure__)
f.__qualname__ = '.'.join((TimeFormula.__qualname__, i))
# adjust the signature
sig = inspect.signature(j)
if ('omega' in sig.parameters):
f.__signature__ = sig.replace(
parameters = [p.replace(name='t') if name == 'omega' else p
for name, p in sig.parameters.items()])
# and finally insert the new method in the class
setattr(TimeFormula, i, f)
Sounds like you would achieve what you need through the use of class inheritance elegantly.
Please check out class inheritance python documentation or here.
Let's say I have the following python code
y = 2
def f(x, y):
y = y**2
return x*y
for i in range(5):
print(f(2,y))
Is it somehow possible to make the change to y within f global while still passing it to f as an argument?
I know that
y = 2
def f(x, y):
global y
y = y**2
return x*y
for i in range(5):
print(f(2,y))
will not work because y cannot be both global and a function parameter.
The 'ugly solution that I have is simply not to pass y as an argument:
y = 2
def f(x):
global y
y = y**2
return x*y
for i in range(5):
print(f(2,y))
but I am not satisfied with this, as I would like to explicitly pass y to the function and basically call it by reference.
The background of this question is that I would like to use scipy's odeint, and I have to use sparse matrices in the computation of the derivative that also change with time.
If I want to avoid converting these to numpy and back to sparse at every timestep, I have to store them globally and modify them from within the function. Because the output of the function is dictated by odeint (it has to be said derivative) it is not an option to include these matrices in the output (and I don't know how that would work anyway, because I'd have to mix scalars and matrices in the output array).
It would be nice if I could somehow pass them as a parameter but make the changes to them from within the function globally permanent.
Just use a different name for the formal argument to f:
y = 2
def f(x, y2):
global y
y = y2**2
return x*y
for i in range(5):
print(f(2,y))
If I understand your intent, then I believe this should work for you.
You cannot do this exactly, for the reason you have described: a variable cannot be at the same time global and a local argument.
However, one solution would be to do this:
y_default = 2
def f(x, y=None):
if y is None:
y = y_default
y = y**2
return x*y
This will do what you want, as you can now call f(2) or f(2,3)
Essentially the problem is that y is global and local as the error message will suggest. Therefore you avoid the local variable issue by introducing a variable z locally. You can still pass y into z, which then yields the desired result.
y = 2
def f(x, z):
y = z**2
global y
return x*y
for i in range(5):
print f(2,y)
I am doing a multi integral with 4 variables, among them 2 have limits as functions. However the error appears on one of my constant-limit variable. Really cannot figure our why. Many thanks for your advice!
from numpy import sqrt, sin, cos, pi, arcsin, maximum
from sympy.functions.special.delta_functions import Heaviside
from scipy.integrate import nquad
def bmax(x):
return 1.14*10**9/sin(x/2)**(9/7)
def thetal(x,y,z):
return arcsin(3.7*10**15*sqrt(cos(x/2)**2/10**6-1.23*10**10/z+0.003*sin(x/2)**2*(2.51*10**63/sin(x/2)**9/y**7-1))/(z*sin(x/2)**2*cos(x/2)*(2.51*10**63/sin(x/2)**9/y**7-1)))
def rt(x,y):
return 3.69*10**12/(2.5*10**63/sin(x/2)**7*y**7-sin(x/2)**2)
def rd(x,y):
return maximum(1.23*10**10,rt(x,y))
def rl(x,y):
return rd(x,y)*(sqrt(1+5.04*10**16/(rd(x,y)*cos(x/2)**2))-1)/2
def wbound():
return [1.23*10**10,3.1*10**16]
def zbound():
return [10**(-10),pi-10**(-10)]
def ybound(z):
return [0,bmax(z)-10**(-10)]
def xbound(z,y,w):
return [thetal(z,y,w),pi-thetal(z,y,w)]
def f(x,y,z,w):
return [5.77/10**30*sin(z)*sin(z/2)*y*sin(x)*Heaviside(w-rl(z,y))*Heaviside(w-rd(z,y))/w**2]
result = nquad(f, [xbound, ybound,zbound,wbound])
The reason for that error is that although you don't want these bounds to depend on the variables, nquad still passes the variables to the functions you provide to it. So the bound functions have to take the right number of variables:
def wbound():
return [1.23*10**10,3.1*10**16]
def zbound(w_foo):
return [10**(-10),pi-10**(-10)]
def ybound(z, w_foo):
return [0,bmax(z)-10**(-10)]
def xbound(z,y,w):
return [thetal(z,y,w),pi-thetal(z,y,w)]
Now the functions zbound and ybound accept the extra variables but simply ignore them.
I'm not sure about the last bound, xbound(...): Do you want the variables y and z to be flipped? The supposedly correct ordering according to the definition of scipy.integrate.nquad would be
def xbound(y,z,w):
...
Edit: As kazemakase pointed out, the function f should return a float instead of a list so the brackets [...] in the return statement should be removed.
nquad expects a sequence of bounds for its second argument, with a rather stringent syntax.
If the integrand f depends on x, y, z, w and this is the order of definition, the terms in bounds must be, in sequence, xb, yb, zb and wb, where each of the bounds can be either a 2-tuple, e.g., xb = (xmin, xmax)
or a function that returns a 2-tuple.
The critical point is, the arguments of those functions... when we perform the inner integration, in dx, we have available y, z and w for computing the bounds in x, so that it must be
def xb(y,z,w): return(..., ...) — likewise
def yb(z,w): return (..., ...) and
def zb(w): return (..., ...).
The bounds with respect to the last variable of integration must be constant.
To summarize
# DEFINITIONS
def f(x, y, z, w): return .. . # x inner integration, ..., w outer integration
def xb(y,z,w): return (...,...) # or simply xb=(...,...) if it's a constant
def yb(z,w): return (...,...) # or yb=(...,...)
def zb(w): return (...,...) # or zb=(...,...)
wb = (...,...)
# INTEGRATION
result, _ = nquad(f, [xb, yb, zb, wb])
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, ...).
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