123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- from sympy.core.numbers import mod_inverse
- from .common import MatrixError, NonSquareMatrixError, NonInvertibleMatrixError
- from .utilities import _iszero
- def _pinv_full_rank(M):
- """Subroutine for full row or column rank matrices.
- For full row rank matrices, inverse of ``A * A.H`` Exists.
- For full column rank matrices, inverse of ``A.H * A`` Exists.
- This routine can apply for both cases by checking the shape
- and have small decision.
- """
- if M.is_zero_matrix:
- return M.H
- if M.rows >= M.cols:
- return M.H.multiply(M).inv().multiply(M.H)
- else:
- return M.H.multiply(M.multiply(M.H).inv())
- def _pinv_rank_decomposition(M):
- """Subroutine for rank decomposition
- With rank decompositions, `A` can be decomposed into two full-
- rank matrices, and each matrix can take pseudoinverse
- individually.
- """
- if M.is_zero_matrix:
- return M.H
- B, C = M.rank_decomposition()
- Bp = _pinv_full_rank(B)
- Cp = _pinv_full_rank(C)
- return Cp.multiply(Bp)
- def _pinv_diagonalization(M):
- """Subroutine using diagonalization
- This routine can sometimes fail if SymPy's eigenvalue
- computation is not reliable.
- """
- if M.is_zero_matrix:
- return M.H
- A = M
- AH = M.H
- try:
- if M.rows >= M.cols:
- P, D = AH.multiply(A).diagonalize(normalize=True)
- D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x)
- return P.multiply(D_pinv).multiply(P.H).multiply(AH)
- else:
- P, D = A.multiply(AH).diagonalize(
- normalize=True)
- D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x)
- return AH.multiply(P).multiply(D_pinv).multiply(P.H)
- except MatrixError:
- raise NotImplementedError(
- 'pinv for rank-deficient matrices where '
- 'diagonalization of A.H*A fails is not supported yet.')
- def _pinv(M, method='RD'):
- """Calculate the Moore-Penrose pseudoinverse of the matrix.
- The Moore-Penrose pseudoinverse exists and is unique for any matrix.
- If the matrix is invertible, the pseudoinverse is the same as the
- inverse.
- Parameters
- ==========
- method : String, optional
- Specifies the method for computing the pseudoinverse.
- If ``'RD'``, Rank-Decomposition will be used.
- If ``'ED'``, Diagonalization will be used.
- Examples
- ========
- Computing pseudoinverse by rank decomposition :
- >>> from sympy import Matrix
- >>> A = Matrix([[1, 2, 3], [4, 5, 6]])
- >>> A.pinv()
- Matrix([
- [-17/18, 4/9],
- [ -1/9, 1/9],
- [ 13/18, -2/9]])
- Computing pseudoinverse by diagonalization :
- >>> B = A.pinv(method='ED')
- >>> B.simplify()
- >>> B
- Matrix([
- [-17/18, 4/9],
- [ -1/9, 1/9],
- [ 13/18, -2/9]])
- See Also
- ========
- inv
- pinv_solve
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse
- """
- # Trivial case: pseudoinverse of all-zero matrix is its transpose.
- if M.is_zero_matrix:
- return M.H
- if method == 'RD':
- return _pinv_rank_decomposition(M)
- elif method == 'ED':
- return _pinv_diagonalization(M)
- else:
- raise ValueError('invalid pinv method %s' % repr(method))
- def _inv_mod(M, m):
- r"""
- Returns the inverse of the matrix `K` (mod `m`), if it exists.
- Method to find the matrix inverse of `K` (mod `m`) implemented in this function:
- * Compute `\mathrm{adj}(K) = \mathrm{cof}(K)^t`, the adjoint matrix of `K`.
- * Compute `r = 1/\mathrm{det}(K) \pmod m`.
- * `K^{-1} = r\cdot \mathrm{adj}(K) \pmod m`.
- Examples
- ========
- >>> from sympy import Matrix
- >>> A = Matrix(2, 2, [1, 2, 3, 4])
- >>> A.inv_mod(5)
- Matrix([
- [3, 1],
- [4, 2]])
- >>> A.inv_mod(3)
- Matrix([
- [1, 1],
- [0, 1]])
- """
- if not M.is_square:
- raise NonSquareMatrixError()
- N = M.cols
- det_K = M.det()
- det_inv = None
- try:
- det_inv = mod_inverse(det_K, m)
- except ValueError:
- raise NonInvertibleMatrixError('Matrix is not invertible (mod %d)' % m)
- K_adj = M.adjugate()
- K_inv = M.__class__(N, N,
- [det_inv * K_adj[i, j] % m for i in range(N) for j in range(N)])
- return K_inv
- def _verify_invertible(M, iszerofunc=_iszero):
- """Initial check to see if a matrix is invertible. Raises or returns
- determinant for use in _inv_ADJ."""
- if not M.is_square:
- raise NonSquareMatrixError("A Matrix must be square to invert.")
- d = M.det(method='berkowitz')
- zero = d.equals(0)
- if zero is None: # if equals() can't decide, will rref be able to?
- ok = M.rref(simplify=True)[0]
- zero = any(iszerofunc(ok[j, j]) for j in range(ok.rows))
- if zero:
- raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
- return d
- def _inv_ADJ(M, iszerofunc=_iszero):
- """Calculates the inverse using the adjugate matrix and a determinant.
- See Also
- ========
- inv
- inverse_GE
- inverse_LU
- inverse_CH
- inverse_LDL
- """
- d = _verify_invertible(M, iszerofunc=iszerofunc)
- return M.adjugate() / d
- def _inv_GE(M, iszerofunc=_iszero):
- """Calculates the inverse using Gaussian elimination.
- See Also
- ========
- inv
- inverse_ADJ
- inverse_LU
- inverse_CH
- inverse_LDL
- """
- from .dense import Matrix
- if not M.is_square:
- raise NonSquareMatrixError("A Matrix must be square to invert.")
- big = Matrix.hstack(M.as_mutable(), Matrix.eye(M.rows))
- red = big.rref(iszerofunc=iszerofunc, simplify=True)[0]
- if any(iszerofunc(red[j, j]) for j in range(red.rows)):
- raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
- return M._new(red[:, big.rows:])
- def _inv_LU(M, iszerofunc=_iszero):
- """Calculates the inverse using LU decomposition.
- See Also
- ========
- inv
- inverse_ADJ
- inverse_GE
- inverse_CH
- inverse_LDL
- """
- if not M.is_square:
- raise NonSquareMatrixError("A Matrix must be square to invert.")
- if M.free_symbols:
- _verify_invertible(M, iszerofunc=iszerofunc)
- return M.LUsolve(M.eye(M.rows), iszerofunc=_iszero)
- def _inv_CH(M, iszerofunc=_iszero):
- """Calculates the inverse using cholesky decomposition.
- See Also
- ========
- inv
- inverse_ADJ
- inverse_GE
- inverse_LU
- inverse_LDL
- """
- _verify_invertible(M, iszerofunc=iszerofunc)
- return M.cholesky_solve(M.eye(M.rows))
- def _inv_LDL(M, iszerofunc=_iszero):
- """Calculates the inverse using LDL decomposition.
- See Also
- ========
- inv
- inverse_ADJ
- inverse_GE
- inverse_LU
- inverse_CH
- """
- _verify_invertible(M, iszerofunc=iszerofunc)
- return M.LDLsolve(M.eye(M.rows))
- def _inv_QR(M, iszerofunc=_iszero):
- """Calculates the inverse using QR decomposition.
- See Also
- ========
- inv
- inverse_ADJ
- inverse_GE
- inverse_CH
- inverse_LDL
- """
- _verify_invertible(M, iszerofunc=iszerofunc)
- return M.QRsolve(M.eye(M.rows))
- def _inv_block(M, iszerofunc=_iszero):
- """Calculates the inverse using BLOCKWISE inversion.
- See Also
- ========
- inv
- inverse_ADJ
- inverse_GE
- inverse_CH
- inverse_LDL
- """
- from sympy.matrices.expressions.blockmatrix import BlockMatrix
- i = M.shape[0]
- if i <= 20 :
- return M.inv(method="LU", iszerofunc=_iszero)
- A = M[:i // 2, :i //2]
- B = M[:i // 2, i // 2:]
- C = M[i // 2:, :i // 2]
- D = M[i // 2:, i // 2:]
- try:
- D_inv = _inv_block(D)
- except NonInvertibleMatrixError:
- return M.inv(method="LU", iszerofunc=_iszero)
- B_D_i = B*D_inv
- BDC = B_D_i*C
- A_n = A - BDC
- try:
- A_n = _inv_block(A_n)
- except NonInvertibleMatrixError:
- return M.inv(method="LU", iszerofunc=_iszero)
- B_n = -A_n*B_D_i
- dc = D_inv*C
- C_n = -dc*A_n
- D_n = D_inv + dc*-B_n
- nn = BlockMatrix([[A_n, B_n], [C_n, D_n]]).as_explicit()
- return nn
- def _inv(M, method=None, iszerofunc=_iszero, try_block_diag=False):
- """
- Return the inverse of a matrix using the method indicated. Default for
- dense matrices is is Gauss elimination, default for sparse matrices is LDL.
- Parameters
- ==========
- method : ('GE', 'LU', 'ADJ', 'CH', 'LDL')
- iszerofunc : function, optional
- Zero-testing function to use.
- try_block_diag : bool, optional
- If True then will try to form block diagonal matrices using the
- method get_diag_blocks(), invert these individually, and then
- reconstruct the full inverse matrix.
- Examples
- ========
- >>> from sympy import SparseMatrix, Matrix
- >>> A = SparseMatrix([
- ... [ 2, -1, 0],
- ... [-1, 2, -1],
- ... [ 0, 0, 2]])
- >>> A.inv('CH')
- Matrix([
- [2/3, 1/3, 1/6],
- [1/3, 2/3, 1/3],
- [ 0, 0, 1/2]])
- >>> A.inv(method='LDL') # use of 'method=' is optional
- Matrix([
- [2/3, 1/3, 1/6],
- [1/3, 2/3, 1/3],
- [ 0, 0, 1/2]])
- >>> A * _
- Matrix([
- [1, 0, 0],
- [0, 1, 0],
- [0, 0, 1]])
- >>> A = Matrix(A)
- >>> A.inv('CH')
- Matrix([
- [2/3, 1/3, 1/6],
- [1/3, 2/3, 1/3],
- [ 0, 0, 1/2]])
- >>> A.inv('ADJ') == A.inv('GE') == A.inv('LU') == A.inv('CH') == A.inv('LDL') == A.inv('QR')
- True
- Notes
- =====
- According to the ``method`` keyword, it calls the appropriate method:
- GE .... inverse_GE(); default for dense matrices
- LU .... inverse_LU()
- ADJ ... inverse_ADJ()
- CH ... inverse_CH()
- LDL ... inverse_LDL(); default for sparse matrices
- QR ... inverse_QR()
- Note, the GE and LU methods may require the matrix to be simplified
- before it is inverted in order to properly detect zeros during
- pivoting. In difficult cases a custom zero detection function can
- be provided by setting the ``iszerofunc`` argument to a function that
- should return True if its argument is zero. The ADJ routine computes
- the determinant and uses that to detect singular matrices in addition
- to testing for zeros on the diagonal.
- See Also
- ========
- inverse_ADJ
- inverse_GE
- inverse_LU
- inverse_CH
- inverse_LDL
- Raises
- ======
- ValueError
- If the determinant of the matrix is zero.
- """
- from sympy.matrices import diag, SparseMatrix
- if method is None:
- method = 'LDL' if isinstance(M, SparseMatrix) else 'GE'
- if try_block_diag:
- blocks = M.get_diag_blocks()
- r = []
- for block in blocks:
- r.append(block.inv(method=method, iszerofunc=iszerofunc))
- return diag(*r)
- if method == "GE":
- rv = M.inverse_GE(iszerofunc=iszerofunc)
- elif method == "LU":
- rv = M.inverse_LU(iszerofunc=iszerofunc)
- elif method == "ADJ":
- rv = M.inverse_ADJ(iszerofunc=iszerofunc)
- elif method == "CH":
- rv = M.inverse_CH(iszerofunc=iszerofunc)
- elif method == "LDL":
- rv = M.inverse_LDL(iszerofunc=iszerofunc)
- elif method == "QR":
- rv = M.inverse_QR(iszerofunc=iszerofunc)
- elif method == "BLOCK":
- rv = M.inverse_BLOCK(iszerofunc=iszerofunc)
- else:
- raise ValueError("Inversion method unrecognized")
- return M._new(rv)
|