_solvers.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880
  1. """Matrix equation solver routines"""
  2. # Author: Jeffrey Armstrong <jeff@approximatrix.com>
  3. # February 24, 2012
  4. # Modified: Chad Fulton <ChadFulton@gmail.com>
  5. # June 19, 2014
  6. # Modified: Ilhan Polat <ilhanpolat@gmail.com>
  7. # September 13, 2016
  8. import warnings
  9. import numpy as np
  10. from numpy.linalg import inv, LinAlgError, norm, cond, svd
  11. from scipy._lib._util import _apply_over_batch
  12. from ._basic import solve, solve_triangular, matrix_balance
  13. from .lapack import get_lapack_funcs
  14. from ._decomp_schur import schur
  15. from ._decomp_lu import lu
  16. from ._decomp_qr import qr
  17. from ._decomp_qz import ordqz
  18. from ._decomp import _asarray_validated
  19. from ._special_matrices import block_diag
  20. __all__ = ['solve_sylvester',
  21. 'solve_continuous_lyapunov', 'solve_discrete_lyapunov',
  22. 'solve_lyapunov',
  23. 'solve_continuous_are', 'solve_discrete_are']
  24. @_apply_over_batch(('a', 2), ('b', 2), ('q', 2))
  25. def solve_sylvester(a, b, q):
  26. """
  27. Computes a solution (X) to the Sylvester equation :math:`AX + XB = Q`.
  28. Parameters
  29. ----------
  30. a : (M, M) array_like
  31. Leading matrix of the Sylvester equation
  32. b : (N, N) array_like
  33. Trailing matrix of the Sylvester equation
  34. q : (M, N) array_like
  35. Right-hand side
  36. Returns
  37. -------
  38. x : (M, N) ndarray
  39. The solution to the Sylvester equation.
  40. Raises
  41. ------
  42. LinAlgError
  43. If solution was not found
  44. Notes
  45. -----
  46. Computes a solution to the Sylvester matrix equation via the Bartels-
  47. Stewart algorithm. The A and B matrices first undergo Schur
  48. decompositions. The resulting matrices are used to construct an
  49. alternative Sylvester equation (``RY + YS^T = F``) where the R and S
  50. matrices are in quasi-triangular form (or, when R, S or F are complex,
  51. triangular form). The simplified equation is then solved using
  52. ``*TRSYL`` from LAPACK directly.
  53. .. versionadded:: 0.11.0
  54. Examples
  55. --------
  56. Given `a`, `b`, and `q` solve for `x`:
  57. >>> import numpy as np
  58. >>> from scipy import linalg
  59. >>> a = np.array([[-3, -2, 0], [-1, -1, 3], [3, -5, -1]])
  60. >>> b = np.array([[1]])
  61. >>> q = np.array([[1],[2],[3]])
  62. >>> x = linalg.solve_sylvester(a, b, q)
  63. >>> x
  64. array([[ 0.0625],
  65. [-0.5625],
  66. [ 0.6875]])
  67. >>> np.allclose(a.dot(x) + x.dot(b), q)
  68. True
  69. """
  70. # Accommodate empty a
  71. if a.size == 0 or b.size == 0:
  72. tdict = {'s': np.float32, 'd': np.float64,
  73. 'c': np.complex64, 'z': np.complex128}
  74. func, = get_lapack_funcs(('trsyl',), arrays=(a, b, q))
  75. return np.empty(q.shape, dtype=tdict[func.typecode])
  76. # Compute the Schur decomposition form of a
  77. r, u = schur(a, output='real')
  78. # Compute the Schur decomposition of b
  79. s, v = schur(b.conj().transpose(), output='real')
  80. # Construct f = u'*q*v
  81. f = np.dot(np.dot(u.conj().transpose(), q), v)
  82. # Call the Sylvester equation solver
  83. trsyl, = get_lapack_funcs(('trsyl',), (r, s, f))
  84. if trsyl is None:
  85. raise RuntimeError('LAPACK implementation does not contain a proper '
  86. 'Sylvester equation solver (TRSYL)')
  87. y, scale, info = trsyl(r, s, f, tranb='C')
  88. y = scale*y
  89. if info < 0:
  90. raise LinAlgError(f"Illegal value encountered in the {-info} term")
  91. return np.dot(np.dot(u, y), v.conj().transpose())
  92. @_apply_over_batch(('a', 2), ('q', 2))
  93. def solve_continuous_lyapunov(a, q):
  94. """
  95. Solves the continuous Lyapunov equation :math:`AX + XA^H = Q`.
  96. Uses the Bartels-Stewart algorithm to find :math:`X`.
  97. Parameters
  98. ----------
  99. a : array_like
  100. A square matrix
  101. q : array_like
  102. Right-hand side square matrix
  103. Returns
  104. -------
  105. x : ndarray
  106. Solution to the continuous Lyapunov equation
  107. See Also
  108. --------
  109. solve_discrete_lyapunov : computes the solution to the discrete-time
  110. Lyapunov equation
  111. solve_sylvester : computes the solution to the Sylvester equation
  112. Notes
  113. -----
  114. The continuous Lyapunov equation is a special form of the Sylvester
  115. equation, hence this solver relies on LAPACK routine ?TRSYL.
  116. .. versionadded:: 0.11.0
  117. Examples
  118. --------
  119. Given `a` and `q` solve for `x`:
  120. >>> import numpy as np
  121. >>> from scipy import linalg
  122. >>> a = np.array([[-3, -2, 0], [-1, -1, 0], [0, -5, -1]])
  123. >>> b = np.array([2, 4, -1])
  124. >>> q = np.eye(3)
  125. >>> x = linalg.solve_continuous_lyapunov(a, q)
  126. >>> x
  127. array([[ -0.75 , 0.875 , -3.75 ],
  128. [ 0.875 , -1.375 , 5.3125],
  129. [ -3.75 , 5.3125, -27.0625]])
  130. >>> np.allclose(a.dot(x) + x.dot(a.T), q)
  131. True
  132. """
  133. a = np.atleast_2d(_asarray_validated(a, check_finite=True))
  134. q = np.atleast_2d(_asarray_validated(q, check_finite=True))
  135. r_or_c = float
  136. for ind, _ in enumerate((a, q)):
  137. if np.iscomplexobj(_):
  138. r_or_c = complex
  139. if not np.equal(*_.shape):
  140. raise ValueError(f"Matrix {'aq'[ind]} should be square.")
  141. # Shape consistency check
  142. if a.shape != q.shape:
  143. raise ValueError("Matrix a and q should have the same shape.")
  144. # Accommodate empty array
  145. if a.size == 0:
  146. tdict = {'s': np.float32, 'd': np.float64,
  147. 'c': np.complex64, 'z': np.complex128}
  148. func, = get_lapack_funcs(('trsyl',), arrays=(a, q))
  149. return np.empty(a.shape, dtype=tdict[func.typecode])
  150. # Compute the Schur decomposition form of a
  151. r, u = schur(a, output='real')
  152. # Construct f = u'*q*u
  153. f = u.conj().T.dot(q.dot(u))
  154. # Call the Sylvester equation solver
  155. trsyl = get_lapack_funcs('trsyl', (r, f))
  156. dtype_string = 'T' if r_or_c is float else 'C'
  157. y, scale, info = trsyl(r, r, f, tranb=dtype_string)
  158. if info < 0:
  159. raise ValueError('?TRSYL exited with the internal error '
  160. f'"illegal value in argument number {-info}.". See '
  161. 'LAPACK documentation for the ?TRSYL error codes.')
  162. elif info == 1:
  163. warnings.warn('Input "a" has an eigenvalue pair whose sum is '
  164. 'very close to or exactly zero. The solution is '
  165. 'obtained via perturbing the coefficients.',
  166. RuntimeWarning, stacklevel=2)
  167. y *= scale
  168. return u.dot(y).dot(u.conj().T)
  169. # For backwards compatibility, keep the old name
  170. solve_lyapunov = solve_continuous_lyapunov
  171. def _solve_discrete_lyapunov_direct(a, q):
  172. """
  173. Solves the discrete Lyapunov equation directly.
  174. This function is called by the `solve_discrete_lyapunov` function with
  175. `method=direct`. It is not supposed to be called directly.
  176. """
  177. lhs = np.kron(a, a.conj())
  178. lhs = np.eye(lhs.shape[0]) - lhs
  179. x = solve(lhs, q.flatten())
  180. return np.reshape(x, q.shape)
  181. def _solve_discrete_lyapunov_bilinear(a, q):
  182. """
  183. Solves the discrete Lyapunov equation using a bilinear transformation.
  184. This function is called by the `solve_discrete_lyapunov` function with
  185. `method=bilinear`. It is not supposed to be called directly.
  186. """
  187. eye = np.eye(a.shape[0])
  188. aH = a.conj().transpose()
  189. aHI_inv = inv(aH + eye)
  190. b = np.dot(aH - eye, aHI_inv)
  191. c = 2*np.dot(np.dot(inv(a + eye), q), aHI_inv)
  192. return solve_lyapunov(b.conj().transpose(), -c)
  193. @_apply_over_batch(('a', 2), ('q', 2))
  194. def solve_discrete_lyapunov(a, q, method=None):
  195. """
  196. Solves the discrete Lyapunov equation :math:`AXA^H - X + Q = 0`.
  197. Parameters
  198. ----------
  199. a, q : (M, M) array_like
  200. Square matrices corresponding to A and Q in the equation
  201. above respectively. Must have the same shape.
  202. method : {'direct', 'bilinear'}, optional
  203. Type of solver.
  204. If not given, chosen to be ``direct`` if ``M`` is less than 10 and
  205. ``bilinear`` otherwise.
  206. Returns
  207. -------
  208. x : ndarray
  209. Solution to the discrete Lyapunov equation
  210. See Also
  211. --------
  212. solve_continuous_lyapunov : computes the solution to the continuous-time
  213. Lyapunov equation
  214. Notes
  215. -----
  216. This section describes the available solvers that can be selected by the
  217. 'method' parameter. The default method is *direct* if ``M`` is less than 10
  218. and ``bilinear`` otherwise.
  219. Method *direct* uses a direct analytical solution to the discrete Lyapunov
  220. equation. The algorithm is given in, for example, [1]_. However, it requires
  221. the linear solution of a system with dimension :math:`M^2` so that
  222. performance degrades rapidly for even moderately sized matrices.
  223. Method *bilinear* uses a bilinear transformation to convert the discrete
  224. Lyapunov equation to a continuous Lyapunov equation :math:`(BX+XB'=-C)`
  225. where :math:`B=(A-I)(A+I)^{-1}` and
  226. :math:`C=2(A' + I)^{-1} Q (A + I)^{-1}`. The continuous equation can be
  227. efficiently solved since it is a special case of a Sylvester equation.
  228. The transformation algorithm is from Popov (1964) as described in [2]_.
  229. .. versionadded:: 0.11.0
  230. References
  231. ----------
  232. .. [1] "Lyapunov equation", Wikipedia,
  233. https://en.wikipedia.org/wiki/Lyapunov_equation#Discrete_time
  234. .. [2] Gajic, Z., and M.T.J. Qureshi. 2008.
  235. Lyapunov Matrix Equation in System Stability and Control.
  236. Dover Books on Engineering Series. Dover Publications.
  237. Examples
  238. --------
  239. Given `a` and `q` solve for `x`:
  240. >>> import numpy as np
  241. >>> from scipy import linalg
  242. >>> a = np.array([[0.2, 0.5],[0.7, -0.9]])
  243. >>> q = np.eye(2)
  244. >>> x = linalg.solve_discrete_lyapunov(a, q)
  245. >>> x
  246. array([[ 0.70872893, 1.43518822],
  247. [ 1.43518822, -2.4266315 ]])
  248. >>> np.allclose(a.dot(x).dot(a.T)-x, -q)
  249. True
  250. """
  251. a = np.asarray(a)
  252. q = np.asarray(q)
  253. if method is None:
  254. # Select automatically based on size of matrices
  255. if a.shape[0] >= 10:
  256. method = 'bilinear'
  257. else:
  258. method = 'direct'
  259. meth = method.lower()
  260. if meth == 'direct':
  261. x = _solve_discrete_lyapunov_direct(a, q)
  262. elif meth == 'bilinear':
  263. x = _solve_discrete_lyapunov_bilinear(a, q)
  264. else:
  265. raise ValueError(f'Unknown solver {method}')
  266. return x
  267. def solve_continuous_are(a, b, q, r, e=None, s=None, balanced=True):
  268. r"""
  269. Solves the continuous-time algebraic Riccati equation (CARE).
  270. The CARE is defined as
  271. .. math::
  272. X A + A^H X - X B R^{-1} B^H X + Q = 0
  273. The limitations for a solution to exist are :
  274. * All eigenvalues of :math:`A` on the right half plane, should be
  275. controllable.
  276. * The associated hamiltonian pencil (See Notes), should have
  277. eigenvalues sufficiently away from the imaginary axis.
  278. Moreover, if ``e`` or ``s`` is not precisely ``None``, then the
  279. generalized version of CARE
  280. .. math::
  281. E^HXA + A^HXE - (E^HXB + S) R^{-1} (B^HXE + S^H) + Q = 0
  282. is solved. When omitted, ``e`` is assumed to be the identity and ``s``
  283. is assumed to be the zero matrix with sizes compatible with ``a`` and
  284. ``b``, respectively.
  285. The documentation is written assuming array arguments are of specified
  286. "core" shapes. However, array argument(s) of this function may have additional
  287. "batch" dimensions prepended to the core shape. In this case, the array is treated
  288. as a batch of lower-dimensional slices; see :ref:`linalg_batch` for details.
  289. Parameters
  290. ----------
  291. a : (M, M) array_like
  292. Square matrix
  293. b : (M, N) array_like
  294. Input
  295. q : (M, M) array_like
  296. Input
  297. r : (N, N) array_like
  298. Nonsingular square matrix
  299. e : (M, M) array_like, optional
  300. Nonsingular square matrix
  301. s : (M, N) array_like, optional
  302. Input
  303. balanced : bool, optional
  304. The boolean that indicates whether a balancing step is performed
  305. on the data. The default is set to True.
  306. Returns
  307. -------
  308. x : (M, M) ndarray
  309. Solution to the continuous-time algebraic Riccati equation.
  310. Raises
  311. ------
  312. LinAlgError
  313. For cases where the stable subspace of the pencil could not be
  314. isolated. See Notes section and the references for details.
  315. See Also
  316. --------
  317. solve_discrete_are : Solves the discrete-time algebraic Riccati equation
  318. Notes
  319. -----
  320. The equation is solved by forming the extended hamiltonian matrix pencil,
  321. as described in [1]_, :math:`H - \lambda J` given by the block matrices ::
  322. [ A 0 B ] [ E 0 0 ]
  323. [-Q -A^H -S ] - \lambda * [ 0 E^H 0 ]
  324. [ S^H B^H R ] [ 0 0 0 ]
  325. and using a QZ decomposition method.
  326. In this algorithm, the fail conditions are linked to the symmetry
  327. of the product :math:`U_2 U_1^{-1}` and condition number of
  328. :math:`U_1`. Here, :math:`U` is the 2m-by-m matrix that holds the
  329. eigenvectors spanning the stable subspace with 2-m rows and partitioned
  330. into two m-row matrices. See [1]_ and [2]_ for more details.
  331. In order to improve the QZ decomposition accuracy, the pencil goes
  332. through a balancing step where the sum of absolute values of
  333. :math:`H` and :math:`J` entries (after removing the diagonal entries of
  334. the sum) is balanced following the recipe given in [3]_.
  335. .. versionadded:: 0.11.0
  336. References
  337. ----------
  338. .. [1] P. van Dooren , "A Generalized Eigenvalue Approach For Solving
  339. Riccati Equations.", SIAM Journal on Scientific and Statistical
  340. Computing, Vol.2(2), :doi:`10.1137/0902010`
  341. .. [2] A.J. Laub, "A Schur Method for Solving Algebraic Riccati
  342. Equations.", Massachusetts Institute of Technology. Laboratory for
  343. Information and Decision Systems. LIDS-R ; 859. Available online :
  344. http://hdl.handle.net/1721.1/1301
  345. .. [3] P. Benner, "Symplectic Balancing of Hamiltonian Matrices", 2001,
  346. SIAM J. Sci. Comput., 2001, Vol.22(5), :doi:`10.1137/S1064827500367993`
  347. Examples
  348. --------
  349. Given `a`, `b`, `q`, and `r` solve for `x`:
  350. >>> import numpy as np
  351. >>> from scipy import linalg
  352. >>> a = np.array([[4, 3], [-4.5, -3.5]])
  353. >>> b = np.array([[1], [-1]])
  354. >>> q = np.array([[9, 6], [6, 4.]])
  355. >>> r = 1
  356. >>> x = linalg.solve_continuous_are(a, b, q, r)
  357. >>> x
  358. array([[ 21.72792206, 14.48528137],
  359. [ 14.48528137, 9.65685425]])
  360. >>> np.allclose(a.T.dot(x) + x.dot(a)-x.dot(b).dot(b.T).dot(x), -q)
  361. True
  362. """
  363. # ensure that all arguments are present when using `_apply_over_batch` (gh-23336)
  364. return _solve_continuous_are(a, b, q, r, e, s, balanced)
  365. @_apply_over_batch(('a', 2), ('b', 2), ('q', 2), ('r', 2), ('e', 2), ('s', 2))
  366. def _solve_continuous_are(a, b, q, r, e, s, balanced):
  367. # Validate input arguments
  368. a, b, q, r, e, s, m, n, r_or_c, gen_are = _are_validate_args(
  369. a, b, q, r, e, s, 'care')
  370. H = np.empty((2*m+n, 2*m+n), dtype=r_or_c)
  371. H[:m, :m] = a
  372. H[:m, m:2*m] = 0.
  373. H[:m, 2*m:] = b
  374. H[m:2*m, :m] = -q
  375. H[m:2*m, m:2*m] = -a.conj().T
  376. H[m:2*m, 2*m:] = 0. if s is None else -s
  377. H[2*m:, :m] = 0. if s is None else s.conj().T
  378. H[2*m:, m:2*m] = b.conj().T
  379. H[2*m:, 2*m:] = r
  380. if gen_are and e is not None:
  381. J = block_diag(e, e.conj().T, np.zeros_like(r, dtype=r_or_c))
  382. else:
  383. J = block_diag(np.eye(2*m), np.zeros_like(r, dtype=r_or_c))
  384. if balanced:
  385. # xGEBAL does not remove the diagonals before scaling. Also
  386. # to avoid destroying the Symplectic structure, we follow Ref.3
  387. M = np.abs(H) + np.abs(J)
  388. np.fill_diagonal(M, 0.)
  389. _, (sca, _) = matrix_balance(M, separate=1, permute=0)
  390. # do we need to bother?
  391. if not np.allclose(sca, np.ones_like(sca)):
  392. # Now impose diag(D,inv(D)) from Benner where D is
  393. # square root of s_i/s_(n+i) for i=0,....
  394. sca = np.log2(sca)
  395. # NOTE: Py3 uses "Bankers Rounding: round to the nearest even" !!
  396. s = np.round((sca[m:2*m] - sca[:m])/2)
  397. sca = 2 ** np.r_[s, -s, sca[2*m:]]
  398. # Elementwise multiplication via broadcasting.
  399. elwisescale = sca[:, None] * np.reciprocal(sca)
  400. H *= elwisescale
  401. J *= elwisescale
  402. # Deflate the pencil to 2m x 2m ala Ref.1, eq.(55)
  403. q, r = qr(H[:, -n:])
  404. H = q[:, n:].conj().T.dot(H[:, :2*m])
  405. J = q[:2*m, n:].conj().T.dot(J[:2*m, :2*m])
  406. # Decide on which output type is needed for QZ
  407. out_str = 'real' if r_or_c is float else 'complex'
  408. _, _, _, _, _, u = ordqz(H, J, sort='lhp', overwrite_a=True,
  409. overwrite_b=True, check_finite=False,
  410. output=out_str)
  411. # Get the relevant parts of the stable subspace basis
  412. if e is not None:
  413. u, _ = qr(np.vstack((e.dot(u[:m, :m]), u[m:, :m])))
  414. u00 = u[:m, :m]
  415. u10 = u[m:, :m]
  416. # Solve via back-substituion after checking the condition of u00
  417. up, ul, uu = lu(u00)
  418. if 1/cond(uu) < np.spacing(1.):
  419. raise LinAlgError('Failed to find a finite solution.')
  420. # Exploit the triangular structure
  421. x = solve_triangular(ul.conj().T,
  422. solve_triangular(uu.conj().T,
  423. u10.conj().T,
  424. lower=True),
  425. unit_diagonal=True,
  426. ).conj().T.dot(up.conj().T)
  427. if balanced:
  428. x *= sca[:m, None] * sca[:m]
  429. # Check the deviation from symmetry for lack of success
  430. # See proof of Thm.5 item 3 in [2]
  431. u_sym = u00.conj().T.dot(u10)
  432. n_u_sym = norm(u_sym, 1)
  433. u_sym = u_sym - u_sym.conj().T
  434. sym_threshold = np.max([np.spacing(1000.), 0.1*n_u_sym])
  435. if norm(u_sym, 1) > sym_threshold:
  436. raise LinAlgError('The associated Hamiltonian pencil has eigenvalues '
  437. 'too close to the imaginary axis')
  438. return (x + x.conj().T)/2
  439. def solve_discrete_are(a, b, q, r, e=None, s=None, balanced=True):
  440. r"""
  441. Solves the discrete-time algebraic Riccati equation (DARE).
  442. The DARE is defined as
  443. .. math::
  444. A^HXA - X - (A^HXB) (R + B^HXB)^{-1} (B^HXA) + Q = 0
  445. The limitations for a solution to exist are :
  446. * All eigenvalues of :math:`A` outside the unit disc, should be
  447. controllable.
  448. * The associated symplectic pencil (See Notes), should have
  449. eigenvalues sufficiently away from the unit circle.
  450. Moreover, if ``e`` and ``s`` are not both precisely ``None``, then the
  451. generalized version of DARE
  452. .. math::
  453. A^HXA - E^HXE - (A^HXB+S) (R+B^HXB)^{-1} (B^HXA+S^H) + Q = 0
  454. is solved. When omitted, ``e`` is assumed to be the identity and ``s``
  455. is assumed to be the zero matrix.
  456. The documentation is written assuming array arguments are of specified
  457. "core" shapes. However, array argument(s) of this function may have additional
  458. "batch" dimensions prepended to the core shape. In this case, the array is treated
  459. as a batch of lower-dimensional slices; see :ref:`linalg_batch` for details.
  460. Parameters
  461. ----------
  462. a : (M, M) array_like
  463. Square matrix
  464. b : (M, N) array_like
  465. Input
  466. q : (M, M) array_like
  467. Input
  468. r : (N, N) array_like
  469. Square matrix
  470. e : (M, M) array_like, optional
  471. Nonsingular square matrix
  472. s : (M, N) array_like, optional
  473. Input
  474. balanced : bool
  475. The boolean that indicates whether a balancing step is performed
  476. on the data. The default is set to True.
  477. Returns
  478. -------
  479. x : (M, M) ndarray
  480. Solution to the discrete algebraic Riccati equation.
  481. Raises
  482. ------
  483. LinAlgError
  484. For cases where the stable subspace of the pencil could not be
  485. isolated. See Notes section and the references for details.
  486. See Also
  487. --------
  488. solve_continuous_are : Solves the continuous algebraic Riccati equation
  489. Notes
  490. -----
  491. The equation is solved by forming the extended symplectic matrix pencil,
  492. as described in [1]_, :math:`H - \lambda J` given by the block matrices ::
  493. [ A 0 B ] [ E 0 B ]
  494. [ -Q E^H -S ] - \lambda * [ 0 A^H 0 ]
  495. [ S^H 0 R ] [ 0 -B^H 0 ]
  496. and using a QZ decomposition method.
  497. In this algorithm, the fail conditions are linked to the symmetry
  498. of the product :math:`U_2 U_1^{-1}` and condition number of
  499. :math:`U_1`. Here, :math:`U` is the 2m-by-m matrix that holds the
  500. eigenvectors spanning the stable subspace with 2-m rows and partitioned
  501. into two m-row matrices. See [1]_ and [2]_ for more details.
  502. In order to improve the QZ decomposition accuracy, the pencil goes
  503. through a balancing step where the sum of absolute values of
  504. :math:`H` and :math:`J` rows/cols (after removing the diagonal entries)
  505. is balanced following the recipe given in [3]_. If the data has small
  506. numerical noise, balancing may amplify their effects and some clean up
  507. is required.
  508. .. versionadded:: 0.11.0
  509. References
  510. ----------
  511. .. [1] P. van Dooren , "A Generalized Eigenvalue Approach For Solving
  512. Riccati Equations.", SIAM Journal on Scientific and Statistical
  513. Computing, Vol.2(2), :doi:`10.1137/0902010`
  514. .. [2] A.J. Laub, "A Schur Method for Solving Algebraic Riccati
  515. Equations.", Massachusetts Institute of Technology. Laboratory for
  516. Information and Decision Systems. LIDS-R ; 859. Available online :
  517. http://hdl.handle.net/1721.1/1301
  518. .. [3] P. Benner, "Symplectic Balancing of Hamiltonian Matrices", 2001,
  519. SIAM J. Sci. Comput., 2001, Vol.22(5), :doi:`10.1137/S1064827500367993`
  520. Examples
  521. --------
  522. Given `a`, `b`, `q`, and `r` solve for `x`:
  523. >>> import numpy as np
  524. >>> from scipy import linalg as la
  525. >>> a = np.array([[0, 1], [0, -1]])
  526. >>> b = np.array([[1, 0], [2, 1]])
  527. >>> q = np.array([[-4, -4], [-4, 7]])
  528. >>> r = np.array([[9, 3], [3, 1]])
  529. >>> x = la.solve_discrete_are(a, b, q, r)
  530. >>> x
  531. array([[-4., -4.],
  532. [-4., 7.]])
  533. >>> R = la.solve(r + b.T.dot(x).dot(b), b.T.dot(x).dot(a))
  534. >>> np.allclose(a.T.dot(x).dot(a) - x - a.T.dot(x).dot(b).dot(R), -q)
  535. True
  536. """
  537. # ensure that all arguments are present when using `_apply_over_batch` (gh-23336)
  538. return _solve_discrete_are(a, b, q, r, e, s, balanced)
  539. @_apply_over_batch(('a', 2), ('b', 2), ('q', 2), ('r', 2), ('e', 2), ('s', 2))
  540. def _solve_discrete_are(a, b, q, r, e, s, balanced):
  541. # Validate input arguments
  542. a, b, q, r, e, s, m, n, r_or_c, gen_are = _are_validate_args(
  543. a, b, q, r, e, s, 'dare')
  544. # Form the matrix pencil
  545. H = np.zeros((2*m+n, 2*m+n), dtype=r_or_c)
  546. H[:m, :m] = a
  547. H[:m, 2*m:] = b
  548. H[m:2*m, :m] = -q
  549. H[m:2*m, m:2*m] = np.eye(m) if e is None else e.conj().T
  550. H[m:2*m, 2*m:] = 0. if s is None else -s
  551. H[2*m:, :m] = 0. if s is None else s.conj().T
  552. H[2*m:, 2*m:] = r
  553. J = np.zeros_like(H, dtype=r_or_c)
  554. J[:m, :m] = np.eye(m) if e is None else e
  555. J[m:2*m, m:2*m] = a.conj().T
  556. J[2*m:, m:2*m] = -b.conj().T
  557. if balanced:
  558. # xGEBAL does not remove the diagonals before scaling. Also
  559. # to avoid destroying the Symplectic structure, we follow Ref.3
  560. M = np.abs(H) + np.abs(J)
  561. np.fill_diagonal(M, 0.)
  562. _, (sca, _) = matrix_balance(M, separate=1, permute=0)
  563. # do we need to bother?
  564. if not np.allclose(sca, np.ones_like(sca)):
  565. # Now impose diag(D,inv(D)) from Benner where D is
  566. # square root of s_i/s_(n+i) for i=0,....
  567. sca = np.log2(sca)
  568. # NOTE: Py3 uses "Bankers Rounding: round to the nearest even" !!
  569. s = np.round((sca[m:2*m] - sca[:m])/2)
  570. sca = 2 ** np.r_[s, -s, sca[2*m:]]
  571. # Elementwise multiplication via broadcasting.
  572. elwisescale = sca[:, None] * np.reciprocal(sca)
  573. H *= elwisescale
  574. J *= elwisescale
  575. # Deflate the pencil by the R column ala Ref.1
  576. q_of_qr, _ = qr(H[:, -n:])
  577. H = q_of_qr[:, n:].conj().T.dot(H[:, :2*m])
  578. J = q_of_qr[:, n:].conj().T.dot(J[:, :2*m])
  579. # Decide on which output type is needed for QZ
  580. out_str = 'real' if r_or_c is float else 'complex'
  581. _, _, _, _, _, u = ordqz(H, J, sort='iuc',
  582. overwrite_a=True,
  583. overwrite_b=True,
  584. check_finite=False,
  585. output=out_str)
  586. # Get the relevant parts of the stable subspace basis
  587. if e is not None:
  588. u, _ = qr(np.vstack((e.dot(u[:m, :m]), u[m:, :m])))
  589. u00 = u[:m, :m]
  590. u10 = u[m:, :m]
  591. # Solve via back-substituion after checking the condition of u00
  592. up, ul, uu = lu(u00)
  593. if 1/cond(uu) < np.spacing(1.):
  594. raise LinAlgError('Failed to find a finite solution.')
  595. # Exploit the triangular structure
  596. x = solve_triangular(ul.conj().T,
  597. solve_triangular(uu.conj().T,
  598. u10.conj().T,
  599. lower=True),
  600. unit_diagonal=True,
  601. ).conj().T.dot(up.conj().T)
  602. if balanced:
  603. x *= sca[:m, None] * sca[:m]
  604. # Check the deviation from symmetry for lack of success
  605. # See proof of Thm.5 item 3 in [2]
  606. u_sym = u00.conj().T.dot(u10)
  607. n_u_sym = norm(u_sym, 1)
  608. u_sym = u_sym - u_sym.conj().T
  609. sym_threshold = np.max([np.spacing(1000.), 0.1*n_u_sym])
  610. if norm(u_sym, 1) > sym_threshold:
  611. raise LinAlgError('The associated symplectic pencil has eigenvalues '
  612. 'too close to the unit circle')
  613. return (x + x.conj().T)/2
  614. def _are_validate_args(a, b, q, r, e, s, eq_type='care'):
  615. """
  616. A helper function to validate the arguments supplied to the
  617. Riccati equation solvers. Any discrepancy found in the input
  618. matrices leads to a ``ValueError`` exception.
  619. Essentially, it performs:
  620. - a check whether the input is free of NaN and Infs
  621. - a pass for the data through ``numpy.atleast_2d()``
  622. - squareness check of the relevant arrays
  623. - shape consistency check of the arrays
  624. - singularity check of the relevant arrays
  625. - symmetricity check of the relevant matrices
  626. - a check whether the regular or the generalized version is asked.
  627. This function is used by ``solve_continuous_are`` and
  628. ``solve_discrete_are``.
  629. Parameters
  630. ----------
  631. a, b, q, r, e, s : array_like
  632. Input data
  633. eq_type : str
  634. Accepted arguments are 'care' and 'dare'.
  635. Returns
  636. -------
  637. a, b, q, r, e, s : ndarray
  638. Regularized input data
  639. m, n : int
  640. shape of the problem
  641. r_or_c : type
  642. Data type of the problem, returns float or complex
  643. gen_or_not : bool
  644. Type of the equation, True for generalized and False for regular ARE.
  645. """
  646. if eq_type.lower() not in ("dare", "care"):
  647. raise ValueError("Equation type unknown. "
  648. "Only 'care' and 'dare' is understood")
  649. a = np.atleast_2d(_asarray_validated(a, check_finite=True))
  650. b = np.atleast_2d(_asarray_validated(b, check_finite=True))
  651. q = np.atleast_2d(_asarray_validated(q, check_finite=True))
  652. r = np.atleast_2d(_asarray_validated(r, check_finite=True))
  653. # Get the correct data types otherwise NumPy complains
  654. # about pushing complex numbers into real arrays.
  655. r_or_c = complex if np.iscomplexobj(b) else float
  656. for ind, mat in enumerate((a, q, r)):
  657. if np.iscomplexobj(mat):
  658. r_or_c = complex
  659. if not np.equal(*mat.shape):
  660. raise ValueError(f"Matrix {'aqr'[ind]} should be square.")
  661. # Shape consistency checks
  662. m, n = b.shape
  663. if m != a.shape[0]:
  664. raise ValueError("Matrix a and b should have the same number of rows.")
  665. if m != q.shape[0]:
  666. raise ValueError("Matrix a and q should have the same shape.")
  667. if n != r.shape[0]:
  668. raise ValueError("Matrix b and r should have the same number of cols.")
  669. # Check if the data matrices q, r are (sufficiently) hermitian
  670. for ind, mat in enumerate((q, r)):
  671. if norm(mat - mat.conj().T, 1) > np.spacing(norm(mat, 1))*100:
  672. raise ValueError(f"Matrix {'qr'[ind]} should be symmetric/hermitian.")
  673. # Continuous time ARE should have a nonsingular r matrix.
  674. if eq_type == 'care':
  675. min_sv = svd(r, compute_uv=False)[-1]
  676. if min_sv == 0. or min_sv < np.spacing(1.)*norm(r, 1):
  677. raise ValueError('Matrix r is numerically singular.')
  678. # Check if the generalized case is required with omitted arguments
  679. # perform late shape checking etc.
  680. generalized_case = e is not None or s is not None
  681. if generalized_case:
  682. if e is not None:
  683. e = np.atleast_2d(_asarray_validated(e, check_finite=True))
  684. if not np.equal(*e.shape):
  685. raise ValueError("Matrix e should be square.")
  686. if m != e.shape[0]:
  687. raise ValueError("Matrix a and e should have the same shape.")
  688. # numpy.linalg.cond doesn't check for exact zeros and
  689. # emits a runtime warning. Hence the following manual check.
  690. min_sv = svd(e, compute_uv=False)[-1]
  691. if min_sv == 0. or min_sv < np.spacing(1.) * norm(e, 1):
  692. raise ValueError('Matrix e is numerically singular.')
  693. if np.iscomplexobj(e):
  694. r_or_c = complex
  695. if s is not None:
  696. s = np.atleast_2d(_asarray_validated(s, check_finite=True))
  697. if s.shape != b.shape:
  698. raise ValueError("Matrix b and s should have the same shape.")
  699. if np.iscomplexobj(s):
  700. r_or_c = complex
  701. return a, b, q, r, e, s, m, n, r_or_c, generalized_case