Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/scipy/integrate/_ivp/ivp.py : 11%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import inspect
2import numpy as np
3from .bdf import BDF
4from .radau import Radau
5from .rk import RK23, RK45, DOP853
6from .lsoda import LSODA
7from scipy.optimize import OptimizeResult
8from .common import EPS, OdeSolution
9from .base import OdeSolver
12METHODS = {'RK23': RK23,
13 'RK45': RK45,
14 'DOP853': DOP853,
15 'Radau': Radau,
16 'BDF': BDF,
17 'LSODA': LSODA}
20MESSAGES = {0: "The solver successfully reached the end of the integration interval.",
21 1: "A termination event occurred."}
24class OdeResult(OptimizeResult):
25 pass
28def prepare_events(events):
29 """Standardize event functions and extract is_terminal and direction."""
30 if callable(events):
31 events = (events,)
33 if events is not None:
34 is_terminal = np.empty(len(events), dtype=bool)
35 direction = np.empty(len(events))
36 for i, event in enumerate(events):
37 try:
38 is_terminal[i] = event.terminal
39 except AttributeError:
40 is_terminal[i] = False
42 try:
43 direction[i] = event.direction
44 except AttributeError:
45 direction[i] = 0
46 else:
47 is_terminal = None
48 direction = None
50 return events, is_terminal, direction
53def solve_event_equation(event, sol, t_old, t):
54 """Solve an equation corresponding to an ODE event.
56 The equation is ``event(t, y(t)) = 0``, here ``y(t)`` is known from an
57 ODE solver using some sort of interpolation. It is solved by
58 `scipy.optimize.brentq` with xtol=atol=4*EPS.
60 Parameters
61 ----------
62 event : callable
63 Function ``event(t, y)``.
64 sol : callable
65 Function ``sol(t)`` which evaluates an ODE solution between `t_old`
66 and `t`.
67 t_old, t : float
68 Previous and new values of time. They will be used as a bracketing
69 interval.
71 Returns
72 -------
73 root : float
74 Found solution.
75 """
76 from scipy.optimize import brentq
77 return brentq(lambda t: event(t, sol(t)), t_old, t,
78 xtol=4 * EPS, rtol=4 * EPS)
81def handle_events(sol, events, active_events, is_terminal, t_old, t):
82 """Helper function to handle events.
84 Parameters
85 ----------
86 sol : DenseOutput
87 Function ``sol(t)`` which evaluates an ODE solution between `t_old`
88 and `t`.
89 events : list of callables, length n_events
90 Event functions with signatures ``event(t, y)``.
91 active_events : ndarray
92 Indices of events which occurred.
93 is_terminal : ndarray, shape (n_events,)
94 Which events are terminal.
95 t_old, t : float
96 Previous and new values of time.
98 Returns
99 -------
100 root_indices : ndarray
101 Indices of events which take zero between `t_old` and `t` and before
102 a possible termination.
103 roots : ndarray
104 Values of t at which events occurred.
105 terminate : bool
106 Whether a terminal event occurred.
107 """
108 roots = [solve_event_equation(events[event_index], sol, t_old, t)
109 for event_index in active_events]
111 roots = np.asarray(roots)
113 if np.any(is_terminal[active_events]):
114 if t > t_old:
115 order = np.argsort(roots)
116 else:
117 order = np.argsort(-roots)
118 active_events = active_events[order]
119 roots = roots[order]
120 t = np.nonzero(is_terminal[active_events])[0][0]
121 active_events = active_events[:t + 1]
122 roots = roots[:t + 1]
123 terminate = True
124 else:
125 terminate = False
127 return active_events, roots, terminate
130def find_active_events(g, g_new, direction):
131 """Find which event occurred during an integration step.
133 Parameters
134 ----------
135 g, g_new : array_like, shape (n_events,)
136 Values of event functions at a current and next points.
137 direction : ndarray, shape (n_events,)
138 Event "direction" according to the definition in `solve_ivp`.
140 Returns
141 -------
142 active_events : ndarray
143 Indices of events which occurred during the step.
144 """
145 g, g_new = np.asarray(g), np.asarray(g_new)
146 up = (g <= 0) & (g_new >= 0)
147 down = (g >= 0) & (g_new <= 0)
148 either = up | down
149 mask = (up & (direction > 0) |
150 down & (direction < 0) |
151 either & (direction == 0))
153 return np.nonzero(mask)[0]
156def solve_ivp(fun, t_span, y0, method='RK45', t_eval=None, dense_output=False,
157 events=None, vectorized=False, args=None, **options):
158 """Solve an initial value problem for a system of ODEs.
160 This function numerically integrates a system of ordinary differential
161 equations given an initial value::
163 dy / dt = f(t, y)
164 y(t0) = y0
166 Here t is a 1-D independent variable (time), y(t) is an
167 N-D vector-valued function (state), and an N-D
168 vector-valued function f(t, y) determines the differential equations.
169 The goal is to find y(t) approximately satisfying the differential
170 equations, given an initial value y(t0)=y0.
172 Some of the solvers support integration in the complex domain, but note
173 that for stiff ODE solvers, the right-hand side must be
174 complex-differentiable (satisfy Cauchy-Riemann equations [11]_).
175 To solve a problem in the complex domain, pass y0 with a complex data type.
176 Another option always available is to rewrite your problem for real and
177 imaginary parts separately.
179 Parameters
180 ----------
181 fun : callable
182 Right-hand side of the system. The calling signature is ``fun(t, y)``.
183 Here `t` is a scalar, and there are two options for the ndarray `y`:
184 It can either have shape (n,); then `fun` must return array_like with
185 shape (n,). Alternatively, it can have shape (n, k); then `fun`
186 must return an array_like with shape (n, k), i.e., each column
187 corresponds to a single column in `y`. The choice between the two
188 options is determined by `vectorized` argument (see below). The
189 vectorized implementation allows a faster approximation of the Jacobian
190 by finite differences (required for stiff solvers).
191 t_span : 2-tuple of floats
192 Interval of integration (t0, tf). The solver starts with t=t0 and
193 integrates until it reaches t=tf.
194 y0 : array_like, shape (n,)
195 Initial state. For problems in the complex domain, pass `y0` with a
196 complex data type (even if the initial value is purely real).
197 method : string or `OdeSolver`, optional
198 Integration method to use:
200 * 'RK45' (default): Explicit Runge-Kutta method of order 5(4) [1]_.
201 The error is controlled assuming accuracy of the fourth-order
202 method, but steps are taken using the fifth-order accurate
203 formula (local extrapolation is done). A quartic interpolation
204 polynomial is used for the dense output [2]_. Can be applied in
205 the complex domain.
206 * 'RK23': Explicit Runge-Kutta method of order 3(2) [3]_. The error
207 is controlled assuming accuracy of the second-order method, but
208 steps are taken using the third-order accurate formula (local
209 extrapolation is done). A cubic Hermite polynomial is used for the
210 dense output. Can be applied in the complex domain.
211 * 'DOP853': Explicit Runge-Kutta method of order 8 [13]_.
212 Python implementation of the "DOP853" algorithm originally
213 written in Fortran [14]_. A 7-th order interpolation polynomial
214 accurate to 7-th order is used for the dense output.
215 Can be applied in the complex domain.
216 * 'Radau': Implicit Runge-Kutta method of the Radau IIA family of
217 order 5 [4]_. The error is controlled with a third-order accurate
218 embedded formula. A cubic polynomial which satisfies the
219 collocation conditions is used for the dense output.
220 * 'BDF': Implicit multi-step variable-order (1 to 5) method based
221 on a backward differentiation formula for the derivative
222 approximation [5]_. The implementation follows the one described
223 in [6]_. A quasi-constant step scheme is used and accuracy is
224 enhanced using the NDF modification. Can be applied in the
225 complex domain.
226 * 'LSODA': Adams/BDF method with automatic stiffness detection and
227 switching [7]_, [8]_. This is a wrapper of the Fortran solver
228 from ODEPACK.
230 Explicit Runge-Kutta methods ('RK23', 'RK45', 'DOP853') should be used
231 for non-stiff problems and implicit methods ('Radau', 'BDF') for
232 stiff problems [9]_. Among Runge-Kutta methods, 'DOP853' is recommended
233 for solving with high precision (low values of `rtol` and `atol`).
235 If not sure, first try to run 'RK45'. If it makes unusually many
236 iterations, diverges, or fails, your problem is likely to be stiff and
237 you should use 'Radau' or 'BDF'. 'LSODA' can also be a good universal
238 choice, but it might be somewhat less convenient to work with as it
239 wraps old Fortran code.
241 You can also pass an arbitrary class derived from `OdeSolver` which
242 implements the solver.
243 t_eval : array_like or None, optional
244 Times at which to store the computed solution, must be sorted and lie
245 within `t_span`. If None (default), use points selected by the solver.
246 dense_output : bool, optional
247 Whether to compute a continuous solution. Default is False.
248 events : callable, or list of callables, optional
249 Events to track. If None (default), no events will be tracked.
250 Each event occurs at the zeros of a continuous function of time and
251 state. Each function must have the signature ``event(t, y)`` and return
252 a float. The solver will find an accurate value of `t` at which
253 ``event(t, y(t)) = 0`` using a root-finding algorithm. By default, all
254 zeros will be found. The solver looks for a sign change over each step,
255 so if multiple zero crossings occur within one step, events may be
256 missed. Additionally each `event` function might have the following
257 attributes:
259 terminal: bool, optional
260 Whether to terminate integration if this event occurs.
261 Implicitly False if not assigned.
262 direction: float, optional
263 Direction of a zero crossing. If `direction` is positive,
264 `event` will only trigger when going from negative to positive,
265 and vice versa if `direction` is negative. If 0, then either
266 direction will trigger event. Implicitly 0 if not assigned.
268 You can assign attributes like ``event.terminal = True`` to any
269 function in Python.
270 vectorized : bool, optional
271 Whether `fun` is implemented in a vectorized fashion. Default is False.
272 args : tuple, optional
273 Additional arguments to pass to the user-defined functions. If given,
274 the additional arguments are passed to all user-defined functions.
275 So if, for example, `fun` has the signature ``fun(t, y, a, b, c)``,
276 then `jac` (if given) and any event functions must have the same
277 signature, and `args` must be a tuple of length 3.
278 options
279 Options passed to a chosen solver. All options available for already
280 implemented solvers are listed below.
281 first_step : float or None, optional
282 Initial step size. Default is `None` which means that the algorithm
283 should choose.
284 max_step : float, optional
285 Maximum allowed step size. Default is np.inf, i.e., the step size is not
286 bounded and determined solely by the solver.
287 rtol, atol : float or array_like, optional
288 Relative and absolute tolerances. The solver keeps the local error
289 estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a
290 relative accuracy (number of correct digits). But if a component of `y`
291 is approximately below `atol`, the error only needs to fall within
292 the same `atol` threshold, and the number of correct digits is not
293 guaranteed. If components of y have different scales, it might be
294 beneficial to set different `atol` values for different components by
295 passing array_like with shape (n,) for `atol`. Default values are
296 1e-3 for `rtol` and 1e-6 for `atol`.
297 jac : array_like, sparse_matrix, callable or None, optional
298 Jacobian matrix of the right-hand side of the system with respect
299 to y, required by the 'Radau', 'BDF' and 'LSODA' method. The
300 Jacobian matrix has shape (n, n) and its element (i, j) is equal to
301 ``d f_i / d y_j``. There are three ways to define the Jacobian:
303 * If array_like or sparse_matrix, the Jacobian is assumed to
304 be constant. Not supported by 'LSODA'.
305 * If callable, the Jacobian is assumed to depend on both
306 t and y; it will be called as ``jac(t, y)``, as necessary.
307 For 'Radau' and 'BDF' methods, the return value might be a
308 sparse matrix.
309 * If None (default), the Jacobian will be approximated by
310 finite differences.
312 It is generally recommended to provide the Jacobian rather than
313 relying on a finite-difference approximation.
314 jac_sparsity : array_like, sparse matrix or None, optional
315 Defines a sparsity structure of the Jacobian matrix for a finite-
316 difference approximation. Its shape must be (n, n). This argument
317 is ignored if `jac` is not `None`. If the Jacobian has only few
318 non-zero elements in *each* row, providing the sparsity structure
319 will greatly speed up the computations [10]_. A zero entry means that
320 a corresponding element in the Jacobian is always zero. If None
321 (default), the Jacobian is assumed to be dense.
322 Not supported by 'LSODA', see `lband` and `uband` instead.
323 lband, uband : int or None, optional
324 Parameters defining the bandwidth of the Jacobian for the 'LSODA'
325 method, i.e., ``jac[i, j] != 0 only for i - lband <= j <= i + uband``.
326 Default is None. Setting these requires your jac routine to return the
327 Jacobian in the packed format: the returned array must have ``n``
328 columns and ``uband + lband + 1`` rows in which Jacobian diagonals are
329 written. Specifically ``jac_packed[uband + i - j , j] = jac[i, j]``.
330 The same format is used in `scipy.linalg.solve_banded` (check for an
331 illustration). These parameters can be also used with ``jac=None`` to
332 reduce the number of Jacobian elements estimated by finite differences.
333 min_step : float, optional
334 The minimum allowed step size for 'LSODA' method.
335 By default `min_step` is zero.
337 Returns
338 -------
339 Bunch object with the following fields defined:
340 t : ndarray, shape (n_points,)
341 Time points.
342 y : ndarray, shape (n, n_points)
343 Values of the solution at `t`.
344 sol : `OdeSolution` or None
345 Found solution as `OdeSolution` instance; None if `dense_output` was
346 set to False.
347 t_events : list of ndarray or None
348 Contains for each event type a list of arrays at which an event of
349 that type event was detected. None if `events` was None.
350 y_events : list of ndarray or None
351 For each value of `t_events`, the corresponding value of the solution.
352 None if `events` was None.
353 nfev : int
354 Number of evaluations of the right-hand side.
355 njev : int
356 Number of evaluations of the Jacobian.
357 nlu : int
358 Number of LU decompositions.
359 status : int
360 Reason for algorithm termination:
362 * -1: Integration step failed.
363 * 0: The solver successfully reached the end of `tspan`.
364 * 1: A termination event occurred.
366 message : string
367 Human-readable description of the termination reason.
368 success : bool
369 True if the solver reached the interval end or a termination event
370 occurred (``status >= 0``).
372 References
373 ----------
374 .. [1] J. R. Dormand, P. J. Prince, "A family of embedded Runge-Kutta
375 formulae", Journal of Computational and Applied Mathematics, Vol. 6,
376 No. 1, pp. 19-26, 1980.
377 .. [2] L. W. Shampine, "Some Practical Runge-Kutta Formulas", Mathematics
378 of Computation,, Vol. 46, No. 173, pp. 135-150, 1986.
379 .. [3] P. Bogacki, L.F. Shampine, "A 3(2) Pair of Runge-Kutta Formulas",
380 Appl. Math. Lett. Vol. 2, No. 4. pp. 321-325, 1989.
381 .. [4] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations II:
382 Stiff and Differential-Algebraic Problems", Sec. IV.8.
383 .. [5] `Backward Differentiation Formula
384 <https://en.wikipedia.org/wiki/Backward_differentiation_formula>`_
385 on Wikipedia.
386 .. [6] L. F. Shampine, M. W. Reichelt, "THE MATLAB ODE SUITE", SIAM J. SCI.
387 COMPUTE., Vol. 18, No. 1, pp. 1-22, January 1997.
388 .. [7] A. C. Hindmarsh, "ODEPACK, A Systematized Collection of ODE
389 Solvers," IMACS Transactions on Scientific Computation, Vol 1.,
390 pp. 55-64, 1983.
391 .. [8] L. Petzold, "Automatic selection of methods for solving stiff and
392 nonstiff systems of ordinary differential equations", SIAM Journal
393 on Scientific and Statistical Computing, Vol. 4, No. 1, pp. 136-148,
394 1983.
395 .. [9] `Stiff equation <https://en.wikipedia.org/wiki/Stiff_equation>`_ on
396 Wikipedia.
397 .. [10] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
398 sparse Jacobian matrices", Journal of the Institute of Mathematics
399 and its Applications, 13, pp. 117-120, 1974.
400 .. [11] `Cauchy-Riemann equations
401 <https://en.wikipedia.org/wiki/Cauchy-Riemann_equations>`_ on
402 Wikipedia.
403 .. [12] `Lotka-Volterra equations
404 <https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations>`_
405 on Wikipedia.
406 .. [13] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
407 Equations I: Nonstiff Problems", Sec. II.
408 .. [14] `Page with original Fortran code of DOP853
409 <http://www.unige.ch/~hairer/software.html>`_.
411 Examples
412 --------
413 Basic exponential decay showing automatically chosen time points.
415 >>> from scipy.integrate import solve_ivp
416 >>> def exponential_decay(t, y): return -0.5 * y
417 >>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8])
418 >>> print(sol.t)
419 [ 0. 0.11487653 1.26364188 3.06061781 4.81611105 6.57445806
420 8.33328988 10. ]
421 >>> print(sol.y)
422 [[2. 1.88836035 1.06327177 0.43319312 0.18017253 0.07483045
423 0.03107158 0.01350781]
424 [4. 3.7767207 2.12654355 0.86638624 0.36034507 0.14966091
425 0.06214316 0.02701561]
426 [8. 7.5534414 4.25308709 1.73277247 0.72069014 0.29932181
427 0.12428631 0.05403123]]
429 Specifying points where the solution is desired.
431 >>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8],
432 ... t_eval=[0, 1, 2, 4, 10])
433 >>> print(sol.t)
434 [ 0 1 2 4 10]
435 >>> print(sol.y)
436 [[2. 1.21305369 0.73534021 0.27066736 0.01350938]
437 [4. 2.42610739 1.47068043 0.54133472 0.02701876]
438 [8. 4.85221478 2.94136085 1.08266944 0.05403753]]
440 Cannon fired upward with terminal event upon impact. The ``terminal`` and
441 ``direction`` fields of an event are applied by monkey patching a function.
442 Here ``y[0]`` is position and ``y[1]`` is velocity. The projectile starts
443 at position 0 with velocity +10. Note that the integration never reaches
444 t=100 because the event is terminal.
446 >>> def upward_cannon(t, y): return [y[1], -0.5]
447 >>> def hit_ground(t, y): return y[0]
448 >>> hit_ground.terminal = True
449 >>> hit_ground.direction = -1
450 >>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10], events=hit_ground)
451 >>> print(sol.t_events)
452 [array([40.])]
453 >>> print(sol.t)
454 [0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02
455 1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01]
457 Use `dense_output` and `events` to find position, which is 100, at the apex
458 of the cannonball's trajectory. Apex is not defined as terminal, so both
459 apex and hit_ground are found. There is no information at t=20, so the sol
460 attribute is used to evaluate the solution. The sol attribute is returned
461 by setting ``dense_output=True``. Alternatively, the `y_events` attribute
462 can be used to access the solution at the time of the event.
464 >>> def apex(t, y): return y[1]
465 >>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10],
466 ... events=(hit_ground, apex), dense_output=True)
467 >>> print(sol.t_events)
468 [array([40.]), array([20.])]
469 >>> print(sol.t)
470 [0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02
471 1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01]
472 >>> print(sol.sol(sol.t_events[1][0]))
473 [100. 0.]
474 >>> print(sol.y_events)
475 [array([[-5.68434189e-14, -1.00000000e+01]]), array([[1.00000000e+02, 1.77635684e-15]])]
477 As an example of a system with additional parameters, we'll implement
478 the Lotka-Volterra equations [12]_.
480 >>> def lotkavolterra(t, z, a, b, c, d):
481 ... x, y = z
482 ... return [a*x - b*x*y, -c*y + d*x*y]
483 ...
485 We pass in the parameter values a=1.5, b=1, c=3 and d=1 with the `args`
486 argument.
488 >>> sol = solve_ivp(lotkavolterra, [0, 15], [10, 5], args=(1.5, 1, 3, 1),
489 ... dense_output=True)
491 Compute a dense solution and plot it.
493 >>> t = np.linspace(0, 15, 300)
494 >>> z = sol.sol(t)
495 >>> import matplotlib.pyplot as plt
496 >>> plt.plot(t, z.T)
497 >>> plt.xlabel('t')
498 >>> plt.legend(['x', 'y'], shadow=True)
499 >>> plt.title('Lotka-Volterra System')
500 >>> plt.show()
502 """
503 if method not in METHODS and not (
504 inspect.isclass(method) and issubclass(method, OdeSolver)):
505 raise ValueError("`method` must be one of {} or OdeSolver class."
506 .format(METHODS))
508 t0, tf = float(t_span[0]), float(t_span[1])
510 if args is not None:
511 # Wrap the user's fun (and jac, if given) in lambdas to hide the
512 # additional parameters. Pass in the original fun as a keyword
513 # argument to keep it in the scope of the lambda.
514 fun = lambda t, x, fun=fun: fun(t, x, *args)
515 jac = options.get('jac')
516 if callable(jac):
517 options['jac'] = lambda t, x: jac(t, x, *args)
519 if t_eval is not None:
520 t_eval = np.asarray(t_eval)
521 if t_eval.ndim != 1:
522 raise ValueError("`t_eval` must be 1-dimensional.")
524 if np.any(t_eval < min(t0, tf)) or np.any(t_eval > max(t0, tf)):
525 raise ValueError("Values in `t_eval` are not within `t_span`.")
527 d = np.diff(t_eval)
528 if tf > t0 and np.any(d <= 0) or tf < t0 and np.any(d >= 0):
529 raise ValueError("Values in `t_eval` are not properly sorted.")
531 if tf > t0:
532 t_eval_i = 0
533 else:
534 # Make order of t_eval decreasing to use np.searchsorted.
535 t_eval = t_eval[::-1]
536 # This will be an upper bound for slices.
537 t_eval_i = t_eval.shape[0]
539 if method in METHODS:
540 method = METHODS[method]
542 solver = method(fun, t0, y0, tf, vectorized=vectorized, **options)
544 if t_eval is None:
545 ts = [t0]
546 ys = [y0]
547 elif t_eval is not None and dense_output:
548 ts = []
549 ti = [t0]
550 ys = []
551 else:
552 ts = []
553 ys = []
555 interpolants = []
557 events, is_terminal, event_dir = prepare_events(events)
559 if events is not None:
560 if args is not None:
561 # Wrap user functions in lambdas to hide the additional parameters.
562 # The original event function is passed as a keyword argument to the
563 # lambda to keep the original function in scope (i.e., avoid the
564 # late binding closure "gotcha").
565 events = [lambda t, x, event=event: event(t, x, *args)
566 for event in events]
567 g = [event(t0, y0) for event in events]
568 t_events = [[] for _ in range(len(events))]
569 y_events = [[] for _ in range(len(events))]
570 else:
571 t_events = None
572 y_events = None
574 status = None
575 while status is None:
576 message = solver.step()
578 if solver.status == 'finished':
579 status = 0
580 elif solver.status == 'failed':
581 status = -1
582 break
584 t_old = solver.t_old
585 t = solver.t
586 y = solver.y
588 if dense_output:
589 sol = solver.dense_output()
590 interpolants.append(sol)
591 else:
592 sol = None
594 if events is not None:
595 g_new = [event(t, y) for event in events]
596 active_events = find_active_events(g, g_new, event_dir)
597 if active_events.size > 0:
598 if sol is None:
599 sol = solver.dense_output()
601 root_indices, roots, terminate = handle_events(
602 sol, events, active_events, is_terminal, t_old, t)
604 for e, te in zip(root_indices, roots):
605 t_events[e].append(te)
606 y_events[e].append(sol(te))
608 if terminate:
609 status = 1
610 t = roots[-1]
611 y = sol(t)
613 g = g_new
615 if t_eval is None:
616 ts.append(t)
617 ys.append(y)
618 else:
619 # The value in t_eval equal to t will be included.
620 if solver.direction > 0:
621 t_eval_i_new = np.searchsorted(t_eval, t, side='right')
622 t_eval_step = t_eval[t_eval_i:t_eval_i_new]
623 else:
624 t_eval_i_new = np.searchsorted(t_eval, t, side='left')
625 # It has to be done with two slice operations, because
626 # you can't slice to 0th element inclusive using backward
627 # slicing.
628 t_eval_step = t_eval[t_eval_i_new:t_eval_i][::-1]
630 if t_eval_step.size > 0:
631 if sol is None:
632 sol = solver.dense_output()
633 ts.append(t_eval_step)
634 ys.append(sol(t_eval_step))
635 t_eval_i = t_eval_i_new
637 if t_eval is not None and dense_output:
638 ti.append(t)
640 message = MESSAGES.get(status, message)
642 if t_events is not None:
643 t_events = [np.asarray(te) for te in t_events]
644 y_events = [np.asarray(ye) for ye in y_events]
646 if t_eval is None:
647 ts = np.array(ts)
648 ys = np.vstack(ys).T
649 else:
650 ts = np.hstack(ts)
651 ys = np.hstack(ys)
653 if dense_output:
654 if t_eval is None:
655 sol = OdeSolution(ts, interpolants)
656 else:
657 sol = OdeSolution(ti, interpolants)
658 else:
659 sol = None
661 return OdeResult(t=ts, y=ys, sol=sol, t_events=t_events, y_events=y_events,
662 nfev=solver.nfev, njev=solver.njev, nlu=solver.nlu,
663 status=status, message=message, success=status >= 0)