Spaces:
Sleeping
Sleeping
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 | |
""" | |
# Algorithm 2.4, p 17 of reference | |
# get the indices of the elements that are non-zero on or below diag | |
R = [[] for r in range(M.rows)] | |
for r, c, _ in M.row_list(): | |
if c <= r: | |
R[r].append(c) | |
inf = len(R) # nothing will be this large | |
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) # this acts as infinity | |
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: # i == j | |
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: # i == j | |
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) | |
# L is lower triangular ``M.rows x M.rows`` | |
# U is upper triangular ``M.rows x M.cols`` | |
# L has unit diagonal. For each column in combined, the subcolumn | |
# below the diagonal of combined is shared by L. | |
# If L has more columns than combined, then the remaining subcolumns | |
# below the diagonal of L are zero. | |
# The upper triangular portion of L and combined are equal. | |
def entry_L(i, j): | |
if i < j: | |
# Super diagonal entry | |
return M.zero | |
elif i == j: | |
return M.one | |
elif j < combined.cols: | |
return combined[i, j] | |
# Subdiagonal entry of L with no corresponding | |
# entry in combined | |
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: | |
# https://github.com/sympy/sympy/issues/9796 | |
pass | |
if S.Zero in M.shape: | |
# Define LU decomposition of a matrix with no entries as a matrix | |
# of the same dimensions with all zero entries. | |
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): | |
# Search for pivot. Prefer entry that iszeropivot determines | |
# is nonzero, over entry that iszeropivot cannot guarantee | |
# is zero. | |
# XXX ``_find_reasonable_pivot`` uses slow zero testing. Blocked by bug #10279 | |
# Future versions of LUdecomposition_simple can pass iszerofunc and simpfunc | |
# to _find_reasonable_pivot(). | |
# In pass 3 of _find_reasonable_pivot(), the predicate in ``if x.equals(S.Zero):`` | |
# calls sympy.simplify(), and not the simplification function passed in via | |
# the keyword argument simpfunc. | |
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: | |
# All candidate pivots in this column are zero. | |
# Proceed to next column. | |
pivot_col += 1 | |
if rankcheck and pivot_col != pivot_row: | |
# All entries including and below the pivot position are | |
# zero, which indicates that the rank of the matrix is | |
# strictly less than min(num rows, num cols) | |
# Mimic behavior of previous implementation, by throwing a | |
# ValueError. | |
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: | |
# If candidate_pivot_row is None and iszeropivot is True | |
# after pivot search has completed, then the submatrix | |
# below and to the right of (pivot_row, pivot_col) is | |
# all zeros, indicating that Gaussian elimination is | |
# complete. | |
return lu, row_swaps | |
# Update entries simplified during pivot search. | |
for offset, val in ind_simplified_pairs: | |
lu[pivot_row + offset, pivot_col] = val | |
if pivot_row != candidate_pivot_row: | |
# Row swap book keeping: | |
# Record which rows were swapped. | |
# Update stored portion of L factor by multiplying L on the | |
# left and right with the current permutation. | |
# Swap rows of U. | |
row_swaps.append([pivot_row, candidate_pivot_row]) | |
# Update L. | |
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] | |
# Swap pivot row of U with candidate 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] | |
# Introduce zeros below the pivot by adding a multiple of the | |
# pivot row to a row under it, and store the result in the | |
# row under it. | |
# Only entries in the target row whose index is greater than | |
# start_col may be nonzero. | |
start_col = pivot_col + 1 | |
for row in range(pivot_row + 1, lu.rows): | |
# Store factors of L in the subcolumn below | |
# (pivot_row, pivot_row). | |
lu[row, pivot_row] = \ | |
dps(lu[row, pivot_col]/lu[pivot_row, pivot_col]) | |
# Form the linear combination of the pivot row and the current | |
# row below the pivot row that zeros the entries below the pivot. | |
# Employing slicing instead of a loop here raises | |
# NotImplementedError: Cannot add Zero to MutableSparseMatrix | |
# in sympy/matrices/tests/test_sparse.py. | |
# c = pivot_row + 1 if pivot_row == pivot_col else 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: | |
# matrix rank < min(num rows, num cols), | |
# so factors of L are not stored directly below the pivot. | |
# These entries are zero by construction, so don't bother | |
# computing them. | |
for row in range(pivot_row + 1, lu.rows): | |
lu[row, pivot_col] = M.zero | |
pivot_col += 1 | |
if pivot_col == lu.cols: | |
# All candidate pivots are zero implies that Gaussian | |
# elimination is complete. | |
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: | |
# Normalization | |
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 | |