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()
Asked By: carpanaut

||

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.

Answered By: Kevin Powell

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()
Answered By: carpanaut
Categories: questions Tags: , , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.