sharkmas
sharkmas

Reputation: 285

Pass args for solve_ivp (new SciPy ODE API)

For solving simple ODEs using SciPy, I used to use the odeint function, with form:

scipy.integrate.odeint(func, y0, t, args=(), Dfun=None, col_deriv=0, full_output=0, ml=None, mu=None, rtol=None, atol=None, tcrit=None, h0=0.0, hmax=0.0, hmin=0.0, ixpr=0, mxstep=0, mxhnil=0, mxordn=12, mxords=5, printmessg=0)[source]

where a simple function to be integrated could include additional arguments of the form:

def dy_dt(t, y, arg1, arg2):
    # processing code here

In SciPy 1.0, it seems the ode and odeint funcs have been replaced by a newer solve_ivp method.

scipy.integrate.solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, events=None, vectorized=False, **options)

However, this doesn't seem to offer an args parameter, nor any indication in the documentation as to implementing the passing of args.

Therefore, I wonder if arg passing is possible with the new API, or is this a feature that has yet to be added? (It would seem an oversight to me if this features has been intentionally removed?)

Reference: https://docs.scipy.org/doc/scipy/reference/integrate.html

Upvotes: 22

Views: 18538

Answers (6)

Bill
Bill

Reputation: 11613

For completeness, I think you can also do this but I'm not sure why you would bother since the other two options posted here are perfectly fine.

from functools import partial
fun = partial(dy_dt, arg1=arg1, arg2=arg2)
scipy.integrate.solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, events=None, vectorized=False, **options)

Upvotes: 2

Tejas Shetty
Tejas Shetty

Reputation: 715

According to Javier-Acuna's ultra-brief, ultra-useful answer, the feature that you (as well as I) desire has recently been added. This was announced on Github by none other than the great Warren Weckesser (See his Github, StackOverflow) himself. Anyway, jokes aside the docstring of solve_ivp has an example using it in for the `Lotka-Volterra equations

solve_ivp( fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, events=None, vectorized=False, args=None, **options, )

So, just include args as a tuple. In your case

args = (arg1, arg2)

Please don't use my answer unless your scipy version >= 1.4 . There is no args parameter in solve_ivp for versions below it. I have personally experienced my answer failing for version 1.2.1.

The implementation by zahabaz would probably still work fine in case your scipy version < 1.4

Upvotes: 2

Javier-Acuna
Javier-Acuna

Reputation: 161

Recently the 'args' option was added to solve_ivp, see here: https://github.com/scipy/scipy/issues/8352#issuecomment-535689344

Upvotes: 6

zahbaz
zahbaz

Reputation: 183

Adding to Cleb's answer, here's an example for using the lambda t,y: fun(t,y,args) method. We set up the function handle that returns the rhs of a second order homogeneous ODE with two parameters. Then we feed it to our solver, along with a couple options.

import numpy as np
from scipy import integrate
import matplotlib.pyplot as plt


def rhs_2nd_order_ode(t, y, a, b):
    """
    2nd order ODE function handle for use with scipy.integrate.solve_ivp
    Solves u'' + au'+ bu = 0 after reducing order with y[0]=u and y[1]=u'.

    :param t: dependent variable
    :param y: independent variables
    :param a: a
    :param b: b
    :return: Returns the rhs of y[0]' = y[1] and y[1]' = -a*y[1] - b*y[0]
    """
    return [y[1], -a*y[1] - b*y[0]]


if __name__ == "__main__":
    t_span = (0, 10)
    t_eval = np.linspace(t_span[0], t_span[1], 100)
    y0 = [0, 1]
    a = 1
    b = 2
    sol = integrate.solve_ivp(lambda t,y: rhs_2nd_order_ode(t,y,a,b), t_span, y0, 
                              method='RK45', t_eval=t_eval)

    fig, ax = plt.subplots(1, 1)
    ax.plot(sol.t, sol.y[0])
    ax.set(xlabel='t',ylabel='y')

Upvotes: 1

Lev K.
Lev K.

Reputation: 351

Relatively recently there appeared a similar question on scipy's github. Their solution is to use lambda:

solve_ivp(fun=lambda t, y: fun(t, y, *args), ...)

And they argue that there is already enough overhead for this not to matter.

Upvotes: 16

rlee827
rlee827

Reputation: 1873

It doesn't seem like the new function has an args parameter. As a workaround you can create a wrapper like

def wrapper(t, y):
    orig_func(t,y,hardcoded_args)

and pass that in.

Upvotes: 9

Related Questions