|
from warnings import warn |
|
|
|
import numpy as np |
|
from numpy import (atleast_2d, arange, zeros_like, imag, diag, |
|
iscomplexobj, tril, triu, argsort, empty_like) |
|
from scipy._lib._util import ComplexWarning |
|
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. |
|
|
|
See Also |
|
-------- |
|
cholesky, lu |
|
|
|
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 |
|
|
|
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` |
|
|
|
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.]]) |
|
|
|
""" |
|
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.') |
|
|
|
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 |
|
|
|
|
|
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(f'{s.upper()} exited with the internal error "illegal value ' |
|
f'in argument number {-info}". See LAPACK documentation ' |
|
'for the error codes.') |
|
|
|
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 |
|
|
|
|
|
|
|
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 skip_2x2: |
|
skip_2x2 = False |
|
continue |
|
|
|
cur_val = a[ind] |
|
|
|
if cur_val > 0: |
|
if cur_val != ind+1: |
|
|
|
swap_[ind] = swap_[cur_val-1] |
|
pivots[ind] = 1 |
|
|
|
elif cur_val < 0 and cur_val == a[ind+x]: |
|
|
|
if -cur_val != ind+2: |
|
|
|
swap_[ind+x] = swap_[-cur_val-1] |
|
pivots[ind+y] = 2 |
|
skip_2x2 = True |
|
else: |
|
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 |
|
|
|
|
|
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]: |
|
|
|
|
|
inc = blk_i + blk |
|
|
|
if blk == 2: |
|
d[blk_i+x, blk_i+y] = ldu[blk_i+x, blk_i+y] |
|
|
|
|
|
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 the kth entry is m |
|
then rows k,m are swapped. Notice that the mth 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) |
|
|
|
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: |
|
|
|
col_s = ind if lower else 0 |
|
col_e = n if lower else ind+1 |
|
|
|
|
|
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) |
|
|