Kano001's picture
Upload 3077 files
6a86ad5 verified
raw
history blame
13.2 kB
from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError
from sympy.polys.domains import EX
from .exceptions 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 _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 _try_DM(M, use_EX=False):
"""Try to convert a matrix to a ``DomainMatrix``."""
dM = M.to_DM()
K = dM.domain
# Return DomainMatrix if a domain is found. Only use EX if use_EX=True.
if not use_EX and K.is_EXRAW:
return None
elif K.is_EXRAW:
return dM.convert_to(EX)
else:
return dM
def _use_exact_domain(dom):
"""Check whether to convert to an exact domain."""
# DomainMatrix can handle RR and CC with partial pivoting. Other inexact
# domains like RR[a,b,...] can only be handled by converting to an exact
# domain like QQ[a,b,...]
if dom.is_RR or dom.is_CC:
return False
else:
return not dom.is_Exact
def _inv_DM(dM, cancel=True):
"""Calculates the inverse using ``DomainMatrix``.
See Also
========
inv
inverse_ADJ
inverse_GE
inverse_CH
inverse_LDL
sympy.polys.matrices.domainmatrix.DomainMatrix.inv
"""
m, n = dM.shape
dom = dM.domain
if m != n:
raise NonSquareMatrixError("A Matrix must be square to invert.")
# Convert RR[a,b,...] to QQ[a,b,...]
use_exact = _use_exact_domain(dom)
if use_exact:
dom_exact = dom.get_exact()
dM = dM.convert_to(dom_exact)
try:
dMi, den = dM.inv_den()
except DMNonInvertibleMatrixError:
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
if use_exact:
dMi = dMi.convert_to(dom)
den = dom.convert_from(den, dom_exact)
if cancel:
# Convert to field and cancel with the denominator.
if not dMi.domain.is_Field:
dMi = dMi.to_field()
Mi = (dMi / den).to_Matrix()
else:
# Convert to Matrix and divide without cancelling
Mi = dMi.to_Matrix() / dMi.domain.to_sympy(den)
return Mi
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. The default
is DM if a suitable domain is found or otherwise GE for dense matrices
LDL for sparse matrices.
Parameters
==========
method : ('DM', 'DMNC', 'GE', 'LU', 'ADJ', 'CH', 'LDL', 'QR')
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:
DM .... Use DomainMatrix ``inv_den`` method
DMNC .... Use DomainMatrix ``inv_den`` method without cancellation
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 not M.is_square:
raise NonSquareMatrixError("A Matrix must be square to invert.")
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)
# Default: Use DomainMatrix if the domain is not EX.
# If DM is requested explicitly then use it even if the domain is EX.
if method is None and iszerofunc is _iszero:
dM = _try_DM(M, use_EX=False)
if dM is not None:
method = 'DM'
elif method in ("DM", "DMNC"):
dM = _try_DM(M, use_EX=True)
# A suitable domain was not found, fall back to GE for dense matrices
# and LDL for sparse matrices.
if method is None:
if isinstance(M, SparseMatrix):
method = 'LDL'
else:
method = 'GE'
if method == "DM":
rv = _inv_DM(dM)
elif method == "DMNC":
rv = _inv_DM(dM, cancel=False)
elif 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)