|
import copy |
|
|
|
from sympy.core import S |
|
from sympy.core.function import expand_mul |
|
from sympy.functions.elementary.miscellaneous import Min, sqrt |
|
from sympy.functions.elementary.complexes import sign |
|
|
|
from .exceptions import NonSquareMatrixError, NonPositiveDefiniteMatrixError |
|
from .utilities import _get_intermediate_simp, _iszero |
|
from .determinant import _find_reasonable_pivot_naive |
|
|
|
|
|
def _rank_decomposition(M, iszerofunc=_iszero, simplify=False): |
|
r"""Returns a pair of matrices (`C`, `F`) with matching rank |
|
such that `A = C F`. |
|
|
|
Parameters |
|
========== |
|
|
|
iszerofunc : Function, optional |
|
A function used for detecting whether an element can |
|
act as a pivot. ``lambda x: x.is_zero`` is used by default. |
|
|
|
simplify : Bool or Function, optional |
|
A function used to simplify elements when looking for a |
|
pivot. By default SymPy's ``simplify`` is used. |
|
|
|
Returns |
|
======= |
|
|
|
(C, F) : Matrices |
|
`C` and `F` are full-rank matrices with rank as same as `A`, |
|
whose product gives `A`. |
|
|
|
See Notes for additional mathematical details. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix |
|
>>> A = Matrix([ |
|
... [1, 3, 1, 4], |
|
... [2, 7, 3, 9], |
|
... [1, 5, 3, 1], |
|
... [1, 2, 0, 8] |
|
... ]) |
|
>>> C, F = A.rank_decomposition() |
|
>>> C |
|
Matrix([ |
|
[1, 3, 4], |
|
[2, 7, 9], |
|
[1, 5, 1], |
|
[1, 2, 8]]) |
|
>>> F |
|
Matrix([ |
|
[1, 0, -2, 0], |
|
[0, 1, 1, 0], |
|
[0, 0, 0, 1]]) |
|
>>> C * F == A |
|
True |
|
|
|
Notes |
|
===== |
|
|
|
Obtaining `F`, an RREF of `A`, is equivalent to creating a |
|
product |
|
|
|
.. math:: |
|
E_n E_{n-1} ... E_1 A = F |
|
|
|
where `E_n, E_{n-1}, \dots, E_1` are the elimination matrices or |
|
permutation matrices equivalent to each row-reduction step. |
|
|
|
The inverse of the same product of elimination matrices gives |
|
`C`: |
|
|
|
.. math:: |
|
C = \left(E_n E_{n-1} \dots E_1\right)^{-1} |
|
|
|
It is not necessary, however, to actually compute the inverse: |
|
the columns of `C` are those from the original matrix with the |
|
same column indices as the indices of the pivot columns of `F`. |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Rank_factorization |
|
|
|
.. [2] Piziak, R.; Odell, P. L. (1 June 1999). |
|
"Full Rank Factorization of Matrices". |
|
Mathematics Magazine. 72 (3): 193. doi:10.2307/2690882 |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.matrixbase.MatrixBase.rref |
|
""" |
|
|
|
F, pivot_cols = M.rref(simplify=simplify, iszerofunc=iszerofunc, |
|
pivots=True) |
|
rank = len(pivot_cols) |
|
|
|
C = M.extract(range(M.rows), pivot_cols) |
|
F = F[:rank, :] |
|
|
|
return C, F |
|
|
|
|
|
def _liupc(M): |
|
"""Liu's algorithm, for pre-determination of the Elimination Tree of |
|
the given matrix, used in row-based symbolic Cholesky factorization. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import SparseMatrix |
|
>>> S = SparseMatrix([ |
|
... [1, 0, 3, 2], |
|
... [0, 0, 1, 0], |
|
... [4, 0, 0, 5], |
|
... [0, 6, 7, 0]]) |
|
>>> S.liupc() |
|
([[0], [], [0], [1, 2]], [4, 3, 4, 4]) |
|
|
|
References |
|
========== |
|
|
|
.. [1] Symbolic Sparse Cholesky Factorization using Elimination Trees, |
|
Jeroen Van Grondelle (1999) |
|
https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.39.7582 |
|
""" |
|
|
|
|
|
|
|
R = [[] for r in range(M.rows)] |
|
|
|
for r, c, _ in M.row_list(): |
|
if c <= r: |
|
R[r].append(c) |
|
|
|
inf = len(R) |
|
parent = [inf]*M.rows |
|
virtual = [inf]*M.rows |
|
|
|
for r in range(M.rows): |
|
for c in R[r][:-1]: |
|
while virtual[c] < r: |
|
t = virtual[c] |
|
virtual[c] = r |
|
c = t |
|
|
|
if virtual[c] == inf: |
|
parent[c] = virtual[c] = r |
|
|
|
return R, parent |
|
|
|
def _row_structure_symbolic_cholesky(M): |
|
"""Symbolic cholesky factorization, for pre-determination of the |
|
non-zero structure of the Cholesky factororization. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import SparseMatrix |
|
>>> S = SparseMatrix([ |
|
... [1, 0, 3, 2], |
|
... [0, 0, 1, 0], |
|
... [4, 0, 0, 5], |
|
... [0, 6, 7, 0]]) |
|
>>> S.row_structure_symbolic_cholesky() |
|
[[0], [], [0], [1, 2]] |
|
|
|
References |
|
========== |
|
|
|
.. [1] Symbolic Sparse Cholesky Factorization using Elimination Trees, |
|
Jeroen Van Grondelle (1999) |
|
https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.39.7582 |
|
""" |
|
|
|
R, parent = M.liupc() |
|
inf = len(R) |
|
Lrow = copy.deepcopy(R) |
|
|
|
for k in range(M.rows): |
|
for j in R[k]: |
|
while j != inf and j != k: |
|
Lrow[k].append(j) |
|
j = parent[j] |
|
|
|
Lrow[k] = sorted(set(Lrow[k])) |
|
|
|
return Lrow |
|
|
|
|
|
def _cholesky(M, hermitian=True): |
|
"""Returns the Cholesky-type decomposition L of a matrix A |
|
such that L * L.H == A if hermitian flag is True, |
|
or L * L.T == A if hermitian is False. |
|
|
|
A must be a Hermitian positive-definite matrix if hermitian is True, |
|
or a symmetric matrix if it is False. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix |
|
>>> A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) |
|
>>> A.cholesky() |
|
Matrix([ |
|
[ 5, 0, 0], |
|
[ 3, 3, 0], |
|
[-1, 1, 3]]) |
|
>>> A.cholesky() * A.cholesky().T |
|
Matrix([ |
|
[25, 15, -5], |
|
[15, 18, 0], |
|
[-5, 0, 11]]) |
|
|
|
The matrix can have complex entries: |
|
|
|
>>> from sympy import I |
|
>>> A = Matrix(((9, 3*I), (-3*I, 5))) |
|
>>> A.cholesky() |
|
Matrix([ |
|
[ 3, 0], |
|
[-I, 2]]) |
|
>>> A.cholesky() * A.cholesky().H |
|
Matrix([ |
|
[ 9, 3*I], |
|
[-3*I, 5]]) |
|
|
|
Non-hermitian Cholesky-type decomposition may be useful when the |
|
matrix is not positive-definite. |
|
|
|
>>> A = Matrix([[1, 2], [2, 1]]) |
|
>>> L = A.cholesky(hermitian=False) |
|
>>> L |
|
Matrix([ |
|
[1, 0], |
|
[2, sqrt(3)*I]]) |
|
>>> L*L.T == A |
|
True |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.dense.DenseMatrix.LDLdecomposition |
|
sympy.matrices.matrixbase.MatrixBase.LUdecomposition |
|
QRdecomposition |
|
""" |
|
|
|
from .dense import MutableDenseMatrix |
|
|
|
if not M.is_square: |
|
raise NonSquareMatrixError("Matrix must be square.") |
|
if hermitian and not M.is_hermitian: |
|
raise ValueError("Matrix must be Hermitian.") |
|
if not hermitian and not M.is_symmetric(): |
|
raise ValueError("Matrix must be symmetric.") |
|
|
|
L = MutableDenseMatrix.zeros(M.rows, M.rows) |
|
|
|
if hermitian: |
|
for i in range(M.rows): |
|
for j in range(i): |
|
L[i, j] = ((1 / L[j, j])*(M[i, j] - |
|
sum(L[i, k]*L[j, k].conjugate() for k in range(j)))) |
|
|
|
Lii2 = (M[i, i] - |
|
sum(L[i, k]*L[i, k].conjugate() for k in range(i))) |
|
|
|
if Lii2.is_positive is False: |
|
raise NonPositiveDefiniteMatrixError( |
|
"Matrix must be positive-definite") |
|
|
|
L[i, i] = sqrt(Lii2) |
|
|
|
else: |
|
for i in range(M.rows): |
|
for j in range(i): |
|
L[i, j] = ((1 / L[j, j])*(M[i, j] - |
|
sum(L[i, k]*L[j, k] for k in range(j)))) |
|
|
|
L[i, i] = sqrt(M[i, i] - |
|
sum(L[i, k]**2 for k in range(i))) |
|
|
|
return M._new(L) |
|
|
|
def _cholesky_sparse(M, hermitian=True): |
|
""" |
|
Returns the Cholesky decomposition L of a matrix A |
|
such that L * L.T = A |
|
|
|
A must be a square, symmetric, positive-definite |
|
and non-singular matrix |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import SparseMatrix |
|
>>> A = SparseMatrix(((25,15,-5),(15,18,0),(-5,0,11))) |
|
>>> A.cholesky() |
|
Matrix([ |
|
[ 5, 0, 0], |
|
[ 3, 3, 0], |
|
[-1, 1, 3]]) |
|
>>> A.cholesky() * A.cholesky().T == A |
|
True |
|
|
|
The matrix can have complex entries: |
|
|
|
>>> from sympy import I |
|
>>> A = SparseMatrix(((9, 3*I), (-3*I, 5))) |
|
>>> A.cholesky() |
|
Matrix([ |
|
[ 3, 0], |
|
[-I, 2]]) |
|
>>> A.cholesky() * A.cholesky().H |
|
Matrix([ |
|
[ 9, 3*I], |
|
[-3*I, 5]]) |
|
|
|
Non-hermitian Cholesky-type decomposition may be useful when the |
|
matrix is not positive-definite. |
|
|
|
>>> A = SparseMatrix([[1, 2], [2, 1]]) |
|
>>> L = A.cholesky(hermitian=False) |
|
>>> L |
|
Matrix([ |
|
[1, 0], |
|
[2, sqrt(3)*I]]) |
|
>>> L*L.T == A |
|
True |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.sparse.SparseMatrix.LDLdecomposition |
|
sympy.matrices.matrixbase.MatrixBase.LUdecomposition |
|
QRdecomposition |
|
""" |
|
|
|
from .dense import MutableDenseMatrix |
|
|
|
if not M.is_square: |
|
raise NonSquareMatrixError("Matrix must be square.") |
|
if hermitian and not M.is_hermitian: |
|
raise ValueError("Matrix must be Hermitian.") |
|
if not hermitian and not M.is_symmetric(): |
|
raise ValueError("Matrix must be symmetric.") |
|
|
|
dps = _get_intermediate_simp(expand_mul, expand_mul) |
|
Crowstruc = M.row_structure_symbolic_cholesky() |
|
C = MutableDenseMatrix.zeros(M.rows) |
|
|
|
for i in range(len(Crowstruc)): |
|
for j in Crowstruc[i]: |
|
if i != j: |
|
C[i, j] = M[i, j] |
|
summ = 0 |
|
|
|
for p1 in Crowstruc[i]: |
|
if p1 < j: |
|
for p2 in Crowstruc[j]: |
|
if p2 < j: |
|
if p1 == p2: |
|
if hermitian: |
|
summ += C[i, p1]*C[j, p1].conjugate() |
|
else: |
|
summ += C[i, p1]*C[j, p1] |
|
else: |
|
break |
|
else: |
|
break |
|
|
|
C[i, j] = dps((C[i, j] - summ) / C[j, j]) |
|
|
|
else: |
|
C[j, j] = M[j, j] |
|
summ = 0 |
|
|
|
for k in Crowstruc[j]: |
|
if k < j: |
|
if hermitian: |
|
summ += C[j, k]*C[j, k].conjugate() |
|
else: |
|
summ += C[j, k]**2 |
|
else: |
|
break |
|
|
|
Cjj2 = dps(C[j, j] - summ) |
|
|
|
if hermitian and Cjj2.is_positive is False: |
|
raise NonPositiveDefiniteMatrixError( |
|
"Matrix must be positive-definite") |
|
|
|
C[j, j] = sqrt(Cjj2) |
|
|
|
return M._new(C) |
|
|
|
|
|
def _LDLdecomposition(M, hermitian=True): |
|
"""Returns the LDL Decomposition (L, D) of matrix A, |
|
such that L * D * L.H == A if hermitian flag is True, or |
|
L * D * L.T == A if hermitian is False. |
|
This method eliminates the use of square root. |
|
Further this ensures that all the diagonal entries of L are 1. |
|
A must be a Hermitian positive-definite matrix if hermitian is True, |
|
or a symmetric matrix otherwise. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix, eye |
|
>>> A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) |
|
>>> L, D = A.LDLdecomposition() |
|
>>> L |
|
Matrix([ |
|
[ 1, 0, 0], |
|
[ 3/5, 1, 0], |
|
[-1/5, 1/3, 1]]) |
|
>>> D |
|
Matrix([ |
|
[25, 0, 0], |
|
[ 0, 9, 0], |
|
[ 0, 0, 9]]) |
|
>>> L * D * L.T * A.inv() == eye(A.rows) |
|
True |
|
|
|
The matrix can have complex entries: |
|
|
|
>>> from sympy import I |
|
>>> A = Matrix(((9, 3*I), (-3*I, 5))) |
|
>>> L, D = A.LDLdecomposition() |
|
>>> L |
|
Matrix([ |
|
[ 1, 0], |
|
[-I/3, 1]]) |
|
>>> D |
|
Matrix([ |
|
[9, 0], |
|
[0, 4]]) |
|
>>> L*D*L.H == A |
|
True |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.dense.DenseMatrix.cholesky |
|
sympy.matrices.matrixbase.MatrixBase.LUdecomposition |
|
QRdecomposition |
|
""" |
|
|
|
from .dense import MutableDenseMatrix |
|
|
|
if not M.is_square: |
|
raise NonSquareMatrixError("Matrix must be square.") |
|
if hermitian and not M.is_hermitian: |
|
raise ValueError("Matrix must be Hermitian.") |
|
if not hermitian and not M.is_symmetric(): |
|
raise ValueError("Matrix must be symmetric.") |
|
|
|
D = MutableDenseMatrix.zeros(M.rows, M.rows) |
|
L = MutableDenseMatrix.eye(M.rows) |
|
|
|
if hermitian: |
|
for i in range(M.rows): |
|
for j in range(i): |
|
L[i, j] = (1 / D[j, j])*(M[i, j] - sum( |
|
L[i, k]*L[j, k].conjugate()*D[k, k] for k in range(j))) |
|
|
|
D[i, i] = (M[i, i] - |
|
sum(L[i, k]*L[i, k].conjugate()*D[k, k] for k in range(i))) |
|
|
|
if D[i, i].is_positive is False: |
|
raise NonPositiveDefiniteMatrixError( |
|
"Matrix must be positive-definite") |
|
|
|
else: |
|
for i in range(M.rows): |
|
for j in range(i): |
|
L[i, j] = (1 / D[j, j])*(M[i, j] - sum( |
|
L[i, k]*L[j, k]*D[k, k] for k in range(j))) |
|
|
|
D[i, i] = M[i, i] - sum(L[i, k]**2*D[k, k] for k in range(i)) |
|
|
|
return M._new(L), M._new(D) |
|
|
|
def _LDLdecomposition_sparse(M, hermitian=True): |
|
""" |
|
Returns the LDL Decomposition (matrices ``L`` and ``D``) of matrix |
|
``A``, such that ``L * D * L.T == A``. ``A`` must be a square, |
|
symmetric, positive-definite and non-singular. |
|
|
|
This method eliminates the use of square root and ensures that all |
|
the diagonal entries of L are 1. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import SparseMatrix |
|
>>> A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11))) |
|
>>> L, D = A.LDLdecomposition() |
|
>>> L |
|
Matrix([ |
|
[ 1, 0, 0], |
|
[ 3/5, 1, 0], |
|
[-1/5, 1/3, 1]]) |
|
>>> D |
|
Matrix([ |
|
[25, 0, 0], |
|
[ 0, 9, 0], |
|
[ 0, 0, 9]]) |
|
>>> L * D * L.T == A |
|
True |
|
|
|
""" |
|
|
|
from .dense import MutableDenseMatrix |
|
|
|
if not M.is_square: |
|
raise NonSquareMatrixError("Matrix must be square.") |
|
if hermitian and not M.is_hermitian: |
|
raise ValueError("Matrix must be Hermitian.") |
|
if not hermitian and not M.is_symmetric(): |
|
raise ValueError("Matrix must be symmetric.") |
|
|
|
dps = _get_intermediate_simp(expand_mul, expand_mul) |
|
Lrowstruc = M.row_structure_symbolic_cholesky() |
|
L = MutableDenseMatrix.eye(M.rows) |
|
D = MutableDenseMatrix.zeros(M.rows, M.cols) |
|
|
|
for i in range(len(Lrowstruc)): |
|
for j in Lrowstruc[i]: |
|
if i != j: |
|
L[i, j] = M[i, j] |
|
summ = 0 |
|
|
|
for p1 in Lrowstruc[i]: |
|
if p1 < j: |
|
for p2 in Lrowstruc[j]: |
|
if p2 < j: |
|
if p1 == p2: |
|
if hermitian: |
|
summ += L[i, p1]*L[j, p1].conjugate()*D[p1, p1] |
|
else: |
|
summ += L[i, p1]*L[j, p1]*D[p1, p1] |
|
else: |
|
break |
|
else: |
|
break |
|
|
|
L[i, j] = dps((L[i, j] - summ) / D[j, j]) |
|
|
|
else: |
|
D[i, i] = M[i, i] |
|
summ = 0 |
|
|
|
for k in Lrowstruc[i]: |
|
if k < i: |
|
if hermitian: |
|
summ += L[i, k]*L[i, k].conjugate()*D[k, k] |
|
else: |
|
summ += L[i, k]**2*D[k, k] |
|
else: |
|
break |
|
|
|
D[i, i] = dps(D[i, i] - summ) |
|
|
|
if hermitian and D[i, i].is_positive is False: |
|
raise NonPositiveDefiniteMatrixError( |
|
"Matrix must be positive-definite") |
|
|
|
return M._new(L), M._new(D) |
|
|
|
|
|
def _LUdecomposition(M, iszerofunc=_iszero, simpfunc=None, rankcheck=False): |
|
"""Returns (L, U, perm) where L is a lower triangular matrix with unit |
|
diagonal, U is an upper triangular matrix, and perm is a list of row |
|
swap index pairs. If A is the original matrix, then |
|
``A = (L*U).permuteBkwd(perm)``, and the row permutation matrix P such |
|
that $P A = L U$ can be computed by ``P = eye(A.rows).permuteFwd(perm)``. |
|
|
|
See documentation for LUCombined for details about the keyword argument |
|
rankcheck, iszerofunc, and simpfunc. |
|
|
|
Parameters |
|
========== |
|
|
|
rankcheck : bool, optional |
|
Determines if this function should detect the rank |
|
deficiency of the matrixis and should raise a |
|
``ValueError``. |
|
|
|
iszerofunc : function, optional |
|
A function which determines if a given expression is zero. |
|
|
|
The function should be a callable that takes a single |
|
SymPy expression and returns a 3-valued boolean value |
|
``True``, ``False``, or ``None``. |
|
|
|
It is internally used by the pivot searching algorithm. |
|
See the notes section for a more information about the |
|
pivot searching algorithm. |
|
|
|
simpfunc : function or None, optional |
|
A function that simplifies the input. |
|
|
|
If this is specified as a function, this function should be |
|
a callable that takes a single SymPy expression and returns |
|
an another SymPy expression that is algebraically |
|
equivalent. |
|
|
|
If ``None``, it indicates that the pivot search algorithm |
|
should not attempt to simplify any candidate pivots. |
|
|
|
It is internally used by the pivot searching algorithm. |
|
See the notes section for a more information about the |
|
pivot searching algorithm. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix |
|
>>> a = Matrix([[4, 3], [6, 3]]) |
|
>>> L, U, _ = a.LUdecomposition() |
|
>>> L |
|
Matrix([ |
|
[ 1, 0], |
|
[3/2, 1]]) |
|
>>> U |
|
Matrix([ |
|
[4, 3], |
|
[0, -3/2]]) |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.dense.DenseMatrix.cholesky |
|
sympy.matrices.dense.DenseMatrix.LDLdecomposition |
|
QRdecomposition |
|
LUdecomposition_Simple |
|
LUdecompositionFF |
|
LUsolve |
|
""" |
|
|
|
combined, p = M.LUdecomposition_Simple(iszerofunc=iszerofunc, |
|
simpfunc=simpfunc, rankcheck=rankcheck) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def entry_L(i, j): |
|
if i < j: |
|
|
|
return M.zero |
|
elif i == j: |
|
return M.one |
|
elif j < combined.cols: |
|
return combined[i, j] |
|
|
|
|
|
|
|
return M.zero |
|
|
|
def entry_U(i, j): |
|
return M.zero if i > j else combined[i, j] |
|
|
|
L = M._new(combined.rows, combined.rows, entry_L) |
|
U = M._new(combined.rows, combined.cols, entry_U) |
|
|
|
return L, U, p |
|
|
|
def _LUdecomposition_Simple(M, iszerofunc=_iszero, simpfunc=None, |
|
rankcheck=False): |
|
r"""Compute the PLU decomposition of the matrix. |
|
|
|
Parameters |
|
========== |
|
|
|
rankcheck : bool, optional |
|
Determines if this function should detect the rank |
|
deficiency of the matrixis and should raise a |
|
``ValueError``. |
|
|
|
iszerofunc : function, optional |
|
A function which determines if a given expression is zero. |
|
|
|
The function should be a callable that takes a single |
|
SymPy expression and returns a 3-valued boolean value |
|
``True``, ``False``, or ``None``. |
|
|
|
It is internally used by the pivot searching algorithm. |
|
See the notes section for a more information about the |
|
pivot searching algorithm. |
|
|
|
simpfunc : function or None, optional |
|
A function that simplifies the input. |
|
|
|
If this is specified as a function, this function should be |
|
a callable that takes a single SymPy expression and returns |
|
an another SymPy expression that is algebraically |
|
equivalent. |
|
|
|
If ``None``, it indicates that the pivot search algorithm |
|
should not attempt to simplify any candidate pivots. |
|
|
|
It is internally used by the pivot searching algorithm. |
|
See the notes section for a more information about the |
|
pivot searching algorithm. |
|
|
|
Returns |
|
======= |
|
|
|
(lu, row_swaps) : (Matrix, list) |
|
If the original matrix is a $m, n$ matrix: |
|
|
|
*lu* is a $m, n$ matrix, which contains result of the |
|
decomposition in a compressed form. See the notes section |
|
to see how the matrix is compressed. |
|
|
|
*row_swaps* is a $m$-element list where each element is a |
|
pair of row exchange indices. |
|
|
|
``A = (L*U).permute_backward(perm)``, and the row |
|
permutation matrix $P$ from the formula $P A = L U$ can be |
|
computed by ``P=eye(A.row).permute_forward(perm)``. |
|
|
|
Raises |
|
====== |
|
|
|
ValueError |
|
Raised if ``rankcheck=True`` and the matrix is found to |
|
be rank deficient during the computation. |
|
|
|
Notes |
|
===== |
|
|
|
About the PLU decomposition: |
|
|
|
PLU decomposition is a generalization of a LU decomposition |
|
which can be extended for rank-deficient matrices. |
|
|
|
It can further be generalized for non-square matrices, and this |
|
is the notation that SymPy is using. |
|
|
|
PLU decomposition is a decomposition of a $m, n$ matrix $A$ in |
|
the form of $P A = L U$ where |
|
|
|
* $L$ is a $m, m$ lower triangular matrix with unit diagonal |
|
entries. |
|
* $U$ is a $m, n$ upper triangular matrix. |
|
* $P$ is a $m, m$ permutation matrix. |
|
|
|
So, for a square matrix, the decomposition would look like: |
|
|
|
.. math:: |
|
L = \begin{bmatrix} |
|
1 & 0 & 0 & \cdots & 0 \\ |
|
L_{1, 0} & 1 & 0 & \cdots & 0 \\ |
|
L_{2, 0} & L_{2, 1} & 1 & \cdots & 0 \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots & 1 |
|
\end{bmatrix} |
|
|
|
.. math:: |
|
U = \begin{bmatrix} |
|
U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ |
|
0 & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ |
|
0 & 0 & U_{2, 2} & \cdots & U_{2, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
0 & 0 & 0 & \cdots & U_{n-1, n-1} |
|
\end{bmatrix} |
|
|
|
And for a matrix with more rows than the columns, |
|
the decomposition would look like: |
|
|
|
.. math:: |
|
L = \begin{bmatrix} |
|
1 & 0 & 0 & \cdots & 0 & 0 & \cdots & 0 \\ |
|
L_{1, 0} & 1 & 0 & \cdots & 0 & 0 & \cdots & 0 \\ |
|
L_{2, 0} & L_{2, 1} & 1 & \cdots & 0 & 0 & \cdots & 0 \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \ddots |
|
& \vdots \\ |
|
L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots & 1 & 0 |
|
& \cdots & 0 \\ |
|
L_{n, 0} & L_{n, 1} & L_{n, 2} & \cdots & L_{n, n-1} & 1 |
|
& \cdots & 0 \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots & \vdots |
|
& \ddots & \vdots \\ |
|
L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots & L_{m-1, n-1} |
|
& 0 & \cdots & 1 \\ |
|
\end{bmatrix} |
|
|
|
.. math:: |
|
U = \begin{bmatrix} |
|
U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ |
|
0 & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ |
|
0 & 0 & U_{2, 2} & \cdots & U_{2, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
0 & 0 & 0 & \cdots & U_{n-1, n-1} \\ |
|
0 & 0 & 0 & \cdots & 0 \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
0 & 0 & 0 & \cdots & 0 |
|
\end{bmatrix} |
|
|
|
Finally, for a matrix with more columns than the rows, the |
|
decomposition would look like: |
|
|
|
.. math:: |
|
L = \begin{bmatrix} |
|
1 & 0 & 0 & \cdots & 0 \\ |
|
L_{1, 0} & 1 & 0 & \cdots & 0 \\ |
|
L_{2, 0} & L_{2, 1} & 1 & \cdots & 0 \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots & 1 |
|
\end{bmatrix} |
|
|
|
.. math:: |
|
U = \begin{bmatrix} |
|
U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, m-1} |
|
& \cdots & U_{0, n-1} \\ |
|
0 & U_{1, 1} & U_{1, 2} & \cdots & U_{1, m-1} |
|
& \cdots & U_{1, n-1} \\ |
|
0 & 0 & U_{2, 2} & \cdots & U_{2, m-1} |
|
& \cdots & U_{2, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots |
|
& \cdots & \vdots \\ |
|
0 & 0 & 0 & \cdots & U_{m-1, m-1} |
|
& \cdots & U_{m-1, n-1} \\ |
|
\end{bmatrix} |
|
|
|
About the compressed LU storage: |
|
|
|
The results of the decomposition are often stored in compressed |
|
forms rather than returning $L$ and $U$ matrices individually. |
|
|
|
It may be less intiuitive, but it is commonly used for a lot of |
|
numeric libraries because of the efficiency. |
|
|
|
The storage matrix is defined as following for this specific |
|
method: |
|
|
|
* The subdiagonal elements of $L$ are stored in the subdiagonal |
|
portion of $LU$, that is $LU_{i, j} = L_{i, j}$ whenever |
|
$i > j$. |
|
* The elements on the diagonal of $L$ are all 1, and are not |
|
explicitly stored. |
|
* $U$ is stored in the upper triangular portion of $LU$, that is |
|
$LU_{i, j} = U_{i, j}$ whenever $i <= j$. |
|
* For a case of $m > n$, the right side of the $L$ matrix is |
|
trivial to store. |
|
* For a case of $m < n$, the below side of the $U$ matrix is |
|
trivial to store. |
|
|
|
So, for a square matrix, the compressed output matrix would be: |
|
|
|
.. math:: |
|
LU = \begin{bmatrix} |
|
U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ |
|
L_{1, 0} & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ |
|
L_{2, 0} & L_{2, 1} & U_{2, 2} & \cdots & U_{2, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots & U_{n-1, n-1} |
|
\end{bmatrix} |
|
|
|
For a matrix with more rows than the columns, the compressed |
|
output matrix would be: |
|
|
|
.. math:: |
|
LU = \begin{bmatrix} |
|
U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, n-1} \\ |
|
L_{1, 0} & U_{1, 1} & U_{1, 2} & \cdots & U_{1, n-1} \\ |
|
L_{2, 0} & L_{2, 1} & U_{2, 2} & \cdots & U_{2, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
L_{n-1, 0} & L_{n-1, 1} & L_{n-1, 2} & \cdots |
|
& U_{n-1, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots \\ |
|
L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots |
|
& L_{m-1, n-1} \\ |
|
\end{bmatrix} |
|
|
|
For a matrix with more columns than the rows, the compressed |
|
output matrix would be: |
|
|
|
.. math:: |
|
LU = \begin{bmatrix} |
|
U_{0, 0} & U_{0, 1} & U_{0, 2} & \cdots & U_{0, m-1} |
|
& \cdots & U_{0, n-1} \\ |
|
L_{1, 0} & U_{1, 1} & U_{1, 2} & \cdots & U_{1, m-1} |
|
& \cdots & U_{1, n-1} \\ |
|
L_{2, 0} & L_{2, 1} & U_{2, 2} & \cdots & U_{2, m-1} |
|
& \cdots & U_{2, n-1} \\ |
|
\vdots & \vdots & \vdots & \ddots & \vdots |
|
& \cdots & \vdots \\ |
|
L_{m-1, 0} & L_{m-1, 1} & L_{m-1, 2} & \cdots & U_{m-1, m-1} |
|
& \cdots & U_{m-1, n-1} \\ |
|
\end{bmatrix} |
|
|
|
About the pivot searching algorithm: |
|
|
|
When a matrix contains symbolic entries, the pivot search algorithm |
|
differs from the case where every entry can be categorized as zero or |
|
nonzero. |
|
The algorithm searches column by column through the submatrix whose |
|
top left entry coincides with the pivot position. |
|
If it exists, the pivot is the first entry in the current search |
|
column that iszerofunc guarantees is nonzero. |
|
If no such candidate exists, then each candidate pivot is simplified |
|
if simpfunc is not None. |
|
The search is repeated, with the difference that a candidate may be |
|
the pivot if ``iszerofunc()`` cannot guarantee that it is nonzero. |
|
In the second search the pivot is the first candidate that |
|
iszerofunc can guarantee is nonzero. |
|
If no such candidate exists, then the pivot is the first candidate |
|
for which iszerofunc returns None. |
|
If no such candidate exists, then the search is repeated in the next |
|
column to the right. |
|
The pivot search algorithm differs from the one in ``rref()``, which |
|
relies on ``_find_reasonable_pivot()``. |
|
Future versions of ``LUdecomposition_simple()`` may use |
|
``_find_reasonable_pivot()``. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.matrixbase.MatrixBase.LUdecomposition |
|
LUdecompositionFF |
|
LUsolve |
|
""" |
|
|
|
if rankcheck: |
|
|
|
pass |
|
|
|
if S.Zero in M.shape: |
|
|
|
|
|
return M.zeros(M.rows, M.cols), [] |
|
|
|
dps = _get_intermediate_simp() |
|
lu = M.as_mutable() |
|
row_swaps = [] |
|
|
|
pivot_col = 0 |
|
|
|
for pivot_row in range(0, lu.rows - 1): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
iszeropivot = True |
|
|
|
while pivot_col != M.cols and iszeropivot: |
|
sub_col = (lu[r, pivot_col] for r in range(pivot_row, M.rows)) |
|
|
|
pivot_row_offset, pivot_value, is_assumed_non_zero, ind_simplified_pairs =\ |
|
_find_reasonable_pivot_naive(sub_col, iszerofunc, simpfunc) |
|
|
|
iszeropivot = pivot_value is None |
|
|
|
if iszeropivot: |
|
|
|
|
|
pivot_col += 1 |
|
|
|
if rankcheck and pivot_col != pivot_row: |
|
|
|
|
|
|
|
|
|
|
|
raise ValueError("Rank of matrix is strictly less than" |
|
" number of rows or columns." |
|
" Pass keyword argument" |
|
" rankcheck=False to compute" |
|
" the LU decomposition of this matrix.") |
|
|
|
candidate_pivot_row = None if pivot_row_offset is None else pivot_row + pivot_row_offset |
|
|
|
if candidate_pivot_row is None and iszeropivot: |
|
|
|
|
|
|
|
|
|
|
|
return lu, row_swaps |
|
|
|
|
|
for offset, val in ind_simplified_pairs: |
|
lu[pivot_row + offset, pivot_col] = val |
|
|
|
if pivot_row != candidate_pivot_row: |
|
|
|
|
|
|
|
|
|
|
|
row_swaps.append([pivot_row, candidate_pivot_row]) |
|
|
|
|
|
lu[pivot_row, 0:pivot_row], lu[candidate_pivot_row, 0:pivot_row] = \ |
|
lu[candidate_pivot_row, 0:pivot_row], lu[pivot_row, 0:pivot_row] |
|
|
|
|
|
lu[pivot_row, pivot_col:lu.cols], lu[candidate_pivot_row, pivot_col:lu.cols] = \ |
|
lu[candidate_pivot_row, pivot_col:lu.cols], lu[pivot_row, pivot_col:lu.cols] |
|
|
|
|
|
|
|
|
|
|
|
|
|
start_col = pivot_col + 1 |
|
|
|
for row in range(pivot_row + 1, lu.rows): |
|
|
|
|
|
lu[row, pivot_row] = \ |
|
dps(lu[row, pivot_col]/lu[pivot_row, pivot_col]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for c in range(start_col, lu.cols): |
|
lu[row, c] = dps(lu[row, c] - lu[row, pivot_row]*lu[pivot_row, c]) |
|
|
|
if pivot_row != pivot_col: |
|
|
|
|
|
|
|
|
|
for row in range(pivot_row + 1, lu.rows): |
|
lu[row, pivot_col] = M.zero |
|
|
|
pivot_col += 1 |
|
|
|
if pivot_col == lu.cols: |
|
|
|
|
|
return lu, row_swaps |
|
|
|
if rankcheck: |
|
if iszerofunc( |
|
lu[Min(lu.rows, lu.cols) - 1, Min(lu.rows, lu.cols) - 1]): |
|
raise ValueError("Rank of matrix is strictly less than" |
|
" number of rows or columns." |
|
" Pass keyword argument" |
|
" rankcheck=False to compute" |
|
" the LU decomposition of this matrix.") |
|
|
|
return lu, row_swaps |
|
|
|
def _LUdecompositionFF(M): |
|
"""Compute a fraction-free LU decomposition. |
|
|
|
Returns 4 matrices P, L, D, U such that PA = L D**-1 U. |
|
If the elements of the matrix belong to some integral domain I, then all |
|
elements of L, D and U are guaranteed to belong to I. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.matrixbase.MatrixBase.LUdecomposition |
|
LUdecomposition_Simple |
|
LUsolve |
|
|
|
References |
|
========== |
|
|
|
.. [1] W. Zhou & D.J. Jeffrey, "Fraction-free matrix factors: new forms |
|
for LU and QR factors". Frontiers in Computer Science in China, |
|
Vol 2, no. 1, pp. 67-80, 2008. |
|
""" |
|
|
|
from sympy.matrices import SparseMatrix |
|
|
|
zeros = SparseMatrix.zeros |
|
eye = SparseMatrix.eye |
|
n, m = M.rows, M.cols |
|
U, L, P = M.as_mutable(), eye(n), eye(n) |
|
DD = zeros(n, n) |
|
oldpivot = 1 |
|
|
|
for k in range(n - 1): |
|
if U[k, k] == 0: |
|
for kpivot in range(k + 1, n): |
|
if U[kpivot, k]: |
|
break |
|
else: |
|
raise ValueError("Matrix is not full rank") |
|
|
|
U[k, k:], U[kpivot, k:] = U[kpivot, k:], U[k, k:] |
|
L[k, :k], L[kpivot, :k] = L[kpivot, :k], L[k, :k] |
|
P[k, :], P[kpivot, :] = P[kpivot, :], P[k, :] |
|
|
|
L [k, k] = Ukk = U[k, k] |
|
DD[k, k] = oldpivot * Ukk |
|
|
|
for i in range(k + 1, n): |
|
L[i, k] = Uik = U[i, k] |
|
|
|
for j in range(k + 1, m): |
|
U[i, j] = (Ukk * U[i, j] - U[k, j] * Uik) / oldpivot |
|
|
|
U[i, k] = 0 |
|
|
|
oldpivot = Ukk |
|
|
|
DD[n - 1, n - 1] = oldpivot |
|
|
|
return P, L, DD, U |
|
|
|
def _singular_value_decomposition(A): |
|
r"""Returns a Condensed Singular Value decomposition. |
|
|
|
Explanation |
|
=========== |
|
|
|
A Singular Value decomposition is a decomposition in the form $A = U \Sigma V^H$ |
|
where |
|
|
|
- $U, V$ are column orthogonal matrix. |
|
- $\Sigma$ is a diagonal matrix, where the main diagonal contains singular |
|
values of matrix A. |
|
|
|
A column orthogonal matrix satisfies |
|
$\mathbb{I} = U^H U$ while a full orthogonal matrix satisfies |
|
relation $\mathbb{I} = U U^H = U^H U$ where $\mathbb{I}$ is an identity |
|
matrix with matching dimensions. |
|
|
|
For matrices which are not square or are rank-deficient, it is |
|
sufficient to return a column orthogonal matrix because augmenting |
|
them may introduce redundant computations. |
|
In condensed Singular Value Decomposition we only return column orthogonal |
|
matrices because of this reason |
|
|
|
If you want to augment the results to return a full orthogonal |
|
decomposition, you should use the following procedures. |
|
|
|
- Augment the $U, V$ matrices with columns that are orthogonal to every |
|
other columns and make it square. |
|
- Augment the $\Sigma$ matrix with zero rows to make it have the same |
|
shape as the original matrix. |
|
|
|
The procedure will be illustrated in the examples section. |
|
|
|
Examples |
|
======== |
|
|
|
we take a full rank matrix first: |
|
|
|
>>> from sympy import Matrix |
|
>>> A = Matrix([[1, 2],[2,1]]) |
|
>>> U, S, V = A.singular_value_decomposition() |
|
>>> U |
|
Matrix([ |
|
[ sqrt(2)/2, sqrt(2)/2], |
|
[-sqrt(2)/2, sqrt(2)/2]]) |
|
>>> S |
|
Matrix([ |
|
[1, 0], |
|
[0, 3]]) |
|
>>> V |
|
Matrix([ |
|
[-sqrt(2)/2, sqrt(2)/2], |
|
[ sqrt(2)/2, sqrt(2)/2]]) |
|
|
|
If a matrix if square and full rank both U, V |
|
are orthogonal in both directions |
|
|
|
>>> U * U.H |
|
Matrix([ |
|
[1, 0], |
|
[0, 1]]) |
|
>>> U.H * U |
|
Matrix([ |
|
[1, 0], |
|
[0, 1]]) |
|
|
|
>>> V * V.H |
|
Matrix([ |
|
[1, 0], |
|
[0, 1]]) |
|
>>> V.H * V |
|
Matrix([ |
|
[1, 0], |
|
[0, 1]]) |
|
>>> A == U * S * V.H |
|
True |
|
|
|
>>> C = Matrix([ |
|
... [1, 0, 0, 0, 2], |
|
... [0, 0, 3, 0, 0], |
|
... [0, 0, 0, 0, 0], |
|
... [0, 2, 0, 0, 0], |
|
... ]) |
|
>>> U, S, V = C.singular_value_decomposition() |
|
|
|
>>> V.H * V |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]]) |
|
>>> V * V.H |
|
Matrix([ |
|
[1/5, 0, 0, 0, 2/5], |
|
[ 0, 1, 0, 0, 0], |
|
[ 0, 0, 1, 0, 0], |
|
[ 0, 0, 0, 0, 0], |
|
[2/5, 0, 0, 0, 4/5]]) |
|
|
|
If you want to augment the results to be a full orthogonal |
|
decomposition, you should augment $V$ with an another orthogonal |
|
column. |
|
|
|
You are able to append an arbitrary standard basis that are linearly |
|
independent to every other columns and you can run the Gram-Schmidt |
|
process to make them augmented as orthogonal basis. |
|
|
|
>>> V_aug = V.row_join(Matrix([[0,0,0,0,1], |
|
... [0,0,0,1,0]]).H) |
|
>>> V_aug = V_aug.QRdecomposition()[0] |
|
>>> V_aug |
|
Matrix([ |
|
[0, sqrt(5)/5, 0, -2*sqrt(5)/5, 0], |
|
[1, 0, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 0, 1], |
|
[0, 2*sqrt(5)/5, 0, sqrt(5)/5, 0]]) |
|
>>> V_aug.H * V_aug |
|
Matrix([ |
|
[1, 0, 0, 0, 0], |
|
[0, 1, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 1, 0], |
|
[0, 0, 0, 0, 1]]) |
|
>>> V_aug * V_aug.H |
|
Matrix([ |
|
[1, 0, 0, 0, 0], |
|
[0, 1, 0, 0, 0], |
|
[0, 0, 1, 0, 0], |
|
[0, 0, 0, 1, 0], |
|
[0, 0, 0, 0, 1]]) |
|
|
|
Similarly we augment U |
|
|
|
>>> U_aug = U.row_join(Matrix([0,0,1,0])) |
|
>>> U_aug = U_aug.QRdecomposition()[0] |
|
>>> U_aug |
|
Matrix([ |
|
[0, 1, 0, 0], |
|
[0, 0, 1, 0], |
|
[0, 0, 0, 1], |
|
[1, 0, 0, 0]]) |
|
|
|
>>> U_aug.H * U_aug |
|
Matrix([ |
|
[1, 0, 0, 0], |
|
[0, 1, 0, 0], |
|
[0, 0, 1, 0], |
|
[0, 0, 0, 1]]) |
|
>>> U_aug * U_aug.H |
|
Matrix([ |
|
[1, 0, 0, 0], |
|
[0, 1, 0, 0], |
|
[0, 0, 1, 0], |
|
[0, 0, 0, 1]]) |
|
|
|
We add 2 zero columns and one row to S |
|
|
|
>>> S_aug = S.col_join(Matrix([[0,0,0]])) |
|
>>> S_aug = S_aug.row_join(Matrix([[0,0,0,0], |
|
... [0,0,0,0]]).H) |
|
>>> S_aug |
|
Matrix([ |
|
[2, 0, 0, 0, 0], |
|
[0, sqrt(5), 0, 0, 0], |
|
[0, 0, 3, 0, 0], |
|
[0, 0, 0, 0, 0]]) |
|
|
|
|
|
|
|
>>> U_aug * S_aug * V_aug.H == C |
|
True |
|
|
|
""" |
|
|
|
AH = A.H |
|
m, n = A.shape |
|
if m >= n: |
|
V, S = (AH * A).diagonalize() |
|
|
|
ranked = [] |
|
for i, x in enumerate(S.diagonal()): |
|
if not x.is_zero: |
|
ranked.append(i) |
|
|
|
V = V[:, ranked] |
|
|
|
Singular_vals = [sqrt(S[i, i]) for i in range(S.rows) if i in ranked] |
|
|
|
S = S.diag(*Singular_vals) |
|
V, _ = V.QRdecomposition() |
|
U = A * V * S.inv() |
|
else: |
|
U, S = (A * AH).diagonalize() |
|
|
|
ranked = [] |
|
for i, x in enumerate(S.diagonal()): |
|
if not x.is_zero: |
|
ranked.append(i) |
|
|
|
U = U[:, ranked] |
|
Singular_vals = [sqrt(S[i, i]) for i in range(S.rows) if i in ranked] |
|
|
|
S = S.diag(*Singular_vals) |
|
U, _ = U.QRdecomposition() |
|
V = AH * U * S.inv() |
|
|
|
return U, S, V |
|
|
|
def _QRdecomposition_optional(M, normalize=True): |
|
def dot(u, v): |
|
return u.dot(v, hermitian=True) |
|
|
|
dps = _get_intermediate_simp(expand_mul, expand_mul) |
|
|
|
A = M.as_mutable() |
|
ranked = [] |
|
|
|
Q = A |
|
R = A.zeros(A.cols) |
|
|
|
for j in range(A.cols): |
|
for i in range(j): |
|
if Q[:, i].is_zero_matrix: |
|
continue |
|
|
|
R[i, j] = dot(Q[:, i], Q[:, j]) / dot(Q[:, i], Q[:, i]) |
|
R[i, j] = dps(R[i, j]) |
|
Q[:, j] -= Q[:, i] * R[i, j] |
|
|
|
Q[:, j] = dps(Q[:, j]) |
|
if Q[:, j].is_zero_matrix is not True: |
|
ranked.append(j) |
|
R[j, j] = M.one |
|
|
|
Q = Q.extract(range(Q.rows), ranked) |
|
R = R.extract(ranked, range(R.cols)) |
|
|
|
if normalize: |
|
|
|
for i in range(Q.cols): |
|
norm = Q[:, i].norm() |
|
Q[:, i] /= norm |
|
R[i, :] *= norm |
|
|
|
return M.__class__(Q), M.__class__(R) |
|
|
|
|
|
def _QRdecomposition(M): |
|
r"""Returns a QR decomposition. |
|
|
|
Explanation |
|
=========== |
|
|
|
A QR decomposition is a decomposition in the form $A = Q R$ |
|
where |
|
|
|
- $Q$ is a column orthogonal matrix. |
|
- $R$ is a upper triangular (trapezoidal) matrix. |
|
|
|
A column orthogonal matrix satisfies |
|
$\mathbb{I} = Q^H Q$ while a full orthogonal matrix satisfies |
|
relation $\mathbb{I} = Q Q^H = Q^H Q$ where $I$ is an identity |
|
matrix with matching dimensions. |
|
|
|
For matrices which are not square or are rank-deficient, it is |
|
sufficient to return a column orthogonal matrix because augmenting |
|
them may introduce redundant computations. |
|
And an another advantage of this is that you can easily inspect the |
|
matrix rank by counting the number of columns of $Q$. |
|
|
|
If you want to augment the results to return a full orthogonal |
|
decomposition, you should use the following procedures. |
|
|
|
- Augment the $Q$ matrix with columns that are orthogonal to every |
|
other columns and make it square. |
|
- Augment the $R$ matrix with zero rows to make it have the same |
|
shape as the original matrix. |
|
|
|
The procedure will be illustrated in the examples section. |
|
|
|
Examples |
|
======== |
|
|
|
A full rank matrix example: |
|
|
|
>>> from sympy import Matrix |
|
>>> A = Matrix([[12, -51, 4], [6, 167, -68], [-4, 24, -41]]) |
|
>>> Q, R = A.QRdecomposition() |
|
>>> Q |
|
Matrix([ |
|
[ 6/7, -69/175, -58/175], |
|
[ 3/7, 158/175, 6/175], |
|
[-2/7, 6/35, -33/35]]) |
|
>>> R |
|
Matrix([ |
|
[14, 21, -14], |
|
[ 0, 175, -70], |
|
[ 0, 0, 35]]) |
|
|
|
If the matrix is square and full rank, the $Q$ matrix becomes |
|
orthogonal in both directions, and needs no augmentation. |
|
|
|
>>> Q * Q.H |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]]) |
|
>>> Q.H * Q |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]]) |
|
|
|
>>> A == Q*R |
|
True |
|
|
|
A rank deficient matrix example: |
|
|
|
>>> A = Matrix([[12, -51, 0], [6, 167, 0], [-4, 24, 0]]) |
|
>>> Q, R = A.QRdecomposition() |
|
>>> Q |
|
Matrix([ |
|
[ 6/7, -69/175], |
|
[ 3/7, 158/175], |
|
[-2/7, 6/35]]) |
|
>>> R |
|
Matrix([ |
|
[14, 21, 0], |
|
[ 0, 175, 0]]) |
|
|
|
QRdecomposition might return a matrix Q that is rectangular. |
|
In this case the orthogonality condition might be satisfied as |
|
$\mathbb{I} = Q.H*Q$ but not in the reversed product |
|
$\mathbb{I} = Q * Q.H$. |
|
|
|
>>> Q.H * Q |
|
Matrix([ |
|
[1, 0], |
|
[0, 1]]) |
|
>>> Q * Q.H |
|
Matrix([ |
|
[27261/30625, 348/30625, -1914/6125], |
|
[ 348/30625, 30589/30625, 198/6125], |
|
[ -1914/6125, 198/6125, 136/1225]]) |
|
|
|
If you want to augment the results to be a full orthogonal |
|
decomposition, you should augment $Q$ with an another orthogonal |
|
column. |
|
|
|
You are able to append an identity matrix, |
|
and you can run the Gram-Schmidt |
|
process to make them augmented as orthogonal basis. |
|
|
|
>>> Q_aug = Q.row_join(Matrix.eye(3)) |
|
>>> Q_aug = Q_aug.QRdecomposition()[0] |
|
>>> Q_aug |
|
Matrix([ |
|
[ 6/7, -69/175, 58/175], |
|
[ 3/7, 158/175, -6/175], |
|
[-2/7, 6/35, 33/35]]) |
|
>>> Q_aug.H * Q_aug |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]]) |
|
>>> Q_aug * Q_aug.H |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]]) |
|
|
|
Augmenting the $R$ matrix with zero row is straightforward. |
|
|
|
>>> R_aug = R.col_join(Matrix([[0, 0, 0]])) |
|
>>> R_aug |
|
Matrix([ |
|
[14, 21, 0], |
|
[ 0, 175, 0], |
|
[ 0, 0, 0]]) |
|
>>> Q_aug * R_aug == A |
|
True |
|
|
|
A zero matrix example: |
|
|
|
>>> from sympy import Matrix |
|
>>> A = Matrix.zeros(3, 4) |
|
>>> Q, R = A.QRdecomposition() |
|
|
|
They may return matrices with zero rows and columns. |
|
|
|
>>> Q |
|
Matrix(3, 0, []) |
|
>>> R |
|
Matrix(0, 4, []) |
|
>>> Q*R |
|
Matrix([ |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0]]) |
|
|
|
As the same augmentation rule described above, $Q$ can be augmented |
|
with columns of an identity matrix and $R$ can be augmented with |
|
rows of a zero matrix. |
|
|
|
>>> Q_aug = Q.row_join(Matrix.eye(3)) |
|
>>> R_aug = R.col_join(Matrix.zeros(3, 4)) |
|
>>> Q_aug * Q_aug.T |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, 1, 0], |
|
[0, 0, 1]]) |
|
>>> R_aug |
|
Matrix([ |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0], |
|
[0, 0, 0, 0]]) |
|
>>> Q_aug * R_aug == A |
|
True |
|
|
|
See Also |
|
======== |
|
|
|
sympy.matrices.dense.DenseMatrix.cholesky |
|
sympy.matrices.dense.DenseMatrix.LDLdecomposition |
|
sympy.matrices.matrixbase.MatrixBase.LUdecomposition |
|
QRsolve |
|
""" |
|
return _QRdecomposition_optional(M, normalize=True) |
|
|
|
def _upper_hessenberg_decomposition(A): |
|
"""Converts a matrix into Hessenberg matrix H. |
|
|
|
Returns 2 matrices H, P s.t. |
|
$P H P^{T} = A$, where H is an upper hessenberg matrix |
|
and P is an orthogonal matrix |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import Matrix |
|
>>> A = Matrix([ |
|
... [1,2,3], |
|
... [-3,5,6], |
|
... [4,-8,9], |
|
... ]) |
|
>>> H, P = A.upper_hessenberg_decomposition() |
|
>>> H |
|
Matrix([ |
|
[1, 6/5, 17/5], |
|
[5, 213/25, -134/25], |
|
[0, 216/25, 137/25]]) |
|
>>> P |
|
Matrix([ |
|
[1, 0, 0], |
|
[0, -3/5, 4/5], |
|
[0, 4/5, 3/5]]) |
|
>>> P * H * P.H == A |
|
True |
|
|
|
|
|
References |
|
========== |
|
|
|
.. [#] https://mathworld.wolfram.com/HessenbergDecomposition.html |
|
""" |
|
|
|
M = A.as_mutable() |
|
|
|
if not M.is_square: |
|
raise NonSquareMatrixError("Matrix must be square.") |
|
|
|
n = M.cols |
|
P = M.eye(n) |
|
H = M |
|
|
|
for j in range(n - 2): |
|
|
|
u = H[j + 1:, j] |
|
|
|
if u[1:, :].is_zero_matrix: |
|
continue |
|
|
|
if sign(u[0]) != 0: |
|
u[0] = u[0] + sign(u[0]) * u.norm() |
|
else: |
|
u[0] = u[0] + u.norm() |
|
|
|
v = u / u.norm() |
|
|
|
H[j + 1:, :] = H[j + 1:, :] - 2 * v * (v.H * H[j + 1:, :]) |
|
H[:, j + 1:] = H[:, j + 1:] - (H[:, j + 1:] * (2 * v)) * v.H |
|
P[:, j + 1:] = P[:, j + 1:] - (P[:, j + 1:] * (2 * v)) * v.H |
|
|
|
return H, P |
|
|