300 lines
7.3 KiB
Python
300 lines
7.3 KiB
Python
from sympy.assumptions.ask import ask, Q
|
|
from sympy.core.relational import Eq
|
|
from sympy.core.singleton import S
|
|
from sympy.core.sympify import _sympify
|
|
from sympy.functions.special.tensor_functions import KroneckerDelta
|
|
from sympy.matrices.common import NonInvertibleMatrixError
|
|
from .matexpr import MatrixExpr
|
|
|
|
|
|
class ZeroMatrix(MatrixExpr):
|
|
"""The Matrix Zero 0 - additive identity
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import MatrixSymbol, ZeroMatrix
|
|
>>> A = MatrixSymbol('A', 3, 5)
|
|
>>> Z = ZeroMatrix(3, 5)
|
|
>>> A + Z
|
|
A
|
|
>>> Z*A.T
|
|
0
|
|
"""
|
|
is_ZeroMatrix = True
|
|
|
|
def __new__(cls, m, n):
|
|
m, n = _sympify(m), _sympify(n)
|
|
cls._check_dim(m)
|
|
cls._check_dim(n)
|
|
|
|
return super().__new__(cls, m, n)
|
|
|
|
@property
|
|
def shape(self):
|
|
return (self.args[0], self.args[1])
|
|
|
|
def _eval_power(self, exp):
|
|
# exp = -1, 0, 1 are already handled at this stage
|
|
if (exp < 0) == True:
|
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible")
|
|
return self
|
|
|
|
def _eval_transpose(self):
|
|
return ZeroMatrix(self.cols, self.rows)
|
|
|
|
def _eval_adjoint(self):
|
|
return ZeroMatrix(self.cols, self.rows)
|
|
|
|
def _eval_trace(self):
|
|
return S.Zero
|
|
|
|
def _eval_determinant(self):
|
|
return S.Zero
|
|
|
|
def _eval_inverse(self):
|
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
|
|
|
def _eval_as_real_imag(self):
|
|
return (self, self)
|
|
|
|
def _eval_conjugate(self):
|
|
return self
|
|
|
|
def _entry(self, i, j, **kwargs):
|
|
return S.Zero
|
|
|
|
|
|
class GenericZeroMatrix(ZeroMatrix):
|
|
"""
|
|
A zero matrix without a specified shape
|
|
|
|
This exists primarily so MatAdd() with no arguments can return something
|
|
meaningful.
|
|
"""
|
|
def __new__(cls):
|
|
# super(ZeroMatrix, cls) instead of super(GenericZeroMatrix, cls)
|
|
# because ZeroMatrix.__new__ doesn't have the same signature
|
|
return super(ZeroMatrix, cls).__new__(cls)
|
|
|
|
@property
|
|
def rows(self):
|
|
raise TypeError("GenericZeroMatrix does not have a specified shape")
|
|
|
|
@property
|
|
def cols(self):
|
|
raise TypeError("GenericZeroMatrix does not have a specified shape")
|
|
|
|
@property
|
|
def shape(self):
|
|
raise TypeError("GenericZeroMatrix does not have a specified shape")
|
|
|
|
# Avoid Matrix.__eq__ which might call .shape
|
|
def __eq__(self, other):
|
|
return isinstance(other, GenericZeroMatrix)
|
|
|
|
def __ne__(self, other):
|
|
return not (self == other)
|
|
|
|
def __hash__(self):
|
|
return super().__hash__()
|
|
|
|
|
|
|
|
class Identity(MatrixExpr):
|
|
"""The Matrix Identity I - multiplicative identity
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy import Identity, MatrixSymbol
|
|
>>> A = MatrixSymbol('A', 3, 5)
|
|
>>> I = Identity(3)
|
|
>>> I*A
|
|
A
|
|
"""
|
|
|
|
is_Identity = True
|
|
|
|
def __new__(cls, n):
|
|
n = _sympify(n)
|
|
cls._check_dim(n)
|
|
|
|
return super().__new__(cls, n)
|
|
|
|
@property
|
|
def rows(self):
|
|
return self.args[0]
|
|
|
|
@property
|
|
def cols(self):
|
|
return self.args[0]
|
|
|
|
@property
|
|
def shape(self):
|
|
return (self.args[0], self.args[0])
|
|
|
|
@property
|
|
def is_square(self):
|
|
return True
|
|
|
|
def _eval_transpose(self):
|
|
return self
|
|
|
|
def _eval_trace(self):
|
|
return self.rows
|
|
|
|
def _eval_inverse(self):
|
|
return self
|
|
|
|
def _eval_as_real_imag(self):
|
|
return (self, ZeroMatrix(*self.shape))
|
|
|
|
def _eval_conjugate(self):
|
|
return self
|
|
|
|
def _eval_adjoint(self):
|
|
return self
|
|
|
|
def _entry(self, i, j, **kwargs):
|
|
eq = Eq(i, j)
|
|
if eq is S.true:
|
|
return S.One
|
|
elif eq is S.false:
|
|
return S.Zero
|
|
return KroneckerDelta(i, j, (0, self.cols-1))
|
|
|
|
def _eval_determinant(self):
|
|
return S.One
|
|
|
|
def _eval_power(self, exp):
|
|
return self
|
|
|
|
|
|
class GenericIdentity(Identity):
|
|
"""
|
|
An identity matrix without a specified shape
|
|
|
|
This exists primarily so MatMul() with no arguments can return something
|
|
meaningful.
|
|
"""
|
|
def __new__(cls):
|
|
# super(Identity, cls) instead of super(GenericIdentity, cls) because
|
|
# Identity.__new__ doesn't have the same signature
|
|
return super(Identity, cls).__new__(cls)
|
|
|
|
@property
|
|
def rows(self):
|
|
raise TypeError("GenericIdentity does not have a specified shape")
|
|
|
|
@property
|
|
def cols(self):
|
|
raise TypeError("GenericIdentity does not have a specified shape")
|
|
|
|
@property
|
|
def shape(self):
|
|
raise TypeError("GenericIdentity does not have a specified shape")
|
|
|
|
@property
|
|
def is_square(self):
|
|
return True
|
|
|
|
# Avoid Matrix.__eq__ which might call .shape
|
|
def __eq__(self, other):
|
|
return isinstance(other, GenericIdentity)
|
|
|
|
def __ne__(self, other):
|
|
return not (self == other)
|
|
|
|
def __hash__(self):
|
|
return super().__hash__()
|
|
|
|
|
|
class OneMatrix(MatrixExpr):
|
|
"""
|
|
Matrix whose all entries are ones.
|
|
"""
|
|
def __new__(cls, m, n, evaluate=False):
|
|
m, n = _sympify(m), _sympify(n)
|
|
cls._check_dim(m)
|
|
cls._check_dim(n)
|
|
|
|
if evaluate:
|
|
condition = Eq(m, 1) & Eq(n, 1)
|
|
if condition == True:
|
|
return Identity(1)
|
|
|
|
obj = super().__new__(cls, m, n)
|
|
return obj
|
|
|
|
@property
|
|
def shape(self):
|
|
return self._args
|
|
|
|
@property
|
|
def is_Identity(self):
|
|
return self._is_1x1() == True
|
|
|
|
def as_explicit(self):
|
|
from sympy.matrices.immutable import ImmutableDenseMatrix
|
|
return ImmutableDenseMatrix.ones(*self.shape)
|
|
|
|
def doit(self, **hints):
|
|
args = self.args
|
|
if hints.get('deep', True):
|
|
args = [a.doit(**hints) for a in args]
|
|
return self.func(*args, evaluate=True)
|
|
|
|
def _eval_power(self, exp):
|
|
# exp = -1, 0, 1 are already handled at this stage
|
|
if self._is_1x1() == True:
|
|
return Identity(1)
|
|
if (exp < 0) == True:
|
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible")
|
|
if ask(Q.integer(exp)):
|
|
return self.shape[0] ** (exp - 1) * OneMatrix(*self.shape)
|
|
return super()._eval_power(exp)
|
|
|
|
def _eval_transpose(self):
|
|
return OneMatrix(self.cols, self.rows)
|
|
|
|
def _eval_adjoint(self):
|
|
return OneMatrix(self.cols, self.rows)
|
|
|
|
def _eval_trace(self):
|
|
return S.One*self.rows
|
|
|
|
def _is_1x1(self):
|
|
"""Returns true if the matrix is known to be 1x1"""
|
|
shape = self.shape
|
|
return Eq(shape[0], 1) & Eq(shape[1], 1)
|
|
|
|
def _eval_determinant(self):
|
|
condition = self._is_1x1()
|
|
if condition == True:
|
|
return S.One
|
|
elif condition == False:
|
|
return S.Zero
|
|
else:
|
|
from sympy.matrices.expressions.determinant import Determinant
|
|
return Determinant(self)
|
|
|
|
def _eval_inverse(self):
|
|
condition = self._is_1x1()
|
|
if condition == True:
|
|
return Identity(1)
|
|
elif condition == False:
|
|
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
|
else:
|
|
from .inverse import Inverse
|
|
return Inverse(self)
|
|
|
|
def _eval_as_real_imag(self):
|
|
return (self, ZeroMatrix(*self.shape))
|
|
|
|
def _eval_conjugate(self):
|
|
return self
|
|
|
|
def _entry(self, i, j, **kwargs):
|
|
return S.One
|