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

85 statements  

« prev     ^ index     » next       coverage.py v7.3.1, created at 2023-09-23 06:43 +0000

1from warnings import warn 

2 

3import numpy as np 

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

5 iscomplexobj, tril, triu, argsort, empty_like) 

6from scipy._lib._util import ComplexWarning 

7from ._decomp import _asarray_validated 

8from .lapack import get_lapack_funcs, _compute_lwork 

9 

10__all__ = ['ldl'] 

11 

12 

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

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

15 hermitian matrix. 

16 

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

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

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

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

21 triangular matrices are returned as outer factors. 

22 

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

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

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

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

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

28 

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

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

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

32 supplied. 

33 

34 Parameters 

35 ---------- 

36 A : array_like 

37 Square input array 

38 lower : bool, optional 

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

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

41 hermitian : bool, optional 

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

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

44 effect. 

45 overwrite_a : bool, optional 

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

47 is False. 

48 check_finite : bool, optional 

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

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

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

52 

53 Returns 

54 ------- 

55 lu : ndarray 

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

57 factorization. 

58 d : ndarray 

59 The block diagonal multiplier of the factorization. 

60 perm : ndarray 

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

62 

63 Raises 

64 ------ 

65 ValueError 

66 If input array is not square. 

67 ComplexWarning 

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

69 diagonal is given and hermitian is set to True. 

70 

71 See Also 

72 -------- 

73 cholesky, lu 

74 

75 Notes 

76 ----- 

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

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

79 the algorithm details. 

80 

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

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

83 the structure of the outer factors of the factorization. 

84 

85 .. versionadded:: 1.1.0 

86 

87 References 

88 ---------- 

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

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

91 1977. :doi:`10.2307/2005787` 

92 

93 Examples 

94 -------- 

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

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

97 

98 >>> import numpy as np 

99 >>> from scipy.linalg import ldl 

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

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

102 >>> lu 

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

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

105 [ 1. , 1. , 1.5]]) 

106 >>> d 

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

108 [ 0. , 1.5, 0. ], 

109 [ 0. , 0. , 2. ]]) 

110 >>> perm 

111 array([2, 1, 0]) 

112 >>> lu[perm, :] 

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

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

115 [ 0. , 0. , 1. ]]) 

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

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

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

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

120 

121 """ 

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

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

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

125 # Return empty arrays for empty square input 

126 if a.size == 0: 

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

128 

129 n = a.shape[0] 

130 r_or_c = complex if iscomplexobj(a) else float 

131 

132 # Get the LAPACK routine 

133 if r_or_c is complex and hermitian: 

134 s, sl = 'hetrf', 'hetrf_lwork' 

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

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

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

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

139 else: 

140 s, sl = 'sytrf', 'sytrf_lwork' 

141 

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

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

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

145 overwrite_a=overwrite_a) 

146 if info < 0: 

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

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

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

150 

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

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

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

154 

155 return lu, d, perm 

156 

157 

158def _ldl_sanitize_ipiv(a, lower=True): 

159 """ 

160 This helper function takes the rather strangely encoded permutation array 

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

162 regularized permutation and diagonal pivot size format. 

163 

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

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

166 below. 

167 

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

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

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

171 

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

173 

174 This denotes a diagonal matrix of the form :: 

175 

176 [x x ] 

177 [x x ] 

178 [ x x ] 

179 [ x x ] 

180 [ x ] 

181 [ x] 

182 

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

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

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

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

187 losing the block structure. 

188 

189 Parameters 

190 ---------- 

191 a : ndarray 

192 The permutation array ipiv returned by LAPACK 

193 lower : bool, optional 

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

195 the LAPACK call. 

196 

197 Returns 

198 ------- 

199 swap_ : ndarray 

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

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

202 pivots : ndarray 

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

204 

205 """ 

206 n = a.size 

207 swap_ = arange(n) 

208 pivots = zeros_like(swap_, dtype=int) 

209 skip_2x2 = False 

210 

211 # Some upper/lower dependent offset values 

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

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

214 

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

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

217 if skip_2x2: 

218 skip_2x2 = False 

219 continue 

220 

221 cur_val = a[ind] 

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

223 if cur_val > 0: 

224 if cur_val != ind+1: 

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

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

227 pivots[ind] = 1 

228 # Not. 

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

230 # first neg entry of 2x2 block identifier 

231 if -cur_val != ind+2: 

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

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

234 pivots[ind+y] = 2 

235 skip_2x2 = True 

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

237 raise ValueError('While parsing the permutation array ' 

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

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

240 return swap_, pivots 

241 

242 

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

244 """ 

245 Helper function to extract the diagonal and triangular matrices for 

246 LDL.T factorization. 

247 

248 Parameters 

249 ---------- 

250 ldu : ndarray 

251 The compact output returned by the LAPACK routing 

252 pivs : ndarray 

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

254 every 2 there is a succeeding 0. 

255 lower : bool, optional 

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

257 hermitian : bool, optional 

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

259 

260 Returns 

261 ------- 

262 d : ndarray 

263 The block diagonal matrix. 

264 lu : ndarray 

265 The upper/lower triangular matrix 

266 """ 

267 is_c = iscomplexobj(ldu) 

268 d = diag(diag(ldu)) 

269 n = d.shape[0] 

270 blk_i = 0 # block index 

271 

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

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

274 

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

276 diag_inds = arange(n) 

277 lu[diag_inds, diag_inds] = 1 

278 

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

280 # increment the block index and check for 2s 

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

282 inc = blk_i + blk 

283 

284 if blk == 2: 

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

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

287 # should be conjugated. 

288 if is_c and hermitian: 

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

290 else: 

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

292 

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

294 blk_i = inc 

295 

296 return d, lu 

297 

298 

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

300 """ 

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

302 

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

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

305 LAPACK documentation for more details. 

306 

307 Parameters 

308 ---------- 

309 lu : ndarray 

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

311 ones on the diagonals. 

312 swap_vec : ndarray 

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

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

315 k to avoid undoing the swapping. 

316 pivs : ndarray 

317 The array that defines the block diagonal structure returned by 

318 _ldl_sanitize_ipiv(). 

319 lower : bool, optional 

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

321 

322 Returns 

323 ------- 

324 lu : ndarray 

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

326 perm : ndarray 

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

328 

329 Notes 

330 ----- 

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

332 

333 """ 

334 n = lu.shape[0] 

335 perm = arange(n) 

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

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

338 

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

340 s_ind = swap_vec[ind] 

341 if s_ind != ind: 

342 # Column start and end positions 

343 col_s = ind if lower else 0 

344 col_e = n if lower else ind+1 

345 

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

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

348 col_s += -1 if lower else 0 

349 col_e += 0 if lower else 1 

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

351 perm[[s_ind, ind]] = perm[[ind, s_ind]] 

352 

353 return lu, argsort(perm)