1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

from __future__ import division, print_function, absolute_import 

 

from warnings import warn 

 

import numpy as np 

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

iscomplexobj, tril, triu, argsort, empty_like) 

from .decomp import _asarray_validated 

from .lapack import get_lapack_funcs, _compute_lwork 

 

__all__ = ['ldl'] 

 

 

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

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

hermitian matrix. 

 

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

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

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

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

triangular matrices are returned as outer factors. 

 

The permutation array can be used to triangularize the outer factors 

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

triangular matrix. This is also equivalent to multiplication with a 

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

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

 

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

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

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

supplied. 

 

Parameters 

---------- 

a : array_like 

Square input array 

lower : bool, optional 

This switches between the lower and upper triangular outer factors of 

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

hermitian : bool, optional 

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

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

effect. 

overwrite_a : bool, optional 

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

is False. 

check_finite : bool, optional 

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

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

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

 

Returns 

------- 

lu : ndarray 

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

factorization. 

d : ndarray 

The block diagonal multiplier of the factorization. 

perm : ndarray 

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

 

Raises 

------ 

ValueError 

If input array is not square. 

ComplexWarning 

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

diagonal is given and hermitian is set to True. 

 

Examples 

-------- 

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

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

 

>>> import numpy as np 

>>> from scipy.linalg import ldl 

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

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

>>> lu 

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

[ 0. , 1. , -0.5], 

[ 1. , 1. , 1.5]]) 

>>> d 

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

[ 0. , 1.5, 0. ], 

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

>>> perm 

array([2, 1, 0]) 

>>> lu[perm, :] 

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

[ 0. , 1. , -0.5], 

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

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

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

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

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

 

Notes 

----- 

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

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

the algorithm details. 

 

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

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

the structure of the outer factors of the factorization. 

 

.. versionadded:: 1.1.0 

 

See also 

-------- 

cholesky, lu 

 

References 

---------- 

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

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

1977. DOI: 10.2307/2005787 

 

""" 

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

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

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

# Return empty arrays for empty square input 

if a.size == 0: 

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

 

n = a.shape[0] 

r_or_c = complex if iscomplexobj(a) else float 

 

# Get the LAPACK routine 

if r_or_c is complex and hermitian: 

s, sl = 'hetrf', 'hetrf_lwork' 

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

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

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

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

else: 

s, sl = 'sytrf', 'sytrf_lwork' 

 

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

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

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

overwrite_a=overwrite_a) 

if info < 0: 

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

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

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

 

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

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

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

 

return lu, d, perm 

 

 

def _ldl_sanitize_ipiv(a, lower=True): 

""" 

This helper function takes the rather strangely encoded permutation array 

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

regularized permutation and diagonal pivot size format. 

 

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

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

below. 

 

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

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

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

 

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

 

This denotes a diagonal matrix of the form :: 

 

[x x ] 

[x x ] 

[ x x ] 

[ x x ] 

[ x ] 

[ x] 

 

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

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

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

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

losing the block structure. 

 

Parameters 

---------- 

a : ndarray 

The permutation array ipiv returned by LAPACK 

lower : bool, optional 

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

the LAPACK call. 

 

Returns 

------- 

swap_ : ndarray 

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

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

pivots : ndarray 

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

 

""" 

n = a.size 

swap_ = arange(n) 

pivots = zeros_like(swap_, dtype=int) 

skip_2x2 = False 

 

# Some upper/lower dependent offset values 

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

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

 

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

# If previous spin belonged already to a 2x2 block 

if skip_2x2: 

skip_2x2 = False 

continue 

 

cur_val = a[ind] 

# do we have a 1x1 block or not? 

if cur_val > 0: 

if cur_val != ind+1: 

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

swap_[ind] = swap_[cur_val-1] 

pivots[ind] = 1 

# Not. 

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

# first neg entry of 2x2 block identifier 

if -cur_val != ind+2: 

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

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

pivots[ind+y] = 2 

skip_2x2 = True 

else: # Doesn't make sense, give up 

raise ValueError('While parsing the permutation array ' 

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

'found. The array syntax is invalid.') 

return swap_, pivots 

 

 

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

""" 

Helper function to extract the diagonal and triangular matrices for 

LDL.T factorization. 

 

Parameters 

---------- 

ldu : ndarray 

The compact output returned by the LAPACK routing 

pivs : ndarray 

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

every 2 there is a succeeding 0. 

lower : bool, optional 

If set to False, upper triangular part is considered. 

hermitian : bool, optional 

If set to False a symmetric complex array is assumed. 

 

Returns 

------- 

d : ndarray 

The block diagonal matrix. 

lu : ndarray 

The upper/lower triangular matrix 

""" 

is_c = iscomplexobj(ldu) 

d = diag(diag(ldu)) 

n = d.shape[0] 

blk_i = 0 # block index 

 

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

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

 

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

diag_inds = arange(n) 

lu[diag_inds, diag_inds] = 1 

 

for blk in pivs[pivs != 0]: 

# increment the block index and check for 2s 

# if 2 then copy the off diagonals depending on uplo 

inc = blk_i + blk 

 

if blk == 2: 

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

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

# should be conjugated. 

if is_c and hermitian: 

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

else: 

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

 

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

blk_i = inc 

 

return d, lu 

 

 

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

""" 

Helper function to construct explicit outer factors of LDL factorization. 

 

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

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

LAPACK documentation for more details. 

 

Parameters 

---------- 

lu : ndarray 

The triangular array that is extracted from LAPACK routine call with 

ones on the diagonals. 

swap_vec : ndarray 

The array that defines the row swapping indices. If k'th entry is m 

then rows k,m are swapped. Notice that m'th entry is not necessarily 

k to avoid undoing the swapping. 

pivs : ndarray 

The array that defines the block diagonal structure returned by 

_ldl_sanitize_ipiv(). 

lower : bool, optional 

The boolean to switch between lower and upper triangular structure. 

 

Returns 

------- 

lu : ndarray 

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

perm : ndarray 

The permutation vector that brings the lu to the triangular form 

 

Notes 

----- 

Note that the original argument "lu" is overwritten. 

 

""" 

n = lu.shape[0] 

perm = arange(n) 

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

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

 

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

s_ind = swap_vec[ind] 

if s_ind != ind: 

# Column start and end positions 

col_s = ind if lower else 0 

col_e = n if lower else ind+1 

 

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

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

col_s += -1 if lower else 0 

col_e += 0 if lower else 1 

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

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

 

return lu, argsort(perm)