_solvers.py 28 KB

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