|
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) |
|
|
|
@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.matrixbase import MatrixBase |
|
vector = self._vector |
|
|
|
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() |
|
|