Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/scipy/linalg/_decomp_cossin.py: 9%

81 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-03-22 06:44 +0000

1from collections.abc import Iterable 

2import numpy as np 

3 

4from scipy._lib._util import _asarray_validated 

5from scipy.linalg import block_diag, LinAlgError 

6from .lapack import _compute_lwork, get_lapack_funcs 

7 

8__all__ = ['cossin'] 

9 

10 

11def cossin(X, p=None, q=None, separate=False, 

12 swap_sign=False, compute_u=True, compute_vh=True): 

13 """ 

14 Compute the cosine-sine (CS) decomposition of an orthogonal/unitary matrix. 

15 

16 X is an ``(m, m)`` orthogonal/unitary matrix, partitioned as the following 

17 where upper left block has the shape of ``(p, q)``:: 

18 

19 ┌ ┐ 

20 │ I 0 0 │ 0 0 0 │ 

21 ┌ ┐ ┌ ┐│ 0 C 0 │ 0 -S 0 │┌ ┐* 

22 │ X11 │ X12 │ │ U1 │ ││ 0 0 0 │ 0 0 -I ││ V1 │ │ 

23 │ ────┼──── │ = │────┼────││─────────┼─────────││────┼────│ 

24 │ X21 │ X22 │ │ │ U2 ││ 0 0 0 │ I 0 0 ││ │ V2 │ 

25 └ ┘ └ ┘│ 0 S 0 │ 0 C 0 │└ ┘ 

26 │ 0 0 I │ 0 0 0 │ 

27 └ ┘ 

28 

29 ``U1``, ``U2``, ``V1``, ``V2`` are square orthogonal/unitary matrices of 

30 dimensions ``(p,p)``, ``(m-p,m-p)``, ``(q,q)``, and ``(m-q,m-q)`` 

31 respectively, and ``C`` and ``S`` are ``(r, r)`` nonnegative diagonal 

32 matrices satisfying ``C^2 + S^2 = I`` where ``r = min(p, m-p, q, m-q)``. 

33 

34 Moreover, the rank of the identity matrices are ``min(p, q) - r``, 

35 ``min(p, m - q) - r``, ``min(m - p, q) - r``, and ``min(m - p, m - q) - r`` 

36 respectively. 

37 

38 X can be supplied either by itself and block specifications p, q or its 

39 subblocks in an iterable from which the shapes would be derived. See the 

40 examples below. 

41 

42 Parameters 

43 ---------- 

44 X : array_like, iterable 

45 complex unitary or real orthogonal matrix to be decomposed, or iterable 

46 of subblocks ``X11``, ``X12``, ``X21``, ``X22``, when ``p``, ``q`` are 

47 omitted. 

48 p : int, optional 

49 Number of rows of the upper left block ``X11``, used only when X is 

50 given as an array. 

51 q : int, optional 

52 Number of columns of the upper left block ``X11``, used only when X is 

53 given as an array. 

54 separate : bool, optional 

55 if ``True``, the low level components are returned instead of the 

56 matrix factors, i.e. ``(u1,u2)``, ``theta``, ``(v1h,v2h)`` instead of 

57 ``u``, ``cs``, ``vh``. 

58 swap_sign : bool, optional 

59 if ``True``, the ``-S``, ``-I`` block will be the bottom left, 

60 otherwise (by default) they will be in the upper right block. 

61 compute_u : bool, optional 

62 if ``False``, ``u`` won't be computed and an empty array is returned. 

63 compute_vh : bool, optional 

64 if ``False``, ``vh`` won't be computed and an empty array is returned. 

65 

66 Returns 

67 ------- 

68 u : ndarray 

69 When ``compute_u=True``, contains the block diagonal orthogonal/unitary 

70 matrix consisting of the blocks ``U1`` (``p`` x ``p``) and ``U2`` 

71 (``m-p`` x ``m-p``) orthogonal/unitary matrices. If ``separate=True``, 

72 this contains the tuple of ``(U1, U2)``. 

73 cs : ndarray 

74 The cosine-sine factor with the structure described above. 

75 If ``separate=True``, this contains the ``theta`` array containing the 

76 angles in radians. 

77 vh : ndarray 

78 When ``compute_vh=True`, contains the block diagonal orthogonal/unitary 

79 matrix consisting of the blocks ``V1H`` (``q`` x ``q``) and ``V2H`` 

80 (``m-q`` x ``m-q``) orthogonal/unitary matrices. If ``separate=True``, 

81 this contains the tuple of ``(V1H, V2H)``. 

82 

83 References 

84 ---------- 

85 .. [1] Brian D. Sutton. Computing the complete CS decomposition. Numer. 

86 Algorithms, 50(1):33-65, 2009. 

87 

88 Examples 

89 -------- 

90 >>> import numpy as np 

91 >>> from scipy.linalg import cossin 

92 >>> from scipy.stats import unitary_group 

93 >>> x = unitary_group.rvs(4) 

94 >>> u, cs, vdh = cossin(x, p=2, q=2) 

95 >>> np.allclose(x, u @ cs @ vdh) 

96 True 

97 

98 Same can be entered via subblocks without the need of ``p`` and ``q``. Also 

99 let's skip the computation of ``u`` 

100 

101 >>> ue, cs, vdh = cossin((x[:2, :2], x[:2, 2:], x[2:, :2], x[2:, 2:]), 

102 ... compute_u=False) 

103 >>> print(ue) 

104 [] 

105 >>> np.allclose(x, u @ cs @ vdh) 

106 True 

107 

108 """ 

109 

110 if p or q: 

111 p = 1 if p is None else int(p) 

112 q = 1 if q is None else int(q) 

113 X = _asarray_validated(X, check_finite=True) 

114 if not np.equal(*X.shape): 

115 raise ValueError("Cosine Sine decomposition only supports square" 

116 f" matrices, got {X.shape}") 

117 m = X.shape[0] 

118 if p >= m or p <= 0: 

119 raise ValueError(f"invalid p={p}, 0<p<{X.shape[0]} must hold") 

120 if q >= m or q <= 0: 

121 raise ValueError(f"invalid q={q}, 0<q<{X.shape[0]} must hold") 

122 

123 x11, x12, x21, x22 = X[:p, :q], X[:p, q:], X[p:, :q], X[p:, q:] 

124 elif not isinstance(X, Iterable): 

125 raise ValueError("When p and q are None, X must be an Iterable" 

126 " containing the subblocks of X") 

127 else: 

128 if len(X) != 4: 

129 raise ValueError("When p and q are None, exactly four arrays" 

130 f" should be in X, got {len(X)}") 

131 

132 x11, x12, x21, x22 = (np.atleast_2d(x) for x in X) 

133 for name, block in zip(["x11", "x12", "x21", "x22"], 

134 [x11, x12, x21, x22]): 

135 if block.shape[1] == 0: 

136 raise ValueError(f"{name} can't be empty") 

137 p, q = x11.shape 

138 mmp, mmq = x22.shape 

139 

140 if x12.shape != (p, mmq): 

141 raise ValueError(f"Invalid x12 dimensions: desired {(p, mmq)}, " 

142 f"got {x12.shape}") 

143 

144 if x21.shape != (mmp, q): 

145 raise ValueError(f"Invalid x21 dimensions: desired {(mmp, q)}, " 

146 f"got {x21.shape}") 

147 

148 if p + mmp != q + mmq: 

149 raise ValueError("The subblocks have compatible sizes but " 

150 "don't form a square array (instead they form a" 

151 " {}x{} array). This might be due to missing " 

152 "p, q arguments.".format(p + mmp, q + mmq)) 

153 

154 m = p + mmp 

155 

156 cplx = any([np.iscomplexobj(x) for x in [x11, x12, x21, x22]]) 

157 driver = "uncsd" if cplx else "orcsd" 

158 csd, csd_lwork = get_lapack_funcs([driver, driver + "_lwork"], 

159 [x11, x12, x21, x22]) 

160 lwork = _compute_lwork(csd_lwork, m=m, p=p, q=q) 

161 lwork_args = ({'lwork': lwork[0], 'lrwork': lwork[1]} if cplx else 

162 {'lwork': lwork}) 

163 *_, theta, u1, u2, v1h, v2h, info = csd(x11=x11, x12=x12, x21=x21, x22=x22, 

164 compute_u1=compute_u, 

165 compute_u2=compute_u, 

166 compute_v1t=compute_vh, 

167 compute_v2t=compute_vh, 

168 trans=False, signs=swap_sign, 

169 **lwork_args) 

170 

171 method_name = csd.typecode + driver 

172 if info < 0: 

173 raise ValueError(f'illegal value in argument {-info} ' 

174 f'of internal {method_name}') 

175 if info > 0: 

176 raise LinAlgError(f"{method_name} did not converge: {info}") 

177 

178 if separate: 

179 return (u1, u2), theta, (v1h, v2h) 

180 

181 U = block_diag(u1, u2) 

182 VDH = block_diag(v1h, v2h) 

183 

184 # Construct the middle factor CS 

185 c = np.diag(np.cos(theta)) 

186 s = np.diag(np.sin(theta)) 

187 r = min(p, q, m - p, m - q) 

188 n11 = min(p, q) - r 

189 n12 = min(p, m - q) - r 

190 n21 = min(m - p, q) - r 

191 n22 = min(m - p, m - q) - r 

192 Id = np.eye(np.max([n11, n12, n21, n22, r]), dtype=theta.dtype) 

193 CS = np.zeros((m, m), dtype=theta.dtype) 

194 

195 CS[:n11, :n11] = Id[:n11, :n11] 

196 

197 xs = n11 + r 

198 xe = n11 + r + n12 

199 ys = n11 + n21 + n22 + 2 * r 

200 ye = n11 + n21 + n22 + 2 * r + n12 

201 CS[xs: xe, ys:ye] = Id[:n12, :n12] if swap_sign else -Id[:n12, :n12] 

202 

203 xs = p + n22 + r 

204 xe = p + n22 + r + + n21 

205 ys = n11 + r 

206 ye = n11 + r + n21 

207 CS[xs:xe, ys:ye] = -Id[:n21, :n21] if swap_sign else Id[:n21, :n21] 

208 

209 CS[p:p + n22, q:q + n22] = Id[:n22, :n22] 

210 CS[n11:n11 + r, n11:n11 + r] = c 

211 CS[p + n22:p + n22 + r, r + n21 + n22:2 * r + n21 + n22] = c 

212 

213 xs = n11 

214 xe = n11 + r 

215 ys = n11 + n21 + n22 + r 

216 ye = n11 + n21 + n22 + 2 * r 

217 CS[xs:xe, ys:ye] = s if swap_sign else -s 

218 

219 CS[p + n22:p + n22 + r, n11:n11 + r] = -s if swap_sign else s 

220 

221 return U, CS, VDH