Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Matrix equation solver routines""" 

2# Author: Jeffrey Armstrong <jeff@approximatrix.com> 

3# February 24, 2012 

4 

5# Modified: Chad Fulton <ChadFulton@gmail.com> 

6# June 19, 2014 

7 

8# Modified: Ilhan Polat <ilhanpolat@gmail.com> 

9# September 13, 2016 

10 

11import warnings 

12import numpy as np 

13from numpy.linalg import inv, LinAlgError, norm, cond, svd 

14 

15from .basic import solve, solve_triangular, matrix_balance 

16from .lapack import get_lapack_funcs 

17from .decomp_schur import schur 

18from .decomp_lu import lu 

19from .decomp_qr import qr 

20from ._decomp_qz import ordqz 

21from .decomp import _asarray_validated 

22from .special_matrices import kron, block_diag 

23 

24__all__ = ['solve_sylvester', 

25 'solve_continuous_lyapunov', 'solve_discrete_lyapunov', 

26 'solve_lyapunov', 

27 'solve_continuous_are', 'solve_discrete_are'] 

28 

29 

30def solve_sylvester(a, b, q): 

31 """ 

32 Computes a solution (X) to the Sylvester equation :math:`AX + XB = Q`. 

33 

34 Parameters 

35 ---------- 

36 a : (M, M) array_like 

37 Leading matrix of the Sylvester equation 

38 b : (N, N) array_like 

39 Trailing matrix of the Sylvester equation 

40 q : (M, N) array_like 

41 Right-hand side 

42 

43 Returns 

44 ------- 

45 x : (M, N) ndarray 

46 The solution to the Sylvester equation. 

47 

48 Raises 

49 ------ 

50 LinAlgError 

51 If solution was not found 

52 

53 Notes 

54 ----- 

55 Computes a solution to the Sylvester matrix equation via the Bartels- 

56 Stewart algorithm. The A and B matrices first undergo Schur 

57 decompositions. The resulting matrices are used to construct an 

58 alternative Sylvester equation (``RY + YS^T = F``) where the R and S 

59 matrices are in quasi-triangular form (or, when R, S or F are complex, 

60 triangular form). The simplified equation is then solved using 

61 ``*TRSYL`` from LAPACK directly. 

62 

63 .. versionadded:: 0.11.0 

64 

65 Examples 

66 -------- 

67 Given `a`, `b`, and `q` solve for `x`: 

68 

69 >>> from scipy import linalg 

70 >>> a = np.array([[-3, -2, 0], [-1, -1, 3], [3, -5, -1]]) 

71 >>> b = np.array([[1]]) 

72 >>> q = np.array([[1],[2],[3]]) 

73 >>> x = linalg.solve_sylvester(a, b, q) 

74 >>> x 

75 array([[ 0.0625], 

76 [-0.5625], 

77 [ 0.6875]]) 

78 >>> np.allclose(a.dot(x) + x.dot(b), q) 

79 True 

80 

81 """ 

82 

83 # Compute the Schur decomposition form of a 

84 r, u = schur(a, output='real') 

85 

86 # Compute the Schur decomposition of b 

87 s, v = schur(b.conj().transpose(), output='real') 

88 

89 # Construct f = u'*q*v 

90 f = np.dot(np.dot(u.conj().transpose(), q), v) 

91 

92 # Call the Sylvester equation solver 

93 trsyl, = get_lapack_funcs(('trsyl',), (r, s, f)) 

94 if trsyl is None: 

95 raise RuntimeError('LAPACK implementation does not contain a proper ' 

96 'Sylvester equation solver (TRSYL)') 

97 y, scale, info = trsyl(r, s, f, tranb='C') 

98 

99 y = scale*y 

100 

101 if info < 0: 

102 raise LinAlgError("Illegal value encountered in " 

103 "the %d term" % (-info,)) 

104 

105 return np.dot(np.dot(u, y), v.conj().transpose()) 

106 

107 

108def solve_continuous_lyapunov(a, q): 

109 """ 

110 Solves the continuous Lyapunov equation :math:`AX + XA^H = Q`. 

111 

112 Uses the Bartels-Stewart algorithm to find :math:`X`. 

113 

114 Parameters 

115 ---------- 

116 a : array_like 

117 A square matrix 

118 

119 q : array_like 

120 Right-hand side square matrix 

121 

122 Returns 

123 ------- 

124 x : ndarray 

125 Solution to the continuous Lyapunov equation 

126 

127 See Also 

128 -------- 

129 solve_discrete_lyapunov : computes the solution to the discrete-time 

130 Lyapunov equation 

131 solve_sylvester : computes the solution to the Sylvester equation 

132 

133 Notes 

134 ----- 

135 The continuous Lyapunov equation is a special form of the Sylvester 

136 equation, hence this solver relies on LAPACK routine ?TRSYL. 

137 

138 .. versionadded:: 0.11.0 

139 

140 Examples 

141 -------- 

142 Given `a` and `q` solve for `x`: 

143 

144 >>> from scipy import linalg 

145 >>> a = np.array([[-3, -2, 0], [-1, -1, 0], [0, -5, -1]]) 

146 >>> b = np.array([2, 4, -1]) 

147 >>> q = np.eye(3) 

148 >>> x = linalg.solve_continuous_lyapunov(a, q) 

149 >>> x 

150 array([[ -0.75 , 0.875 , -3.75 ], 

151 [ 0.875 , -1.375 , 5.3125], 

152 [ -3.75 , 5.3125, -27.0625]]) 

153 >>> np.allclose(a.dot(x) + x.dot(a.T), q) 

154 True 

155 """ 

156 

157 a = np.atleast_2d(_asarray_validated(a, check_finite=True)) 

158 q = np.atleast_2d(_asarray_validated(q, check_finite=True)) 

159 

160 r_or_c = float 

161 

162 for ind, _ in enumerate((a, q)): 

163 if np.iscomplexobj(_): 

164 r_or_c = complex 

165 

166 if not np.equal(*_.shape): 

167 raise ValueError("Matrix {} should be square.".format("aq"[ind])) 

168 

169 # Shape consistency check 

170 if a.shape != q.shape: 

171 raise ValueError("Matrix a and q should have the same shape.") 

172 

173 # Compute the Schur decomposition form of a 

174 r, u = schur(a, output='real') 

175 

176 # Construct f = u'*q*u 

177 f = u.conj().T.dot(q.dot(u)) 

178 

179 # Call the Sylvester equation solver 

180 trsyl = get_lapack_funcs('trsyl', (r, f)) 

181 

182 dtype_string = 'T' if r_or_c == float else 'C' 

183 y, scale, info = trsyl(r, r, f, tranb=dtype_string) 

184 

185 if info < 0: 

186 raise ValueError('?TRSYL exited with the internal error ' 

187 '"illegal value in argument number {}.". See ' 

188 'LAPACK documentation for the ?TRSYL error codes.' 

189 ''.format(-info)) 

190 elif info == 1: 

191 warnings.warn('Input "a" has an eigenvalue pair whose sum is ' 

192 'very close to or exactly zero. The solution is ' 

193 'obtained via perturbing the coefficients.', 

194 RuntimeWarning) 

195 y *= scale 

196 

197 return u.dot(y).dot(u.conj().T) 

198 

199 

200# For backwards compatibility, keep the old name 

201solve_lyapunov = solve_continuous_lyapunov 

202 

203 

204def _solve_discrete_lyapunov_direct(a, q): 

205 """ 

206 Solves the discrete Lyapunov equation directly. 

207 

208 This function is called by the `solve_discrete_lyapunov` function with 

209 `method=direct`. It is not supposed to be called directly. 

210 """ 

211 

212 lhs = kron(a, a.conj()) 

213 lhs = np.eye(lhs.shape[0]) - lhs 

214 x = solve(lhs, q.flatten()) 

215 

216 return np.reshape(x, q.shape) 

217 

218 

219def _solve_discrete_lyapunov_bilinear(a, q): 

220 """ 

221 Solves the discrete Lyapunov equation using a bilinear transformation. 

222 

223 This function is called by the `solve_discrete_lyapunov` function with 

224 `method=bilinear`. It is not supposed to be called directly. 

225 """ 

226 eye = np.eye(a.shape[0]) 

227 aH = a.conj().transpose() 

228 aHI_inv = inv(aH + eye) 

229 b = np.dot(aH - eye, aHI_inv) 

230 c = 2*np.dot(np.dot(inv(a + eye), q), aHI_inv) 

231 return solve_lyapunov(b.conj().transpose(), -c) 

232 

233 

234def solve_discrete_lyapunov(a, q, method=None): 

235 """ 

236 Solves the discrete Lyapunov equation :math:`AXA^H - X + Q = 0`. 

237 

238 Parameters 

239 ---------- 

240 a, q : (M, M) array_like 

241 Square matrices corresponding to A and Q in the equation 

242 above respectively. Must have the same shape. 

243 

244 method : {'direct', 'bilinear'}, optional 

245 Type of solver. 

246 

247 If not given, chosen to be ``direct`` if ``M`` is less than 10 and 

248 ``bilinear`` otherwise. 

249 

250 Returns 

251 ------- 

252 x : ndarray 

253 Solution to the discrete Lyapunov equation 

254 

255 See Also 

256 -------- 

257 solve_continuous_lyapunov : computes the solution to the continuous-time 

258 Lyapunov equation 

259 

260 Notes 

261 ----- 

262 This section describes the available solvers that can be selected by the 

263 'method' parameter. The default method is *direct* if ``M`` is less than 10 

264 and ``bilinear`` otherwise. 

265 

266 Method *direct* uses a direct analytical solution to the discrete Lyapunov 

267 equation. The algorithm is given in, for example, [1]_. However, it requires 

268 the linear solution of a system with dimension :math:`M^2` so that 

269 performance degrades rapidly for even moderately sized matrices. 

270 

271 Method *bilinear* uses a bilinear transformation to convert the discrete 

272 Lyapunov equation to a continuous Lyapunov equation :math:`(BX+XB'=-C)` 

273 where :math:`B=(A-I)(A+I)^{-1}` and 

274 :math:`C=2(A' + I)^{-1} Q (A + I)^{-1}`. The continuous equation can be 

275 efficiently solved since it is a special case of a Sylvester equation. 

276 The transformation algorithm is from Popov (1964) as described in [2]_. 

277 

278 .. versionadded:: 0.11.0 

279 

280 References 

281 ---------- 

282 .. [1] Hamilton, James D. Time Series Analysis, Princeton: Princeton 

283 University Press, 1994. 265. Print. 

284 http://doc1.lbfl.li/aca/FLMF037168.pdf 

285 .. [2] Gajic, Z., and M.T.J. Qureshi. 2008. 

286 Lyapunov Matrix Equation in System Stability and Control. 

287 Dover Books on Engineering Series. Dover Publications. 

288 

289 Examples 

290 -------- 

291 Given `a` and `q` solve for `x`: 

292 

293 >>> from scipy import linalg 

294 >>> a = np.array([[0.2, 0.5],[0.7, -0.9]]) 

295 >>> q = np.eye(2) 

296 >>> x = linalg.solve_discrete_lyapunov(a, q) 

297 >>> x 

298 array([[ 0.70872893, 1.43518822], 

299 [ 1.43518822, -2.4266315 ]]) 

300 >>> np.allclose(a.dot(x).dot(a.T)-x, -q) 

301 True 

302 

303 """ 

304 a = np.asarray(a) 

305 q = np.asarray(q) 

306 if method is None: 

307 # Select automatically based on size of matrices 

308 if a.shape[0] >= 10: 

309 method = 'bilinear' 

310 else: 

311 method = 'direct' 

312 

313 meth = method.lower() 

314 

315 if meth == 'direct': 

316 x = _solve_discrete_lyapunov_direct(a, q) 

317 elif meth == 'bilinear': 

318 x = _solve_discrete_lyapunov_bilinear(a, q) 

319 else: 

320 raise ValueError('Unknown solver %s' % method) 

321 

322 return x 

323 

324 

325def solve_continuous_are(a, b, q, r, e=None, s=None, balanced=True): 

326 r""" 

327 Solves the continuous-time algebraic Riccati equation (CARE). 

328 

329 The CARE is defined as 

330 

331 .. math:: 

332 

333 X A + A^H X - X B R^{-1} B^H X + Q = 0 

334 

335 The limitations for a solution to exist are : 

336 

337 * All eigenvalues of :math:`A` on the right half plane, should be 

338 controllable. 

339 

340 * The associated hamiltonian pencil (See Notes), should have 

341 eigenvalues sufficiently away from the imaginary axis. 

342 

343 Moreover, if ``e`` or ``s`` is not precisely ``None``, then the 

344 generalized version of CARE 

345 

346 .. math:: 

347 

348 E^HXA + A^HXE - (E^HXB + S) R^{-1} (B^HXE + S^H) + Q = 0 

349 

350 is solved. When omitted, ``e`` is assumed to be the identity and ``s`` 

351 is assumed to be the zero matrix with sizes compatible with ``a`` and 

352 ``b``, respectively. 

353 

354 Parameters 

355 ---------- 

356 a : (M, M) array_like 

357 Square matrix 

358 b : (M, N) array_like 

359 Input 

360 q : (M, M) array_like 

361 Input 

362 r : (N, N) array_like 

363 Nonsingular square matrix 

364 e : (M, M) array_like, optional 

365 Nonsingular square matrix 

366 s : (M, N) array_like, optional 

367 Input 

368 balanced : bool, optional 

369 The boolean that indicates whether a balancing step is performed 

370 on the data. The default is set to True. 

371 

372 Returns 

373 ------- 

374 x : (M, M) ndarray 

375 Solution to the continuous-time algebraic Riccati equation. 

376 

377 Raises 

378 ------ 

379 LinAlgError 

380 For cases where the stable subspace of the pencil could not be 

381 isolated. See Notes section and the references for details. 

382 

383 See Also 

384 -------- 

385 solve_discrete_are : Solves the discrete-time algebraic Riccati equation 

386 

387 Notes 

388 ----- 

389 The equation is solved by forming the extended hamiltonian matrix pencil, 

390 as described in [1]_, :math:`H - \lambda J` given by the block matrices :: 

391 

392 [ A 0 B ] [ E 0 0 ] 

393 [-Q -A^H -S ] - \lambda * [ 0 E^H 0 ] 

394 [ S^H B^H R ] [ 0 0 0 ] 

395 

396 and using a QZ decomposition method. 

397 

398 In this algorithm, the fail conditions are linked to the symmetry 

399 of the product :math:`U_2 U_1^{-1}` and condition number of 

400 :math:`U_1`. Here, :math:`U` is the 2m-by-m matrix that holds the 

401 eigenvectors spanning the stable subspace with 2-m rows and partitioned 

402 into two m-row matrices. See [1]_ and [2]_ for more details. 

403 

404 In order to improve the QZ decomposition accuracy, the pencil goes 

405 through a balancing step where the sum of absolute values of 

406 :math:`H` and :math:`J` entries (after removing the diagonal entries of 

407 the sum) is balanced following the recipe given in [3]_. 

408 

409 .. versionadded:: 0.11.0 

410 

411 References 

412 ---------- 

413 .. [1] P. van Dooren , "A Generalized Eigenvalue Approach For Solving 

414 Riccati Equations.", SIAM Journal on Scientific and Statistical 

415 Computing, Vol.2(2), DOI: 10.1137/0902010 

416 

417 .. [2] A.J. Laub, "A Schur Method for Solving Algebraic Riccati 

418 Equations.", Massachusetts Institute of Technology. Laboratory for 

419 Information and Decision Systems. LIDS-R ; 859. Available online : 

420 http://hdl.handle.net/1721.1/1301 

421 

422 .. [3] P. Benner, "Symplectic Balancing of Hamiltonian Matrices", 2001, 

423 SIAM J. Sci. Comput., 2001, Vol.22(5), DOI: 10.1137/S1064827500367993 

424 

425 Examples 

426 -------- 

427 Given `a`, `b`, `q`, and `r` solve for `x`: 

428 

429 >>> from scipy import linalg 

430 >>> a = np.array([[4, 3], [-4.5, -3.5]]) 

431 >>> b = np.array([[1], [-1]]) 

432 >>> q = np.array([[9, 6], [6, 4.]]) 

433 >>> r = 1 

434 >>> x = linalg.solve_continuous_are(a, b, q, r) 

435 >>> x 

436 array([[ 21.72792206, 14.48528137], 

437 [ 14.48528137, 9.65685425]]) 

438 >>> np.allclose(a.T.dot(x) + x.dot(a)-x.dot(b).dot(b.T).dot(x), -q) 

439 True 

440 

441 """ 

442 

443 # Validate input arguments 

444 a, b, q, r, e, s, m, n, r_or_c, gen_are = _are_validate_args( 

445 a, b, q, r, e, s, 'care') 

446 

447 H = np.empty((2*m+n, 2*m+n), dtype=r_or_c) 

448 H[:m, :m] = a 

449 H[:m, m:2*m] = 0. 

450 H[:m, 2*m:] = b 

451 H[m:2*m, :m] = -q 

452 H[m:2*m, m:2*m] = -a.conj().T 

453 H[m:2*m, 2*m:] = 0. if s is None else -s 

454 H[2*m:, :m] = 0. if s is None else s.conj().T 

455 H[2*m:, m:2*m] = b.conj().T 

456 H[2*m:, 2*m:] = r 

457 

458 if gen_are and e is not None: 

459 J = block_diag(e, e.conj().T, np.zeros_like(r, dtype=r_or_c)) 

460 else: 

461 J = block_diag(np.eye(2*m), np.zeros_like(r, dtype=r_or_c)) 

462 

463 if balanced: 

464 # xGEBAL does not remove the diagonals before scaling. Also 

465 # to avoid destroying the Symplectic structure, we follow Ref.3 

466 M = np.abs(H) + np.abs(J) 

467 M[np.diag_indices_from(M)] = 0. 

468 _, (sca, _) = matrix_balance(M, separate=1, permute=0) 

469 # do we need to bother? 

470 if not np.allclose(sca, np.ones_like(sca)): 

471 # Now impose diag(D,inv(D)) from Benner where D is 

472 # square root of s_i/s_(n+i) for i=0,.... 

473 sca = np.log2(sca) 

474 # NOTE: Py3 uses "Bankers Rounding: round to the nearest even" !! 

475 s = np.round((sca[m:2*m] - sca[:m])/2) 

476 sca = 2 ** np.r_[s, -s, sca[2*m:]] 

477 # Elementwise multiplication via broadcasting. 

478 elwisescale = sca[:, None] * np.reciprocal(sca) 

479 H *= elwisescale 

480 J *= elwisescale 

481 

482 # Deflate the pencil to 2m x 2m ala Ref.1, eq.(55) 

483 q, r = qr(H[:, -n:]) 

484 H = q[:, n:].conj().T.dot(H[:, :2*m]) 

485 J = q[:2*m, n:].conj().T.dot(J[:2*m, :2*m]) 

486 

487 # Decide on which output type is needed for QZ 

488 out_str = 'real' if r_or_c == float else 'complex' 

489 

490 _, _, _, _, _, u = ordqz(H, J, sort='lhp', overwrite_a=True, 

491 overwrite_b=True, check_finite=False, 

492 output=out_str) 

493 

494 # Get the relevant parts of the stable subspace basis 

495 if e is not None: 

496 u, _ = qr(np.vstack((e.dot(u[:m, :m]), u[m:, :m]))) 

497 u00 = u[:m, :m] 

498 u10 = u[m:, :m] 

499 

500 # Solve via back-substituion after checking the condition of u00 

501 up, ul, uu = lu(u00) 

502 if 1/cond(uu) < np.spacing(1.): 

503 raise LinAlgError('Failed to find a finite solution.') 

504 

505 # Exploit the triangular structure 

506 x = solve_triangular(ul.conj().T, 

507 solve_triangular(uu.conj().T, 

508 u10.conj().T, 

509 lower=True), 

510 unit_diagonal=True, 

511 ).conj().T.dot(up.conj().T) 

512 if balanced: 

513 x *= sca[:m, None] * sca[:m] 

514 

515 # Check the deviation from symmetry for lack of success 

516 # See proof of Thm.5 item 3 in [2] 

517 u_sym = u00.conj().T.dot(u10) 

518 n_u_sym = norm(u_sym, 1) 

519 u_sym = u_sym - u_sym.conj().T 

520 sym_threshold = np.max([np.spacing(1000.), 0.1*n_u_sym]) 

521 

522 if norm(u_sym, 1) > sym_threshold: 

523 raise LinAlgError('The associated Hamiltonian pencil has eigenvalues ' 

524 'too close to the imaginary axis') 

525 

526 return (x + x.conj().T)/2 

527 

528 

529def solve_discrete_are(a, b, q, r, e=None, s=None, balanced=True): 

530 r""" 

531 Solves the discrete-time algebraic Riccati equation (DARE). 

532 

533 The DARE is defined as 

534 

535 .. math:: 

536 

537 A^HXA - X - (A^HXB) (R + B^HXB)^{-1} (B^HXA) + Q = 0 

538 

539 The limitations for a solution to exist are : 

540 

541 * All eigenvalues of :math:`A` outside the unit disc, should be 

542 controllable. 

543 

544 * The associated symplectic pencil (See Notes), should have 

545 eigenvalues sufficiently away from the unit circle. 

546 

547 Moreover, if ``e`` and ``s`` are not both precisely ``None``, then the 

548 generalized version of DARE 

549 

550 .. math:: 

551 

552 A^HXA - E^HXE - (A^HXB+S) (R+B^HXB)^{-1} (B^HXA+S^H) + Q = 0 

553 

554 is solved. When omitted, ``e`` is assumed to be the identity and ``s`` 

555 is assumed to be the zero matrix. 

556 

557 Parameters 

558 ---------- 

559 a : (M, M) array_like 

560 Square matrix 

561 b : (M, N) array_like 

562 Input 

563 q : (M, M) array_like 

564 Input 

565 r : (N, N) array_like 

566 Square matrix 

567 e : (M, M) array_like, optional 

568 Nonsingular square matrix 

569 s : (M, N) array_like, optional 

570 Input 

571 balanced : bool 

572 The boolean that indicates whether a balancing step is performed 

573 on the data. The default is set to True. 

574 

575 Returns 

576 ------- 

577 x : (M, M) ndarray 

578 Solution to the discrete algebraic Riccati equation. 

579 

580 Raises 

581 ------ 

582 LinAlgError 

583 For cases where the stable subspace of the pencil could not be 

584 isolated. See Notes section and the references for details. 

585 

586 See Also 

587 -------- 

588 solve_continuous_are : Solves the continuous algebraic Riccati equation 

589 

590 Notes 

591 ----- 

592 The equation is solved by forming the extended symplectic matrix pencil, 

593 as described in [1]_, :math:`H - \lambda J` given by the block matrices :: 

594 

595 [ A 0 B ] [ E 0 B ] 

596 [ -Q E^H -S ] - \lambda * [ 0 A^H 0 ] 

597 [ S^H 0 R ] [ 0 -B^H 0 ] 

598 

599 and using a QZ decomposition method. 

600 

601 In this algorithm, the fail conditions are linked to the symmetry 

602 of the product :math:`U_2 U_1^{-1}` and condition number of 

603 :math:`U_1`. Here, :math:`U` is the 2m-by-m matrix that holds the 

604 eigenvectors spanning the stable subspace with 2-m rows and partitioned 

605 into two m-row matrices. See [1]_ and [2]_ for more details. 

606 

607 In order to improve the QZ decomposition accuracy, the pencil goes 

608 through a balancing step where the sum of absolute values of 

609 :math:`H` and :math:`J` rows/cols (after removing the diagonal entries) 

610 is balanced following the recipe given in [3]_. If the data has small 

611 numerical noise, balancing may amplify their effects and some clean up 

612 is required. 

613 

614 .. versionadded:: 0.11.0 

615 

616 References 

617 ---------- 

618 .. [1] P. van Dooren , "A Generalized Eigenvalue Approach For Solving 

619 Riccati Equations.", SIAM Journal on Scientific and Statistical 

620 Computing, Vol.2(2), DOI: 10.1137/0902010 

621 

622 .. [2] A.J. Laub, "A Schur Method for Solving Algebraic Riccati 

623 Equations.", Massachusetts Institute of Technology. Laboratory for 

624 Information and Decision Systems. LIDS-R ; 859. Available online : 

625 http://hdl.handle.net/1721.1/1301 

626 

627 .. [3] P. Benner, "Symplectic Balancing of Hamiltonian Matrices", 2001, 

628 SIAM J. Sci. Comput., 2001, Vol.22(5), DOI: 10.1137/S1064827500367993 

629 

630 Examples 

631 -------- 

632 Given `a`, `b`, `q`, and `r` solve for `x`: 

633 

634 >>> from scipy import linalg as la 

635 >>> a = np.array([[0, 1], [0, -1]]) 

636 >>> b = np.array([[1, 0], [2, 1]]) 

637 >>> q = np.array([[-4, -4], [-4, 7]]) 

638 >>> r = np.array([[9, 3], [3, 1]]) 

639 >>> x = la.solve_discrete_are(a, b, q, r) 

640 >>> x 

641 array([[-4., -4.], 

642 [-4., 7.]]) 

643 >>> R = la.solve(r + b.T.dot(x).dot(b), b.T.dot(x).dot(a)) 

644 >>> np.allclose(a.T.dot(x).dot(a) - x - a.T.dot(x).dot(b).dot(R), -q) 

645 True 

646 

647 """ 

648 

649 # Validate input arguments 

650 a, b, q, r, e, s, m, n, r_or_c, gen_are = _are_validate_args( 

651 a, b, q, r, e, s, 'dare') 

652 

653 # Form the matrix pencil 

654 H = np.zeros((2*m+n, 2*m+n), dtype=r_or_c) 

655 H[:m, :m] = a 

656 H[:m, 2*m:] = b 

657 H[m:2*m, :m] = -q 

658 H[m:2*m, m:2*m] = np.eye(m) if e is None else e.conj().T 

659 H[m:2*m, 2*m:] = 0. if s is None else -s 

660 H[2*m:, :m] = 0. if s is None else s.conj().T 

661 H[2*m:, 2*m:] = r 

662 

663 J = np.zeros_like(H, dtype=r_or_c) 

664 J[:m, :m] = np.eye(m) if e is None else e 

665 J[m:2*m, m:2*m] = a.conj().T 

666 J[2*m:, m:2*m] = -b.conj().T 

667 

668 if balanced: 

669 # xGEBAL does not remove the diagonals before scaling. Also 

670 # to avoid destroying the Symplectic structure, we follow Ref.3 

671 M = np.abs(H) + np.abs(J) 

672 M[np.diag_indices_from(M)] = 0. 

673 _, (sca, _) = matrix_balance(M, separate=1, permute=0) 

674 # do we need to bother? 

675 if not np.allclose(sca, np.ones_like(sca)): 

676 # Now impose diag(D,inv(D)) from Benner where D is 

677 # square root of s_i/s_(n+i) for i=0,.... 

678 sca = np.log2(sca) 

679 # NOTE: Py3 uses "Bankers Rounding: round to the nearest even" !! 

680 s = np.round((sca[m:2*m] - sca[:m])/2) 

681 sca = 2 ** np.r_[s, -s, sca[2*m:]] 

682 # Elementwise multiplication via broadcasting. 

683 elwisescale = sca[:, None] * np.reciprocal(sca) 

684 H *= elwisescale 

685 J *= elwisescale 

686 

687 # Deflate the pencil by the R column ala Ref.1 

688 q_of_qr, _ = qr(H[:, -n:]) 

689 H = q_of_qr[:, n:].conj().T.dot(H[:, :2*m]) 

690 J = q_of_qr[:, n:].conj().T.dot(J[:, :2*m]) 

691 

692 # Decide on which output type is needed for QZ 

693 out_str = 'real' if r_or_c == float else 'complex' 

694 

695 _, _, _, _, _, u = ordqz(H, J, sort='iuc', 

696 overwrite_a=True, 

697 overwrite_b=True, 

698 check_finite=False, 

699 output=out_str) 

700 

701 # Get the relevant parts of the stable subspace basis 

702 if e is not None: 

703 u, _ = qr(np.vstack((e.dot(u[:m, :m]), u[m:, :m]))) 

704 u00 = u[:m, :m] 

705 u10 = u[m:, :m] 

706 

707 # Solve via back-substituion after checking the condition of u00 

708 up, ul, uu = lu(u00) 

709 

710 if 1/cond(uu) < np.spacing(1.): 

711 raise LinAlgError('Failed to find a finite solution.') 

712 

713 # Exploit the triangular structure 

714 x = solve_triangular(ul.conj().T, 

715 solve_triangular(uu.conj().T, 

716 u10.conj().T, 

717 lower=True), 

718 unit_diagonal=True, 

719 ).conj().T.dot(up.conj().T) 

720 if balanced: 

721 x *= sca[:m, None] * sca[:m] 

722 

723 # Check the deviation from symmetry for lack of success 

724 # See proof of Thm.5 item 3 in [2] 

725 u_sym = u00.conj().T.dot(u10) 

726 n_u_sym = norm(u_sym, 1) 

727 u_sym = u_sym - u_sym.conj().T 

728 sym_threshold = np.max([np.spacing(1000.), 0.1*n_u_sym]) 

729 

730 if norm(u_sym, 1) > sym_threshold: 

731 raise LinAlgError('The associated symplectic pencil has eigenvalues' 

732 'too close to the unit circle') 

733 

734 return (x + x.conj().T)/2 

735 

736 

737def _are_validate_args(a, b, q, r, e, s, eq_type='care'): 

738 """ 

739 A helper function to validate the arguments supplied to the 

740 Riccati equation solvers. Any discrepancy found in the input 

741 matrices leads to a ``ValueError`` exception. 

742 

743 Essentially, it performs: 

744 

745 - a check whether the input is free of NaN and Infs 

746 - a pass for the data through ``numpy.atleast_2d()`` 

747 - squareness check of the relevant arrays 

748 - shape consistency check of the arrays 

749 - singularity check of the relevant arrays 

750 - symmetricity check of the relevant matrices 

751 - a check whether the regular or the generalized version is asked. 

752 

753 This function is used by ``solve_continuous_are`` and 

754 ``solve_discrete_are``. 

755 

756 Parameters 

757 ---------- 

758 a, b, q, r, e, s : array_like 

759 Input data 

760 eq_type : str 

761 Accepted arguments are 'care' and 'dare'. 

762 

763 Returns 

764 ------- 

765 a, b, q, r, e, s : ndarray 

766 Regularized input data 

767 m, n : int 

768 shape of the problem 

769 r_or_c : type 

770 Data type of the problem, returns float or complex 

771 gen_or_not : bool 

772 Type of the equation, True for generalized and False for regular ARE. 

773 

774 """ 

775 

776 if not eq_type.lower() in ('dare', 'care'): 

777 raise ValueError("Equation type unknown. " 

778 "Only 'care' and 'dare' is understood") 

779 

780 a = np.atleast_2d(_asarray_validated(a, check_finite=True)) 

781 b = np.atleast_2d(_asarray_validated(b, check_finite=True)) 

782 q = np.atleast_2d(_asarray_validated(q, check_finite=True)) 

783 r = np.atleast_2d(_asarray_validated(r, check_finite=True)) 

784 

785 # Get the correct data types otherwise NumPy complains 

786 # about pushing complex numbers into real arrays. 

787 r_or_c = complex if np.iscomplexobj(b) else float 

788 

789 for ind, mat in enumerate((a, q, r)): 

790 if np.iscomplexobj(mat): 

791 r_or_c = complex 

792 

793 if not np.equal(*mat.shape): 

794 raise ValueError("Matrix {} should be square.".format("aqr"[ind])) 

795 

796 # Shape consistency checks 

797 m, n = b.shape 

798 if m != a.shape[0]: 

799 raise ValueError("Matrix a and b should have the same number of rows.") 

800 if m != q.shape[0]: 

801 raise ValueError("Matrix a and q should have the same shape.") 

802 if n != r.shape[0]: 

803 raise ValueError("Matrix b and r should have the same number of cols.") 

804 

805 # Check if the data matrices q, r are (sufficiently) hermitian 

806 for ind, mat in enumerate((q, r)): 

807 if norm(mat - mat.conj().T, 1) > np.spacing(norm(mat, 1))*100: 

808 raise ValueError("Matrix {} should be symmetric/hermitian." 

809 "".format("qr"[ind])) 

810 

811 # Continuous time ARE should have a nonsingular r matrix. 

812 if eq_type == 'care': 

813 min_sv = svd(r, compute_uv=False)[-1] 

814 if min_sv == 0. or min_sv < np.spacing(1.)*norm(r, 1): 

815 raise ValueError('Matrix r is numerically singular.') 

816 

817 # Check if the generalized case is required with omitted arguments 

818 # perform late shape checking etc. 

819 generalized_case = e is not None or s is not None 

820 

821 if generalized_case: 

822 if e is not None: 

823 e = np.atleast_2d(_asarray_validated(e, check_finite=True)) 

824 if not np.equal(*e.shape): 

825 raise ValueError("Matrix e should be square.") 

826 if m != e.shape[0]: 

827 raise ValueError("Matrix a and e should have the same shape.") 

828 # numpy.linalg.cond doesn't check for exact zeros and 

829 # emits a runtime warning. Hence the following manual check. 

830 min_sv = svd(e, compute_uv=False)[-1] 

831 if min_sv == 0. or min_sv < np.spacing(1.) * norm(e, 1): 

832 raise ValueError('Matrix e is numerically singular.') 

833 if np.iscomplexobj(e): 

834 r_or_c = complex 

835 if s is not None: 

836 s = np.atleast_2d(_asarray_validated(s, check_finite=True)) 

837 if s.shape != b.shape: 

838 raise ValueError("Matrix b and s should have the same shape.") 

839 if np.iscomplexobj(s): 

840 r_or_c = complex 

841 

842 return a, b, q, r, e, s, m, n, r_or_c, generalized_case