123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629 |
- 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 .common 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.matrices.MatrixReductions.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)
- http://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)
- http://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] = list(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.matrices.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.matrices.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.matrices.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 compresed 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.matrices.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.matrices.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$
- 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 orthognal
- 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.
- - Augument 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.zeros(len(Singular_vals))
- for i in range(len(Singular_vals)):
- S[i, i] = Singular_vals[i]
- 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.zeros(len(Singular_vals))
- for i in range(len(Singular_vals)):
- S[i, i] = Singular_vals[i]
- 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 = list()
- 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 False:
- 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.
- - Augument 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 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.
- >>> Q_aug = Q.row_join(Matrix([0, 0, 1]))
- >>> 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.matrices.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
|