radau.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572
  1. import numpy as np
  2. from scipy.linalg import lu_factor, lu_solve
  3. from scipy.sparse import csc_matrix, issparse, eye
  4. from scipy.sparse.linalg import splu
  5. from scipy.optimize._numdiff import group_columns
  6. from .common import (validate_max_step, validate_tol, select_initial_step,
  7. norm, num_jac, EPS, warn_extraneous,
  8. validate_first_step)
  9. from .base import OdeSolver, DenseOutput
  10. S6 = 6 ** 0.5
  11. # Butcher tableau. A is not used directly, see below.
  12. C = np.array([(4 - S6) / 10, (4 + S6) / 10, 1])
  13. E = np.array([-13 - 7 * S6, -13 + 7 * S6, -1]) / 3
  14. # Eigendecomposition of A is done: A = T L T**-1. There is 1 real eigenvalue
  15. # and a complex conjugate pair. They are written below.
  16. MU_REAL = 3 + 3 ** (2 / 3) - 3 ** (1 / 3)
  17. MU_COMPLEX = (3 + 0.5 * (3 ** (1 / 3) - 3 ** (2 / 3))
  18. - 0.5j * (3 ** (5 / 6) + 3 ** (7 / 6)))
  19. # These are transformation matrices.
  20. T = np.array([
  21. [0.09443876248897524, -0.14125529502095421, 0.03002919410514742],
  22. [0.25021312296533332, 0.20412935229379994, -0.38294211275726192],
  23. [1, 1, 0]])
  24. TI = np.array([
  25. [4.17871859155190428, 0.32768282076106237, 0.52337644549944951],
  26. [-4.17871859155190428, -0.32768282076106237, 0.47662355450055044],
  27. [0.50287263494578682, -2.57192694985560522, 0.59603920482822492]])
  28. # These linear combinations are used in the algorithm.
  29. TI_REAL = TI[0]
  30. TI_COMPLEX = TI[1] + 1j * TI[2]
  31. # Interpolator coefficients.
  32. P = np.array([
  33. [13/3 + 7*S6/3, -23/3 - 22*S6/3, 10/3 + 5 * S6],
  34. [13/3 - 7*S6/3, -23/3 + 22*S6/3, 10/3 - 5 * S6],
  35. [1/3, -8/3, 10/3]])
  36. NEWTON_MAXITER = 6 # Maximum number of Newton iterations.
  37. MIN_FACTOR = 0.2 # Minimum allowed decrease in a step size.
  38. MAX_FACTOR = 10 # Maximum allowed increase in a step size.
  39. def solve_collocation_system(fun, t, y, h, Z0, scale, tol,
  40. LU_real, LU_complex, solve_lu):
  41. """Solve the collocation system.
  42. Parameters
  43. ----------
  44. fun : callable
  45. Right-hand side of the system.
  46. t : float
  47. Current time.
  48. y : ndarray, shape (n,)
  49. Current state.
  50. h : float
  51. Step to try.
  52. Z0 : ndarray, shape (3, n)
  53. Initial guess for the solution. It determines new values of `y` at
  54. ``t + h * C`` as ``y + Z0``, where ``C`` is the Radau method constants.
  55. scale : ndarray, shape (n)
  56. Problem tolerance scale, i.e. ``rtol * abs(y) + atol``.
  57. tol : float
  58. Tolerance to which solve the system. This value is compared with
  59. the normalized by `scale` error.
  60. LU_real, LU_complex
  61. LU decompositions of the system Jacobians.
  62. solve_lu : callable
  63. Callable which solves a linear system given a LU decomposition. The
  64. signature is ``solve_lu(LU, b)``.
  65. Returns
  66. -------
  67. converged : bool
  68. Whether iterations converged.
  69. n_iter : int
  70. Number of completed iterations.
  71. Z : ndarray, shape (3, n)
  72. Found solution.
  73. rate : float
  74. The rate of convergence.
  75. """
  76. n = y.shape[0]
  77. M_real = MU_REAL / h
  78. M_complex = MU_COMPLEX / h
  79. W = TI.dot(Z0)
  80. Z = Z0
  81. F = np.empty((3, n))
  82. ch = h * C
  83. dW_norm_old = None
  84. dW = np.empty_like(W)
  85. converged = False
  86. rate = None
  87. for k in range(NEWTON_MAXITER):
  88. for i in range(3):
  89. F[i] = fun(t + ch[i], y + Z[i])
  90. if not np.all(np.isfinite(F)):
  91. break
  92. f_real = F.T.dot(TI_REAL) - M_real * W[0]
  93. f_complex = F.T.dot(TI_COMPLEX) - M_complex * (W[1] + 1j * W[2])
  94. dW_real = solve_lu(LU_real, f_real)
  95. dW_complex = solve_lu(LU_complex, f_complex)
  96. dW[0] = dW_real
  97. dW[1] = dW_complex.real
  98. dW[2] = dW_complex.imag
  99. dW_norm = norm(dW / scale)
  100. if dW_norm_old is not None:
  101. rate = dW_norm / dW_norm_old
  102. if (rate is not None and (rate >= 1 or
  103. rate ** (NEWTON_MAXITER - k) / (1 - rate) * dW_norm > tol)):
  104. break
  105. W += dW
  106. Z = T.dot(W)
  107. if (dW_norm == 0 or
  108. rate is not None and rate / (1 - rate) * dW_norm < tol):
  109. converged = True
  110. break
  111. dW_norm_old = dW_norm
  112. return converged, k + 1, Z, rate
  113. def predict_factor(h_abs, h_abs_old, error_norm, error_norm_old):
  114. """Predict by which factor to increase/decrease the step size.
  115. The algorithm is described in [1]_.
  116. Parameters
  117. ----------
  118. h_abs, h_abs_old : float
  119. Current and previous values of the step size, `h_abs_old` can be None
  120. (see Notes).
  121. error_norm, error_norm_old : float
  122. Current and previous values of the error norm, `error_norm_old` can
  123. be None (see Notes).
  124. Returns
  125. -------
  126. factor : float
  127. Predicted factor.
  128. Notes
  129. -----
  130. If `h_abs_old` and `error_norm_old` are both not None then a two-step
  131. algorithm is used, otherwise a one-step algorithm is used.
  132. References
  133. ----------
  134. .. [1] E. Hairer, S. P. Norsett G. Wanner, "Solving Ordinary Differential
  135. Equations II: Stiff and Differential-Algebraic Problems", Sec. IV.8.
  136. """
  137. if error_norm_old is None or h_abs_old is None or error_norm == 0:
  138. multiplier = 1
  139. else:
  140. multiplier = h_abs / h_abs_old * (error_norm_old / error_norm) ** 0.25
  141. with np.errstate(divide='ignore'):
  142. factor = min(1, multiplier) * error_norm ** -0.25
  143. return factor
  144. class Radau(OdeSolver):
  145. """Implicit Runge-Kutta method of Radau IIA family of order 5.
  146. The implementation follows [1]_. The error is controlled with a
  147. third-order accurate embedded formula. A cubic polynomial which satisfies
  148. the collocation conditions is used for the dense output.
  149. Parameters
  150. ----------
  151. fun : callable
  152. Right-hand side of the system: the time derivative of the state ``y``
  153. at time ``t``. The calling signature is ``fun(t, y)``, where ``t`` is a
  154. scalar and ``y`` is an ndarray with ``len(y) = len(y0)``. ``fun`` must
  155. return an array of the same shape as ``y``. See `vectorized` for more
  156. information.
  157. t0 : float
  158. Initial time.
  159. y0 : array_like, shape (n,)
  160. Initial state.
  161. t_bound : float
  162. Boundary time - the integration won't continue beyond it. It also
  163. determines the direction of the integration.
  164. first_step : float or None, optional
  165. Initial step size. Default is ``None`` which means that the algorithm
  166. should choose.
  167. max_step : float, optional
  168. Maximum allowed step size. Default is np.inf, i.e., the step size is not
  169. bounded and determined solely by the solver.
  170. rtol, atol : float and array_like, optional
  171. Relative and absolute tolerances. The solver keeps the local error
  172. estimates less than ``atol + rtol * abs(y)``. HHere `rtol` controls a
  173. relative accuracy (number of correct digits), while `atol` controls
  174. absolute accuracy (number of correct decimal places). To achieve the
  175. desired `rtol`, set `atol` to be smaller than the smallest value that
  176. can be expected from ``rtol * abs(y)`` so that `rtol` dominates the
  177. allowable error. If `atol` is larger than ``rtol * abs(y)`` the
  178. number of correct digits is not guaranteed. Conversely, to achieve the
  179. desired `atol` set `rtol` such that ``rtol * abs(y)`` is always smaller
  180. than `atol`. If components of y have different scales, it might be
  181. beneficial to set different `atol` values for different components by
  182. passing array_like with shape (n,) for `atol`. Default values are
  183. 1e-3 for `rtol` and 1e-6 for `atol`.
  184. jac : {None, array_like, sparse_matrix, callable}, optional
  185. Jacobian matrix of the right-hand side of the system with respect to
  186. y, required by this method. The Jacobian matrix has shape (n, n) and
  187. its element (i, j) is equal to ``d f_i / d y_j``.
  188. There are three ways to define the Jacobian:
  189. * If array_like or sparse_matrix, the Jacobian is assumed to
  190. be constant.
  191. * If callable, the Jacobian is assumed to depend on both
  192. t and y; it will be called as ``jac(t, y)`` as necessary.
  193. For the 'Radau' and 'BDF' methods, the return value might be a
  194. sparse matrix.
  195. * If None (default), the Jacobian will be approximated by
  196. finite differences.
  197. It is generally recommended to provide the Jacobian rather than
  198. relying on a finite-difference approximation.
  199. jac_sparsity : {None, array_like, sparse matrix}, optional
  200. Defines a sparsity structure of the Jacobian matrix for a
  201. finite-difference approximation. Its shape must be (n, n). This argument
  202. is ignored if `jac` is not `None`. If the Jacobian has only few non-zero
  203. elements in *each* row, providing the sparsity structure will greatly
  204. speed up the computations [2]_. A zero entry means that a corresponding
  205. element in the Jacobian is always zero. If None (default), the Jacobian
  206. is assumed to be dense.
  207. vectorized : bool, optional
  208. Whether `fun` can be called in a vectorized fashion. Default is False.
  209. If ``vectorized`` is False, `fun` will always be called with ``y`` of
  210. shape ``(n,)``, where ``n = len(y0)``.
  211. If ``vectorized`` is True, `fun` may be called with ``y`` of shape
  212. ``(n, k)``, where ``k`` is an integer. In this case, `fun` must behave
  213. such that ``fun(t, y)[:, i] == fun(t, y[:, i])`` (i.e. each column of
  214. the returned array is the time derivative of the state corresponding
  215. with a column of ``y``).
  216. Setting ``vectorized=True`` allows for faster finite difference
  217. approximation of the Jacobian by this method, but may result in slower
  218. execution overall in some circumstances (e.g. small ``len(y0)``).
  219. Attributes
  220. ----------
  221. n : int
  222. Number of equations.
  223. status : string
  224. Current status of the solver: 'running', 'finished' or 'failed'.
  225. t_bound : float
  226. Boundary time.
  227. direction : float
  228. Integration direction: +1 or -1.
  229. t : float
  230. Current time.
  231. y : ndarray
  232. Current state.
  233. t_old : float
  234. Previous time. None if no steps were made yet.
  235. step_size : float
  236. Size of the last successful step. None if no steps were made yet.
  237. nfev : int
  238. Number of evaluations of the right-hand side.
  239. njev : int
  240. Number of evaluations of the Jacobian.
  241. nlu : int
  242. Number of LU decompositions.
  243. References
  244. ----------
  245. .. [1] E. Hairer, G. Wanner, "Solving Ordinary Differential Equations II:
  246. Stiff and Differential-Algebraic Problems", Sec. IV.8.
  247. .. [2] A. Curtis, M. J. D. Powell, and J. Reid, "On the estimation of
  248. sparse Jacobian matrices", Journal of the Institute of Mathematics
  249. and its Applications, 13, pp. 117-120, 1974.
  250. """
  251. def __init__(self, fun, t0, y0, t_bound, max_step=np.inf,
  252. rtol=1e-3, atol=1e-6, jac=None, jac_sparsity=None,
  253. vectorized=False, first_step=None, **extraneous):
  254. warn_extraneous(extraneous)
  255. super().__init__(fun, t0, y0, t_bound, vectorized)
  256. self.y_old = None
  257. self.max_step = validate_max_step(max_step)
  258. self.rtol, self.atol = validate_tol(rtol, atol, self.n)
  259. self.f = self.fun(self.t, self.y)
  260. # Select initial step assuming the same order which is used to control
  261. # the error.
  262. if first_step is None:
  263. self.h_abs = select_initial_step(
  264. self.fun, self.t, self.y, t_bound, max_step, self.f, self.direction,
  265. 3, self.rtol, self.atol)
  266. else:
  267. self.h_abs = validate_first_step(first_step, t0, t_bound)
  268. self.h_abs_old = None
  269. self.error_norm_old = None
  270. self.newton_tol = max(10 * EPS / rtol, min(0.03, rtol ** 0.5))
  271. self.sol = None
  272. self.jac_factor = None
  273. self.jac, self.J = self._validate_jac(jac, jac_sparsity)
  274. if issparse(self.J):
  275. def lu(A):
  276. self.nlu += 1
  277. return splu(A)
  278. def solve_lu(LU, b):
  279. return LU.solve(b)
  280. I = eye(self.n, format='csc')
  281. else:
  282. def lu(A):
  283. self.nlu += 1
  284. return lu_factor(A, overwrite_a=True)
  285. def solve_lu(LU, b):
  286. return lu_solve(LU, b, overwrite_b=True)
  287. I = np.identity(self.n)
  288. self.lu = lu
  289. self.solve_lu = solve_lu
  290. self.I = I
  291. self.current_jac = True
  292. self.LU_real = None
  293. self.LU_complex = None
  294. self.Z = None
  295. def _validate_jac(self, jac, sparsity):
  296. t0 = self.t
  297. y0 = self.y
  298. if jac is None:
  299. if sparsity is not None:
  300. if issparse(sparsity):
  301. sparsity = csc_matrix(sparsity)
  302. groups = group_columns(sparsity)
  303. sparsity = (sparsity, groups)
  304. def jac_wrapped(t, y, f):
  305. self.njev += 1
  306. J, self.jac_factor = num_jac(self.fun_vectorized, t, y, f,
  307. self.atol, self.jac_factor,
  308. sparsity)
  309. return J
  310. J = jac_wrapped(t0, y0, self.f)
  311. elif callable(jac):
  312. J = jac(t0, y0)
  313. self.njev = 1
  314. if issparse(J):
  315. J = csc_matrix(J)
  316. def jac_wrapped(t, y, _=None):
  317. self.njev += 1
  318. return csc_matrix(jac(t, y), dtype=float)
  319. else:
  320. J = np.asarray(J, dtype=float)
  321. def jac_wrapped(t, y, _=None):
  322. self.njev += 1
  323. return np.asarray(jac(t, y), dtype=float)
  324. if J.shape != (self.n, self.n):
  325. raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)},"
  326. f" but actually has {J.shape}.")
  327. else:
  328. if issparse(jac):
  329. J = csc_matrix(jac)
  330. else:
  331. J = np.asarray(jac, dtype=float)
  332. if J.shape != (self.n, self.n):
  333. raise ValueError(f"`jac` is expected to have shape {(self.n, self.n)},"
  334. f" but actually has {J.shape}.")
  335. jac_wrapped = None
  336. return jac_wrapped, J
  337. def _step_impl(self):
  338. t = self.t
  339. y = self.y
  340. f = self.f
  341. max_step = self.max_step
  342. atol = self.atol
  343. rtol = self.rtol
  344. min_step = 10 * np.abs(np.nextafter(t, self.direction * np.inf) - t)
  345. if self.h_abs > max_step:
  346. h_abs = max_step
  347. h_abs_old = None
  348. error_norm_old = None
  349. elif self.h_abs < min_step:
  350. h_abs = min_step
  351. h_abs_old = None
  352. error_norm_old = None
  353. else:
  354. h_abs = self.h_abs
  355. h_abs_old = self.h_abs_old
  356. error_norm_old = self.error_norm_old
  357. J = self.J
  358. LU_real = self.LU_real
  359. LU_complex = self.LU_complex
  360. current_jac = self.current_jac
  361. jac = self.jac
  362. rejected = False
  363. step_accepted = False
  364. message = None
  365. while not step_accepted:
  366. if h_abs < min_step:
  367. return False, self.TOO_SMALL_STEP
  368. h = h_abs * self.direction
  369. t_new = t + h
  370. if self.direction * (t_new - self.t_bound) > 0:
  371. t_new = self.t_bound
  372. h = t_new - t
  373. h_abs = np.abs(h)
  374. if self.sol is None:
  375. Z0 = np.zeros((3, y.shape[0]))
  376. else:
  377. Z0 = self.sol(t + h * C).T - y
  378. scale = atol + np.abs(y) * rtol
  379. converged = False
  380. while not converged:
  381. if LU_real is None or LU_complex is None:
  382. LU_real = self.lu(MU_REAL / h * self.I - J)
  383. LU_complex = self.lu(MU_COMPLEX / h * self.I - J)
  384. converged, n_iter, Z, rate = solve_collocation_system(
  385. self.fun, t, y, h, Z0, scale, self.newton_tol,
  386. LU_real, LU_complex, self.solve_lu)
  387. if not converged:
  388. if current_jac:
  389. break
  390. J = self.jac(t, y, f)
  391. current_jac = True
  392. LU_real = None
  393. LU_complex = None
  394. if not converged:
  395. h_abs *= 0.5
  396. LU_real = None
  397. LU_complex = None
  398. continue
  399. y_new = y + Z[-1]
  400. ZE = Z.T.dot(E) / h
  401. error = self.solve_lu(LU_real, f + ZE)
  402. scale = atol + np.maximum(np.abs(y), np.abs(y_new)) * rtol
  403. error_norm = norm(error / scale)
  404. safety = 0.9 * (2 * NEWTON_MAXITER + 1) / (2 * NEWTON_MAXITER
  405. + n_iter)
  406. if rejected and error_norm > 1:
  407. error = self.solve_lu(LU_real, self.fun(t, y + error) + ZE)
  408. error_norm = norm(error / scale)
  409. if error_norm > 1:
  410. factor = predict_factor(h_abs, h_abs_old,
  411. error_norm, error_norm_old)
  412. h_abs *= max(MIN_FACTOR, safety * factor)
  413. LU_real = None
  414. LU_complex = None
  415. rejected = True
  416. else:
  417. step_accepted = True
  418. recompute_jac = jac is not None and n_iter > 2 and rate > 1e-3
  419. factor = predict_factor(h_abs, h_abs_old, error_norm, error_norm_old)
  420. factor = min(MAX_FACTOR, safety * factor)
  421. if not recompute_jac and factor < 1.2:
  422. factor = 1
  423. else:
  424. LU_real = None
  425. LU_complex = None
  426. f_new = self.fun(t_new, y_new)
  427. if recompute_jac:
  428. J = jac(t_new, y_new, f_new)
  429. current_jac = True
  430. elif jac is not None:
  431. current_jac = False
  432. self.h_abs_old = self.h_abs
  433. self.error_norm_old = error_norm
  434. self.h_abs = h_abs * factor
  435. self.y_old = y
  436. self.t = t_new
  437. self.y = y_new
  438. self.f = f_new
  439. self.Z = Z
  440. self.LU_real = LU_real
  441. self.LU_complex = LU_complex
  442. self.current_jac = current_jac
  443. self.J = J
  444. self.t_old = t
  445. self.sol = self._compute_dense_output()
  446. return step_accepted, message
  447. def _compute_dense_output(self):
  448. Q = np.dot(self.Z.T, P)
  449. return RadauDenseOutput(self.t_old, self.t, self.y_old, Q)
  450. def _dense_output_impl(self):
  451. return self.sol
  452. class RadauDenseOutput(DenseOutput):
  453. def __init__(self, t_old, t, y_old, Q):
  454. super().__init__(t_old, t)
  455. self.h = t - t_old
  456. self.Q = Q
  457. self.order = Q.shape[1] - 1
  458. self.y_old = y_old
  459. def _call_impl(self, t):
  460. x = (t - self.t_old) / self.h
  461. if t.ndim == 0:
  462. p = np.tile(x, self.order + 1)
  463. p = np.cumprod(p)
  464. else:
  465. p = np.tile(x, (self.order + 1, 1))
  466. p = np.cumprod(p, axis=0)
  467. # Here we don't multiply by h, not a mistake.
  468. y = np.dot(self.Q, p)
  469. if y.ndim == 2:
  470. y += self.y_old[:, None]
  471. else:
  472. y += self.y_old
  473. return y