_decomp_qz.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. import warnings
  2. import numpy as np
  3. from numpy import asarray_chkfinite
  4. from scipy._lib._util import _apply_over_batch
  5. from ._misc import LinAlgError, _datacopied, LinAlgWarning
  6. from .lapack import get_lapack_funcs
  7. __all__ = ['qz', 'ordqz']
  8. _double_precision = ['i', 'l', 'd']
  9. def _select_function(sort):
  10. if callable(sort):
  11. # assume the user knows what they're doing
  12. sfunction = sort
  13. elif sort == 'lhp':
  14. sfunction = _lhp
  15. elif sort == 'rhp':
  16. sfunction = _rhp
  17. elif sort == 'iuc':
  18. sfunction = _iuc
  19. elif sort == 'ouc':
  20. sfunction = _ouc
  21. else:
  22. raise ValueError("sort parameter must be None, a callable, or "
  23. "one of ('lhp','rhp','iuc','ouc')")
  24. return sfunction
  25. def _lhp(x, y):
  26. out = np.empty_like(x, dtype=bool)
  27. nonzero = (y != 0)
  28. # handles (x, y) = (0, 0) too
  29. out[~nonzero] = False
  30. out[nonzero] = (np.real(x[nonzero]/y[nonzero]) < 0.0)
  31. return out
  32. def _rhp(x, y):
  33. out = np.empty_like(x, dtype=bool)
  34. nonzero = (y != 0)
  35. # handles (x, y) = (0, 0) too
  36. out[~nonzero] = False
  37. out[nonzero] = (np.real(x[nonzero]/y[nonzero]) > 0.0)
  38. return out
  39. def _iuc(x, y):
  40. out = np.empty_like(x, dtype=bool)
  41. nonzero = (y != 0)
  42. # handles (x, y) = (0, 0) too
  43. out[~nonzero] = False
  44. out[nonzero] = (abs(x[nonzero]/y[nonzero]) < 1.0)
  45. return out
  46. def _ouc(x, y):
  47. out = np.empty_like(x, dtype=bool)
  48. xzero = (x == 0)
  49. yzero = (y == 0)
  50. out[xzero & yzero] = False
  51. out[~xzero & yzero] = True
  52. out[~yzero] = (abs(x[~yzero]/y[~yzero]) > 1.0)
  53. return out
  54. def _qz(A, B, output='real', lwork=None, sort=None, overwrite_a=False,
  55. overwrite_b=False, check_finite=True):
  56. if sort is not None:
  57. # Disabled due to segfaults on win32, see ticket 1717.
  58. raise ValueError("The 'sort' input of qz() has to be None and will be "
  59. "removed in a future release. Use ordqz instead.")
  60. if output not in ['real', 'complex', 'r', 'c']:
  61. raise ValueError("argument must be 'real', or 'complex'")
  62. if check_finite:
  63. a1 = asarray_chkfinite(A)
  64. b1 = asarray_chkfinite(B)
  65. else:
  66. a1 = np.asarray(A)
  67. b1 = np.asarray(B)
  68. a_m, a_n = a1.shape
  69. b_m, b_n = b1.shape
  70. if not (a_m == a_n == b_m == b_n):
  71. raise ValueError("Array dimensions must be square and agree")
  72. typa = a1.dtype.char
  73. if output in ['complex', 'c'] and typa not in ['F', 'D']:
  74. if typa in _double_precision:
  75. a1 = a1.astype('D')
  76. typa = 'D'
  77. else:
  78. a1 = a1.astype('F')
  79. typa = 'F'
  80. typb = b1.dtype.char
  81. if output in ['complex', 'c'] and typb not in ['F', 'D']:
  82. if typb in _double_precision:
  83. b1 = b1.astype('D')
  84. typb = 'D'
  85. else:
  86. b1 = b1.astype('F')
  87. typb = 'F'
  88. overwrite_a = overwrite_a or (_datacopied(a1, A))
  89. overwrite_b = overwrite_b or (_datacopied(b1, B))
  90. gges, = get_lapack_funcs(('gges',), (a1, b1))
  91. if lwork is None or lwork == -1:
  92. # get optimal work array size
  93. result = gges(lambda x: None, a1, b1, lwork=-1)
  94. lwork = result[-2][0].real.astype(int)
  95. def sfunction(x):
  96. return None
  97. result = gges(sfunction, a1, b1, lwork=lwork, overwrite_a=overwrite_a,
  98. overwrite_b=overwrite_b, sort_t=0)
  99. info = result[-1]
  100. if info < 0:
  101. raise ValueError(f"Illegal value in argument {-info} of gges")
  102. elif info > 0 and info <= a_n:
  103. warnings.warn("The QZ iteration failed. (a,b) are not in Schur "
  104. "form, but ALPHAR(j), ALPHAI(j), and BETA(j) should be "
  105. f"correct for J={info-1},...,N", LinAlgWarning,
  106. stacklevel=3)
  107. elif info == a_n+1:
  108. raise LinAlgError("Something other than QZ iteration failed")
  109. elif info == a_n+2:
  110. raise LinAlgError("After reordering, roundoff changed values of some "
  111. "complex eigenvalues so that leading eigenvalues "
  112. "in the Generalized Schur form no longer satisfy "
  113. "sort=True. This could also be due to scaling.")
  114. elif info == a_n+3:
  115. raise LinAlgError("Reordering failed in <s,d,c,z>tgsen")
  116. return result, gges.typecode
  117. @_apply_over_batch(('A', 2), ('B', 2))
  118. def qz(A, B, output='real', lwork=None, sort=None, overwrite_a=False,
  119. overwrite_b=False, check_finite=True):
  120. """
  121. QZ decomposition for generalized eigenvalues of a pair of matrices.
  122. The QZ, or generalized Schur, decomposition for a pair of n-by-n
  123. matrices (A,B) is::
  124. (A,B) = (Q @ AA @ Z*, Q @ BB @ Z*)
  125. where AA, BB is in generalized Schur form if BB is upper-triangular
  126. with non-negative diagonal and AA is upper-triangular, or for real QZ
  127. decomposition (``output='real'``) block upper triangular with 1x1
  128. and 2x2 blocks. In this case, the 1x1 blocks correspond to real
  129. generalized eigenvalues and 2x2 blocks are 'standardized' by making
  130. the corresponding elements of BB have the form::
  131. [ a 0 ]
  132. [ 0 b ]
  133. and the pair of corresponding 2x2 blocks in AA and BB will have a complex
  134. conjugate pair of generalized eigenvalues. If (``output='complex'``) or
  135. A and B are complex matrices, Z' denotes the conjugate-transpose of Z.
  136. Q and Z are unitary matrices.
  137. Parameters
  138. ----------
  139. A : (N, N) array_like
  140. 2-D array to decompose
  141. B : (N, N) array_like
  142. 2-D array to decompose
  143. output : {'real', 'complex'}, optional
  144. Construct the real or complex QZ decomposition for real matrices.
  145. Default is 'real'.
  146. lwork : int, optional
  147. Work array size. If None or -1, it is automatically computed.
  148. sort : {None, callable, 'lhp', 'rhp', 'iuc', 'ouc'}, optional
  149. NOTE: THIS INPUT IS DISABLED FOR NOW. Use ordqz instead.
  150. Specifies whether the upper eigenvalues should be sorted. A callable
  151. may be passed that, given an eigenvalue, returns a boolean denoting
  152. whether the eigenvalue should be sorted to the top-left (True). For
  153. real matrix pairs, the sort function takes three real arguments
  154. (alphar, alphai, beta). The eigenvalue
  155. ``x = (alphar + alphai*1j)/beta``. For complex matrix pairs or
  156. output='complex', the sort function takes two complex arguments
  157. (alpha, beta). The eigenvalue ``x = (alpha/beta)``. Alternatively,
  158. string parameters may be used:
  159. - 'lhp' Left-hand plane (x.real < 0.0)
  160. - 'rhp' Right-hand plane (x.real > 0.0)
  161. - 'iuc' Inside the unit circle (x*x.conjugate() < 1.0)
  162. - 'ouc' Outside the unit circle (x*x.conjugate() > 1.0)
  163. Defaults to None (no sorting).
  164. overwrite_a : bool, optional
  165. Whether to overwrite data in a (may improve performance)
  166. overwrite_b : bool, optional
  167. Whether to overwrite data in b (may improve performance)
  168. check_finite : bool, optional
  169. If true checks the elements of `A` and `B` are finite numbers. If
  170. false does no checking and passes matrix through to
  171. underlying algorithm.
  172. Returns
  173. -------
  174. AA : (N, N) ndarray
  175. Generalized Schur form of A.
  176. BB : (N, N) ndarray
  177. Generalized Schur form of B.
  178. Q : (N, N) ndarray
  179. The left Schur vectors.
  180. Z : (N, N) ndarray
  181. The right Schur vectors.
  182. See Also
  183. --------
  184. ordqz
  185. Notes
  186. -----
  187. Q is transposed versus the equivalent function in Matlab.
  188. .. versionadded:: 0.11.0
  189. Examples
  190. --------
  191. >>> import numpy as np
  192. >>> from scipy.linalg import qz
  193. >>> A = np.array([[1, 2, -1], [5, 5, 5], [2, 4, -8]])
  194. >>> B = np.array([[1, 1, -3], [3, 1, -1], [5, 6, -2]])
  195. Compute the decomposition. The QZ decomposition is not unique, so
  196. depending on the underlying library that is used, there may be
  197. differences in the signs of coefficients in the following output.
  198. >>> AA, BB, Q, Z = qz(A, B)
  199. >>> AA
  200. array([[-1.36949157, -4.05459025, 7.44389431],
  201. [ 0. , 7.65653432, 5.13476017],
  202. [ 0. , -0.65978437, 2.4186015 ]]) # may vary
  203. >>> BB
  204. array([[ 1.71890633, -1.64723705, -0.72696385],
  205. [ 0. , 8.6965692 , -0. ],
  206. [ 0. , 0. , 2.27446233]]) # may vary
  207. >>> Q
  208. array([[-0.37048362, 0.1903278 , 0.90912992],
  209. [-0.90073232, 0.16534124, -0.40167593],
  210. [ 0.22676676, 0.96769706, -0.11017818]]) # may vary
  211. >>> Z
  212. array([[-0.67660785, 0.63528924, -0.37230283],
  213. [ 0.70243299, 0.70853819, -0.06753907],
  214. [ 0.22088393, -0.30721526, -0.92565062]]) # may vary
  215. Verify the QZ decomposition. With real output, we only need the
  216. transpose of ``Z`` in the following expressions.
  217. >>> Q @ AA @ Z.T # Should be A
  218. array([[ 1., 2., -1.],
  219. [ 5., 5., 5.],
  220. [ 2., 4., -8.]])
  221. >>> Q @ BB @ Z.T # Should be B
  222. array([[ 1., 1., -3.],
  223. [ 3., 1., -1.],
  224. [ 5., 6., -2.]])
  225. Repeat the decomposition, but with ``output='complex'``.
  226. >>> AA, BB, Q, Z = qz(A, B, output='complex')
  227. For conciseness in the output, we use ``np.set_printoptions()`` to set
  228. the output precision of NumPy arrays to 3 and display tiny values as 0.
  229. >>> np.set_printoptions(precision=3, suppress=True)
  230. >>> AA
  231. array([[-1.369+0.j , 2.248+4.237j, 4.861-5.022j],
  232. [ 0. +0.j , 7.037+2.922j, 0.794+4.932j],
  233. [ 0. +0.j , 0. +0.j , 2.655-1.103j]]) # may vary
  234. >>> BB
  235. array([[ 1.719+0.j , -1.115+1.j , -0.763-0.646j],
  236. [ 0. +0.j , 7.24 +0.j , -3.144+3.322j],
  237. [ 0. +0.j , 0. +0.j , 2.732+0.j ]]) # may vary
  238. >>> Q
  239. array([[ 0.326+0.175j, -0.273-0.029j, -0.886-0.052j],
  240. [ 0.794+0.426j, -0.093+0.134j, 0.402-0.02j ],
  241. [-0.2 -0.107j, -0.816+0.482j, 0.151-0.167j]]) # may vary
  242. >>> Z
  243. array([[ 0.596+0.32j , -0.31 +0.414j, 0.393-0.347j],
  244. [-0.619-0.332j, -0.479+0.314j, 0.154-0.393j],
  245. [-0.195-0.104j, 0.576+0.27j , 0.715+0.187j]]) # may vary
  246. With complex arrays, we must use ``Z.conj().T`` in the following
  247. expressions to verify the decomposition.
  248. >>> Q @ AA @ Z.conj().T # Should be A
  249. array([[ 1.-0.j, 2.-0.j, -1.-0.j],
  250. [ 5.+0.j, 5.+0.j, 5.-0.j],
  251. [ 2.+0.j, 4.+0.j, -8.+0.j]])
  252. >>> Q @ BB @ Z.conj().T # Should be B
  253. array([[ 1.+0.j, 1.+0.j, -3.+0.j],
  254. [ 3.-0.j, 1.-0.j, -1.+0.j],
  255. [ 5.+0.j, 6.+0.j, -2.+0.j]])
  256. """
  257. # output for real
  258. # AA, BB, sdim, alphar, alphai, beta, vsl, vsr, work, info
  259. # output for complex
  260. # AA, BB, sdim, alpha, beta, vsl, vsr, work, info
  261. result, _ = _qz(A, B, output=output, lwork=lwork, sort=sort,
  262. overwrite_a=overwrite_a, overwrite_b=overwrite_b,
  263. check_finite=check_finite)
  264. return result[0], result[1], result[-4], result[-3]
  265. @_apply_over_batch(('A', 2), ('B', 2))
  266. def ordqz(A, B, sort='lhp', output='real', overwrite_a=False,
  267. overwrite_b=False, check_finite=True):
  268. """QZ decomposition for a pair of matrices with reordering.
  269. Parameters
  270. ----------
  271. A : (N, N) array_like
  272. 2-D array to decompose
  273. B : (N, N) array_like
  274. 2-D array to decompose
  275. sort : {callable, 'lhp', 'rhp', 'iuc', 'ouc'}, optional
  276. Specifies whether the upper eigenvalues should be sorted. A
  277. callable may be passed that, given an ordered pair ``(alpha,
  278. beta)`` representing the eigenvalue ``x = (alpha/beta)``,
  279. returns a boolean denoting whether the eigenvalue should be
  280. sorted to the top-left (True). For the real matrix pairs
  281. ``beta`` is real while ``alpha`` can be complex, and for
  282. complex matrix pairs both ``alpha`` and ``beta`` can be
  283. complex. The callable must be able to accept a NumPy
  284. array. Alternatively, string parameters may be used:
  285. - 'lhp' Left-hand plane (x.real < 0.0)
  286. - 'rhp' Right-hand plane (x.real > 0.0)
  287. - 'iuc' Inside the unit circle (x*x.conjugate() < 1.0)
  288. - 'ouc' Outside the unit circle (x*x.conjugate() > 1.0)
  289. With the predefined sorting functions, an infinite eigenvalue
  290. (i.e., ``alpha != 0`` and ``beta = 0``) is considered to lie in
  291. neither the left-hand nor the right-hand plane, but it is
  292. considered to lie outside the unit circle. For the eigenvalue
  293. ``(alpha, beta) = (0, 0)``, the predefined sorting functions
  294. all return `False`.
  295. output : str {'real','complex'}, optional
  296. Construct the real or complex QZ decomposition for real matrices.
  297. Default is 'real'.
  298. overwrite_a : bool, optional
  299. If True, the contents of A are overwritten.
  300. overwrite_b : bool, optional
  301. If True, the contents of B are overwritten.
  302. check_finite : bool, optional
  303. If true checks the elements of `A` and `B` are finite numbers. If
  304. false does no checking and passes matrix through to
  305. underlying algorithm.
  306. Returns
  307. -------
  308. AA : (N, N) ndarray
  309. Generalized Schur form of A.
  310. BB : (N, N) ndarray
  311. Generalized Schur form of B.
  312. alpha : (N,) ndarray
  313. alpha = alphar + alphai * 1j. See notes.
  314. beta : (N,) ndarray
  315. See notes.
  316. Q : (N, N) ndarray
  317. The left Schur vectors.
  318. Z : (N, N) ndarray
  319. The right Schur vectors.
  320. See Also
  321. --------
  322. qz
  323. Notes
  324. -----
  325. On exit, ``(ALPHAR(j) + ALPHAI(j)*i)/BETA(j), j=1,...,N``, will be the
  326. generalized eigenvalues. ``ALPHAR(j) + ALPHAI(j)*i`` and
  327. ``BETA(j),j=1,...,N`` are the diagonals of the complex Schur form (S,T)
  328. that would result if the 2-by-2 diagonal blocks of the real generalized
  329. Schur form of (A,B) were further reduced to triangular form using complex
  330. unitary transformations. If ALPHAI(j) is zero, then the jth eigenvalue is
  331. real; if positive, then the ``j``\\ th and ``(j+1)``\\ st eigenvalues are a
  332. complex conjugate pair, with ``ALPHAI(j+1)`` negative.
  333. .. versionadded:: 0.17.0
  334. Examples
  335. --------
  336. >>> import numpy as np
  337. >>> from scipy.linalg import ordqz
  338. >>> A = np.array([[2, 5, 8, 7], [5, 2, 2, 8], [7, 5, 6, 6], [5, 4, 4, 8]])
  339. >>> B = np.array([[0, 6, 0, 0], [5, 0, 2, 1], [5, 2, 6, 6], [4, 7, 7, 7]])
  340. >>> AA, BB, alpha, beta, Q, Z = ordqz(A, B, sort='lhp')
  341. Since we have sorted for left half plane eigenvalues, negatives come first
  342. >>> (alpha/beta).real < 0
  343. array([ True, True, False, False], dtype=bool)
  344. """
  345. (AA, BB, _, *ab, Q, Z, _, _), typ = _qz(A, B, output=output, sort=None,
  346. overwrite_a=overwrite_a,
  347. overwrite_b=overwrite_b,
  348. check_finite=check_finite)
  349. if typ == 's':
  350. alpha, beta = ab[0] + ab[1]*np.complex64(1j), ab[2]
  351. elif typ == 'd':
  352. alpha, beta = ab[0] + ab[1]*1.j, ab[2]
  353. else:
  354. alpha, beta = ab
  355. sfunction = _select_function(sort)
  356. select = sfunction(alpha, beta)
  357. tgsen = get_lapack_funcs('tgsen', (AA, BB))
  358. # the real case needs 4n + 16 lwork
  359. lwork = 4*AA.shape[0] + 16 if typ in 'sd' else 1
  360. AAA, BBB, *ab, QQ, ZZ, _, _, _, _, info = tgsen(select, AA, BB, Q, Z,
  361. ijob=0,
  362. lwork=lwork, liwork=1)
  363. # Once more for tgsen output
  364. if typ == 's':
  365. alpha, beta = ab[0] + ab[1]*np.complex64(1j), ab[2]
  366. elif typ == 'd':
  367. alpha, beta = ab[0] + ab[1]*1.j, ab[2]
  368. else:
  369. alpha, beta = ab
  370. if info < 0:
  371. raise ValueError(f"Illegal value in argument {-info} of tgsen")
  372. elif info == 1:
  373. raise ValueError("Reordering of (A, B) failed because the transformed"
  374. " matrix pair (A, B) would be too far from "
  375. "generalized Schur form; the problem is very "
  376. "ill-conditioned. (A, B) may have been partially "
  377. "reordered.")
  378. return AAA, BBB, alpha, beta, QQ, ZZ