Hide keyboard shortcuts

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 

10 

11 

12METHODS = {'RK23': RK23, 

13 'RK45': RK45, 

14 'DOP853': DOP853, 

15 'Radau': Radau, 

16 'BDF': BDF, 

17 'LSODA': LSODA} 

18 

19 

20MESSAGES = {0: "The solver successfully reached the end of the integration interval.", 

21 1: "A termination event occurred."} 

22 

23 

24class OdeResult(OptimizeResult): 

25 pass 

26 

27 

28def prepare_events(events): 

29 """Standardize event functions and extract is_terminal and direction.""" 

30 if callable(events): 

31 events = (events,) 

32 

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 

41 

42 try: 

43 direction[i] = event.direction 

44 except AttributeError: 

45 direction[i] = 0 

46 else: 

47 is_terminal = None 

48 direction = None 

49 

50 return events, is_terminal, direction 

51 

52 

53def solve_event_equation(event, sol, t_old, t): 

54 """Solve an equation corresponding to an ODE event. 

55 

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. 

59 

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. 

70 

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) 

79 

80 

81def handle_events(sol, events, active_events, is_terminal, t_old, t): 

82 """Helper function to handle events. 

83 

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. 

97 

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] 

110 

111 roots = np.asarray(roots) 

112 

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 

126 

127 return active_events, roots, terminate 

128 

129 

130def find_active_events(g, g_new, direction): 

131 """Find which event occurred during an integration step. 

132 

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`. 

139 

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)) 

152 

153 return np.nonzero(mask)[0] 

154 

155 

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. 

159 

160 This function numerically integrates a system of ordinary differential 

161 equations given an initial value:: 

162 

163 dy / dt = f(t, y) 

164 y(t0) = y0 

165 

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. 

171 

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. 

178 

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: 

199 

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. 

229 

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`). 

234 

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. 

240 

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: 

258 

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. 

267 

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: 

302 

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. 

311 

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. 

336 

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: 

361 

362 * -1: Integration step failed. 

363 * 0: The solver successfully reached the end of `tspan`. 

364 * 1: A termination event occurred. 

365 

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``). 

371 

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>`_. 

410 

411 Examples 

412 -------- 

413 Basic exponential decay showing automatically chosen time points. 

414 

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]] 

428 

429 Specifying points where the solution is desired. 

430 

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]] 

439 

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. 

445 

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] 

456 

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. 

463 

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]])] 

476 

477 As an example of a system with additional parameters, we'll implement 

478 the Lotka-Volterra equations [12]_. 

479 

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 ... 

484 

485 We pass in the parameter values a=1.5, b=1, c=3 and d=1 with the `args` 

486 argument. 

487 

488 >>> sol = solve_ivp(lotkavolterra, [0, 15], [10, 5], args=(1.5, 1, 3, 1), 

489 ... dense_output=True) 

490 

491 Compute a dense solution and plot it. 

492 

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() 

501 

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)) 

507 

508 t0, tf = float(t_span[0]), float(t_span[1]) 

509 

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) 

518 

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.") 

523 

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`.") 

526 

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.") 

530 

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] 

538 

539 if method in METHODS: 

540 method = METHODS[method] 

541 

542 solver = method(fun, t0, y0, tf, vectorized=vectorized, **options) 

543 

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 = [] 

554 

555 interpolants = [] 

556 

557 events, is_terminal, event_dir = prepare_events(events) 

558 

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 

573 

574 status = None 

575 while status is None: 

576 message = solver.step() 

577 

578 if solver.status == 'finished': 

579 status = 0 

580 elif solver.status == 'failed': 

581 status = -1 

582 break 

583 

584 t_old = solver.t_old 

585 t = solver.t 

586 y = solver.y 

587 

588 if dense_output: 

589 sol = solver.dense_output() 

590 interpolants.append(sol) 

591 else: 

592 sol = None 

593 

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() 

600 

601 root_indices, roots, terminate = handle_events( 

602 sol, events, active_events, is_terminal, t_old, t) 

603 

604 for e, te in zip(root_indices, roots): 

605 t_events[e].append(te) 

606 y_events[e].append(sol(te)) 

607 

608 if terminate: 

609 status = 1 

610 t = roots[-1] 

611 y = sol(t) 

612 

613 g = g_new 

614 

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] 

629 

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 

636 

637 if t_eval is not None and dense_output: 

638 ti.append(t) 

639 

640 message = MESSAGES.get(status, message) 

641 

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] 

645 

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) 

652 

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 

660 

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)