Coverage for /usr/lib/python3/dist-packages/scipy/integrate/_ivp/ivp.py: 11%

168 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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 time derivative of the state ``y`` 

183 at time ``t``. The calling signature is ``fun(t, y)``, where ``t`` is a 

184 scalar and ``y`` is an ndarray with ``len(y) = len(y0)``. ``fun`` must 

185 return an array of the same shape as ``y``. See `vectorized` for more 

186 information. 

187 t_span : 2-member sequence 

188 Interval of integration (t0, tf). The solver starts with t=t0 and 

189 integrates until it reaches t=tf. Both t0 and tf must be floats 

190 or values interpretable by the float conversion function. 

191 y0 : array_like, shape (n,) 

192 Initial state. For problems in the complex domain, pass `y0` with a 

193 complex data type (even if the initial value is purely real). 

194 method : string or `OdeSolver`, optional 

195 Integration method to use: 

196 

197 * 'RK45' (default): Explicit Runge-Kutta method of order 5(4) [1]_. 

198 The error is controlled assuming accuracy of the fourth-order 

199 method, but steps are taken using the fifth-order accurate 

200 formula (local extrapolation is done). A quartic interpolation 

201 polynomial is used for the dense output [2]_. Can be applied in 

202 the complex domain. 

203 * 'RK23': Explicit Runge-Kutta method of order 3(2) [3]_. The error 

204 is controlled assuming accuracy of the second-order method, but 

205 steps are taken using the third-order accurate formula (local 

206 extrapolation is done). A cubic Hermite polynomial is used for the 

207 dense output. Can be applied in the complex domain. 

208 * 'DOP853': Explicit Runge-Kutta method of order 8 [13]_. 

209 Python implementation of the "DOP853" algorithm originally 

210 written in Fortran [14]_. A 7-th order interpolation polynomial 

211 accurate to 7-th order is used for the dense output. 

212 Can be applied in the complex domain. 

213 * 'Radau': Implicit Runge-Kutta method of the Radau IIA family of 

214 order 5 [4]_. The error is controlled with a third-order accurate 

215 embedded formula. A cubic polynomial which satisfies the 

216 collocation conditions is used for the dense output. 

217 * 'BDF': Implicit multi-step variable-order (1 to 5) method based 

218 on a backward differentiation formula for the derivative 

219 approximation [5]_. The implementation follows the one described 

220 in [6]_. A quasi-constant step scheme is used and accuracy is 

221 enhanced using the NDF modification. Can be applied in the 

222 complex domain. 

223 * 'LSODA': Adams/BDF method with automatic stiffness detection and 

224 switching [7]_, [8]_. This is a wrapper of the Fortran solver 

225 from ODEPACK. 

226 

227 Explicit Runge-Kutta methods ('RK23', 'RK45', 'DOP853') should be used 

228 for non-stiff problems and implicit methods ('Radau', 'BDF') for 

229 stiff problems [9]_. Among Runge-Kutta methods, 'DOP853' is recommended 

230 for solving with high precision (low values of `rtol` and `atol`). 

231 

232 If not sure, first try to run 'RK45'. If it makes unusually many 

233 iterations, diverges, or fails, your problem is likely to be stiff and 

234 you should use 'Radau' or 'BDF'. 'LSODA' can also be a good universal 

235 choice, but it might be somewhat less convenient to work with as it 

236 wraps old Fortran code. 

237 

238 You can also pass an arbitrary class derived from `OdeSolver` which 

239 implements the solver. 

240 t_eval : array_like or None, optional 

241 Times at which to store the computed solution, must be sorted and lie 

242 within `t_span`. If None (default), use points selected by the solver. 

243 dense_output : bool, optional 

244 Whether to compute a continuous solution. Default is False. 

245 events : callable, or list of callables, optional 

246 Events to track. If None (default), no events will be tracked. 

247 Each event occurs at the zeros of a continuous function of time and 

248 state. Each function must have the signature ``event(t, y)`` and return 

249 a float. The solver will find an accurate value of `t` at which 

250 ``event(t, y(t)) = 0`` using a root-finding algorithm. By default, all 

251 zeros will be found. The solver looks for a sign change over each step, 

252 so if multiple zero crossings occur within one step, events may be 

253 missed. Additionally each `event` function might have the following 

254 attributes: 

255 

256 terminal: bool, optional 

257 Whether to terminate integration if this event occurs. 

258 Implicitly False if not assigned. 

259 direction: float, optional 

260 Direction of a zero crossing. If `direction` is positive, 

261 `event` will only trigger when going from negative to positive, 

262 and vice versa if `direction` is negative. If 0, then either 

263 direction will trigger event. Implicitly 0 if not assigned. 

264 

265 You can assign attributes like ``event.terminal = True`` to any 

266 function in Python. 

267 vectorized : bool, optional 

268 Whether `fun` can be called in a vectorized fashion. Default is False. 

269 

270 If ``vectorized`` is False, `fun` will always be called with ``y`` of 

271 shape ``(n,)``, where ``n = len(y0)``. 

272 

273 If ``vectorized`` is True, `fun` may be called with ``y`` of shape 

274 ``(n, k)``, where ``k`` is an integer. In this case, `fun` must behave 

275 such that ``fun(t, y)[:, i] == fun(t, y[:, i])`` (i.e. each column of 

276 the returned array is the time derivative of the state corresponding 

277 with a column of ``y``). 

278 

279 Setting ``vectorized=True`` allows for faster finite difference 

280 approximation of the Jacobian by methods 'Radau' and 'BDF', but 

281 will result in slower execution for other methods and for 'Radau' and 

282 'BDF' in some circumstances (e.g. small ``len(y0)``). 

283 args : tuple, optional 

284 Additional arguments to pass to the user-defined functions. If given, 

285 the additional arguments are passed to all user-defined functions. 

286 So if, for example, `fun` has the signature ``fun(t, y, a, b, c)``, 

287 then `jac` (if given) and any event functions must have the same 

288 signature, and `args` must be a tuple of length 3. 

289 **options 

290 Options passed to a chosen solver. All options available for already 

291 implemented solvers are listed below. 

292 first_step : float or None, optional 

293 Initial step size. Default is `None` which means that the algorithm 

294 should choose. 

295 max_step : float, optional 

296 Maximum allowed step size. Default is np.inf, i.e., the step size is not 

297 bounded and determined solely by the solver. 

298 rtol, atol : float or array_like, optional 

299 Relative and absolute tolerances. The solver keeps the local error 

300 estimates less than ``atol + rtol * abs(y)``. Here `rtol` controls a 

301 relative accuracy (number of correct digits), while `atol` controls 

302 absolute accuracy (number of correct decimal places). To achieve the 

303 desired `rtol`, set `atol` to be smaller than the smallest value that 

304 can be expected from ``rtol * abs(y)`` so that `rtol` dominates the 

305 allowable error. If `atol` is larger than ``rtol * abs(y)`` the 

306 number of correct digits is not guaranteed. Conversely, to achieve the 

307 desired `atol` set `rtol` such that ``rtol * abs(y)`` is always smaller 

308 than `atol`. If components of y have different scales, it might be 

309 beneficial to set different `atol` values for different components by 

310 passing array_like with shape (n,) for `atol`. Default values are 

311 1e-3 for `rtol` and 1e-6 for `atol`. 

312 jac : array_like, sparse_matrix, callable or None, optional 

313 Jacobian matrix of the right-hand side of the system with respect 

314 to y, required by the 'Radau', 'BDF' and 'LSODA' method. The 

315 Jacobian matrix has shape (n, n) and its element (i, j) is equal to 

316 ``d f_i / d y_j``. There are three ways to define the Jacobian: 

317 

318 * If array_like or sparse_matrix, the Jacobian is assumed to 

319 be constant. Not supported by 'LSODA'. 

320 * If callable, the Jacobian is assumed to depend on both 

321 t and y; it will be called as ``jac(t, y)``, as necessary. 

322 For 'Radau' and 'BDF' methods, the return value might be a 

323 sparse matrix. 

324 * If None (default), the Jacobian will be approximated by 

325 finite differences. 

326 

327 It is generally recommended to provide the Jacobian rather than 

328 relying on a finite-difference approximation. 

329 jac_sparsity : array_like, sparse matrix or None, optional 

330 Defines a sparsity structure of the Jacobian matrix for a finite- 

331 difference approximation. Its shape must be (n, n). This argument 

332 is ignored if `jac` is not `None`. If the Jacobian has only few 

333 non-zero elements in *each* row, providing the sparsity structure 

334 will greatly speed up the computations [10]_. A zero entry means that 

335 a corresponding element in the Jacobian is always zero. If None 

336 (default), the Jacobian is assumed to be dense. 

337 Not supported by 'LSODA', see `lband` and `uband` instead. 

338 lband, uband : int or None, optional 

339 Parameters defining the bandwidth of the Jacobian for the 'LSODA' 

340 method, i.e., ``jac[i, j] != 0 only for i - lband <= j <= i + uband``. 

341 Default is None. Setting these requires your jac routine to return the 

342 Jacobian in the packed format: the returned array must have ``n`` 

343 columns and ``uband + lband + 1`` rows in which Jacobian diagonals are 

344 written. Specifically ``jac_packed[uband + i - j , j] = jac[i, j]``. 

345 The same format is used in `scipy.linalg.solve_banded` (check for an 

346 illustration). These parameters can be also used with ``jac=None`` to 

347 reduce the number of Jacobian elements estimated by finite differences. 

348 min_step : float, optional 

349 The minimum allowed step size for 'LSODA' method. 

350 By default `min_step` is zero. 

351 

352 Returns 

353 ------- 

354 Bunch object with the following fields defined: 

355 t : ndarray, shape (n_points,) 

356 Time points. 

357 y : ndarray, shape (n, n_points) 

358 Values of the solution at `t`. 

359 sol : `OdeSolution` or None 

360 Found solution as `OdeSolution` instance; None if `dense_output` was 

361 set to False. 

362 t_events : list of ndarray or None 

363 Contains for each event type a list of arrays at which an event of 

364 that type event was detected. None if `events` was None. 

365 y_events : list of ndarray or None 

366 For each value of `t_events`, the corresponding value of the solution. 

367 None if `events` was None. 

368 nfev : int 

369 Number of evaluations of the right-hand side. 

370 njev : int 

371 Number of evaluations of the Jacobian. 

372 nlu : int 

373 Number of LU decompositions. 

374 status : int 

375 Reason for algorithm termination: 

376 

377 * -1: Integration step failed. 

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

379 * 1: A termination event occurred. 

380 

381 message : string 

382 Human-readable description of the termination reason. 

383 success : bool 

384 True if the solver reached the interval end or a termination event 

385 occurred (``status >= 0``). 

386 

387 References 

388 ---------- 

389 .. [1] J. R. Dormand, P. J. Prince, "A family of embedded Runge-Kutta 

390 formulae", Journal of Computational and Applied Mathematics, Vol. 6, 

391 No. 1, pp. 19-26, 1980. 

392 .. [2] L. W. Shampine, "Some Practical Runge-Kutta Formulas", Mathematics 

393 of Computation,, Vol. 46, No. 173, pp. 135-150, 1986. 

394 .. [3] P. Bogacki, L.F. Shampine, "A 3(2) Pair of Runge-Kutta Formulas", 

395 Appl. Math. Lett. Vol. 2, No. 4. pp. 321-325, 1989. 

396 .. [4] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations II: 

397 Stiff and Differential-Algebraic Problems", Sec. IV.8. 

398 .. [5] `Backward Differentiation Formula 

399 <https://en.wikipedia.org/wiki/Backward_differentiation_formula>`_ 

400 on Wikipedia. 

401 .. [6] L. F. Shampine, M. W. Reichelt, "THE MATLAB ODE SUITE", SIAM J. SCI. 

402 COMPUTE., Vol. 18, No. 1, pp. 1-22, January 1997. 

403 .. [7] A. C. Hindmarsh, "ODEPACK, A Systematized Collection of ODE 

404 Solvers," IMACS Transactions on Scientific Computation, Vol 1., 

405 pp. 55-64, 1983. 

406 .. [8] L. Petzold, "Automatic selection of methods for solving stiff and 

407 nonstiff systems of ordinary differential equations", SIAM Journal 

408 on Scientific and Statistical Computing, Vol. 4, No. 1, pp. 136-148, 

409 1983. 

410 .. [9] `Stiff equation <https://en.wikipedia.org/wiki/Stiff_equation>`_ on 

411 Wikipedia. 

412 .. [10] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of 

413 sparse Jacobian matrices", Journal of the Institute of Mathematics 

414 and its Applications, 13, pp. 117-120, 1974. 

415 .. [11] `Cauchy-Riemann equations 

416 <https://en.wikipedia.org/wiki/Cauchy-Riemann_equations>`_ on 

417 Wikipedia. 

418 .. [12] `Lotka-Volterra equations 

419 <https://en.wikipedia.org/wiki/Lotka%E2%80%93Volterra_equations>`_ 

420 on Wikipedia. 

421 .. [13] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential 

422 Equations I: Nonstiff Problems", Sec. II. 

423 .. [14] `Page with original Fortran code of DOP853 

424 <http://www.unige.ch/~hairer/software.html>`_. 

425 

426 Examples 

427 -------- 

428 Basic exponential decay showing automatically chosen time points. 

429 

430 >>> import numpy as np 

431 >>> from scipy.integrate import solve_ivp 

432 >>> def exponential_decay(t, y): return -0.5 * y 

433 >>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8]) 

434 >>> print(sol.t) 

435 [ 0. 0.11487653 1.26364188 3.06061781 4.81611105 6.57445806 

436 8.33328988 10. ] 

437 >>> print(sol.y) 

438 [[2. 1.88836035 1.06327177 0.43319312 0.18017253 0.07483045 

439 0.03107158 0.01350781] 

440 [4. 3.7767207 2.12654355 0.86638624 0.36034507 0.14966091 

441 0.06214316 0.02701561] 

442 [8. 7.5534414 4.25308709 1.73277247 0.72069014 0.29932181 

443 0.12428631 0.05403123]] 

444 

445 Specifying points where the solution is desired. 

446 

447 >>> sol = solve_ivp(exponential_decay, [0, 10], [2, 4, 8], 

448 ... t_eval=[0, 1, 2, 4, 10]) 

449 >>> print(sol.t) 

450 [ 0 1 2 4 10] 

451 >>> print(sol.y) 

452 [[2. 1.21305369 0.73534021 0.27066736 0.01350938] 

453 [4. 2.42610739 1.47068043 0.54133472 0.02701876] 

454 [8. 4.85221478 2.94136085 1.08266944 0.05403753]] 

455 

456 Cannon fired upward with terminal event upon impact. The ``terminal`` and 

457 ``direction`` fields of an event are applied by monkey patching a function. 

458 Here ``y[0]`` is position and ``y[1]`` is velocity. The projectile starts 

459 at position 0 with velocity +10. Note that the integration never reaches 

460 t=100 because the event is terminal. 

461 

462 >>> def upward_cannon(t, y): return [y[1], -0.5] 

463 >>> def hit_ground(t, y): return y[0] 

464 >>> hit_ground.terminal = True 

465 >>> hit_ground.direction = -1 

466 >>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10], events=hit_ground) 

467 >>> print(sol.t_events) 

468 [array([40.])] 

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 

473 Use `dense_output` and `events` to find position, which is 100, at the apex 

474 of the cannonball's trajectory. Apex is not defined as terminal, so both 

475 apex and hit_ground are found. There is no information at t=20, so the sol 

476 attribute is used to evaluate the solution. The sol attribute is returned 

477 by setting ``dense_output=True``. Alternatively, the `y_events` attribute 

478 can be used to access the solution at the time of the event. 

479 

480 >>> def apex(t, y): return y[1] 

481 >>> sol = solve_ivp(upward_cannon, [0, 100], [0, 10], 

482 ... events=(hit_ground, apex), dense_output=True) 

483 >>> print(sol.t_events) 

484 [array([40.]), array([20.])] 

485 >>> print(sol.t) 

486 [0.00000000e+00 9.99900010e-05 1.09989001e-03 1.10988901e-02 

487 1.11088891e-01 1.11098890e+00 1.11099890e+01 4.00000000e+01] 

488 >>> print(sol.sol(sol.t_events[1][0])) 

489 [100. 0.] 

490 >>> print(sol.y_events) 

491 [array([[-5.68434189e-14, -1.00000000e+01]]), array([[1.00000000e+02, 1.77635684e-15]])] 

492 

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

494 the Lotka-Volterra equations [12]_. 

495 

496 >>> def lotkavolterra(t, z, a, b, c, d): 

497 ... x, y = z 

498 ... return [a*x - b*x*y, -c*y + d*x*y] 

499 ... 

500 

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

502 argument. 

503 

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

505 ... dense_output=True) 

506 

507 Compute a dense solution and plot it. 

508 

509 >>> t = np.linspace(0, 15, 300) 

510 >>> z = sol.sol(t) 

511 >>> import matplotlib.pyplot as plt 

512 >>> plt.plot(t, z.T) 

513 >>> plt.xlabel('t') 

514 >>> plt.legend(['x', 'y'], shadow=True) 

515 >>> plt.title('Lotka-Volterra System') 

516 >>> plt.show() 

517 

518 """ 

519 if method not in METHODS and not ( 

520 inspect.isclass(method) and issubclass(method, OdeSolver)): 

521 raise ValueError("`method` must be one of {} or OdeSolver class." 

522 .format(METHODS)) 

523 

524 t0, tf = map(float, t_span) 

525 

526 if args is not None: 

527 # Wrap the user's fun (and jac, if given) in lambdas to hide the 

528 # additional parameters. Pass in the original fun as a keyword 

529 # argument to keep it in the scope of the lambda. 

530 try: 

531 _ = [*(args)] 

532 except TypeError as exp: 

533 suggestion_tuple = ( 

534 "Supplied 'args' cannot be unpacked. Please supply `args`" 

535 f" as a tuple (e.g. `args=({args},)`)" 

536 ) 

537 raise TypeError(suggestion_tuple) from exp 

538 

539 def fun(t, x, fun=fun): 

540 return fun(t, x, *args) 

541 jac = options.get('jac') 

542 if callable(jac): 

543 options['jac'] = lambda t, x: jac(t, x, *args) 

544 

545 if t_eval is not None: 

546 t_eval = np.asarray(t_eval) 

547 if t_eval.ndim != 1: 

548 raise ValueError("`t_eval` must be 1-dimensional.") 

549 

550 if np.any(t_eval < min(t0, tf)) or np.any(t_eval > max(t0, tf)): 

551 raise ValueError("Values in `t_eval` are not within `t_span`.") 

552 

553 d = np.diff(t_eval) 

554 if tf > t0 and np.any(d <= 0) or tf < t0 and np.any(d >= 0): 

555 raise ValueError("Values in `t_eval` are not properly sorted.") 

556 

557 if tf > t0: 

558 t_eval_i = 0 

559 else: 

560 # Make order of t_eval decreasing to use np.searchsorted. 

561 t_eval = t_eval[::-1] 

562 # This will be an upper bound for slices. 

563 t_eval_i = t_eval.shape[0] 

564 

565 if method in METHODS: 

566 method = METHODS[method] 

567 

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

569 

570 if t_eval is None: 

571 ts = [t0] 

572 ys = [y0] 

573 elif t_eval is not None and dense_output: 

574 ts = [] 

575 ti = [t0] 

576 ys = [] 

577 else: 

578 ts = [] 

579 ys = [] 

580 

581 interpolants = [] 

582 

583 events, is_terminal, event_dir = prepare_events(events) 

584 

585 if events is not None: 

586 if args is not None: 

587 # Wrap user functions in lambdas to hide the additional parameters. 

588 # The original event function is passed as a keyword argument to the 

589 # lambda to keep the original function in scope (i.e., avoid the 

590 # late binding closure "gotcha"). 

591 events = [lambda t, x, event=event: event(t, x, *args) 

592 for event in events] 

593 g = [event(t0, y0) for event in events] 

594 t_events = [[] for _ in range(len(events))] 

595 y_events = [[] for _ in range(len(events))] 

596 else: 

597 t_events = None 

598 y_events = None 

599 

600 status = None 

601 while status is None: 

602 message = solver.step() 

603 

604 if solver.status == 'finished': 

605 status = 0 

606 elif solver.status == 'failed': 

607 status = -1 

608 break 

609 

610 t_old = solver.t_old 

611 t = solver.t 

612 y = solver.y 

613 

614 if dense_output: 

615 sol = solver.dense_output() 

616 interpolants.append(sol) 

617 else: 

618 sol = None 

619 

620 if events is not None: 

621 g_new = [event(t, y) for event in events] 

622 active_events = find_active_events(g, g_new, event_dir) 

623 if active_events.size > 0: 

624 if sol is None: 

625 sol = solver.dense_output() 

626 

627 root_indices, roots, terminate = handle_events( 

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

629 

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

631 t_events[e].append(te) 

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

633 

634 if terminate: 

635 status = 1 

636 t = roots[-1] 

637 y = sol(t) 

638 

639 g = g_new 

640 

641 if t_eval is None: 

642 ts.append(t) 

643 ys.append(y) 

644 else: 

645 # The value in t_eval equal to t will be included. 

646 if solver.direction > 0: 

647 t_eval_i_new = np.searchsorted(t_eval, t, side='right') 

648 t_eval_step = t_eval[t_eval_i:t_eval_i_new] 

649 else: 

650 t_eval_i_new = np.searchsorted(t_eval, t, side='left') 

651 # It has to be done with two slice operations, because 

652 # you can't slice to 0th element inclusive using backward 

653 # slicing. 

654 t_eval_step = t_eval[t_eval_i_new:t_eval_i][::-1] 

655 

656 if t_eval_step.size > 0: 

657 if sol is None: 

658 sol = solver.dense_output() 

659 ts.append(t_eval_step) 

660 ys.append(sol(t_eval_step)) 

661 t_eval_i = t_eval_i_new 

662 

663 if t_eval is not None and dense_output: 

664 ti.append(t) 

665 

666 message = MESSAGES.get(status, message) 

667 

668 if t_events is not None: 

669 t_events = [np.asarray(te) for te in t_events] 

670 y_events = [np.asarray(ye) for ye in y_events] 

671 

672 if t_eval is None: 

673 ts = np.array(ts) 

674 ys = np.vstack(ys).T 

675 elif ts: 

676 ts = np.hstack(ts) 

677 ys = np.hstack(ys) 

678 

679 if dense_output: 

680 if t_eval is None: 

681 sol = OdeSolution(ts, interpolants) 

682 else: 

683 sol = OdeSolution(ti, interpolants) 

684 else: 

685 sol = None 

686 

687 return OdeResult(t=ts, y=ys, sol=sol, t_events=t_events, y_events=y_events, 

688 nfev=solver.nfev, njev=solver.njev, nlu=solver.nlu, 

689 status=status, message=message, success=status >= 0)