You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

221 lines
6.2 KiB

5 months ago
from sympy.core.sympify import _sympify
from sympy.matrices.expressions import MatrixExpr
from sympy.core import S, Eq, Ge
from sympy.core.mul import Mul
from sympy.functions.special.tensor_functions import KroneckerDelta
class DiagonalMatrix(MatrixExpr):
"""DiagonalMatrix(M) will create a matrix expression that
behaves as though all off-diagonal elements,
`M[i, j]` where `i != j`, are zero.
Examples
========
>>> from sympy import MatrixSymbol, DiagonalMatrix, Symbol
>>> n = Symbol('n', integer=True)
>>> m = Symbol('m', integer=True)
>>> D = DiagonalMatrix(MatrixSymbol('x', 2, 3))
>>> D[1, 2]
0
>>> D[1, 1]
x[1, 1]
The length of the diagonal -- the lesser of the two dimensions of `M` --
is accessed through the `diagonal_length` property:
>>> D.diagonal_length
2
>>> DiagonalMatrix(MatrixSymbol('x', n + 1, n)).diagonal_length
n
When one of the dimensions is symbolic the other will be treated as
though it is smaller:
>>> tall = DiagonalMatrix(MatrixSymbol('x', n, 3))
>>> tall.diagonal_length
3
>>> tall[10, 1]
0
When the size of the diagonal is not known, a value of None will
be returned:
>>> DiagonalMatrix(MatrixSymbol('x', n, m)).diagonal_length is None
True
"""
arg = property(lambda self: self.args[0])
shape = property(lambda self: self.arg.shape) # type:ignore
@property
def diagonal_length(self):
r, c = self.shape
if r.is_Integer and c.is_Integer:
m = min(r, c)
elif r.is_Integer and not c.is_Integer:
m = r
elif c.is_Integer and not r.is_Integer:
m = c
elif r == c:
m = r
else:
try:
m = min(r, c)
except TypeError:
m = None
return m
def _entry(self, i, j, **kwargs):
if self.diagonal_length is not None:
if Ge(i, self.diagonal_length) is S.true:
return S.Zero
elif Ge(j, self.diagonal_length) is S.true:
return S.Zero
eq = Eq(i, j)
if eq is S.true:
return self.arg[i, i]
elif eq is S.false:
return S.Zero
return self.arg[i, j]*KroneckerDelta(i, j)
class DiagonalOf(MatrixExpr):
"""DiagonalOf(M) will create a matrix expression that
is equivalent to the diagonal of `M`, represented as
a single column matrix.
Examples
========
>>> from sympy import MatrixSymbol, DiagonalOf, Symbol
>>> n = Symbol('n', integer=True)
>>> m = Symbol('m', integer=True)
>>> x = MatrixSymbol('x', 2, 3)
>>> diag = DiagonalOf(x)
>>> diag.shape
(2, 1)
The diagonal can be addressed like a matrix or vector and will
return the corresponding element of the original matrix:
>>> diag[1, 0] == diag[1] == x[1, 1]
True
The length of the diagonal -- the lesser of the two dimensions of `M` --
is accessed through the `diagonal_length` property:
>>> diag.diagonal_length
2
>>> DiagonalOf(MatrixSymbol('x', n + 1, n)).diagonal_length
n
When only one of the dimensions is symbolic the other will be
treated as though it is smaller:
>>> dtall = DiagonalOf(MatrixSymbol('x', n, 3))
>>> dtall.diagonal_length
3
When the size of the diagonal is not known, a value of None will
be returned:
>>> DiagonalOf(MatrixSymbol('x', n, m)).diagonal_length is None
True
"""
arg = property(lambda self: self.args[0])
@property
def shape(self):
r, c = self.arg.shape
if r.is_Integer and c.is_Integer:
m = min(r, c)
elif r.is_Integer and not c.is_Integer:
m = r
elif c.is_Integer and not r.is_Integer:
m = c
elif r == c:
m = r
else:
try:
m = min(r, c)
except TypeError:
m = None
return m, S.One
@property
def diagonal_length(self):
return self.shape[0]
def _entry(self, i, j, **kwargs):
return self.arg._entry(i, i, **kwargs)
class DiagMatrix(MatrixExpr):
"""
Turn a vector into a diagonal matrix.
"""
def __new__(cls, vector):
vector = _sympify(vector)
obj = MatrixExpr.__new__(cls, vector)
shape = vector.shape
dim = shape[1] if shape[0] == 1 else shape[0]
if vector.shape[0] != 1:
obj._iscolumn = True
else:
obj._iscolumn = False
obj._shape = (dim, dim)
obj._vector = vector
return obj
@property
def shape(self):
return self._shape
def _entry(self, i, j, **kwargs):
if self._iscolumn:
result = self._vector._entry(i, 0, **kwargs)
else:
result = self._vector._entry(0, j, **kwargs)
if i != j:
result *= KroneckerDelta(i, j)
return result
def _eval_transpose(self):
return self
def as_explicit(self):
from sympy.matrices.dense import diag
return diag(*list(self._vector.as_explicit()))
def doit(self, **hints):
from sympy.assumptions import ask, Q
from sympy.matrices.expressions.matmul import MatMul
from sympy.matrices.expressions.transpose import Transpose
from sympy.matrices.dense import eye
from sympy.matrices.matrices import MatrixBase
vector = self._vector
# This accounts for shape (1, 1) and identity matrices, among others:
if ask(Q.diagonal(vector)):
return vector
if isinstance(vector, MatrixBase):
ret = eye(max(vector.shape))
for i in range(ret.shape[0]):
ret[i, i] = vector[i]
return type(vector)(ret)
if vector.is_MatMul:
matrices = [arg for arg in vector.args if arg.is_Matrix]
scalars = [arg for arg in vector.args if arg not in matrices]
if scalars:
return Mul.fromiter(scalars)*DiagMatrix(MatMul.fromiter(matrices).doit()).doit()
if isinstance(vector, Transpose):
vector = vector.arg
return DiagMatrix(vector)
def diagonalize_vector(vector):
return DiagMatrix(vector).doit()