Why can't I pass a decorated function to scipy.integrate.ode?
Question:
When I pass a decorated function to scipy.integrate.ode, the wrapper function gets called, but *args is empty. Why is this happening?
This works:
y0, t0 = 1, 0
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
This doesn’t:
y0, t0 = 1, 0
def dec_func(func):
def wrap_func(*args, **kwargs):
return func(*args, **kwargs)
return wrap_func
@dec_func
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
It return this error: TypeError: dydt() missing 2 required positional arguments: 't' and 'y'
When I insert a line into the wrapper function to print the length of args, it comes back as 0.
EDIT:
This doesn’t work either. Same result as main post
from functools import wraps
y0, t0 = 1, 0
def dec_func(func):
@wraps(func)
def wrap_func(*args, **kwargs):
return func(*args, **kwargs)
return wrap_func
@dec_func
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
Answers:
The way you are constructing the decorator — in particular the inner function — seems to mangle the function signature of dydt
. scipy.integrate.ode
needs to be passed a callable f(t,y) with signature such that t is a scalar, y.shape == (n,)
and f
returns a scalar or list (taken from the scipy docs). The result of decorating dydt
the way you did does not seem to conform — although the difference is somewhat lost on me.
doing it this way works:
def dec_func(func):
def wrap_func(t, y):
return func(t, y)
return wrap_func
@dec_func
def dydt2(t, y):
return y*0.5
r = scipy.integrate.ode(dydt2)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
I did try a few things that I thought would allow you to keep the inner function generic (args, kwargs), but I never found anything that worked.
It’s not that you can’t pass a decorated function. Unfortunately there seem to be some hidden requirements as to the way the function is decorated, though.
So it’s still an open question exactly what is going on inside scipy. Here is the workaround I’m going with.
y0, t0 = 1, 0
def dec_func(func):
def wrap_func(t, y, *args, **kwargs):
return func(t, y, *args, **kwargs)
return wrap_func
@dec_func
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
When I pass a decorated function to scipy.integrate.ode, the wrapper function gets called, but *args is empty. Why is this happening?
This works:
y0, t0 = 1, 0
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
This doesn’t:
y0, t0 = 1, 0
def dec_func(func):
def wrap_func(*args, **kwargs):
return func(*args, **kwargs)
return wrap_func
@dec_func
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
It return this error: TypeError: dydt() missing 2 required positional arguments: 't' and 'y'
When I insert a line into the wrapper function to print the length of args, it comes back as 0.
EDIT:
This doesn’t work either. Same result as main post
from functools import wraps
y0, t0 = 1, 0
def dec_func(func):
@wraps(func)
def wrap_func(*args, **kwargs):
return func(*args, **kwargs)
return wrap_func
@dec_func
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
The way you are constructing the decorator — in particular the inner function — seems to mangle the function signature of dydt
. scipy.integrate.ode
needs to be passed a callable f(t,y) with signature such that t is a scalar, y.shape == (n,)
and f
returns a scalar or list (taken from the scipy docs). The result of decorating dydt
the way you did does not seem to conform — although the difference is somewhat lost on me.
doing it this way works:
def dec_func(func):
def wrap_func(t, y):
return func(t, y)
return wrap_func
@dec_func
def dydt2(t, y):
return y*0.5
r = scipy.integrate.ode(dydt2)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()
I did try a few things that I thought would allow you to keep the inner function generic (args, kwargs), but I never found anything that worked.
It’s not that you can’t pass a decorated function. Unfortunately there seem to be some hidden requirements as to the way the function is decorated, though.
So it’s still an open question exactly what is going on inside scipy. Here is the workaround I’m going with.
y0, t0 = 1, 0
def dec_func(func):
def wrap_func(t, y, *args, **kwargs):
return func(t, y, *args, **kwargs)
return wrap_func
@dec_func
def dydt(t, y):
return y*0.5
r = scipy.integrate.ode(dydt)
r.set_initial_value(y0)
r.integrate(10)
assert r.successful()