Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/scipy/linalg/_decomp_ldl.py: 12%

84 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-12 06:31 +0000

1from warnings import warn 

2 

3import numpy as np 

4from numpy import (atleast_2d, ComplexWarning, arange, zeros_like, imag, diag, 

5 iscomplexobj, tril, triu, argsort, empty_like) 

6from ._decomp import _asarray_validated 

7from .lapack import get_lapack_funcs, _compute_lwork 

8 

9__all__ = ['ldl'] 

10 

11 

12def ldl(A, lower=True, hermitian=True, overwrite_a=False, check_finite=True): 

13 """ Computes the LDLt or Bunch-Kaufman factorization of a symmetric/ 

14 hermitian matrix. 

15 

16 This function returns a block diagonal matrix D consisting blocks of size 

17 at most 2x2 and also a possibly permuted unit lower triangular matrix 

18 ``L`` such that the factorization ``A = L D L^H`` or ``A = L D L^T`` 

19 holds. If `lower` is False then (again possibly permuted) upper 

20 triangular matrices are returned as outer factors. 

21 

22 The permutation array can be used to triangularize the outer factors 

23 simply by a row shuffle, i.e., ``lu[perm, :]`` is an upper/lower 

24 triangular matrix. This is also equivalent to multiplication with a 

25 permutation matrix ``P.dot(lu)``, where ``P`` is a column-permuted 

26 identity matrix ``I[:, perm]``. 

27 

28 Depending on the value of the boolean `lower`, only upper or lower 

29 triangular part of the input array is referenced. Hence, a triangular 

30 matrix on entry would give the same result as if the full matrix is 

31 supplied. 

32 

33 Parameters 

34 ---------- 

35 A : array_like 

36 Square input array 

37 lower : bool, optional 

38 This switches between the lower and upper triangular outer factors of 

39 the factorization. Lower triangular (``lower=True``) is the default. 

40 hermitian : bool, optional 

41 For complex-valued arrays, this defines whether ``A = A.conj().T`` or 

42 ``A = A.T`` is assumed. For real-valued arrays, this switch has no 

43 effect. 

44 overwrite_a : bool, optional 

45 Allow overwriting data in `A` (may enhance performance). The default 

46 is False. 

47 check_finite : bool, optional 

48 Whether to check that the input matrices contain only finite numbers. 

49 Disabling may give a performance gain, but may result in problems 

50 (crashes, non-termination) if the inputs do contain infinities or NaNs. 

51 

52 Returns 

53 ------- 

54 lu : ndarray 

55 The (possibly) permuted upper/lower triangular outer factor of the 

56 factorization. 

57 d : ndarray 

58 The block diagonal multiplier of the factorization. 

59 perm : ndarray 

60 The row-permutation index array that brings lu into triangular form. 

61 

62 Raises 

63 ------ 

64 ValueError 

65 If input array is not square. 

66 ComplexWarning 

67 If a complex-valued array with nonzero imaginary parts on the 

68 diagonal is given and hermitian is set to True. 

69 

70 See Also 

71 -------- 

72 cholesky, lu 

73 

74 Notes 

75 ----- 

76 This function uses ``?SYTRF`` routines for symmetric matrices and 

77 ``?HETRF`` routines for Hermitian matrices from LAPACK. See [1]_ for 

78 the algorithm details. 

79 

80 Depending on the `lower` keyword value, only lower or upper triangular 

81 part of the input array is referenced. Moreover, this keyword also defines 

82 the structure of the outer factors of the factorization. 

83 

84 .. versionadded:: 1.1.0 

85 

86 References 

87 ---------- 

88 .. [1] J.R. Bunch, L. Kaufman, Some stable methods for calculating 

89 inertia and solving symmetric linear systems, Math. Comput. Vol.31, 

90 1977. :doi:`10.2307/2005787` 

91 

92 Examples 

93 -------- 

94 Given an upper triangular array ``a`` that represents the full symmetric 

95 array with its entries, obtain ``l``, 'd' and the permutation vector `perm`: 

96 

97 >>> import numpy as np 

98 >>> from scipy.linalg import ldl 

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

100 >>> lu, d, perm = ldl(a, lower=0) # Use the upper part 

101 >>> lu 

102 array([[ 0. , 0. , 1. ], 

103 [ 0. , 1. , -0.5], 

104 [ 1. , 1. , 1.5]]) 

105 >>> d 

106 array([[-5. , 0. , 0. ], 

107 [ 0. , 1.5, 0. ], 

108 [ 0. , 0. , 2. ]]) 

109 >>> perm 

110 array([2, 1, 0]) 

111 >>> lu[perm, :] 

112 array([[ 1. , 1. , 1.5], 

113 [ 0. , 1. , -0.5], 

114 [ 0. , 0. , 1. ]]) 

115 >>> lu.dot(d).dot(lu.T) 

116 array([[ 2., -1., 3.], 

117 [-1., 2., 0.], 

118 [ 3., 0., 1.]]) 

119 

120 """ 

121 a = atleast_2d(_asarray_validated(A, check_finite=check_finite)) 

122 if a.shape[0] != a.shape[1]: 

123 raise ValueError('The input array "a" should be square.') 

124 # Return empty arrays for empty square input 

125 if a.size == 0: 

126 return empty_like(a), empty_like(a), np.array([], dtype=int) 

127 

128 n = a.shape[0] 

129 r_or_c = complex if iscomplexobj(a) else float 

130 

131 # Get the LAPACK routine 

132 if r_or_c is complex and hermitian: 

133 s, sl = 'hetrf', 'hetrf_lwork' 

134 if np.any(imag(diag(a))): 

135 warn('scipy.linalg.ldl():\nThe imaginary parts of the diagonal' 

136 'are ignored. Use "hermitian=False" for factorization of' 

137 'complex symmetric arrays.', ComplexWarning, stacklevel=2) 

138 else: 

139 s, sl = 'sytrf', 'sytrf_lwork' 

140 

141 solver, solver_lwork = get_lapack_funcs((s, sl), (a,)) 

142 lwork = _compute_lwork(solver_lwork, n, lower=lower) 

143 ldu, piv, info = solver(a, lwork=lwork, lower=lower, 

144 overwrite_a=overwrite_a) 

145 if info < 0: 

146 raise ValueError('{} exited with the internal error "illegal value ' 

147 'in argument number {}". See LAPACK documentation ' 

148 'for the error codes.'.format(s.upper(), -info)) 

149 

150 swap_arr, pivot_arr = _ldl_sanitize_ipiv(piv, lower=lower) 

151 d, lu = _ldl_get_d_and_l(ldu, pivot_arr, lower=lower, hermitian=hermitian) 

152 lu, perm = _ldl_construct_tri_factor(lu, swap_arr, pivot_arr, lower=lower) 

153 

154 return lu, d, perm 

155 

156 

157def _ldl_sanitize_ipiv(a, lower=True): 

158 """ 

159 This helper function takes the rather strangely encoded permutation array 

160 returned by the LAPACK routines ?(HE/SY)TRF and converts it into 

161 regularized permutation and diagonal pivot size format. 

162 

163 Since FORTRAN uses 1-indexing and LAPACK uses different start points for 

164 upper and lower formats there are certain offsets in the indices used 

165 below. 

166 

167 Let's assume a result where the matrix is 6x6 and there are two 2x2 

168 and two 1x1 blocks reported by the routine. To ease the coding efforts, 

169 we still populate a 6-sized array and fill zeros as the following :: 

170 

171 pivots = [2, 0, 2, 0, 1, 1] 

172 

173 This denotes a diagonal matrix of the form :: 

174 

175 [x x ] 

176 [x x ] 

177 [ x x ] 

178 [ x x ] 

179 [ x ] 

180 [ x] 

181 

182 In other words, we write 2 when the 2x2 block is first encountered and 

183 automatically write 0 to the next entry and skip the next spin of the 

184 loop. Thus, a separate counter or array appends to keep track of block 

185 sizes are avoided. If needed, zeros can be filtered out later without 

186 losing the block structure. 

187 

188 Parameters 

189 ---------- 

190 a : ndarray 

191 The permutation array ipiv returned by LAPACK 

192 lower : bool, optional 

193 The switch to select whether upper or lower triangle is chosen in 

194 the LAPACK call. 

195 

196 Returns 

197 ------- 

198 swap_ : ndarray 

199 The array that defines the row/column swap operations. For example, 

200 if row two is swapped with row four, the result is [0, 3, 2, 3]. 

201 pivots : ndarray 

202 The array that defines the block diagonal structure as given above. 

203 

204 """ 

205 n = a.size 

206 swap_ = arange(n) 

207 pivots = zeros_like(swap_, dtype=int) 

208 skip_2x2 = False 

209 

210 # Some upper/lower dependent offset values 

211 # range (s)tart, r(e)nd, r(i)ncrement 

212 x, y, rs, re, ri = (1, 0, 0, n, 1) if lower else (-1, -1, n-1, -1, -1) 

213 

214 for ind in range(rs, re, ri): 

215 # If previous spin belonged already to a 2x2 block 

216 if skip_2x2: 

217 skip_2x2 = False 

218 continue 

219 

220 cur_val = a[ind] 

221 # do we have a 1x1 block or not? 

222 if cur_val > 0: 

223 if cur_val != ind+1: 

224 # Index value != array value --> permutation required 

225 swap_[ind] = swap_[cur_val-1] 

226 pivots[ind] = 1 

227 # Not. 

228 elif cur_val < 0 and cur_val == a[ind+x]: 

229 # first neg entry of 2x2 block identifier 

230 if -cur_val != ind+2: 

231 # Index value != array value --> permutation required 

232 swap_[ind+x] = swap_[-cur_val-1] 

233 pivots[ind+y] = 2 

234 skip_2x2 = True 

235 else: # Doesn't make sense, give up 

236 raise ValueError('While parsing the permutation array ' 

237 'in "scipy.linalg.ldl", invalid entries ' 

238 'found. The array syntax is invalid.') 

239 return swap_, pivots 

240 

241 

242def _ldl_get_d_and_l(ldu, pivs, lower=True, hermitian=True): 

243 """ 

244 Helper function to extract the diagonal and triangular matrices for 

245 LDL.T factorization. 

246 

247 Parameters 

248 ---------- 

249 ldu : ndarray 

250 The compact output returned by the LAPACK routing 

251 pivs : ndarray 

252 The sanitized array of {0, 1, 2} denoting the sizes of the pivots. For 

253 every 2 there is a succeeding 0. 

254 lower : bool, optional 

255 If set to False, upper triangular part is considered. 

256 hermitian : bool, optional 

257 If set to False a symmetric complex array is assumed. 

258 

259 Returns 

260 ------- 

261 d : ndarray 

262 The block diagonal matrix. 

263 lu : ndarray 

264 The upper/lower triangular matrix 

265 """ 

266 is_c = iscomplexobj(ldu) 

267 d = diag(diag(ldu)) 

268 n = d.shape[0] 

269 blk_i = 0 # block index 

270 

271 # row/column offsets for selecting sub-, super-diagonal 

272 x, y = (1, 0) if lower else (0, 1) 

273 

274 lu = tril(ldu, -1) if lower else triu(ldu, 1) 

275 diag_inds = arange(n) 

276 lu[diag_inds, diag_inds] = 1 

277 

278 for blk in pivs[pivs != 0]: 

279 # increment the block index and check for 2s 

280 # if 2 then copy the off diagonals depending on uplo 

281 inc = blk_i + blk 

282 

283 if blk == 2: 

284 d[blk_i+x, blk_i+y] = ldu[blk_i+x, blk_i+y] 

285 # If Hermitian matrix is factorized, the cross-offdiagonal element 

286 # should be conjugated. 

287 if is_c and hermitian: 

288 d[blk_i+y, blk_i+x] = ldu[blk_i+x, blk_i+y].conj() 

289 else: 

290 d[blk_i+y, blk_i+x] = ldu[blk_i+x, blk_i+y] 

291 

292 lu[blk_i+x, blk_i+y] = 0. 

293 blk_i = inc 

294 

295 return d, lu 

296 

297 

298def _ldl_construct_tri_factor(lu, swap_vec, pivs, lower=True): 

299 """ 

300 Helper function to construct explicit outer factors of LDL factorization. 

301 

302 If lower is True the permuted factors are multiplied as L(1)*L(2)*...*L(k). 

303 Otherwise, the permuted factors are multiplied as L(k)*...*L(2)*L(1). See 

304 LAPACK documentation for more details. 

305 

306 Parameters 

307 ---------- 

308 lu : ndarray 

309 The triangular array that is extracted from LAPACK routine call with 

310 ones on the diagonals. 

311 swap_vec : ndarray 

312 The array that defines the row swapping indices. If the kth entry is m 

313 then rows k,m are swapped. Notice that the mth entry is not necessarily 

314 k to avoid undoing the swapping. 

315 pivs : ndarray 

316 The array that defines the block diagonal structure returned by 

317 _ldl_sanitize_ipiv(). 

318 lower : bool, optional 

319 The boolean to switch between lower and upper triangular structure. 

320 

321 Returns 

322 ------- 

323 lu : ndarray 

324 The square outer factor which satisfies the L * D * L.T = A 

325 perm : ndarray 

326 The permutation vector that brings the lu to the triangular form 

327 

328 Notes 

329 ----- 

330 Note that the original argument "lu" is overwritten. 

331 

332 """ 

333 n = lu.shape[0] 

334 perm = arange(n) 

335 # Setup the reading order of the permutation matrix for upper/lower 

336 rs, re, ri = (n-1, -1, -1) if lower else (0, n, 1) 

337 

338 for ind in range(rs, re, ri): 

339 s_ind = swap_vec[ind] 

340 if s_ind != ind: 

341 # Column start and end positions 

342 col_s = ind if lower else 0 

343 col_e = n if lower else ind+1 

344 

345 # If we stumble upon a 2x2 block include both cols in the perm. 

346 if pivs[ind] == (0 if lower else 2): 

347 col_s += -1 if lower else 0 

348 col_e += 0 if lower else 1 

349 lu[[s_ind, ind], col_s:col_e] = lu[[ind, s_ind], col_s:col_e] 

350 perm[[s_ind, ind]] = perm[[ind, s_ind]] 

351 

352 return lu, argsort(perm)