scijit.integrate.solve_ivp¶
- scijit.integrate.solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False, events=None, vectorized=False, args=None, rtol=0.001, atol=1e-06, max_step=inf, mxstep=0, npoints=100, terminal=None, direction=None, first_step=None, **options)¶
Solve an initial value problem for a system of ODEs.
Integrates
dy/dt = fun(t, y)fromt_span[0]tot_span[1], starting aty0.- Parameters:
- fun@njit function
f(t, y)orf(t, y, args) Right-hand side, t-first.
yis a 1-D float64 array and the return must be a new 1-D float64 array of the same length. The three-argument form receivesargs, on every method. A plain@njitfunction, on every method.- t_span2-tuple or length-2 array of float
(t0, tf).tf < t0integrates backwards, on every method. Withoutt_eval,tf == t0returns the initial state twice; witht_evalit reports nothing.- y0float64 or complex128 array, shape (n,)
Initial state. Must be 1-D. A
complex128y0integrates in the complex domain on'RK45','RK23'and'DOP853', and the result’syandsolarecomplex128.'LSODA'raises there. The dtype is read when the call compiles, so the result type never depends on a value.- methodstr, optional
'RK45'(default),'RK23','DOP853'or'LSODA'. Case sensitive. Inside@njitit must be a string literal, since it selects which solver is compiled in.'Radau'and'BDF'raise.- t_evalfloat64 array or None, optional
Times to report at, inside
t_spanand STRICTLY ordered in the direction of travel, so a repeat is refused.Nonemeans the solver’s own steps, on every method. An emptyt_evalreports nothing on every method, which is a different input fromNone. Must be 1-D.- dense_outputbool, optional
Compile-time literal.
Trueadds a callablesolto the result and changes the return type. Available on every method.- events@njit function
g(t, y)org(t, y, args), optional Event functions, evaluated together and returning a float64 array of
ngvalues, one per event. A root of any of them is reported.ngis learnt by calling the function once at(t_span[0], y0). A single event may return a scalar. The three-argument form receivesargs, and the arity must agree withargsin both directions or the call raisesTypeError.- vectorizedbool, optional
Whether the right-hand side is written to take a block of states:
Truemeans it is called asfun(t, y[:, None]), shape(n, 1), and its result ravelled. Accepted on every method and on both callback spellings.Inside
@njitit must be a literal, since it selects which adapter is built when the call compiles. A variable raises.- argsfloat64 array or None, optional
Extra parameters, one flat float64 buffer. Reaches
fun,jacandeventson every method. The callback has to be able to receive it: a three-argumentf(t, y, args). A two-argumentf(t, y)givenargsraisesTypeError, and a three-argument one given noargsraisesTypeError, in both directions. An EMPTYargscarries no parameters, so it counts as absent in both:f(t, y)runs andf(t, y, args)raises, from Python and from inside@njit.- rtol, atolfloat or float64 array, optional
Tolerances,
1e-3and1e-6. Either may be a scalar or an array with one entry per component ofy0, on every method.atolis shape-checked andrtolis not, so a length-1atolarray raises wherelen(y0) > 1and a length-1rtolarray broadcasts.An
rtolbelow100 * eps, 2.220446049250313e-14, is raised to it and aUserWarningsays so, on every method.atol = 0is legal and makes the tolerance purely relative; a negativeatolraises. A purely relative tolerance collapses the step size wherever a component passes through zero.- max_stepfloat, optional
Largest step allowed.
np.infby default.- mxstepint, optional
'LSODA'only: step limit per output interval.0selects LSODA’s own 500. On the RK methods it is ignored with aUserWarning.- npointsint, optional
'LSODA'witht_eval=Noneonly: how many uniformly spaced output times to produce, INSTEAD of the solver’s own steps. The default, 100, reports the steps; any other value reports the grid. On the RK methods it is ignored with aUserWarning.- terminalint64 array, shape (ng,), optional
nstops the integration at that event’sn-th occurrence and0never stops it.None(the default) makes every event non-terminal. A negative or fractional entry raisesValueError.With two terminal events crossing in one step the earlier root in TIME stops the run, not the lower event index.
- directionfloat64 array, shape (ng,), optional
Positive keeps upward crossings only, negative downward only, zero keeps both.
None(the default) keeps both for every event.- first_stepfloat or None, optional
Initial step size.
None, the default, lets the solver choose one. Reaches all four methods.- **optionsdict, optional
The keyword-only
'LSODA'controlsmin_step,lband,ubandandjac, listed under Other Parameters. Any other keyword raisesTypeError.
- fun@njit function
- Returns:
- resOdeResult, or OdeResultDense when
dense_output=True res.tshape(m,);res.yshape(n, m), component-major;res.nfevright-hand-side evaluations;res.njevJacobian evaluations;res.nluLU decompositions;res.status0 when the end oft_spanwas reached and -1 when a step failed;res.messageandres.success.res.solis present only underdense_output=Trueand is callable asres.sol(t)for a scalar or an array of times. Withoutevents,res.t_eventsandres.y_eventsareNone; witheventsboth are lists of arrays, one entry per event, shapes(k_i,)and(k_i, n).
- resOdeResult, or OdeResultDense when
- Other Parameters:
- min_stepfloat, optional
'LSODA'only: smallest step allowed.0.0, the default, means no floor. On the RK methods it is ignored with aUserWarning.- lband, ubandint or None, optional
'LSODA'only: ignored with aUserWarningon the RK methods. Half-bandwidths of the Jacobian, counting the sub- and super-diagonals and excluding the main diagonal, sod f_i / d y_jis taken as zero outsidei - lband <= j <= i + uband. Setting either one selects LSODA’s banded Jacobian and the other becomes 0.Noneon both, the default, keeps the full Jacobian.LSODA rebuilds the Jacobian by finite differences unless
jacis given, and a full rebuild costslen(y0)right-hand-side evaluations againstlband + uband + 1for a banded one. Measured on an 80-point heat equation by method of lines, whose Jacobian is tridiagonal,rtol=1e-6 atol=1e-9over[0, 0.5]: 6 rebuilds,nfev597 without the bands and 135 withlband=uband=1, values agreeing to 0.000e+00, and 2.85x less wall time over 20 solves.A value outside
0 <= lband, uband <= len(y0) - 1reaches ODEPACK, which reports “Illegal input detected (internal error)” and returnssuccess=False.A band NARROWER than the Jacobian is legal and expensive. ODEPACK takes it as an approximation, and the corrector then needs many more iterations. Measured on the same 40-point problem,
lband=1alone against the correctlband=uband=1:nfev4523 andnjev640 against 265 and 17, landing 1.083e-06 away. This can exhaustmxstep, which ODEPACK counts PER OUTPUT INTERVAL, and the run then reports “Excess work done on this call”. Raisemxstep: atmxstep=100000every narrow band above succeeds.- jac@njit function
j(t, y)orj(t, y, args), optional 'LSODA'only: ignored with aUserWarningon the RK methods. The Jacobiand f / d y. Without it LSODA builds one by finite differences, at the cost above.Returns
(n, n)withjac[i, j] = d f_i / d y_j. Withlband/ubandit returns the packed banded form instead,(lband + uband + 1, n)withjac_packed[uband + i - j, j] = d f_i / d y_j. A wrong shape raisesValueError, from one evaluation at(t_span[0], y0).The three-argument form receives
args, asfun’s does.Measured on Robertson, 3 states,
rtol=1e-8 atol=1e-10:nfev502 withoutjacand 424 with,njev26 either way, values agreeing to 1.369777e-11. The 78 evaluations removed are exactly the26 * 3the finite-difference rebuilds cost.
- Raises:
- ValueError
methodoutside'RK45','RK23','DOP853','LSODA';'Radau'or'BDF'; at_spanwithout exactly two elements; ananint_span; ay0that is not 1-D or not finite; at_evalthat is not 1-D, leavest_spanor is not strictly ordered; a non-positivemax_step; a non-positivefirst_step, or one exceeding the span; a negativemin_step; a negativeatolor one of the wrong shape; anrtolof the wrong length; aterminalentry that is negative or fractional; aterminalordirectionwhose length is notng; ajacof the wrong shape; a right-hand side returning the wrong length; a complexy0on'LSODA','Radau'or'BDF'; a complexy0together withevents; and, on'LSODA', a right-hand side that never wroteydot.- TypeError
A nested or 2-D
t_span; an arity mismatch betweenargsandfunorevents, in either direction.- RuntimeError
An event root that Brent’s method did not bracket in 100 iterations.
See also
scipy.integrate.solve_ivpThe scipy routine this mirrors.
Notes
Every method is
prange-safe.'RK45','RK23'and'DOP853'hold no module state.'LSODA'reaches Fortran, and its callback slot is!$omp threadprivate, so each thread reads its own copy.The solution object.
res.sol(t)takes a float or a float64 array. A Python list is not an array: numba types it as a reflected list, which no array guard admits, sores.sol([0.1, 0.2])raisesTypeError. Wrap it,res.sol(np.array([0.1, 0.2])).A solution object reaches compiled code as an argument, as a module global, or captured in a closure; a registered jitclass instance is a compile-time constant.
Reading a shared solution object from a
prangeloop is safe and reproduces the serial result exactly. Writing a field from several threads is a data race.Differences from scipy.
solis absent from the result unlessdense_output=True, where scipy always carries the field and sets it toNone. Sohasattr(res, 'sol')answers whatres.sol is Noneanswers in scipy.eventsis one function returningngvalues, where scipy takes a callable or a list of them, andterminal/directionare arrays passed alongside rather than attributes set on the function. A single event may return a scalar, as scipy’s do.nfevafter a terminal event covers the WHOLE span. scipy leaves its stepping loop at the event, so its count stops there; this integrates the span and trims the history afterwards.jac,lband,uband,min_step,mxstepandnpointson'RK45','RK23'and'DOP853'are ignored with oneUserWarningper call, “The following arguments have no effect for a chosen solver: mxstep, npoints, min_step, jac, lband, uband.”, which is character for character scipy’s on the same call. The answer is the one the same call gives without them.min_step,jac,lbandandubandwarn when the argument was PASSED, whatever its value, sosolve_ivp(..., 'RK45', jac=None)warns.mxstepandnpointswarn when they carry a value other than their default, sosolve_ivp(..., 'RK45', mxstep=0)does not. Neither has a scipy counterpart.The names are listed in signature order. scipy lists them in the order the caller passed them, which a compiled call site does not preserve.
An unrecognised keyword argument raises
TypeError. scipy absorbs it into**optionsand names it in the sameUserWarning.jacis a plain@njitfunction takingargsas one flat float64 buffer, matchingfun, rather than scipy’s callable with unpacked parameters.argsreaches the callback as one flat float64 buffer rather than unpacked into separate parameters. scipy callsf(t, y, *args); this callsf(t, y, args)with everything packed into one array, because numba has no*args. Which methods accept it no longer differs: every one does, as in scipy.An arity mismatch between
funandargsis refused in both directions, as in scipy:argsgiven to anf(t, y), andargsomitted for anf(t, y, args). An emptyargsis not a mismatch either way. The exception isTypeError, scipy’s, from python, andTypingErrorfrom inside@njit. One case differs there: an array’s length is not known when the call compiles, so an emptyargsreads as given inside@njitand as absent from python.tf == t0with at_eval, and an emptyt_evalon any span, report nothing, as in scipy. scipy returnstandyas empty Python lists there and this returns empty arrays, shapes(0,)and(n, 0).eventsand a complexy0together raise. scipy carries both: it stores the state at each event root, and here that store isfloat64. Every other combination of a complexy0witht_eval,dense_outputandargsis supported.'Radau'and'BDF'raise. They are planned.The result is a namedtuple with scipy’s field names, so
res.yworks andres['y']does not.'LSODA'with at_evaland neitherdense_outputnoreventsreports through a run that stops at each requested time, the route scipy’sodeinttakes, where scipy’ssolve_ivpsteps freely and interpolates. This returns scipy’sodeintanswer. Asking fordense_outputoreventstakes the stepping route instead, matching scipy’ssolve_ivp.
Examples
>>> import numpy as np >>> from numba import njit >>> import scijit.integrate as si >>> @njit ... def rhs(t, y): # y'' = -4 y ... out = np.empty(2) ... out[0] = y[1] ... out[1] = -4.0 * y[0] ... return out >>> @njit ... def run(): ... return si.solve_ivp(rhs, (0.0, 1.0), np.array([1.0, 0.0]), ... 'RK45', np.array([0.0, 0.5, 1.0])) >>> res = run() >>> res.y.shape # (n_states, n_times) (2, 3) >>> res.y[0] array([ 1. , 0.54038693, -0.41629169]) >>> res.success True
dense_output=Trueaddsres.sol, callable on a scalar or an array of times:>>> res = si.solve_ivp(rhs, (0.0, 1.0), np.array([1.0, 0.0]), ... 'RK45', None, True) >>> res.sol(0.5) array([ 0.54038693, -1.68337531]) >>> res.sol(np.array([0.25, 0.5])) array([[ 0.8775798 , 0.54038693], [-0.95885456, -1.68337531]])
eventsis one@njitfunction returning one value per event. A root of any of them is reported inres.t_events:>>> @njit ... def hits_zero(t, y): ... out = np.empty(1) ... out[0] = y[0] ... return out >>> res = si.solve_ivp(rhs, (0.0, 4.0), np.array([1.0, 0.0]), 'RK45', ... events=hits_zero) >>> res.t_events[0] array([0.78528433, 2.35559604, 3.92597172])
terminalstops the integration at the first root, where scipy sets a.terminalattribute on the event function:>>> res = si.solve_ivp(rhs, (0.0, 4.0), np.array([1.0, 0.0]), 'RK45', ... events=hits_zero, terminal=np.array([1])) >>> res.t_events[0] array([0.78528433]) >>> res.status 1 >>> res.message 'A termination event occurred.'