chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
""" A module which handles Matrix Expressions """
|
||||
|
||||
from .slice import MatrixSlice
|
||||
from .blockmatrix import BlockMatrix, BlockDiagMatrix, block_collapse, blockcut
|
||||
from .companion import CompanionMatrix
|
||||
from .funcmatrix import FunctionMatrix
|
||||
from .inverse import Inverse
|
||||
from .matadd import MatAdd
|
||||
from .matexpr import MatrixExpr, MatrixSymbol, matrix_symbols
|
||||
from .matmul import MatMul
|
||||
from .matpow import MatPow
|
||||
from .trace import Trace, trace
|
||||
from .determinant import Determinant, det, Permanent, per
|
||||
from .transpose import Transpose
|
||||
from .adjoint import Adjoint
|
||||
from .hadamard import hadamard_product, HadamardProduct, hadamard_power, HadamardPower
|
||||
from .diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector
|
||||
from .dotproduct import DotProduct
|
||||
from .kronecker import kronecker_product, KroneckerProduct, combine_kronecker
|
||||
from .permutation import PermutationMatrix, MatrixPermute
|
||||
from .sets import MatrixSet
|
||||
from .special import ZeroMatrix, Identity, OneMatrix
|
||||
|
||||
__all__ = [
|
||||
'MatrixSlice',
|
||||
|
||||
'BlockMatrix', 'BlockDiagMatrix', 'block_collapse', 'blockcut',
|
||||
'FunctionMatrix',
|
||||
|
||||
'CompanionMatrix',
|
||||
|
||||
'Inverse',
|
||||
|
||||
'MatAdd',
|
||||
|
||||
'Identity', 'MatrixExpr', 'MatrixSymbol', 'ZeroMatrix', 'OneMatrix',
|
||||
'matrix_symbols', 'MatrixSet',
|
||||
|
||||
'MatMul',
|
||||
|
||||
'MatPow',
|
||||
|
||||
'Trace', 'trace',
|
||||
|
||||
'Determinant', 'det',
|
||||
|
||||
'Transpose',
|
||||
|
||||
'Adjoint',
|
||||
|
||||
'hadamard_product', 'HadamardProduct', 'hadamard_power', 'HadamardPower',
|
||||
|
||||
'DiagonalMatrix', 'DiagonalOf', 'DiagMatrix', 'diagonalize_vector',
|
||||
|
||||
'DotProduct',
|
||||
|
||||
'kronecker_product', 'KroneckerProduct', 'combine_kronecker',
|
||||
|
||||
'PermutationMatrix', 'MatrixPermute',
|
||||
|
||||
'Permanent', 'per'
|
||||
]
|
||||
@@ -0,0 +1,102 @@
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.logic.boolalg import Boolean, And
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from typing import Union
|
||||
|
||||
|
||||
def is_matadd_valid(*args: MatrixExpr) -> Boolean:
|
||||
"""Return the symbolic condition how ``MatAdd``, ``HadamardProduct``
|
||||
makes sense.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args
|
||||
The list of arguments of matrices to be tested for.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> from sympy.matrices.expressions._shape import is_matadd_valid
|
||||
|
||||
>>> m, n, p, q = symbols('m n p q')
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', p, q)
|
||||
>>> is_matadd_valid(A, B)
|
||||
Eq(m, p) & Eq(n, q)
|
||||
"""
|
||||
rows, cols = zip(*(arg.shape for arg in args))
|
||||
return And(
|
||||
*(Eq(i, j) for i, j in zip(rows[:-1], rows[1:])),
|
||||
*(Eq(i, j) for i, j in zip(cols[:-1], cols[1:])),
|
||||
)
|
||||
|
||||
|
||||
def is_matmul_valid(*args: Union[MatrixExpr, Expr]) -> Boolean:
|
||||
"""Return the symbolic condition how ``MatMul`` makes sense
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args
|
||||
The list of arguments of matrices and scalar expressions to be tested
|
||||
for.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> from sympy.matrices.expressions._shape import is_matmul_valid
|
||||
|
||||
>>> m, n, p, q = symbols('m n p q')
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', p, q)
|
||||
>>> is_matmul_valid(A, B)
|
||||
Eq(n, p)
|
||||
"""
|
||||
rows, cols = zip(*(arg.shape for arg in args if isinstance(arg, MatrixExpr)))
|
||||
return And(*(Eq(i, j) for i, j in zip(cols[:-1], rows[1:])))
|
||||
|
||||
|
||||
def is_square(arg: MatrixExpr, /) -> Boolean:
|
||||
"""Return the symbolic condition how the matrix is assumed to be square
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
arg
|
||||
The matrix to be tested for.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> from sympy.matrices.expressions._shape import is_square
|
||||
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> is_square(A)
|
||||
Eq(m, n)
|
||||
"""
|
||||
return Eq(arg.rows, arg.cols)
|
||||
|
||||
|
||||
def validate_matadd_integer(*args: MatrixExpr) -> None:
|
||||
"""Validate matrix shape for addition only for integer values"""
|
||||
rows, cols = zip(*(x.shape for x in args))
|
||||
if len(set(filter(lambda x: isinstance(x, (int, Integer)), rows))) > 1:
|
||||
raise ShapeError(f"Matrices have mismatching shape: {rows}")
|
||||
if len(set(filter(lambda x: isinstance(x, (int, Integer)), cols))) > 1:
|
||||
raise ShapeError(f"Matrices have mismatching shape: {cols}")
|
||||
|
||||
|
||||
def validate_matmul_integer(*args: MatrixExpr) -> None:
|
||||
"""Validate matrix shape for multiplication only for integer values"""
|
||||
for A, B in zip(args[:-1], args[1:]):
|
||||
i, j = A.cols, B.rows
|
||||
if isinstance(i, (int, Integer)) and isinstance(j, (int, Integer)) and i != j:
|
||||
raise ShapeError("Matrices are not aligned", i, j)
|
||||
@@ -0,0 +1,60 @@
|
||||
from sympy.core import Basic
|
||||
from sympy.functions import adjoint, conjugate
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
|
||||
|
||||
class Adjoint(MatrixExpr):
|
||||
"""
|
||||
The Hermitian adjoint of a matrix expression.
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the adjoint, use the ``adjoint()``
|
||||
function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Adjoint, adjoint
|
||||
>>> A = MatrixSymbol('A', 3, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 3)
|
||||
>>> Adjoint(A*B)
|
||||
Adjoint(A*B)
|
||||
>>> adjoint(A*B)
|
||||
Adjoint(B)*Adjoint(A)
|
||||
>>> adjoint(A*B) == Adjoint(A*B)
|
||||
False
|
||||
>>> adjoint(A*B) == Adjoint(A*B).doit()
|
||||
True
|
||||
"""
|
||||
is_Adjoint = True
|
||||
|
||||
def doit(self, **hints):
|
||||
arg = self.arg
|
||||
if hints.get('deep', True) and isinstance(arg, Basic):
|
||||
return adjoint(arg.doit(**hints))
|
||||
else:
|
||||
return adjoint(self.arg)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.arg.shape[::-1]
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return conjugate(self.arg._entry(j, i, **kwargs))
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self.arg
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self.arg.conjugate()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self.arg.transpose()
|
||||
|
||||
def _eval_trace(self):
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
return conjugate(Trace(self.arg))
|
||||
@@ -0,0 +1,204 @@
|
||||
from sympy.core.expr import ExprBuilder
|
||||
from sympy.core.function import (Function, FunctionClass, Lambda)
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.core.sympify import sympify, _sympify
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
|
||||
|
||||
class ElementwiseApplyFunction(MatrixExpr):
|
||||
r"""
|
||||
Apply function to a matrix elementwise without evaluating.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
It can be created by calling ``.applyfunc(<function>)`` on a matrix
|
||||
expression:
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
||||
>>> from sympy import exp
|
||||
>>> X = MatrixSymbol("X", 3, 3)
|
||||
>>> X.applyfunc(exp)
|
||||
Lambda(_d, exp(_d)).(X)
|
||||
|
||||
Otherwise using the class constructor:
|
||||
|
||||
>>> from sympy import eye
|
||||
>>> expr = ElementwiseApplyFunction(exp, eye(3))
|
||||
>>> expr
|
||||
Lambda(_d, exp(_d)).(Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]]))
|
||||
>>> expr.doit()
|
||||
Matrix([
|
||||
[E, 1, 1],
|
||||
[1, E, 1],
|
||||
[1, 1, E]])
|
||||
|
||||
Notice the difference with the real mathematical functions:
|
||||
|
||||
>>> exp(eye(3))
|
||||
Matrix([
|
||||
[E, 0, 0],
|
||||
[0, E, 0],
|
||||
[0, 0, E]])
|
||||
"""
|
||||
|
||||
def __new__(cls, function, expr):
|
||||
expr = _sympify(expr)
|
||||
if not expr.is_Matrix:
|
||||
raise ValueError("{} must be a matrix instance.".format(expr))
|
||||
|
||||
if expr.shape == (1, 1):
|
||||
# Check if the function returns a matrix, in that case, just apply
|
||||
# the function instead of creating an ElementwiseApplyFunc object:
|
||||
ret = function(expr)
|
||||
if isinstance(ret, MatrixExpr):
|
||||
return ret
|
||||
|
||||
if not isinstance(function, (FunctionClass, Lambda)):
|
||||
d = Dummy('d')
|
||||
function = Lambda(d, function(d))
|
||||
|
||||
function = sympify(function)
|
||||
if not isinstance(function, (FunctionClass, Lambda)):
|
||||
raise ValueError(
|
||||
"{} should be compatible with SymPy function classes."
|
||||
.format(function))
|
||||
|
||||
if 1 not in function.nargs:
|
||||
raise ValueError(
|
||||
'{} should be able to accept 1 arguments.'.format(function))
|
||||
|
||||
if not isinstance(function, Lambda):
|
||||
d = Dummy('d')
|
||||
function = Lambda(d, function(d))
|
||||
|
||||
obj = MatrixExpr.__new__(cls, function, expr)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def expr(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.expr.shape
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get("deep", True)
|
||||
expr = self.expr
|
||||
if deep:
|
||||
expr = expr.doit(**hints)
|
||||
function = self.function
|
||||
if isinstance(function, Lambda) and function.is_identity:
|
||||
# This is a Lambda containing the identity function.
|
||||
return expr
|
||||
if isinstance(expr, MatrixBase):
|
||||
return expr.applyfunc(self.function)
|
||||
elif isinstance(expr, ElementwiseApplyFunction):
|
||||
return ElementwiseApplyFunction(
|
||||
lambda x: self.function(expr.function(x)),
|
||||
expr.expr
|
||||
).doit(**hints)
|
||||
else:
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.function(self.expr._entry(i, j, **kwargs))
|
||||
|
||||
def _get_function_fdiff(self):
|
||||
d = Dummy("d")
|
||||
function = self.function(d)
|
||||
fdiff = function.diff(d)
|
||||
if isinstance(fdiff, Function):
|
||||
fdiff = type(fdiff)
|
||||
else:
|
||||
fdiff = Lambda(d, fdiff)
|
||||
return fdiff
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
from sympy.matrices.expressions.hadamard import hadamard_product
|
||||
dexpr = self.expr.diff(x)
|
||||
fdiff = self._get_function_fdiff()
|
||||
return hadamard_product(
|
||||
dexpr,
|
||||
ElementwiseApplyFunction(fdiff, self.expr)
|
||||
)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayContraction
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
|
||||
fdiff = self._get_function_fdiff()
|
||||
lr = self.expr._eval_derivative_matrix_lines(x)
|
||||
ewdiff = ElementwiseApplyFunction(fdiff, self.expr)
|
||||
if 1 in x.shape:
|
||||
# Vector:
|
||||
iscolumn = self.shape[1] == 1
|
||||
for i in lr:
|
||||
if iscolumn:
|
||||
ptr1 = i.first_pointer
|
||||
ptr2 = Identity(self.shape[1])
|
||||
else:
|
||||
ptr1 = Identity(self.shape[0])
|
||||
ptr2 = i.second_pointer
|
||||
|
||||
subexpr = ExprBuilder(
|
||||
ArrayDiagonal,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
ewdiff,
|
||||
ptr1,
|
||||
ptr2,
|
||||
]
|
||||
),
|
||||
(0, 2) if iscolumn else (1, 4)
|
||||
],
|
||||
validator=ArrayDiagonal._validate
|
||||
)
|
||||
i._lines = [subexpr]
|
||||
i._first_pointer_parent = subexpr.args[0].args
|
||||
i._first_pointer_index = 1
|
||||
i._second_pointer_parent = subexpr.args[0].args
|
||||
i._second_pointer_index = 2
|
||||
else:
|
||||
# Matrix case:
|
||||
for i in lr:
|
||||
ptr1 = i.first_pointer
|
||||
ptr2 = i.second_pointer
|
||||
newptr1 = Identity(ptr1.shape[1])
|
||||
newptr2 = Identity(ptr2.shape[1])
|
||||
subexpr = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[ptr1, newptr1, ewdiff, ptr2, newptr2]
|
||||
),
|
||||
(1, 2, 4),
|
||||
(5, 7, 8),
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args
|
||||
i._first_pointer_index = 1
|
||||
i._second_pointer_parent = subexpr.args[0].args
|
||||
i._second_pointer_index = 4
|
||||
i._lines = [subexpr]
|
||||
return lr
|
||||
|
||||
def _eval_transpose(self):
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
return self.func(self.function, Transpose(self.expr).doit())
|
||||
@@ -0,0 +1,975 @@
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core import Basic, Add, Mul, S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.functions.elementary.complexes import re, im
|
||||
from sympy.strategies import typed, exhaust, condition, do_one, unpack
|
||||
from sympy.strategies.traverse import bottom_up
|
||||
from sympy.utilities.iterables import is_sequence, sift
|
||||
from sympy.utilities.misc import filldedent
|
||||
|
||||
from sympy.matrices import Matrix, ShapeError
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.matrices.expressions.determinant import det, Determinant
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr, MatrixElement
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, Identity
|
||||
from sympy.matrices.expressions.trace import trace
|
||||
from sympy.matrices.expressions.transpose import Transpose, transpose
|
||||
|
||||
|
||||
class BlockMatrix(MatrixExpr):
|
||||
"""A BlockMatrix is a Matrix comprised of other matrices.
|
||||
|
||||
The submatrices are stored in a SymPy Matrix object but accessed as part of
|
||||
a Matrix Expression
|
||||
|
||||
>>> from sympy import (MatrixSymbol, BlockMatrix, symbols,
|
||||
... Identity, ZeroMatrix, block_collapse)
|
||||
>>> n,m,l = symbols('n m l')
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> Z = MatrixSymbol('Z', n, m)
|
||||
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]])
|
||||
>>> print(B)
|
||||
Matrix([
|
||||
[X, Z],
|
||||
[0, Y]])
|
||||
|
||||
>>> C = BlockMatrix([[Identity(n), Z]])
|
||||
>>> print(C)
|
||||
Matrix([[I, Z]])
|
||||
|
||||
>>> print(block_collapse(C*B))
|
||||
Matrix([[X, Z + Z*Y]])
|
||||
|
||||
Some matrices might be comprised of rows of blocks with
|
||||
the matrices in each row having the same height and the
|
||||
rows all having the same total number of columns but
|
||||
not having the same number of columns for each matrix
|
||||
in each row. In this case, the matrix is not a block
|
||||
matrix and should be instantiated by Matrix.
|
||||
|
||||
>>> from sympy import ones, Matrix
|
||||
>>> dat = [
|
||||
... [ones(3,2), ones(3,3)*2],
|
||||
... [ones(2,3)*3, ones(2,2)*4]]
|
||||
...
|
||||
>>> BlockMatrix(dat)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError:
|
||||
Although this matrix is comprised of blocks, the blocks do not fill
|
||||
the matrix in a size-symmetric fashion. To create a full matrix from
|
||||
these arguments, pass them directly to Matrix.
|
||||
>>> Matrix(dat)
|
||||
Matrix([
|
||||
[1, 1, 2, 2, 2],
|
||||
[1, 1, 2, 2, 2],
|
||||
[1, 1, 2, 2, 2],
|
||||
[3, 3, 3, 4, 4],
|
||||
[3, 3, 3, 4, 4]])
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.matrixbase.MatrixBase.irregular
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
isMat = lambda i: getattr(i, 'is_Matrix', False)
|
||||
if len(args) != 1 or \
|
||||
not is_sequence(args[0]) or \
|
||||
len({isMat(r) for r in args[0]}) != 1:
|
||||
raise ValueError(filldedent('''
|
||||
expecting a sequence of 1 or more rows
|
||||
containing Matrices.'''))
|
||||
rows = args[0] if args else []
|
||||
if not isMat(rows):
|
||||
if rows and isMat(rows[0]):
|
||||
rows = [rows] # rows is not list of lists or []
|
||||
# regularity check
|
||||
# same number of matrices in each row
|
||||
blocky = ok = len({len(r) for r in rows}) == 1
|
||||
if ok:
|
||||
# same number of rows for each matrix in a row
|
||||
for r in rows:
|
||||
ok = len({i.rows for i in r}) == 1
|
||||
if not ok:
|
||||
break
|
||||
blocky = ok
|
||||
if ok:
|
||||
# same number of cols for each matrix in each col
|
||||
for c in range(len(rows[0])):
|
||||
ok = len({rows[i][c].cols
|
||||
for i in range(len(rows))}) == 1
|
||||
if not ok:
|
||||
break
|
||||
if not ok:
|
||||
# same total cols in each row
|
||||
ok = len({
|
||||
sum(i.cols for i in r) for r in rows}) == 1
|
||||
if blocky and ok:
|
||||
raise ValueError(filldedent('''
|
||||
Although this matrix is comprised of blocks,
|
||||
the blocks do not fill the matrix in a
|
||||
size-symmetric fashion. To create a full matrix
|
||||
from these arguments, pass them directly to
|
||||
Matrix.'''))
|
||||
raise ValueError(filldedent('''
|
||||
When there are not the same number of rows in each
|
||||
row's matrices or there are not the same number of
|
||||
total columns in each row, the matrix is not a
|
||||
block matrix. If this matrix is known to consist of
|
||||
blocks fully filling a 2-D space then see
|
||||
Matrix.irregular.'''))
|
||||
mat = ImmutableDenseMatrix(rows, evaluate=False)
|
||||
obj = Basic.__new__(cls, mat)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
numrows = numcols = 0
|
||||
M = self.blocks
|
||||
for i in range(M.shape[0]):
|
||||
numrows += M[i, 0].shape[0]
|
||||
for i in range(M.shape[1]):
|
||||
numcols += M[0, i].shape[1]
|
||||
return (numrows, numcols)
|
||||
|
||||
@property
|
||||
def blockshape(self):
|
||||
return self.blocks.shape
|
||||
|
||||
@property
|
||||
def blocks(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def rowblocksizes(self):
|
||||
return [self.blocks[i, 0].rows for i in range(self.blockshape[0])]
|
||||
|
||||
@property
|
||||
def colblocksizes(self):
|
||||
return [self.blocks[0, i].cols for i in range(self.blockshape[1])]
|
||||
|
||||
def structurally_equal(self, other):
|
||||
return (isinstance(other, BlockMatrix)
|
||||
and self.shape == other.shape
|
||||
and self.blockshape == other.blockshape
|
||||
and self.rowblocksizes == other.rowblocksizes
|
||||
and self.colblocksizes == other.colblocksizes)
|
||||
|
||||
def _blockmul(self, other):
|
||||
if (isinstance(other, BlockMatrix) and
|
||||
self.colblocksizes == other.rowblocksizes):
|
||||
return BlockMatrix(self.blocks*other.blocks)
|
||||
|
||||
return self * other
|
||||
|
||||
def _blockadd(self, other):
|
||||
if (isinstance(other, BlockMatrix)
|
||||
and self.structurally_equal(other)):
|
||||
return BlockMatrix(self.blocks + other.blocks)
|
||||
|
||||
return self + other
|
||||
|
||||
def _eval_transpose(self):
|
||||
# Flip all the individual matrices
|
||||
matrices = [transpose(matrix) for matrix in self.blocks]
|
||||
# Make a copy
|
||||
M = Matrix(self.blockshape[0], self.blockshape[1], matrices)
|
||||
# Transpose the block structure
|
||||
M = M.transpose()
|
||||
return BlockMatrix(M)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return BlockMatrix(
|
||||
Matrix(self.blockshape[0], self.blockshape[1], self.blocks).adjoint()
|
||||
)
|
||||
|
||||
def _eval_trace(self):
|
||||
if self.rowblocksizes == self.colblocksizes:
|
||||
blocks = [self.blocks[i, i] for i in range(self.blockshape[0])]
|
||||
return Add(*[trace(block) for block in blocks])
|
||||
|
||||
def _eval_determinant(self):
|
||||
if self.blockshape == (1, 1):
|
||||
return det(self.blocks[0, 0])
|
||||
if self.blockshape == (2, 2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
if ask(Q.invertible(A)):
|
||||
return det(A)*det(D - C*A.I*B)
|
||||
elif ask(Q.invertible(D)):
|
||||
return det(D)*det(A - B*D.I*C)
|
||||
return Determinant(self)
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
real_matrices = [re(matrix) for matrix in self.blocks]
|
||||
real_matrices = Matrix(self.blockshape[0], self.blockshape[1], real_matrices)
|
||||
|
||||
im_matrices = [im(matrix) for matrix in self.blocks]
|
||||
im_matrices = Matrix(self.blockshape[0], self.blockshape[1], im_matrices)
|
||||
|
||||
return (BlockMatrix(real_matrices), BlockMatrix(im_matrices))
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
return BlockMatrix(self.blocks.diff(x))
|
||||
|
||||
def transpose(self):
|
||||
"""Return transpose of matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, BlockMatrix, ZeroMatrix
|
||||
>>> from sympy.abc import m, n
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> Z = MatrixSymbol('Z', n, m)
|
||||
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]])
|
||||
>>> B.transpose()
|
||||
Matrix([
|
||||
[X.T, 0],
|
||||
[Z.T, Y.T]])
|
||||
>>> _.transpose()
|
||||
Matrix([
|
||||
[X, Z],
|
||||
[0, Y]])
|
||||
"""
|
||||
return self._eval_transpose()
|
||||
|
||||
def schur(self, mat = 'A', generalized = False):
|
||||
"""Return the Schur Complement of the 2x2 BlockMatrix
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mat : String, optional
|
||||
The matrix with respect to which the
|
||||
Schur Complement is calculated. 'A' is
|
||||
used by default
|
||||
|
||||
generalized : bool, optional
|
||||
If True, returns the generalized Schur
|
||||
Component which uses Moore-Penrose Inverse
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
|
||||
The default Schur Complement is evaluated with "A"
|
||||
|
||||
>>> X.schur()
|
||||
-C*A**(-1)*B + D
|
||||
>>> X.schur('D')
|
||||
A - B*D**(-1)*C
|
||||
|
||||
Schur complement with non-invertible matrices is not
|
||||
defined. Instead, the generalized Schur complement can
|
||||
be calculated which uses the Moore-Penrose Inverse. To
|
||||
achieve this, `generalized` must be set to `True`
|
||||
|
||||
>>> X.schur('B', generalized=True)
|
||||
C - D*(B.T*B)**(-1)*B.T*A
|
||||
>>> X.schur('C', generalized=True)
|
||||
-A*(C.T*C)**(-1)*C.T*D + B
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
M : Matrix
|
||||
The Schur Complement Matrix
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If given matrix is non-invertible
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Wikipedia Article on Schur Component : https://en.wikipedia.org/wiki/Schur_complement
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.matrixbase.MatrixBase.pinv
|
||||
"""
|
||||
|
||||
if self.blockshape == (2, 2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
d={'A' : A, 'B' : B, 'C' : C, 'D' : D}
|
||||
try:
|
||||
inv = (d[mat].T*d[mat]).inv()*d[mat].T if generalized else d[mat].inv()
|
||||
if mat == 'A':
|
||||
return D - C * inv * B
|
||||
elif mat == 'B':
|
||||
return C - D * inv * A
|
||||
elif mat == 'C':
|
||||
return B - A * inv * D
|
||||
elif mat == 'D':
|
||||
return A - B * inv * C
|
||||
#For matrices where no sub-matrix is square
|
||||
return self
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('The given matrix is not invertible. Please set generalized=True \
|
||||
to compute the generalized Schur Complement which uses Moore-Penrose Inverse')
|
||||
else:
|
||||
raise ShapeError('Schur Complement can only be calculated for 2x2 block matrices')
|
||||
|
||||
def LDUdecomposition(self):
|
||||
"""Returns the Block LDU decomposition of
|
||||
a 2x2 Block Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(L, D, U) : Matrices
|
||||
L : Lower Diagonal Matrix
|
||||
D : Diagonal Matrix
|
||||
U : Upper Diagonal Matrix
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
>>> L, D, U = X.LDUdecomposition()
|
||||
>>> block_collapse(L*D*U)
|
||||
Matrix([
|
||||
[A, B],
|
||||
[C, D]])
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If the matrix "A" is non-invertible
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition
|
||||
"""
|
||||
if self.blockshape == (2,2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
try:
|
||||
AI = A.I
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('Block LDU decomposition cannot be calculated when\
|
||||
"A" is singular')
|
||||
Ip = Identity(B.shape[0])
|
||||
Iq = Identity(B.shape[1])
|
||||
Z = ZeroMatrix(*B.shape)
|
||||
L = BlockMatrix([[Ip, Z], [C*AI, Iq]])
|
||||
D = BlockDiagMatrix(A, self.schur())
|
||||
U = BlockMatrix([[Ip, AI*B],[Z.T, Iq]])
|
||||
return L, D, U
|
||||
else:
|
||||
raise ShapeError("Block LDU decomposition is supported only for 2x2 block matrices")
|
||||
|
||||
def UDLdecomposition(self):
|
||||
"""Returns the Block UDL decomposition of
|
||||
a 2x2 Block Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(U, D, L) : Matrices
|
||||
U : Upper Diagonal Matrix
|
||||
D : Diagonal Matrix
|
||||
L : Lower Diagonal Matrix
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
>>> U, D, L = X.UDLdecomposition()
|
||||
>>> block_collapse(U*D*L)
|
||||
Matrix([
|
||||
[A, B],
|
||||
[C, D]])
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If the matrix "D" is non-invertible
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition
|
||||
"""
|
||||
if self.blockshape == (2,2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
try:
|
||||
DI = D.I
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('Block UDL decomposition cannot be calculated when\
|
||||
"D" is singular')
|
||||
Ip = Identity(A.shape[0])
|
||||
Iq = Identity(B.shape[1])
|
||||
Z = ZeroMatrix(*B.shape)
|
||||
U = BlockMatrix([[Ip, B*DI], [Z.T, Iq]])
|
||||
D = BlockDiagMatrix(self.schur('D'), D)
|
||||
L = BlockMatrix([[Ip, Z],[DI*C, Iq]])
|
||||
return U, D, L
|
||||
else:
|
||||
raise ShapeError("Block UDL decomposition is supported only for 2x2 block matrices")
|
||||
|
||||
def LUdecomposition(self):
|
||||
"""Returns the Block LU decomposition of
|
||||
a 2x2 Block Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(L, U) : Matrices
|
||||
L : Lower Diagonal Matrix
|
||||
U : Upper Diagonal Matrix
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
>>> L, U = X.LUdecomposition()
|
||||
>>> block_collapse(L*U)
|
||||
Matrix([
|
||||
[A, B],
|
||||
[C, D]])
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If the matrix "A" is non-invertible
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition
|
||||
"""
|
||||
if self.blockshape == (2,2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
try:
|
||||
A = A**S.Half
|
||||
AI = A.I
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('Block LU decomposition cannot be calculated when\
|
||||
"A" is singular')
|
||||
Z = ZeroMatrix(*B.shape)
|
||||
Q = self.schur()**S.Half
|
||||
L = BlockMatrix([[A, Z], [C*AI, Q]])
|
||||
U = BlockMatrix([[A, AI*B],[Z.T, Q]])
|
||||
return L, U
|
||||
else:
|
||||
raise ShapeError("Block LU decomposition is supported only for 2x2 block matrices")
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
# Find row entry
|
||||
orig_i, orig_j = i, j
|
||||
for row_block, numrows in enumerate(self.rowblocksizes):
|
||||
cmp = i < numrows
|
||||
if cmp == True:
|
||||
break
|
||||
elif cmp == False:
|
||||
i -= numrows
|
||||
elif row_block < self.blockshape[0] - 1:
|
||||
# Can't tell which block and it's not the last one, return unevaluated
|
||||
return MatrixElement(self, orig_i, orig_j)
|
||||
for col_block, numcols in enumerate(self.colblocksizes):
|
||||
cmp = j < numcols
|
||||
if cmp == True:
|
||||
break
|
||||
elif cmp == False:
|
||||
j -= numcols
|
||||
elif col_block < self.blockshape[1] - 1:
|
||||
return MatrixElement(self, orig_i, orig_j)
|
||||
return self.blocks[row_block, col_block][i, j]
|
||||
|
||||
@property
|
||||
def is_Identity(self):
|
||||
if self.blockshape[0] != self.blockshape[1]:
|
||||
return False
|
||||
for i in range(self.blockshape[0]):
|
||||
for j in range(self.blockshape[1]):
|
||||
if i==j and not self.blocks[i, j].is_Identity:
|
||||
return False
|
||||
if i!=j and not self.blocks[i, j].is_ZeroMatrix:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_structurally_symmetric(self):
|
||||
return self.rowblocksizes == self.colblocksizes
|
||||
|
||||
def equals(self, other):
|
||||
if self == other:
|
||||
return True
|
||||
if (isinstance(other, BlockMatrix) and self.blocks == other.blocks):
|
||||
return True
|
||||
return super().equals(other)
|
||||
|
||||
|
||||
class BlockDiagMatrix(BlockMatrix):
|
||||
"""A sparse matrix with block matrices along its diagonals
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, BlockDiagMatrix, symbols
|
||||
>>> n, m, l = symbols('n m l')
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> BlockDiagMatrix(X, Y)
|
||||
Matrix([
|
||||
[X, 0],
|
||||
[0, Y]])
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
If you want to get the individual diagonal blocks, use
|
||||
:meth:`get_diag_blocks`.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.diag
|
||||
"""
|
||||
def __new__(cls, *mats):
|
||||
return Basic.__new__(BlockDiagMatrix, *[_sympify(m) for m in mats])
|
||||
|
||||
@property
|
||||
def diag(self):
|
||||
return self.args
|
||||
|
||||
@property
|
||||
def blocks(self):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
mats = self.args
|
||||
data = [[mats[i] if i == j else ZeroMatrix(mats[i].rows, mats[j].cols)
|
||||
for j in range(len(mats))]
|
||||
for i in range(len(mats))]
|
||||
return ImmutableDenseMatrix(data, evaluate=False)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (sum(block.rows for block in self.args),
|
||||
sum(block.cols for block in self.args))
|
||||
|
||||
@property
|
||||
def blockshape(self):
|
||||
n = len(self.args)
|
||||
return (n, n)
|
||||
|
||||
@property
|
||||
def rowblocksizes(self):
|
||||
return [block.rows for block in self.args]
|
||||
|
||||
@property
|
||||
def colblocksizes(self):
|
||||
return [block.cols for block in self.args]
|
||||
|
||||
def _all_square_blocks(self):
|
||||
"""Returns true if all blocks are square"""
|
||||
return all(mat.is_square for mat in self.args)
|
||||
|
||||
def _eval_determinant(self):
|
||||
if self._all_square_blocks():
|
||||
return Mul(*[det(mat) for mat in self.args])
|
||||
# At least one block is non-square. Since the entire matrix must be square we know there must
|
||||
# be at least two blocks in this matrix, in which case the entire matrix is necessarily rank-deficient
|
||||
return S.Zero
|
||||
|
||||
def _eval_inverse(self, expand='ignored'):
|
||||
if self._all_square_blocks():
|
||||
return BlockDiagMatrix(*[mat.inverse() for mat in self.args])
|
||||
# See comment in _eval_determinant()
|
||||
raise NonInvertibleMatrixError('Matrix det == 0; not invertible.')
|
||||
|
||||
def _eval_transpose(self):
|
||||
return BlockDiagMatrix(*[mat.transpose() for mat in self.args])
|
||||
|
||||
def _blockmul(self, other):
|
||||
if (isinstance(other, BlockDiagMatrix) and
|
||||
self.colblocksizes == other.rowblocksizes):
|
||||
return BlockDiagMatrix(*[a*b for a, b in zip(self.args, other.args)])
|
||||
else:
|
||||
return BlockMatrix._blockmul(self, other)
|
||||
|
||||
def _blockadd(self, other):
|
||||
if (isinstance(other, BlockDiagMatrix) and
|
||||
self.blockshape == other.blockshape and
|
||||
self.rowblocksizes == other.rowblocksizes and
|
||||
self.colblocksizes == other.colblocksizes):
|
||||
return BlockDiagMatrix(*[a + b for a, b in zip(self.args, other.args)])
|
||||
else:
|
||||
return BlockMatrix._blockadd(self, other)
|
||||
|
||||
def get_diag_blocks(self):
|
||||
"""Return the list of diagonal blocks of the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import BlockDiagMatrix, Matrix
|
||||
|
||||
>>> A = Matrix([[1, 2], [3, 4]])
|
||||
>>> B = Matrix([[5, 6], [7, 8]])
|
||||
>>> M = BlockDiagMatrix(A, B)
|
||||
|
||||
How to get diagonal blocks from the block diagonal matrix:
|
||||
|
||||
>>> diag_blocks = M.get_diag_blocks()
|
||||
>>> diag_blocks[0]
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[3, 4]])
|
||||
>>> diag_blocks[1]
|
||||
Matrix([
|
||||
[5, 6],
|
||||
[7, 8]])
|
||||
"""
|
||||
return self.args
|
||||
|
||||
|
||||
def block_collapse(expr):
|
||||
"""Evaluates a block matrix expression
|
||||
|
||||
>>> from sympy import MatrixSymbol, BlockMatrix, symbols, Identity, ZeroMatrix, block_collapse
|
||||
>>> n,m,l = symbols('n m l')
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> Z = MatrixSymbol('Z', n, m)
|
||||
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m, n), Y]])
|
||||
>>> print(B)
|
||||
Matrix([
|
||||
[X, Z],
|
||||
[0, Y]])
|
||||
|
||||
>>> C = BlockMatrix([[Identity(n), Z]])
|
||||
>>> print(C)
|
||||
Matrix([[I, Z]])
|
||||
|
||||
>>> print(block_collapse(C*B))
|
||||
Matrix([[X, Z + Z*Y]])
|
||||
"""
|
||||
from sympy.strategies.util import expr_fns
|
||||
|
||||
hasbm = lambda expr: isinstance(expr, MatrixExpr) and expr.has(BlockMatrix)
|
||||
|
||||
conditioned_rl = condition(
|
||||
hasbm,
|
||||
typed(
|
||||
{MatAdd: do_one(bc_matadd, bc_block_plus_ident),
|
||||
MatMul: do_one(bc_matmul, bc_dist),
|
||||
MatPow: bc_matmul,
|
||||
Transpose: bc_transpose,
|
||||
Inverse: bc_inverse,
|
||||
BlockMatrix: do_one(bc_unpack, deblock)}
|
||||
)
|
||||
)
|
||||
|
||||
rule = exhaust(
|
||||
bottom_up(
|
||||
exhaust(conditioned_rl),
|
||||
fns=expr_fns
|
||||
)
|
||||
)
|
||||
|
||||
result = rule(expr)
|
||||
doit = getattr(result, 'doit', None)
|
||||
if doit is not None:
|
||||
return doit()
|
||||
else:
|
||||
return result
|
||||
|
||||
def bc_unpack(expr):
|
||||
if expr.blockshape == (1, 1):
|
||||
return expr.blocks[0, 0]
|
||||
return expr
|
||||
|
||||
def bc_matadd(expr):
|
||||
args = sift(expr.args, lambda M: isinstance(M, BlockMatrix))
|
||||
blocks = args[True]
|
||||
if not blocks:
|
||||
return expr
|
||||
|
||||
nonblocks = args[False]
|
||||
block = blocks[0]
|
||||
for b in blocks[1:]:
|
||||
block = block._blockadd(b)
|
||||
if nonblocks:
|
||||
return MatAdd(*nonblocks) + block
|
||||
else:
|
||||
return block
|
||||
|
||||
def bc_block_plus_ident(expr):
|
||||
idents = [arg for arg in expr.args if arg.is_Identity]
|
||||
if not idents:
|
||||
return expr
|
||||
|
||||
blocks = [arg for arg in expr.args if isinstance(arg, BlockMatrix)]
|
||||
if (blocks and all(b.structurally_equal(blocks[0]) for b in blocks)
|
||||
and blocks[0].is_structurally_symmetric):
|
||||
block_id = BlockDiagMatrix(*[Identity(k)
|
||||
for k in blocks[0].rowblocksizes])
|
||||
rest = [arg for arg in expr.args if not arg.is_Identity and not isinstance(arg, BlockMatrix)]
|
||||
return MatAdd(block_id * len(idents), *blocks, *rest).doit()
|
||||
|
||||
return expr
|
||||
|
||||
def bc_dist(expr):
|
||||
""" Turn a*[X, Y] into [a*X, a*Y] """
|
||||
factor, mat = expr.as_coeff_mmul()
|
||||
if factor == 1:
|
||||
return expr
|
||||
|
||||
unpacked = unpack(mat)
|
||||
|
||||
if isinstance(unpacked, BlockDiagMatrix):
|
||||
B = unpacked.diag
|
||||
new_B = [factor * mat for mat in B]
|
||||
return BlockDiagMatrix(*new_B)
|
||||
elif isinstance(unpacked, BlockMatrix):
|
||||
B = unpacked.blocks
|
||||
new_B = [
|
||||
[factor * B[i, j] for j in range(B.cols)] for i in range(B.rows)]
|
||||
return BlockMatrix(new_B)
|
||||
return expr
|
||||
|
||||
|
||||
def bc_matmul(expr):
|
||||
if isinstance(expr, MatPow):
|
||||
if expr.args[1].is_Integer and expr.args[1] > 0:
|
||||
factor, matrices = 1, [expr.args[0]]*expr.args[1]
|
||||
else:
|
||||
return expr
|
||||
else:
|
||||
factor, matrices = expr.as_coeff_matrices()
|
||||
|
||||
i = 0
|
||||
while (i+1 < len(matrices)):
|
||||
A, B = matrices[i:i+2]
|
||||
if isinstance(A, BlockMatrix) and isinstance(B, BlockMatrix):
|
||||
matrices[i] = A._blockmul(B)
|
||||
matrices.pop(i+1)
|
||||
elif isinstance(A, BlockMatrix):
|
||||
matrices[i] = A._blockmul(BlockMatrix([[B]]))
|
||||
matrices.pop(i+1)
|
||||
elif isinstance(B, BlockMatrix):
|
||||
matrices[i] = BlockMatrix([[A]])._blockmul(B)
|
||||
matrices.pop(i+1)
|
||||
else:
|
||||
i+=1
|
||||
return MatMul(factor, *matrices).doit()
|
||||
|
||||
def bc_transpose(expr):
|
||||
collapse = block_collapse(expr.arg)
|
||||
return collapse._eval_transpose()
|
||||
|
||||
|
||||
def bc_inverse(expr):
|
||||
if isinstance(expr.arg, BlockDiagMatrix):
|
||||
return expr.inverse()
|
||||
|
||||
expr2 = blockinverse_1x1(expr)
|
||||
if expr != expr2:
|
||||
return expr2
|
||||
return blockinverse_2x2(Inverse(reblock_2x2(expr.arg)))
|
||||
|
||||
def blockinverse_1x1(expr):
|
||||
if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (1, 1):
|
||||
mat = Matrix([[expr.arg.blocks[0].inverse()]])
|
||||
return BlockMatrix(mat)
|
||||
return expr
|
||||
|
||||
|
||||
def blockinverse_2x2(expr):
|
||||
if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (2, 2):
|
||||
# See: Inverses of 2x2 Block Matrices, Tzon-Tzer Lu and Sheng-Hua Shiou
|
||||
[[A, B],
|
||||
[C, D]] = expr.arg.blocks.tolist()
|
||||
|
||||
formula = _choose_2x2_inversion_formula(A, B, C, D)
|
||||
if formula != None:
|
||||
MI = expr.arg.schur(formula).I
|
||||
if formula == 'A':
|
||||
AI = A.I
|
||||
return BlockMatrix([[AI + AI * B * MI * C * AI, -AI * B * MI], [-MI * C * AI, MI]])
|
||||
if formula == 'B':
|
||||
BI = B.I
|
||||
return BlockMatrix([[-MI * D * BI, MI], [BI + BI * A * MI * D * BI, -BI * A * MI]])
|
||||
if formula == 'C':
|
||||
CI = C.I
|
||||
return BlockMatrix([[-CI * D * MI, CI + CI * D * MI * A * CI], [MI, -MI * A * CI]])
|
||||
if formula == 'D':
|
||||
DI = D.I
|
||||
return BlockMatrix([[MI, -MI * B * DI], [-DI * C * MI, DI + DI * C * MI * B * DI]])
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
def _choose_2x2_inversion_formula(A, B, C, D):
|
||||
"""
|
||||
Assuming [[A, B], [C, D]] would form a valid square block matrix, find
|
||||
which of the classical 2x2 block matrix inversion formulas would be
|
||||
best suited.
|
||||
|
||||
Returns 'A', 'B', 'C', 'D' to represent the algorithm involving inversion
|
||||
of the given argument or None if the matrix cannot be inverted using
|
||||
any of those formulas.
|
||||
"""
|
||||
# Try to find a known invertible matrix. Note that the Schur complement
|
||||
# is currently not being considered for this
|
||||
A_inv = ask(Q.invertible(A))
|
||||
if A_inv == True:
|
||||
return 'A'
|
||||
B_inv = ask(Q.invertible(B))
|
||||
if B_inv == True:
|
||||
return 'B'
|
||||
C_inv = ask(Q.invertible(C))
|
||||
if C_inv == True:
|
||||
return 'C'
|
||||
D_inv = ask(Q.invertible(D))
|
||||
if D_inv == True:
|
||||
return 'D'
|
||||
# Otherwise try to find a matrix that isn't known to be non-invertible
|
||||
if A_inv != False:
|
||||
return 'A'
|
||||
if B_inv != False:
|
||||
return 'B'
|
||||
if C_inv != False:
|
||||
return 'C'
|
||||
if D_inv != False:
|
||||
return 'D'
|
||||
return None
|
||||
|
||||
|
||||
def deblock(B):
|
||||
""" Flatten a BlockMatrix of BlockMatrices """
|
||||
if not isinstance(B, BlockMatrix) or not B.blocks.has(BlockMatrix):
|
||||
return B
|
||||
wrap = lambda x: x if isinstance(x, BlockMatrix) else BlockMatrix([[x]])
|
||||
bb = B.blocks.applyfunc(wrap) # everything is a block
|
||||
|
||||
try:
|
||||
MM = Matrix(0, sum(bb[0, i].blocks.shape[1] for i in range(bb.shape[1])), [])
|
||||
for row in range(0, bb.shape[0]):
|
||||
M = Matrix(bb[row, 0].blocks)
|
||||
for col in range(1, bb.shape[1]):
|
||||
M = M.row_join(bb[row, col].blocks)
|
||||
MM = MM.col_join(M)
|
||||
|
||||
return BlockMatrix(MM)
|
||||
except ShapeError:
|
||||
return B
|
||||
|
||||
|
||||
def reblock_2x2(expr):
|
||||
"""
|
||||
Reblock a BlockMatrix so that it has 2x2 blocks of block matrices. If
|
||||
possible in such a way that the matrix continues to be invertible using the
|
||||
classical 2x2 block inversion formulas.
|
||||
"""
|
||||
if not isinstance(expr, BlockMatrix) or not all(d > 2 for d in expr.blockshape):
|
||||
return expr
|
||||
|
||||
BM = BlockMatrix # for brevity's sake
|
||||
rowblocks, colblocks = expr.blockshape
|
||||
blocks = expr.blocks
|
||||
for i in range(1, rowblocks):
|
||||
for j in range(1, colblocks):
|
||||
# try to split rows at i and cols at j
|
||||
A = bc_unpack(BM(blocks[:i, :j]))
|
||||
B = bc_unpack(BM(blocks[:i, j:]))
|
||||
C = bc_unpack(BM(blocks[i:, :j]))
|
||||
D = bc_unpack(BM(blocks[i:, j:]))
|
||||
|
||||
formula = _choose_2x2_inversion_formula(A, B, C, D)
|
||||
if formula is not None:
|
||||
return BlockMatrix([[A, B], [C, D]])
|
||||
|
||||
# else: nothing worked, just split upper left corner
|
||||
return BM([[blocks[0, 0], BM(blocks[0, 1:])],
|
||||
[BM(blocks[1:, 0]), BM(blocks[1:, 1:])]])
|
||||
|
||||
|
||||
def bounds(sizes):
|
||||
""" Convert sequence of numbers into pairs of low-high pairs
|
||||
|
||||
>>> from sympy.matrices.expressions.blockmatrix import bounds
|
||||
>>> bounds((1, 10, 50))
|
||||
[(0, 1), (1, 11), (11, 61)]
|
||||
"""
|
||||
low = 0
|
||||
rv = []
|
||||
for size in sizes:
|
||||
rv.append((low, low + size))
|
||||
low += size
|
||||
return rv
|
||||
|
||||
def blockcut(expr, rowsizes, colsizes):
|
||||
""" Cut a matrix expression into Blocks
|
||||
|
||||
>>> from sympy import ImmutableMatrix, blockcut
|
||||
>>> M = ImmutableMatrix(4, 4, range(16))
|
||||
>>> B = blockcut(M, (1, 3), (1, 3))
|
||||
>>> type(B).__name__
|
||||
'BlockMatrix'
|
||||
>>> ImmutableMatrix(B.blocks[0, 1])
|
||||
Matrix([[1, 2, 3]])
|
||||
"""
|
||||
|
||||
rowbounds = bounds(rowsizes)
|
||||
colbounds = bounds(colsizes)
|
||||
return BlockMatrix([[MatrixSlice(expr, rowbound, colbound)
|
||||
for colbound in colbounds]
|
||||
for rowbound in rowbounds])
|
||||
@@ -0,0 +1,56 @@
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.polys.polytools import Poly
|
||||
|
||||
from .matexpr import MatrixExpr
|
||||
|
||||
|
||||
class CompanionMatrix(MatrixExpr):
|
||||
"""A symbolic companion matrix of a polynomial.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Poly, Symbol, symbols
|
||||
>>> from sympy.matrices.expressions import CompanionMatrix
|
||||
>>> x = Symbol('x')
|
||||
>>> c0, c1, c2, c3, c4 = symbols('c0:5')
|
||||
>>> p = Poly(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + x**5, x)
|
||||
>>> CompanionMatrix(p)
|
||||
CompanionMatrix(Poly(x**5 + c4*x**4 + c3*x**3 + c2*x**2 + c1*x + c0,
|
||||
x, domain='ZZ[c0,c1,c2,c3,c4]'))
|
||||
"""
|
||||
def __new__(cls, poly):
|
||||
poly = _sympify(poly)
|
||||
if not isinstance(poly, Poly):
|
||||
raise ValueError("{} must be a Poly instance.".format(poly))
|
||||
if not poly.is_monic:
|
||||
raise ValueError("{} must be a monic polynomial.".format(poly))
|
||||
if not poly.is_univariate:
|
||||
raise ValueError(
|
||||
"{} must be a univariate polynomial.".format(poly))
|
||||
if not poly.degree() >= 1:
|
||||
raise ValueError(
|
||||
"{} must have degree not less than 1.".format(poly))
|
||||
|
||||
return super().__new__(cls, poly)
|
||||
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
poly = self.args[0]
|
||||
size = poly.degree()
|
||||
return size, size
|
||||
|
||||
|
||||
def _entry(self, i, j):
|
||||
if j == self.cols - 1:
|
||||
return -self.args[0].all_coeffs()[-1 - i]
|
||||
elif i == j + 1:
|
||||
return S.One
|
||||
return S.Zero
|
||||
|
||||
|
||||
def as_explicit(self):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
return ImmutableDenseMatrix.companion(self.args[0])
|
||||
@@ -0,0 +1,148 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
|
||||
|
||||
class Determinant(Expr):
|
||||
"""Matrix Determinant
|
||||
|
||||
Represents the determinant of a matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Determinant, eye
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> Determinant(A)
|
||||
Determinant(A)
|
||||
>>> Determinant(eye(3)).doit()
|
||||
1
|
||||
"""
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, mat):
|
||||
mat = sympify(mat)
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("Input to Determinant, %s, not a matrix" % str(mat))
|
||||
|
||||
if mat.is_square is False:
|
||||
raise NonSquareMatrixError("Det of a non-square matrix")
|
||||
|
||||
return Basic.__new__(cls, mat)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
return self.arg.kind.element_kind
|
||||
|
||||
def doit(self, **hints):
|
||||
arg = self.arg
|
||||
if hints.get('deep', True):
|
||||
arg = arg.doit(**hints)
|
||||
|
||||
result = arg._eval_determinant()
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def det(matexpr):
|
||||
""" Matrix Determinant
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, det, eye
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> det(A)
|
||||
Determinant(A)
|
||||
>>> det(eye(3))
|
||||
1
|
||||
"""
|
||||
|
||||
return Determinant(matexpr).doit()
|
||||
|
||||
class Permanent(Expr):
|
||||
"""Matrix Permanent
|
||||
|
||||
Represents the permanent of a matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Permanent, ones
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> Permanent(A)
|
||||
Permanent(A)
|
||||
>>> Permanent(ones(3, 3)).doit()
|
||||
6
|
||||
"""
|
||||
|
||||
def __new__(cls, mat):
|
||||
mat = sympify(mat)
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("Input to Permanent, %s, not a matrix" % str(mat))
|
||||
|
||||
return Basic.__new__(cls, mat)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
def doit(self, expand=False, **hints):
|
||||
if isinstance(self.arg, MatrixBase):
|
||||
return self.arg.per()
|
||||
else:
|
||||
return self
|
||||
|
||||
def per(matexpr):
|
||||
""" Matrix Permanent
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Matrix, per, ones
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> per(A)
|
||||
Permanent(A)
|
||||
>>> per(ones(5, 5))
|
||||
120
|
||||
>>> M = Matrix([1, 2, 5])
|
||||
>>> per(M)
|
||||
8
|
||||
"""
|
||||
|
||||
return Permanent(matexpr).doit()
|
||||
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
|
||||
|
||||
def refine_Determinant(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine, det
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> det(X)
|
||||
Determinant(X)
|
||||
>>> with assuming(Q.orthogonal(X)):
|
||||
... print(refine(det(X)))
|
||||
1
|
||||
"""
|
||||
if ask(Q.orthogonal(expr.arg), assumptions):
|
||||
return S.One
|
||||
elif ask(Q.singular(expr.arg), assumptions):
|
||||
return S.Zero
|
||||
elif ask(Q.unit_triangular(expr.arg), assumptions):
|
||||
return S.One
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
handlers_dict['Determinant'] = refine_Determinant
|
||||
@@ -0,0 +1,220 @@
|
||||
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.matrixbase 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()
|
||||
@@ -0,0 +1,55 @@
|
||||
from sympy.core import Basic, Expr
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
|
||||
|
||||
class DotProduct(Expr):
|
||||
"""
|
||||
Dot product of vector matrices
|
||||
|
||||
The input should be two 1 x n or n x 1 matrices. The output represents the
|
||||
scalar dotproduct.
|
||||
|
||||
This is similar to using MatrixElement and MatMul, except DotProduct does
|
||||
not require that one vector to be a row vector and the other vector to be
|
||||
a column vector.
|
||||
|
||||
>>> from sympy import MatrixSymbol, DotProduct
|
||||
>>> A = MatrixSymbol('A', 1, 3)
|
||||
>>> B = MatrixSymbol('B', 1, 3)
|
||||
>>> DotProduct(A, B)
|
||||
DotProduct(A, B)
|
||||
>>> DotProduct(A, B).doit()
|
||||
A[0, 0]*B[0, 0] + A[0, 1]*B[0, 1] + A[0, 2]*B[0, 2]
|
||||
"""
|
||||
|
||||
def __new__(cls, arg1, arg2):
|
||||
arg1, arg2 = _sympify((arg1, arg2))
|
||||
|
||||
if not arg1.is_Matrix:
|
||||
raise TypeError("Argument 1 of DotProduct is not a matrix")
|
||||
if not arg2.is_Matrix:
|
||||
raise TypeError("Argument 2 of DotProduct is not a matrix")
|
||||
if not (1 in arg1.shape):
|
||||
raise TypeError("Argument 1 of DotProduct is not a vector")
|
||||
if not (1 in arg2.shape):
|
||||
raise TypeError("Argument 2 of DotProduct is not a vector")
|
||||
|
||||
if set(arg1.shape) != set(arg2.shape):
|
||||
raise TypeError("DotProduct arguments are not the same length")
|
||||
|
||||
return Basic.__new__(cls, arg1, arg2)
|
||||
|
||||
def doit(self, expand=False, **hints):
|
||||
if self.args[0].shape == self.args[1].shape:
|
||||
if self.args[0].shape[0] == 1:
|
||||
mul = self.args[0]*transpose(self.args[1])
|
||||
else:
|
||||
mul = transpose(self.args[0])*self.args[1]
|
||||
else:
|
||||
if self.args[0].shape[0] == 1:
|
||||
mul = self.args[0]*self.args[1]
|
||||
else:
|
||||
mul = transpose(self.args[0])*transpose(self.args[1])
|
||||
|
||||
return mul[0]
|
||||
@@ -0,0 +1,62 @@
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.assumptions.ask import Q
|
||||
|
||||
class Factorization(MatrixExpr):
|
||||
arg = property(lambda self: self.args[0])
|
||||
shape = property(lambda self: self.arg.shape) # type: ignore
|
||||
|
||||
class LofLU(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.lower_triangular,)
|
||||
class UofLU(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.upper_triangular,)
|
||||
|
||||
class LofCholesky(LofLU): pass
|
||||
class UofCholesky(UofLU): pass
|
||||
|
||||
class QofQR(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
class RofQR(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.upper_triangular,)
|
||||
|
||||
class EigenVectors(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
class EigenValues(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.diagonal,)
|
||||
|
||||
class UofSVD(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
class SofSVD(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.diagonal,)
|
||||
class VofSVD(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
|
||||
|
||||
def lu(expr):
|
||||
return LofLU(expr), UofLU(expr)
|
||||
|
||||
def qr(expr):
|
||||
return QofQR(expr), RofQR(expr)
|
||||
|
||||
def eig(expr):
|
||||
return EigenValues(expr), EigenVectors(expr)
|
||||
|
||||
def svd(expr):
|
||||
return UofSVD(expr), SofSVD(expr), VofSVD(expr)
|
||||
@@ -0,0 +1,91 @@
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
|
||||
|
||||
class DFT(MatrixExpr):
|
||||
r"""
|
||||
Returns a discrete Fourier transform matrix. The matrix is scaled
|
||||
with :math:`\frac{1}{\sqrt{n}}` so that it is unitary.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : integer or Symbol
|
||||
Size of the transform.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import n
|
||||
>>> from sympy.matrices.expressions.fourier import DFT
|
||||
>>> DFT(3)
|
||||
DFT(3)
|
||||
>>> DFT(3).as_explicit()
|
||||
Matrix([
|
||||
[sqrt(3)/3, sqrt(3)/3, sqrt(3)/3],
|
||||
[sqrt(3)/3, sqrt(3)*exp(-2*I*pi/3)/3, sqrt(3)*exp(2*I*pi/3)/3],
|
||||
[sqrt(3)/3, sqrt(3)*exp(2*I*pi/3)/3, sqrt(3)*exp(-2*I*pi/3)/3]])
|
||||
>>> DFT(n).shape
|
||||
(n, n)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/DFT_matrix
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
n = _sympify(n)
|
||||
cls._check_dim(n)
|
||||
|
||||
obj = super().__new__(cls, n)
|
||||
return obj
|
||||
|
||||
n = property(lambda self: self.args[0]) # type: ignore
|
||||
shape = property(lambda self: (self.n, self.n)) # type: ignore
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
w = exp(-2*S.Pi*I/self.n)
|
||||
return w**(i*j) / sqrt(self.n)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return IDFT(self.n)
|
||||
|
||||
|
||||
class IDFT(DFT):
|
||||
r"""
|
||||
Returns an inverse discrete Fourier transform matrix. The matrix is scaled
|
||||
with :math:`\frac{1}{\sqrt{n}}` so that it is unitary.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : integer or Symbol
|
||||
Size of the transform
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices.expressions.fourier import DFT, IDFT
|
||||
>>> IDFT(3)
|
||||
IDFT(3)
|
||||
>>> IDFT(4)*DFT(4)
|
||||
I
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
DFT
|
||||
|
||||
"""
|
||||
def _entry(self, i, j, **kwargs):
|
||||
w = exp(-2*S.Pi*I/self.n)
|
||||
return w**(-i*j) / sqrt(self.n)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return DFT(self.n)
|
||||
@@ -0,0 +1,118 @@
|
||||
from .matexpr import MatrixExpr
|
||||
from sympy.core.function import FunctionClass, Lambda
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.core.sympify import _sympify, sympify
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.functions.elementary.complexes import re, im
|
||||
|
||||
|
||||
class FunctionMatrix(MatrixExpr):
|
||||
"""Represents a matrix using a function (``Lambda``) which gives
|
||||
outputs according to the coordinates of each matrix entries.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
rows : nonnegative integer. Can be symbolic.
|
||||
|
||||
cols : nonnegative integer. Can be symbolic.
|
||||
|
||||
lamda : Function, Lambda or str
|
||||
If it is a SymPy ``Function`` or ``Lambda`` instance,
|
||||
it should be able to accept two arguments which represents the
|
||||
matrix coordinates.
|
||||
|
||||
If it is a pure string containing Python ``lambda`` semantics,
|
||||
it is interpreted by the SymPy parser and casted into a SymPy
|
||||
``Lambda`` instance.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Creating a ``FunctionMatrix`` from ``Lambda``:
|
||||
|
||||
>>> from sympy import FunctionMatrix, symbols, Lambda, MatPow
|
||||
>>> i, j, n, m = symbols('i,j,n,m')
|
||||
>>> FunctionMatrix(n, m, Lambda((i, j), i + j))
|
||||
FunctionMatrix(n, m, Lambda((i, j), i + j))
|
||||
|
||||
Creating a ``FunctionMatrix`` from a SymPy function:
|
||||
|
||||
>>> from sympy import KroneckerDelta
|
||||
>>> X = FunctionMatrix(3, 3, KroneckerDelta)
|
||||
>>> X.as_explicit()
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
Creating a ``FunctionMatrix`` from a SymPy undefined function:
|
||||
|
||||
>>> from sympy import Function
|
||||
>>> f = Function('f')
|
||||
>>> X = FunctionMatrix(3, 3, f)
|
||||
>>> X.as_explicit()
|
||||
Matrix([
|
||||
[f(0, 0), f(0, 1), f(0, 2)],
|
||||
[f(1, 0), f(1, 1), f(1, 2)],
|
||||
[f(2, 0), f(2, 1), f(2, 2)]])
|
||||
|
||||
Creating a ``FunctionMatrix`` from Python ``lambda``:
|
||||
|
||||
>>> FunctionMatrix(n, m, 'lambda i, j: i + j')
|
||||
FunctionMatrix(n, m, Lambda((i, j), i + j))
|
||||
|
||||
Example of lazy evaluation of matrix product:
|
||||
|
||||
>>> Y = FunctionMatrix(1000, 1000, Lambda((i, j), i + j))
|
||||
>>> isinstance(Y*Y, MatPow) # this is an expression object
|
||||
True
|
||||
>>> (Y**2)[10,10] # So this is evaluated lazily
|
||||
342923500
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This class provides an alternative way to represent an extremely
|
||||
dense matrix with entries in some form of a sequence, in a most
|
||||
sparse way.
|
||||
"""
|
||||
def __new__(cls, rows, cols, lamda):
|
||||
rows, cols = _sympify(rows), _sympify(cols)
|
||||
cls._check_dim(rows)
|
||||
cls._check_dim(cols)
|
||||
|
||||
lamda = sympify(lamda)
|
||||
if not isinstance(lamda, (FunctionClass, Lambda)):
|
||||
raise ValueError(
|
||||
"{} should be compatible with SymPy function classes."
|
||||
.format(lamda))
|
||||
|
||||
if 2 not in lamda.nargs:
|
||||
raise ValueError(
|
||||
'{} should be able to accept 2 arguments.'.format(lamda))
|
||||
|
||||
if not isinstance(lamda, Lambda):
|
||||
i, j = Dummy('i'), Dummy('j')
|
||||
lamda = Lambda((i, j), lamda(i, j))
|
||||
|
||||
return super().__new__(cls, rows, cols, lamda)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0:2]
|
||||
|
||||
@property
|
||||
def lamda(self):
|
||||
return self.args[2]
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.lamda(i, j)
|
||||
|
||||
def _eval_trace(self):
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.concrete.summations import Sum
|
||||
return Trace(self).rewrite(Sum).doit()
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
return (re(Matrix(self)), im(Matrix(self)))
|
||||
@@ -0,0 +1,464 @@
|
||||
from collections import Counter
|
||||
|
||||
from sympy.core import Mul, sympify
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import ExprBuilder
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions._shape import validate_matadd_integer as validate
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix
|
||||
from sympy.strategies import (
|
||||
unpack, flatten, condition, exhaust, rm_id, sort
|
||||
)
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
|
||||
def hadamard_product(*matrices):
|
||||
"""
|
||||
Return the elementwise (aka Hadamard) product of matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import hadamard_product, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 2, 3)
|
||||
>>> B = MatrixSymbol('B', 2, 3)
|
||||
>>> hadamard_product(A)
|
||||
A
|
||||
>>> hadamard_product(A, B)
|
||||
HadamardProduct(A, B)
|
||||
>>> hadamard_product(A, B)[0, 1]
|
||||
A[0, 1]*B[0, 1]
|
||||
"""
|
||||
if not matrices:
|
||||
raise TypeError("Empty Hadamard product is undefined")
|
||||
if len(matrices) == 1:
|
||||
return matrices[0]
|
||||
return HadamardProduct(*matrices).doit()
|
||||
|
||||
|
||||
class HadamardProduct(MatrixExpr):
|
||||
"""
|
||||
Elementwise product of matrix expressions
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Hadamard product for matrix symbols:
|
||||
|
||||
>>> from sympy import hadamard_product, HadamardProduct, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 5)
|
||||
>>> isinstance(hadamard_product(A, B), HadamardProduct)
|
||||
True
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the product, use the function
|
||||
``hadamard_product()`` or ``HadamardProduct.doit``
|
||||
"""
|
||||
is_HadamardProduct = True
|
||||
|
||||
def __new__(cls, *args, evaluate=False, check=None):
|
||||
args = list(map(sympify, args))
|
||||
if len(args) == 0:
|
||||
# We currently don't have a way to support one-matrices of generic dimensions:
|
||||
raise ValueError("HadamardProduct needs at least one argument")
|
||||
|
||||
if not all(isinstance(arg, MatrixExpr) for arg in args):
|
||||
raise TypeError("Mix of Matrix and Scalar symbols")
|
||||
|
||||
if check is not None:
|
||||
sympy_deprecation_warning(
|
||||
"Passing check to HadamardProduct is deprecated and the check argument will be removed in a future version.",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target='remove-check-argument-from-matrix-operations')
|
||||
|
||||
if check is not False:
|
||||
validate(*args)
|
||||
|
||||
obj = super().__new__(cls, *args)
|
||||
if evaluate:
|
||||
obj = obj.doit(deep=False)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0].shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args])
|
||||
|
||||
def _eval_transpose(self):
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
return HadamardProduct(*list(map(transpose, self.args)))
|
||||
|
||||
def doit(self, **hints):
|
||||
expr = self.func(*(i.doit(**hints) for i in self.args))
|
||||
# Check for explicit matrices:
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
|
||||
explicit = [i for i in expr.args if isinstance(i, MatrixBase)]
|
||||
if explicit:
|
||||
remainder = [i for i in expr.args if i not in explicit]
|
||||
expl_mat = ImmutableMatrix([
|
||||
Mul.fromiter(i) for i in zip(*explicit)
|
||||
]).reshape(*self.shape)
|
||||
expr = HadamardProduct(*([expl_mat] + remainder))
|
||||
|
||||
return canonicalize(expr)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
terms = []
|
||||
args = list(self.args)
|
||||
for i in range(len(args)):
|
||||
factors = args[:i] + [args[i].diff(x)] + args[i+1:]
|
||||
terms.append(hadamard_product(*factors))
|
||||
return Add.fromiter(terms)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.matrices.expressions.matexpr import _make_matrix
|
||||
|
||||
with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)]
|
||||
lines = []
|
||||
for ind in with_x_ind:
|
||||
left_args = self.args[:ind]
|
||||
right_args = self.args[ind+1:]
|
||||
|
||||
d = self.args[ind]._eval_derivative_matrix_lines(x)
|
||||
hadam = hadamard_product(*(right_args + left_args))
|
||||
diagonal = [(0, 2), (3, 4)]
|
||||
diagonal = [e for j, e in enumerate(diagonal) if self.shape[j] != 1]
|
||||
for i in d:
|
||||
l1 = i._lines[i._first_line_index]
|
||||
l2 = i._lines[i._second_line_index]
|
||||
subexpr = ExprBuilder(
|
||||
ArrayDiagonal,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
ExprBuilder(_make_matrix, [l1]),
|
||||
hadam,
|
||||
ExprBuilder(_make_matrix, [l2]),
|
||||
]
|
||||
),
|
||||
*diagonal],
|
||||
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args[0].args
|
||||
i._first_pointer_index = 0
|
||||
i._second_pointer_parent = subexpr.args[0].args[2].args
|
||||
i._second_pointer_index = 0
|
||||
i._lines = [subexpr]
|
||||
lines.append(i)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
# TODO Implement algorithm for rewriting Hadamard product as diagonal matrix
|
||||
# if matmul identy matrix is multiplied.
|
||||
def canonicalize(x):
|
||||
"""Canonicalize the Hadamard product ``x`` with mathematical properties.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, HadamardProduct
|
||||
>>> from sympy import OneMatrix, ZeroMatrix
|
||||
>>> from sympy.matrices.expressions.hadamard import canonicalize
|
||||
>>> from sympy import init_printing
|
||||
>>> init_printing(use_unicode=False)
|
||||
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = MatrixSymbol('B', 2, 2)
|
||||
>>> C = MatrixSymbol('C', 2, 2)
|
||||
|
||||
Hadamard product associativity:
|
||||
|
||||
>>> X = HadamardProduct(A, HadamardProduct(B, C))
|
||||
>>> X
|
||||
A.*(B.*C)
|
||||
>>> canonicalize(X)
|
||||
A.*B.*C
|
||||
|
||||
Hadamard product commutativity:
|
||||
|
||||
>>> X = HadamardProduct(A, B)
|
||||
>>> Y = HadamardProduct(B, A)
|
||||
>>> X
|
||||
A.*B
|
||||
>>> Y
|
||||
B.*A
|
||||
>>> canonicalize(X)
|
||||
A.*B
|
||||
>>> canonicalize(Y)
|
||||
A.*B
|
||||
|
||||
Hadamard product identity:
|
||||
|
||||
>>> X = HadamardProduct(A, OneMatrix(2, 2))
|
||||
>>> X
|
||||
A.*1
|
||||
>>> canonicalize(X)
|
||||
A
|
||||
|
||||
Absorbing element of Hadamard product:
|
||||
|
||||
>>> X = HadamardProduct(A, ZeroMatrix(2, 2))
|
||||
>>> X
|
||||
A.*0
|
||||
>>> canonicalize(X)
|
||||
0
|
||||
|
||||
Rewriting to Hadamard Power
|
||||
|
||||
>>> X = HadamardProduct(A, A, A)
|
||||
>>> X
|
||||
A.*A.*A
|
||||
>>> canonicalize(X)
|
||||
.3
|
||||
A
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
As the Hadamard product is associative, nested products can be flattened.
|
||||
|
||||
The Hadamard product is commutative so that factors can be sorted for
|
||||
canonical form.
|
||||
|
||||
A matrix of only ones is an identity for Hadamard product,
|
||||
so every matrices of only ones can be removed.
|
||||
|
||||
Any zero matrix will make the whole product a zero matrix.
|
||||
|
||||
Duplicate elements can be collected and rewritten as HadamardPower
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Hadamard_product_(matrices)
|
||||
"""
|
||||
# Associativity
|
||||
rule = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
flatten
|
||||
)
|
||||
fun = exhaust(rule)
|
||||
x = fun(x)
|
||||
|
||||
# Identity
|
||||
fun = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
rm_id(lambda x: isinstance(x, OneMatrix))
|
||||
)
|
||||
x = fun(x)
|
||||
|
||||
# Absorbing by Zero Matrix
|
||||
def absorb(x):
|
||||
if any(isinstance(c, ZeroMatrix) for c in x.args):
|
||||
return ZeroMatrix(*x.shape)
|
||||
else:
|
||||
return x
|
||||
fun = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
absorb
|
||||
)
|
||||
x = fun(x)
|
||||
|
||||
# Rewriting with HadamardPower
|
||||
if isinstance(x, HadamardProduct):
|
||||
tally = Counter(x.args)
|
||||
|
||||
new_arg = []
|
||||
for base, exp in tally.items():
|
||||
if exp == 1:
|
||||
new_arg.append(base)
|
||||
else:
|
||||
new_arg.append(HadamardPower(base, exp))
|
||||
|
||||
x = HadamardProduct(*new_arg)
|
||||
|
||||
# Commutativity
|
||||
fun = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
sort(default_sort_key)
|
||||
)
|
||||
x = fun(x)
|
||||
|
||||
# Unpacking
|
||||
x = unpack(x)
|
||||
return x
|
||||
|
||||
|
||||
def hadamard_power(base, exp):
|
||||
base = sympify(base)
|
||||
exp = sympify(exp)
|
||||
if exp == 1:
|
||||
return base
|
||||
if not base.is_Matrix:
|
||||
return base**exp
|
||||
if exp.is_Matrix:
|
||||
raise ValueError("cannot raise expression to a matrix")
|
||||
return HadamardPower(base, exp)
|
||||
|
||||
|
||||
class HadamardPower(MatrixExpr):
|
||||
r"""
|
||||
Elementwise power of matrix expressions
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : scalar or matrix
|
||||
|
||||
exp : scalar or matrix
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
There are four definitions for the hadamard power which can be used.
|
||||
Let's consider `A, B` as `(m, n)` matrices, and `a, b` as scalars.
|
||||
|
||||
Matrix raised to a scalar exponent:
|
||||
|
||||
.. math::
|
||||
A^{\circ b} = \begin{bmatrix}
|
||||
A_{0, 0}^b & A_{0, 1}^b & \cdots & A_{0, n-1}^b \\
|
||||
A_{1, 0}^b & A_{1, 1}^b & \cdots & A_{1, n-1}^b \\
|
||||
\vdots & \vdots & \ddots & \vdots \\
|
||||
A_{m-1, 0}^b & A_{m-1, 1}^b & \cdots & A_{m-1, n-1}^b
|
||||
\end{bmatrix}
|
||||
|
||||
Scalar raised to a matrix exponent:
|
||||
|
||||
.. math::
|
||||
a^{\circ B} = \begin{bmatrix}
|
||||
a^{B_{0, 0}} & a^{B_{0, 1}} & \cdots & a^{B_{0, n-1}} \\
|
||||
a^{B_{1, 0}} & a^{B_{1, 1}} & \cdots & a^{B_{1, n-1}} \\
|
||||
\vdots & \vdots & \ddots & \vdots \\
|
||||
a^{B_{m-1, 0}} & a^{B_{m-1, 1}} & \cdots & a^{B_{m-1, n-1}}
|
||||
\end{bmatrix}
|
||||
|
||||
Matrix raised to a matrix exponent:
|
||||
|
||||
.. math::
|
||||
A^{\circ B} = \begin{bmatrix}
|
||||
A_{0, 0}^{B_{0, 0}} & A_{0, 1}^{B_{0, 1}} &
|
||||
\cdots & A_{0, n-1}^{B_{0, n-1}} \\
|
||||
A_{1, 0}^{B_{1, 0}} & A_{1, 1}^{B_{1, 1}} &
|
||||
\cdots & A_{1, n-1}^{B_{1, n-1}} \\
|
||||
\vdots & \vdots &
|
||||
\ddots & \vdots \\
|
||||
A_{m-1, 0}^{B_{m-1, 0}} & A_{m-1, 1}^{B_{m-1, 1}} &
|
||||
\cdots & A_{m-1, n-1}^{B_{m-1, n-1}}
|
||||
\end{bmatrix}
|
||||
|
||||
Scalar raised to a scalar exponent:
|
||||
|
||||
.. math::
|
||||
a^{\circ b} = a^b
|
||||
"""
|
||||
|
||||
def __new__(cls, base, exp):
|
||||
base = sympify(base)
|
||||
exp = sympify(exp)
|
||||
|
||||
if base.is_scalar and exp.is_scalar:
|
||||
return base ** exp
|
||||
|
||||
if isinstance(base, MatrixExpr) and isinstance(exp, MatrixExpr):
|
||||
validate(base, exp)
|
||||
|
||||
obj = super().__new__(cls, base, exp)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def base(self):
|
||||
return self._args[0]
|
||||
|
||||
@property
|
||||
def exp(self):
|
||||
return self._args[1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
if self.base.is_Matrix:
|
||||
return self.base.shape
|
||||
return self.exp.shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
base = self.base
|
||||
exp = self.exp
|
||||
|
||||
if base.is_Matrix:
|
||||
a = base._entry(i, j, **kwargs)
|
||||
elif base.is_scalar:
|
||||
a = base
|
||||
else:
|
||||
raise ValueError(
|
||||
'The base {} must be a scalar or a matrix.'.format(base))
|
||||
|
||||
if exp.is_Matrix:
|
||||
b = exp._entry(i, j, **kwargs)
|
||||
elif exp.is_scalar:
|
||||
b = exp
|
||||
else:
|
||||
raise ValueError(
|
||||
'The exponent {} must be a scalar or a matrix.'.format(exp))
|
||||
|
||||
return a ** b
|
||||
|
||||
def _eval_transpose(self):
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
return HadamardPower(transpose(self.base), self.exp)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
dexp = self.exp.diff(x)
|
||||
logbase = self.base.applyfunc(log)
|
||||
dlbase = logbase.diff(x)
|
||||
return hadamard_product(
|
||||
dexp*logbase + self.exp*dlbase,
|
||||
self
|
||||
)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
||||
from sympy.matrices.expressions.matexpr import _make_matrix
|
||||
|
||||
lr = self.base._eval_derivative_matrix_lines(x)
|
||||
for i in lr:
|
||||
diagonal = [(1, 2), (3, 4)]
|
||||
diagonal = [e for j, e in enumerate(diagonal) if self.base.shape[j] != 1]
|
||||
l1 = i._lines[i._first_line_index]
|
||||
l2 = i._lines[i._second_line_index]
|
||||
subexpr = ExprBuilder(
|
||||
ArrayDiagonal,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
ExprBuilder(_make_matrix, [l1]),
|
||||
self.exp*hadamard_power(self.base, self.exp-1),
|
||||
ExprBuilder(_make_matrix, [l2]),
|
||||
]
|
||||
),
|
||||
*diagonal],
|
||||
validator=ArrayDiagonal._validate
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args[0].args
|
||||
i._first_pointer_index = 0
|
||||
i._first_line_index = 0
|
||||
i._second_pointer_parent = subexpr.args[0].args[2].args
|
||||
i._second_pointer_index = 0
|
||||
i._second_line_index = 0
|
||||
i._lines = [subexpr]
|
||||
return lr
|
||||
@@ -0,0 +1,112 @@
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.core import S, Basic
|
||||
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
|
||||
|
||||
class Inverse(MatPow):
|
||||
"""
|
||||
The multiplicative inverse of a matrix expression
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the inverse, use the ``.inverse()``
|
||||
method of matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Inverse
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> B = MatrixSymbol('B', 3, 3)
|
||||
>>> Inverse(A)
|
||||
A**(-1)
|
||||
>>> A.inverse() == Inverse(A)
|
||||
True
|
||||
>>> (A*B).inverse()
|
||||
B**(-1)*A**(-1)
|
||||
>>> Inverse(A*B)
|
||||
(A*B)**(-1)
|
||||
|
||||
"""
|
||||
is_Inverse = True
|
||||
exp = S.NegativeOne
|
||||
|
||||
def __new__(cls, mat, exp=S.NegativeOne):
|
||||
# exp is there to make it consistent with
|
||||
# inverse.func(*inverse.args) == inverse
|
||||
mat = _sympify(mat)
|
||||
exp = _sympify(exp)
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("mat should be a matrix")
|
||||
if mat.is_square is False:
|
||||
raise NonSquareMatrixError("Inverse of non-square matrix %s" % mat)
|
||||
return Basic.__new__(cls, mat, exp)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.arg.shape
|
||||
|
||||
def _eval_inverse(self):
|
||||
return self.arg
|
||||
|
||||
def _eval_transpose(self):
|
||||
return Inverse(self.arg.transpose())
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return Inverse(self.arg.adjoint())
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return Inverse(self.arg.conjugate())
|
||||
|
||||
def _eval_determinant(self):
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
return 1/det(self.arg)
|
||||
|
||||
def doit(self, **hints):
|
||||
if 'inv_expand' in hints and hints['inv_expand'] == False:
|
||||
return self
|
||||
|
||||
arg = self.arg
|
||||
if hints.get('deep', True):
|
||||
arg = arg.doit(**hints)
|
||||
|
||||
return arg.inverse()
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
arg = self.args[0]
|
||||
lines = arg._eval_derivative_matrix_lines(x)
|
||||
for line in lines:
|
||||
line.first_pointer *= -self.T
|
||||
line.second_pointer *= self
|
||||
return lines
|
||||
|
||||
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
|
||||
|
||||
def refine_Inverse(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> X.I
|
||||
X**(-1)
|
||||
>>> with assuming(Q.orthogonal(X)):
|
||||
... print(refine(X.I))
|
||||
X.T
|
||||
"""
|
||||
if ask(Q.orthogonal(expr), assumptions):
|
||||
return expr.arg.T
|
||||
elif ask(Q.unitary(expr), assumptions):
|
||||
return expr.arg.conjugate()
|
||||
elif ask(Q.singular(expr), assumptions):
|
||||
raise ValueError("Inverse of singular matrix %s" % expr.arg)
|
||||
|
||||
return expr
|
||||
|
||||
handlers_dict['Inverse'] = refine_Inverse
|
||||
@@ -0,0 +1,434 @@
|
||||
"""Implementation of the Kronecker product"""
|
||||
from functools import reduce
|
||||
from math import prod
|
||||
|
||||
from sympy.core import Mul, sympify
|
||||
from sympy.functions import adjoint
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.strategies import (
|
||||
canon, condition, distribute, do_one, exhaust, flatten, typed, unpack)
|
||||
from sympy.strategies.traverse import bottom_up
|
||||
from sympy.utilities import sift
|
||||
|
||||
from .matadd import MatAdd
|
||||
from .matmul import MatMul
|
||||
from .matpow import MatPow
|
||||
|
||||
|
||||
def kronecker_product(*matrices):
|
||||
"""
|
||||
The Kronecker product of two or more arguments.
|
||||
|
||||
This computes the explicit Kronecker product for subclasses of
|
||||
``MatrixBase`` i.e. explicit matrices. Otherwise, a symbolic
|
||||
``KroneckerProduct`` object is returned.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
For ``MatrixSymbol`` arguments a ``KroneckerProduct`` object is returned.
|
||||
Elements of this matrix can be obtained by indexing, or for MatrixSymbols
|
||||
with known dimension the explicit matrix can be obtained with
|
||||
``.as_explicit()``
|
||||
|
||||
>>> from sympy import kronecker_product, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = MatrixSymbol('B', 2, 2)
|
||||
>>> kronecker_product(A)
|
||||
A
|
||||
>>> kronecker_product(A, B)
|
||||
KroneckerProduct(A, B)
|
||||
>>> kronecker_product(A, B)[0, 1]
|
||||
A[0, 0]*B[0, 1]
|
||||
>>> kronecker_product(A, B).as_explicit()
|
||||
Matrix([
|
||||
[A[0, 0]*B[0, 0], A[0, 0]*B[0, 1], A[0, 1]*B[0, 0], A[0, 1]*B[0, 1]],
|
||||
[A[0, 0]*B[1, 0], A[0, 0]*B[1, 1], A[0, 1]*B[1, 0], A[0, 1]*B[1, 1]],
|
||||
[A[1, 0]*B[0, 0], A[1, 0]*B[0, 1], A[1, 1]*B[0, 0], A[1, 1]*B[0, 1]],
|
||||
[A[1, 0]*B[1, 0], A[1, 0]*B[1, 1], A[1, 1]*B[1, 0], A[1, 1]*B[1, 1]]])
|
||||
|
||||
For explicit matrices the Kronecker product is returned as a Matrix
|
||||
|
||||
>>> from sympy import Matrix, kronecker_product
|
||||
>>> sigma_x = Matrix([
|
||||
... [0, 1],
|
||||
... [1, 0]])
|
||||
...
|
||||
>>> Isigma_y = Matrix([
|
||||
... [0, 1],
|
||||
... [-1, 0]])
|
||||
...
|
||||
>>> kronecker_product(sigma_x, Isigma_y)
|
||||
Matrix([
|
||||
[ 0, 0, 0, 1],
|
||||
[ 0, 0, -1, 0],
|
||||
[ 0, 1, 0, 0],
|
||||
[-1, 0, 0, 0]])
|
||||
|
||||
See Also
|
||||
========
|
||||
KroneckerProduct
|
||||
|
||||
"""
|
||||
if not matrices:
|
||||
raise TypeError("Empty Kronecker product is undefined")
|
||||
if len(matrices) == 1:
|
||||
return matrices[0]
|
||||
else:
|
||||
return KroneckerProduct(*matrices).doit()
|
||||
|
||||
|
||||
class KroneckerProduct(MatrixExpr):
|
||||
"""
|
||||
The Kronecker product of two or more arguments.
|
||||
|
||||
The Kronecker product is a non-commutative product of matrices.
|
||||
Given two matrices of dimension (m, n) and (s, t) it produces a matrix
|
||||
of dimension (m s, n t).
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the product, use the function
|
||||
``kronecker_product()`` or call the ``.doit()`` or ``.as_explicit()``
|
||||
methods.
|
||||
|
||||
>>> from sympy import KroneckerProduct, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 5)
|
||||
>>> isinstance(KroneckerProduct(A, B), KroneckerProduct)
|
||||
True
|
||||
"""
|
||||
is_KroneckerProduct = True
|
||||
|
||||
def __new__(cls, *args, check=True):
|
||||
args = list(map(sympify, args))
|
||||
if all(a.is_Identity for a in args):
|
||||
ret = Identity(prod(a.rows for a in args))
|
||||
if all(isinstance(a, MatrixBase) for a in args):
|
||||
return ret.as_explicit()
|
||||
else:
|
||||
return ret
|
||||
|
||||
if check:
|
||||
validate(*args)
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
rows, cols = self.args[0].shape
|
||||
for mat in self.args[1:]:
|
||||
rows *= mat.rows
|
||||
cols *= mat.cols
|
||||
return (rows, cols)
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
result = 1
|
||||
for mat in reversed(self.args):
|
||||
i, m = divmod(i, mat.rows)
|
||||
j, n = divmod(j, mat.cols)
|
||||
result *= mat[m, n]
|
||||
return result
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return KroneckerProduct(*list(map(adjoint, self.args))).doit()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return KroneckerProduct(*[a.conjugate() for a in self.args]).doit()
|
||||
|
||||
def _eval_transpose(self):
|
||||
return KroneckerProduct(*list(map(transpose, self.args))).doit()
|
||||
|
||||
def _eval_trace(self):
|
||||
from .trace import trace
|
||||
return Mul(*[trace(a) for a in self.args])
|
||||
|
||||
def _eval_determinant(self):
|
||||
from .determinant import det, Determinant
|
||||
if not all(a.is_square for a in self.args):
|
||||
return Determinant(self)
|
||||
|
||||
m = self.rows
|
||||
return Mul(*[det(a)**(m/a.rows) for a in self.args])
|
||||
|
||||
def _eval_inverse(self):
|
||||
try:
|
||||
return KroneckerProduct(*[a.inverse() for a in self.args])
|
||||
except ShapeError:
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
return Inverse(self)
|
||||
|
||||
def structurally_equal(self, other):
|
||||
'''Determine whether two matrices have the same Kronecker product structure
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import KroneckerProduct, MatrixSymbol, symbols
|
||||
>>> m, n = symbols(r'm, n', integer=True)
|
||||
>>> A = MatrixSymbol('A', m, m)
|
||||
>>> B = MatrixSymbol('B', n, n)
|
||||
>>> C = MatrixSymbol('C', m, m)
|
||||
>>> D = MatrixSymbol('D', n, n)
|
||||
>>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(C, D))
|
||||
True
|
||||
>>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(D, C))
|
||||
False
|
||||
>>> KroneckerProduct(A, B).structurally_equal(C)
|
||||
False
|
||||
'''
|
||||
# Inspired by BlockMatrix
|
||||
return (isinstance(other, KroneckerProduct)
|
||||
and self.shape == other.shape
|
||||
and len(self.args) == len(other.args)
|
||||
and all(a.shape == b.shape for (a, b) in zip(self.args, other.args)))
|
||||
|
||||
def has_matching_shape(self, other):
|
||||
'''Determine whether two matrices have the appropriate structure to bring matrix
|
||||
multiplication inside the KroneckerProdut
|
||||
|
||||
Examples
|
||||
========
|
||||
>>> from sympy import KroneckerProduct, MatrixSymbol, symbols
|
||||
>>> m, n = symbols(r'm, n', integer=True)
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(B, A))
|
||||
True
|
||||
>>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(A, B))
|
||||
False
|
||||
>>> KroneckerProduct(A, B).has_matching_shape(A)
|
||||
False
|
||||
'''
|
||||
return (isinstance(other, KroneckerProduct)
|
||||
and self.cols == other.rows
|
||||
and len(self.args) == len(other.args)
|
||||
and all(a.cols == b.rows for (a, b) in zip(self.args, other.args)))
|
||||
|
||||
def _eval_expand_kroneckerproduct(self, **hints):
|
||||
return flatten(canon(typed({KroneckerProduct: distribute(KroneckerProduct, MatAdd)}))(self))
|
||||
|
||||
def _kronecker_add(self, other):
|
||||
if self.structurally_equal(other):
|
||||
return self.__class__(*[a + b for (a, b) in zip(self.args, other.args)])
|
||||
else:
|
||||
return self + other
|
||||
|
||||
def _kronecker_mul(self, other):
|
||||
if self.has_matching_shape(other):
|
||||
return self.__class__(*[a*b for (a, b) in zip(self.args, other.args)])
|
||||
else:
|
||||
return self * other
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = [arg.doit(**hints) for arg in self.args]
|
||||
else:
|
||||
args = self.args
|
||||
return canonicalize(KroneckerProduct(*args))
|
||||
|
||||
|
||||
def validate(*args):
|
||||
if not all(arg.is_Matrix for arg in args):
|
||||
raise TypeError("Mix of Matrix and Scalar symbols")
|
||||
|
||||
|
||||
# rules
|
||||
|
||||
def extract_commutative(kron):
|
||||
c_part = []
|
||||
nc_part = []
|
||||
for arg in kron.args:
|
||||
c, nc = arg.args_cnc()
|
||||
c_part.extend(c)
|
||||
nc_part.append(Mul._from_args(nc))
|
||||
|
||||
c_part = Mul(*c_part)
|
||||
if c_part != 1:
|
||||
return c_part*KroneckerProduct(*nc_part)
|
||||
return kron
|
||||
|
||||
|
||||
def matrix_kronecker_product(*matrices):
|
||||
"""Compute the Kronecker product of a sequence of SymPy Matrices.
|
||||
|
||||
This is the standard Kronecker product of matrices [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
matrices : tuple of MatrixBase instances
|
||||
The matrices to take the Kronecker product of.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
matrix : MatrixBase
|
||||
The Kronecker product matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.matrices.expressions.kronecker import (
|
||||
... matrix_kronecker_product)
|
||||
|
||||
>>> m1 = Matrix([[1,2],[3,4]])
|
||||
>>> m2 = Matrix([[1,0],[0,1]])
|
||||
>>> matrix_kronecker_product(m1, m2)
|
||||
Matrix([
|
||||
[1, 0, 2, 0],
|
||||
[0, 1, 0, 2],
|
||||
[3, 0, 4, 0],
|
||||
[0, 3, 0, 4]])
|
||||
>>> matrix_kronecker_product(m2, m1)
|
||||
Matrix([
|
||||
[1, 2, 0, 0],
|
||||
[3, 4, 0, 0],
|
||||
[0, 0, 1, 2],
|
||||
[0, 0, 3, 4]])
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Kronecker_product
|
||||
"""
|
||||
# Make sure we have a sequence of Matrices
|
||||
if not all(isinstance(m, MatrixBase) for m in matrices):
|
||||
raise TypeError(
|
||||
'Sequence of Matrices expected, got: %s' % repr(matrices)
|
||||
)
|
||||
|
||||
# Pull out the first element in the product.
|
||||
matrix_expansion = matrices[-1]
|
||||
# Do the kronecker product working from right to left.
|
||||
for mat in reversed(matrices[:-1]):
|
||||
rows = mat.rows
|
||||
cols = mat.cols
|
||||
# Go through each row appending kronecker product to.
|
||||
# running matrix_expansion.
|
||||
for i in range(rows):
|
||||
start = matrix_expansion*mat[i*cols]
|
||||
# Go through each column joining each item
|
||||
for j in range(cols - 1):
|
||||
start = start.row_join(
|
||||
matrix_expansion*mat[i*cols + j + 1]
|
||||
)
|
||||
# If this is the first element, make it the start of the
|
||||
# new row.
|
||||
if i == 0:
|
||||
next = start
|
||||
else:
|
||||
next = next.col_join(start)
|
||||
matrix_expansion = next
|
||||
|
||||
MatrixClass = max(matrices, key=lambda M: M._class_priority).__class__
|
||||
if isinstance(matrix_expansion, MatrixClass):
|
||||
return matrix_expansion
|
||||
else:
|
||||
return MatrixClass(matrix_expansion)
|
||||
|
||||
|
||||
def explicit_kronecker_product(kron):
|
||||
# Make sure we have a sequence of Matrices
|
||||
if not all(isinstance(m, MatrixBase) for m in kron.args):
|
||||
return kron
|
||||
|
||||
return matrix_kronecker_product(*kron.args)
|
||||
|
||||
|
||||
rules = (unpack,
|
||||
explicit_kronecker_product,
|
||||
flatten,
|
||||
extract_commutative)
|
||||
|
||||
canonicalize = exhaust(condition(lambda x: isinstance(x, KroneckerProduct),
|
||||
do_one(*rules)))
|
||||
|
||||
|
||||
def _kronecker_dims_key(expr):
|
||||
if isinstance(expr, KroneckerProduct):
|
||||
return tuple(a.shape for a in expr.args)
|
||||
else:
|
||||
return (0,)
|
||||
|
||||
|
||||
def kronecker_mat_add(expr):
|
||||
args = sift(expr.args, _kronecker_dims_key)
|
||||
nonkrons = args.pop((0,), None)
|
||||
if not args:
|
||||
return expr
|
||||
|
||||
krons = [reduce(lambda x, y: x._kronecker_add(y), group)
|
||||
for group in args.values()]
|
||||
|
||||
if not nonkrons:
|
||||
return MatAdd(*krons)
|
||||
else:
|
||||
return MatAdd(*krons) + nonkrons
|
||||
|
||||
|
||||
def kronecker_mat_mul(expr):
|
||||
# modified from block matrix code
|
||||
factor, matrices = expr.as_coeff_matrices()
|
||||
|
||||
i = 0
|
||||
while i < len(matrices) - 1:
|
||||
A, B = matrices[i:i+2]
|
||||
if isinstance(A, KroneckerProduct) and isinstance(B, KroneckerProduct):
|
||||
matrices[i] = A._kronecker_mul(B)
|
||||
matrices.pop(i+1)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return factor*MatMul(*matrices)
|
||||
|
||||
|
||||
def kronecker_mat_pow(expr):
|
||||
if isinstance(expr.base, KroneckerProduct) and all(a.is_square for a in expr.base.args):
|
||||
return KroneckerProduct(*[MatPow(a, expr.exp) for a in expr.base.args])
|
||||
else:
|
||||
return expr
|
||||
|
||||
|
||||
def combine_kronecker(expr):
|
||||
"""Combine KronekeckerProduct with expression.
|
||||
|
||||
If possible write operations on KroneckerProducts of compatible shapes
|
||||
as a single KroneckerProduct.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices.expressions import combine_kronecker
|
||||
>>> from sympy import MatrixSymbol, KroneckerProduct, symbols
|
||||
>>> m, n = symbols(r'm, n', integer=True)
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> combine_kronecker(KroneckerProduct(A, B)*KroneckerProduct(B, A))
|
||||
KroneckerProduct(A*B, B*A)
|
||||
>>> combine_kronecker(KroneckerProduct(A, B)+KroneckerProduct(B.T, A.T))
|
||||
KroneckerProduct(A + B.T, B + A.T)
|
||||
>>> C = MatrixSymbol('C', n, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> combine_kronecker(KroneckerProduct(C, D)**m)
|
||||
KroneckerProduct(C**m, D**m)
|
||||
"""
|
||||
def haskron(expr):
|
||||
return isinstance(expr, MatrixExpr) and expr.has(KroneckerProduct)
|
||||
|
||||
rule = exhaust(
|
||||
bottom_up(exhaust(condition(haskron, typed(
|
||||
{MatAdd: kronecker_mat_add,
|
||||
MatMul: kronecker_mat_mul,
|
||||
MatPow: kronecker_mat_pow})))))
|
||||
result = rule(expr)
|
||||
doit = getattr(result, 'doit', None)
|
||||
if doit is not None:
|
||||
return doit()
|
||||
else:
|
||||
return result
|
||||
@@ -0,0 +1,155 @@
|
||||
from functools import reduce
|
||||
import operator
|
||||
|
||||
from sympy.core import Basic, sympify
|
||||
from sympy.core.add import add, Add, _could_extract_minus_sign
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.functions import adjoint
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
from sympy.strategies import (rm_id, unpack, flatten, sort, condition,
|
||||
exhaust, do_one, glom)
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, GenericZeroMatrix
|
||||
from sympy.matrices.expressions._shape import validate_matadd_integer as validate
|
||||
from sympy.utilities.iterables import sift
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
# XXX: MatAdd should perhaps not subclass directly from Add
|
||||
class MatAdd(MatrixExpr, Add):
|
||||
"""A Sum of Matrix Expressions
|
||||
|
||||
MatAdd inherits from and operates like SymPy Add
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatAdd, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 5)
|
||||
>>> C = MatrixSymbol('C', 5, 5)
|
||||
>>> MatAdd(A, B, C)
|
||||
A + B + C
|
||||
"""
|
||||
is_MatAdd = True
|
||||
|
||||
identity = GenericZeroMatrix()
|
||||
|
||||
def __new__(cls, *args, evaluate=False, check=None, _sympify=True):
|
||||
if not args:
|
||||
return cls.identity
|
||||
|
||||
# This must be removed aggressively in the constructor to avoid
|
||||
# TypeErrors from GenericZeroMatrix().shape
|
||||
args = list(filter(lambda i: cls.identity != i, args))
|
||||
if _sympify:
|
||||
args = list(map(sympify, args))
|
||||
|
||||
if not all(isinstance(arg, MatrixExpr) for arg in args):
|
||||
raise TypeError("Mix of Matrix and Scalar symbols")
|
||||
|
||||
obj = Basic.__new__(cls, *args)
|
||||
|
||||
if check is not None:
|
||||
sympy_deprecation_warning(
|
||||
"Passing check to MatAdd is deprecated and the check argument will be removed in a future version.",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target='remove-check-argument-from-matrix-operations')
|
||||
|
||||
if check is not False:
|
||||
validate(*args)
|
||||
|
||||
if evaluate:
|
||||
obj = cls._evaluate(obj)
|
||||
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def _evaluate(cls, expr):
|
||||
return canonicalize(expr)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0].shape
|
||||
|
||||
def could_extract_minus_sign(self):
|
||||
return _could_extract_minus_sign(self)
|
||||
|
||||
def expand(self, **kwargs):
|
||||
expanded = super(MatAdd, self).expand(**kwargs)
|
||||
return self._evaluate(expanded)
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return Add(*[arg._entry(i, j, **kwargs) for arg in self.args])
|
||||
|
||||
def _eval_transpose(self):
|
||||
return MatAdd(*[transpose(arg) for arg in self.args]).doit()
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return MatAdd(*[adjoint(arg) for arg in self.args]).doit()
|
||||
|
||||
def _eval_trace(self):
|
||||
from .trace import trace
|
||||
return Add(*[trace(arg) for arg in self.args]).doit()
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = [arg.doit(**hints) for arg in self.args]
|
||||
else:
|
||||
args = self.args
|
||||
return canonicalize(MatAdd(*args))
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
add_lines = [arg._eval_derivative_matrix_lines(x) for arg in self.args]
|
||||
return [j for i in add_lines for j in i]
|
||||
|
||||
add.register_handlerclass((Add, MatAdd), MatAdd)
|
||||
|
||||
|
||||
factor_of = lambda arg: arg.as_coeff_mmul()[0]
|
||||
matrix_of = lambda arg: unpack(arg.as_coeff_mmul()[1])
|
||||
def combine(cnt, mat):
|
||||
if cnt == 1:
|
||||
return mat
|
||||
else:
|
||||
return cnt * mat
|
||||
|
||||
|
||||
def merge_explicit(matadd):
|
||||
""" Merge explicit MatrixBase arguments
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, eye, Matrix, MatAdd, pprint
|
||||
>>> from sympy.matrices.expressions.matadd import merge_explicit
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = eye(2)
|
||||
>>> C = Matrix([[1, 2], [3, 4]])
|
||||
>>> X = MatAdd(A, B, C)
|
||||
>>> pprint(X)
|
||||
[1 0] [1 2]
|
||||
A + [ ] + [ ]
|
||||
[0 1] [3 4]
|
||||
>>> pprint(merge_explicit(X))
|
||||
[2 2]
|
||||
A + [ ]
|
||||
[3 5]
|
||||
"""
|
||||
groups = sift(matadd.args, lambda arg: isinstance(arg, MatrixBase))
|
||||
if len(groups[True]) > 1:
|
||||
return MatAdd(*(groups[False] + [reduce(operator.add, groups[True])]))
|
||||
else:
|
||||
return matadd
|
||||
|
||||
|
||||
rules = (rm_id(lambda x: x == 0 or isinstance(x, ZeroMatrix)),
|
||||
unpack,
|
||||
flatten,
|
||||
glom(matrix_of, factor_of, combine),
|
||||
merge_explicit,
|
||||
sort(default_sort_key))
|
||||
|
||||
canonicalize = exhaust(condition(lambda x: isinstance(x, MatAdd),
|
||||
do_one(*rules)))
|
||||
@@ -0,0 +1,888 @@
|
||||
from __future__ import annotations
|
||||
from functools import wraps
|
||||
|
||||
from sympy.core import S, Integer, Basic, Mul, Add
|
||||
from sympy.core.assumptions import check_assumptions
|
||||
from sympy.core.decorators import call_highest_priority
|
||||
from sympy.core.expr import Expr, ExprBuilder
|
||||
from sympy.core.logic import FuzzyBool
|
||||
from sympy.core.symbol import Str, Dummy, symbols, Symbol
|
||||
from sympy.core.sympify import SympifyError, _sympify
|
||||
from sympy.external.gmpy import SYMPY_INTS
|
||||
from sympy.functions import conjugate, adjoint
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.kind import MatrixKind
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.multipledispatch import dispatch
|
||||
from sympy.utilities.misc import filldedent
|
||||
|
||||
|
||||
def _sympifyit(arg, retval=None):
|
||||
# This version of _sympifyit sympifies MutableMatrix objects
|
||||
def deco(func):
|
||||
@wraps(func)
|
||||
def __sympifyit_wrapper(a, b):
|
||||
try:
|
||||
b = _sympify(b)
|
||||
return func(a, b)
|
||||
except SympifyError:
|
||||
return retval
|
||||
|
||||
return __sympifyit_wrapper
|
||||
|
||||
return deco
|
||||
|
||||
|
||||
class MatrixExpr(Expr):
|
||||
"""Superclass for Matrix Expressions
|
||||
|
||||
MatrixExprs represent abstract matrices, linear transformations represented
|
||||
within a particular basis.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> y = MatrixSymbol('y', 3, 1)
|
||||
>>> x = (A.T*A).I * A * y
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
MatrixSymbol, MatAdd, MatMul, Transpose, Inverse
|
||||
"""
|
||||
__slots__: tuple[str, ...] = ()
|
||||
|
||||
# Should not be considered iterable by the
|
||||
# sympy.utilities.iterables.iterable function. Subclass that actually are
|
||||
# iterable (i.e., explicit matrices) should set this to True.
|
||||
_iterable = False
|
||||
|
||||
_op_priority = 11.0
|
||||
|
||||
is_Matrix: bool = True
|
||||
is_MatrixExpr: bool = True
|
||||
is_Identity: FuzzyBool = None
|
||||
is_Inverse = False
|
||||
is_Transpose = False
|
||||
is_ZeroMatrix = False
|
||||
is_MatAdd = False
|
||||
is_MatMul = False
|
||||
|
||||
is_commutative = False
|
||||
is_number = False
|
||||
is_symbol = False
|
||||
is_scalar = False
|
||||
|
||||
kind: MatrixKind = MatrixKind()
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
args = map(_sympify, args)
|
||||
return Basic.__new__(cls, *args, **kwargs)
|
||||
|
||||
# The following is adapted from the core Expr object
|
||||
|
||||
@property
|
||||
def shape(self) -> tuple[Expr, Expr]:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def _add_handler(self):
|
||||
return MatAdd
|
||||
|
||||
@property
|
||||
def _mul_handler(self):
|
||||
return MatMul
|
||||
|
||||
def __neg__(self):
|
||||
return MatMul(S.NegativeOne, self).doit()
|
||||
|
||||
def __abs__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__radd__')
|
||||
def __add__(self, other):
|
||||
return MatAdd(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__add__')
|
||||
def __radd__(self, other):
|
||||
return MatAdd(other, self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rsub__')
|
||||
def __sub__(self, other):
|
||||
return MatAdd(self, -other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__sub__')
|
||||
def __rsub__(self, other):
|
||||
return MatAdd(other, -self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rmul__')
|
||||
def __mul__(self, other):
|
||||
return MatMul(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rmul__')
|
||||
def __matmul__(self, other):
|
||||
return MatMul(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__mul__')
|
||||
def __rmul__(self, other):
|
||||
return MatMul(other, self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__mul__')
|
||||
def __rmatmul__(self, other):
|
||||
return MatMul(other, self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rpow__')
|
||||
def __pow__(self, other):
|
||||
return MatPow(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__pow__')
|
||||
def __rpow__(self, other):
|
||||
raise NotImplementedError("Matrix Power not defined")
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rtruediv__')
|
||||
def __truediv__(self, other):
|
||||
return self * other**S.NegativeOne
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__truediv__')
|
||||
def __rtruediv__(self, other):
|
||||
raise NotImplementedError()
|
||||
#return MatMul(other, Pow(self, S.NegativeOne))
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
return self.shape[0]
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
return self.shape[1]
|
||||
|
||||
@property
|
||||
def is_square(self) -> bool | None:
|
||||
rows, cols = self.shape
|
||||
if isinstance(rows, Integer) and isinstance(cols, Integer):
|
||||
return rows == cols
|
||||
if rows == cols:
|
||||
return True
|
||||
return None
|
||||
|
||||
def _eval_conjugate(self):
|
||||
from sympy.matrices.expressions.adjoint import Adjoint
|
||||
return Adjoint(Transpose(self))
|
||||
|
||||
def as_real_imag(self, deep=True, **hints):
|
||||
return self._eval_as_real_imag()
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
real = S.Half * (self + self._eval_conjugate())
|
||||
im = (self - self._eval_conjugate())/(2*S.ImaginaryUnit)
|
||||
return (real, im)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return Inverse(self)
|
||||
|
||||
def _eval_determinant(self):
|
||||
return Determinant(self)
|
||||
|
||||
def _eval_transpose(self):
|
||||
return Transpose(self)
|
||||
|
||||
def _eval_trace(self):
|
||||
return None
|
||||
|
||||
def _eval_power(self, exp):
|
||||
"""
|
||||
Override this in sub-classes to implement simplification of powers. The cases where the exponent
|
||||
is -1, 0, 1 are already covered in MatPow.doit(), so implementations can exclude these cases.
|
||||
"""
|
||||
return MatPow(self, exp)
|
||||
|
||||
def _eval_simplify(self, **kwargs):
|
||||
if self.is_Atom:
|
||||
return self
|
||||
else:
|
||||
from sympy.simplify import simplify
|
||||
return self.func(*[simplify(x, **kwargs) for x in self.args])
|
||||
|
||||
def _eval_adjoint(self):
|
||||
from sympy.matrices.expressions.adjoint import Adjoint
|
||||
return Adjoint(self)
|
||||
|
||||
def _eval_derivative_n_times(self, x, n):
|
||||
return Basic._eval_derivative_n_times(self, x, n)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
# `x` is a scalar:
|
||||
if self.has(x):
|
||||
# See if there are other methods using it:
|
||||
return super()._eval_derivative(x)
|
||||
else:
|
||||
return ZeroMatrix(*self.shape)
|
||||
|
||||
@classmethod
|
||||
def _check_dim(cls, dim):
|
||||
"""Helper function to check invalid matrix dimensions"""
|
||||
ok = not dim.is_Float and check_assumptions(
|
||||
dim, integer=True, nonnegative=True)
|
||||
if ok is False:
|
||||
raise ValueError(
|
||||
"The dimension specification {} should be "
|
||||
"a nonnegative integer.".format(dim))
|
||||
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
raise NotImplementedError(
|
||||
"Indexing not implemented for %s" % self.__class__.__name__)
|
||||
|
||||
def adjoint(self):
|
||||
return adjoint(self)
|
||||
|
||||
def as_coeff_Mul(self, rational=False):
|
||||
"""Efficiently extract the coefficient of a product."""
|
||||
return S.One, self
|
||||
|
||||
def conjugate(self):
|
||||
return conjugate(self)
|
||||
|
||||
def transpose(self):
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
return transpose(self)
|
||||
|
||||
@property
|
||||
def T(self):
|
||||
'''Matrix transposition'''
|
||||
return self.transpose()
|
||||
|
||||
def inverse(self):
|
||||
if self.is_square is False:
|
||||
raise NonSquareMatrixError('Inverse of non-square matrix')
|
||||
return self._eval_inverse()
|
||||
|
||||
def inv(self):
|
||||
return self.inverse()
|
||||
|
||||
def det(self):
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
return det(self)
|
||||
|
||||
@property
|
||||
def I(self):
|
||||
return self.inverse()
|
||||
|
||||
def valid_index(self, i, j):
|
||||
def is_valid(idx):
|
||||
return isinstance(idx, (int, Integer, Symbol, Expr))
|
||||
return (is_valid(i) and is_valid(j) and
|
||||
(self.rows is None or
|
||||
(i >= -self.rows) != False and (i < self.rows) != False) and
|
||||
(j >= -self.cols) != False and (j < self.cols) != False)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if not isinstance(key, tuple) and isinstance(key, slice):
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
return MatrixSlice(self, key, (0, None, 1))
|
||||
if isinstance(key, tuple) and len(key) == 2:
|
||||
i, j = key
|
||||
if isinstance(i, slice) or isinstance(j, slice):
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
return MatrixSlice(self, i, j)
|
||||
i, j = _sympify(i), _sympify(j)
|
||||
if self.valid_index(i, j) != False:
|
||||
return self._entry(i, j)
|
||||
else:
|
||||
raise IndexError("Invalid indices (%s, %s)" % (i, j))
|
||||
elif isinstance(key, (SYMPY_INTS, Integer)):
|
||||
# row-wise decomposition of matrix
|
||||
rows, cols = self.shape
|
||||
# allow single indexing if number of columns is known
|
||||
if not isinstance(cols, Integer):
|
||||
raise IndexError(filldedent('''
|
||||
Single indexing is only supported when the number
|
||||
of columns is known.'''))
|
||||
key = _sympify(key)
|
||||
i = key // cols
|
||||
j = key % cols
|
||||
if self.valid_index(i, j) != False:
|
||||
return self._entry(i, j)
|
||||
else:
|
||||
raise IndexError("Invalid index %s" % key)
|
||||
elif isinstance(key, (Symbol, Expr)):
|
||||
raise IndexError(filldedent('''
|
||||
Only integers may be used when addressing the matrix
|
||||
with a single index.'''))
|
||||
raise IndexError("Invalid index, wanted %s[i,j]" % self)
|
||||
|
||||
def _is_shape_symbolic(self) -> bool:
|
||||
return (not isinstance(self.rows, (SYMPY_INTS, Integer))
|
||||
or not isinstance(self.cols, (SYMPY_INTS, Integer)))
|
||||
|
||||
def as_explicit(self):
|
||||
"""
|
||||
Returns a dense Matrix with elements represented explicitly
|
||||
|
||||
Returns an object of type ImmutableDenseMatrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Identity
|
||||
>>> I = Identity(3)
|
||||
>>> I
|
||||
I
|
||||
>>> I.as_explicit()
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
See Also
|
||||
========
|
||||
as_mutable: returns mutable Matrix type
|
||||
|
||||
"""
|
||||
if self._is_shape_symbolic():
|
||||
raise ValueError(
|
||||
'Matrix with symbolic shape '
|
||||
'cannot be represented explicitly.')
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
return ImmutableDenseMatrix([[self[i, j]
|
||||
for j in range(self.cols)]
|
||||
for i in range(self.rows)])
|
||||
|
||||
def as_mutable(self):
|
||||
"""
|
||||
Returns a dense, mutable matrix with elements represented explicitly
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Identity
|
||||
>>> I = Identity(3)
|
||||
>>> I
|
||||
I
|
||||
>>> I.shape
|
||||
(3, 3)
|
||||
>>> I.as_mutable()
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
See Also
|
||||
========
|
||||
as_explicit: returns ImmutableDenseMatrix
|
||||
"""
|
||||
return self.as_explicit().as_mutable()
|
||||
|
||||
def __array__(self, dtype=object, copy=None):
|
||||
if copy is not None and not copy:
|
||||
raise TypeError("Cannot implement copy=False when converting Matrix to ndarray")
|
||||
from numpy import empty
|
||||
a = empty(self.shape, dtype=object)
|
||||
for i in range(self.rows):
|
||||
for j in range(self.cols):
|
||||
a[i, j] = self[i, j]
|
||||
return a
|
||||
|
||||
def equals(self, other):
|
||||
"""
|
||||
Test elementwise equality between matrices, potentially of different
|
||||
types
|
||||
|
||||
>>> from sympy import Identity, eye
|
||||
>>> Identity(3).equals(eye(3))
|
||||
True
|
||||
"""
|
||||
return self.as_explicit().equals(other)
|
||||
|
||||
def canonicalize(self):
|
||||
return self
|
||||
|
||||
def as_coeff_mmul(self):
|
||||
return S.One, MatMul(self)
|
||||
|
||||
@staticmethod
|
||||
def from_index_summation(expr, first_index=None, last_index=None, dimensions=None):
|
||||
r"""
|
||||
Parse expression of matrices with explicitly summed indices into a
|
||||
matrix expression without indices, if possible.
|
||||
|
||||
This transformation expressed in mathematical notation:
|
||||
|
||||
`\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}`
|
||||
|
||||
Optional parameter ``first_index``: specify which free index to use as
|
||||
the index starting the expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, MatrixExpr, Sum
|
||||
>>> from sympy.abc import i, j, k, l, N
|
||||
>>> A = MatrixSymbol("A", N, N)
|
||||
>>> B = MatrixSymbol("B", N, N)
|
||||
>>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
A*B
|
||||
|
||||
Transposition is detected:
|
||||
|
||||
>>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
A.T*B
|
||||
|
||||
Detect the trace:
|
||||
|
||||
>>> expr = Sum(A[i, i], (i, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
Trace(A)
|
||||
|
||||
More complicated expressions:
|
||||
|
||||
>>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
A*B.T*A.T
|
||||
"""
|
||||
from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
first_indices = []
|
||||
if first_index is not None:
|
||||
first_indices.append(first_index)
|
||||
if last_index is not None:
|
||||
first_indices.append(last_index)
|
||||
arr = convert_indexed_to_array(expr, first_indices=first_indices)
|
||||
return convert_array_to_matrix(arr)
|
||||
|
||||
def applyfunc(self, func):
|
||||
from .applyfunc import ElementwiseApplyFunction
|
||||
return ElementwiseApplyFunction(func, self)
|
||||
|
||||
|
||||
@dispatch(MatrixExpr, Expr)
|
||||
def _eval_is_eq(lhs, rhs): # noqa:F811
|
||||
return False
|
||||
|
||||
@dispatch(MatrixExpr, MatrixExpr) # type: ignore
|
||||
def _eval_is_eq(lhs, rhs): # noqa:F811
|
||||
if lhs.shape != rhs.shape:
|
||||
return False
|
||||
if (lhs - rhs).is_ZeroMatrix:
|
||||
return True
|
||||
|
||||
def get_postprocessor(cls):
|
||||
def _postprocessor(expr):
|
||||
# To avoid circular imports, we can't have MatMul/MatAdd on the top level
|
||||
mat_class = {Mul: MatMul, Add: MatAdd}[cls]
|
||||
nonmatrices = []
|
||||
matrices = []
|
||||
for term in expr.args:
|
||||
if isinstance(term, MatrixExpr):
|
||||
matrices.append(term)
|
||||
else:
|
||||
nonmatrices.append(term)
|
||||
|
||||
if not matrices:
|
||||
return cls._from_args(nonmatrices)
|
||||
|
||||
if nonmatrices:
|
||||
if cls == Mul:
|
||||
for i in range(len(matrices)):
|
||||
if not matrices[i].is_MatrixExpr:
|
||||
# If one of the matrices explicit, absorb the scalar into it
|
||||
# (doit will combine all explicit matrices into one, so it
|
||||
# doesn't matter which)
|
||||
matrices[i] = matrices[i].__mul__(cls._from_args(nonmatrices))
|
||||
nonmatrices = []
|
||||
break
|
||||
|
||||
else:
|
||||
# Maintain the ability to create Add(scalar, matrix) without
|
||||
# raising an exception. That way different algorithms can
|
||||
# replace matrix expressions with non-commutative symbols to
|
||||
# manipulate them like non-commutative scalars.
|
||||
return cls._from_args(nonmatrices + [mat_class(*matrices).doit(deep=False)])
|
||||
|
||||
if mat_class == MatAdd:
|
||||
return mat_class(*matrices).doit(deep=False)
|
||||
return mat_class(cls._from_args(nonmatrices), *matrices).doit(deep=False)
|
||||
return _postprocessor
|
||||
|
||||
|
||||
Basic._constructor_postprocessor_mapping[MatrixExpr] = {
|
||||
"Mul": [get_postprocessor(Mul)],
|
||||
"Add": [get_postprocessor(Add)],
|
||||
}
|
||||
|
||||
|
||||
def _matrix_derivative(expr, x, old_algorithm=False):
|
||||
|
||||
if isinstance(expr, MatrixBase) or isinstance(x, MatrixBase):
|
||||
# Do not use array expressions for explicit matrices:
|
||||
old_algorithm = True
|
||||
|
||||
if old_algorithm:
|
||||
return _matrix_derivative_old_algorithm(expr, x)
|
||||
|
||||
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
||||
from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
|
||||
array_expr = convert_matrix_to_array(expr)
|
||||
diff_array_expr = array_derive(array_expr, x)
|
||||
diff_matrix_expr = convert_array_to_matrix(diff_array_expr)
|
||||
return diff_matrix_expr
|
||||
|
||||
|
||||
def _matrix_derivative_old_algorithm(expr, x):
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
lines = expr._eval_derivative_matrix_lines(x)
|
||||
|
||||
parts = [i.build() for i in lines]
|
||||
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
|
||||
parts = [[convert_array_to_matrix(j) for j in i] for i in parts]
|
||||
|
||||
def _get_shape(elem):
|
||||
if isinstance(elem, MatrixExpr):
|
||||
return elem.shape
|
||||
return 1, 1
|
||||
|
||||
def get_rank(parts):
|
||||
return sum(j not in (1, None) for i in parts for j in _get_shape(i))
|
||||
|
||||
ranks = [get_rank(i) for i in parts]
|
||||
rank = ranks[0]
|
||||
|
||||
def contract_one_dims(parts):
|
||||
if len(parts) == 1:
|
||||
return parts[0]
|
||||
else:
|
||||
p1, p2 = parts[:2]
|
||||
if p2.is_Matrix:
|
||||
p2 = p2.T
|
||||
if p1 == Identity(1):
|
||||
pbase = p2
|
||||
elif p2 == Identity(1):
|
||||
pbase = p1
|
||||
else:
|
||||
pbase = p1*p2
|
||||
if len(parts) == 2:
|
||||
return pbase
|
||||
else: # len(parts) > 2
|
||||
if pbase.is_Matrix:
|
||||
raise ValueError("")
|
||||
return pbase*Mul.fromiter(parts[2:])
|
||||
|
||||
if rank <= 2:
|
||||
return Add.fromiter([contract_one_dims(i) for i in parts])
|
||||
|
||||
return ArrayDerivative(expr, x)
|
||||
|
||||
|
||||
class MatrixElement(Expr):
|
||||
parent = property(lambda self: self.args[0])
|
||||
i = property(lambda self: self.args[1])
|
||||
j = property(lambda self: self.args[2])
|
||||
_diff_wrt = True
|
||||
is_symbol = True
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, name, n, m):
|
||||
n, m = map(_sympify, (n, m))
|
||||
if isinstance(name, str):
|
||||
name = Symbol(name)
|
||||
else:
|
||||
if isinstance(name, MatrixBase):
|
||||
if n.is_Integer and m.is_Integer:
|
||||
return name[n, m]
|
||||
name = _sympify(name) # change mutable into immutable
|
||||
else:
|
||||
name = _sympify(name)
|
||||
if not isinstance(name.kind, MatrixKind):
|
||||
raise TypeError("First argument of MatrixElement should be a matrix")
|
||||
if not getattr(name, 'valid_index', lambda n, m: True)(n, m):
|
||||
raise IndexError('indices out of range')
|
||||
obj = Expr.__new__(cls, name, n, m)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
return self.args[0]
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = [arg.doit(**hints) for arg in self.args]
|
||||
else:
|
||||
args = self.args
|
||||
return args[0][args[1], args[2]]
|
||||
|
||||
@property
|
||||
def indices(self):
|
||||
return self.args[1:]
|
||||
|
||||
def _eval_derivative(self, v):
|
||||
|
||||
if not isinstance(v, MatrixElement):
|
||||
return self.parent.diff(v)[self.i, self.j]
|
||||
|
||||
M = self.args[0]
|
||||
|
||||
m, n = self.parent.shape
|
||||
|
||||
if M == v.args[0]:
|
||||
return KroneckerDelta(self.args[1], v.args[1], (0, m-1)) * \
|
||||
KroneckerDelta(self.args[2], v.args[2], (0, n-1))
|
||||
|
||||
if isinstance(M, Inverse):
|
||||
from sympy.concrete.summations import Sum
|
||||
i, j = self.args[1:]
|
||||
i1, i2 = symbols("z1, z2", cls=Dummy)
|
||||
Y = M.args[0]
|
||||
r1, r2 = Y.shape
|
||||
return -Sum(M[i, i1]*Y[i1, i2].diff(v)*M[i2, j], (i1, 0, r1-1), (i2, 0, r2-1))
|
||||
|
||||
if self.has(v.args[0]):
|
||||
return None
|
||||
|
||||
return S.Zero
|
||||
|
||||
|
||||
class MatrixSymbol(MatrixExpr):
|
||||
"""Symbolic representation of a Matrix object
|
||||
|
||||
Creates a SymPy Symbol to represent a Matrix. This matrix has a shape and
|
||||
can be included in Matrix Expressions
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Identity
|
||||
>>> A = MatrixSymbol('A', 3, 4) # A 3 by 4 Matrix
|
||||
>>> B = MatrixSymbol('B', 4, 3) # A 4 by 3 Matrix
|
||||
>>> A.shape
|
||||
(3, 4)
|
||||
>>> 2*A*B + Identity(3)
|
||||
I + 2*A*B
|
||||
"""
|
||||
is_commutative = False
|
||||
is_symbol = True
|
||||
_diff_wrt = True
|
||||
|
||||
def __new__(cls, name, n, m):
|
||||
n, m = _sympify(n), _sympify(m)
|
||||
|
||||
cls._check_dim(m)
|
||||
cls._check_dim(n)
|
||||
|
||||
if isinstance(name, str):
|
||||
name = Str(name)
|
||||
obj = Basic.__new__(cls, name, n, m)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[1], self.args[2]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.args[0].name
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return MatrixElement(self, i, j)
|
||||
|
||||
@property
|
||||
def free_symbols(self):
|
||||
return {self}
|
||||
|
||||
def _eval_simplify(self, **kwargs):
|
||||
return self
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
# x is a scalar:
|
||||
return ZeroMatrix(self.shape[0], self.shape[1])
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
if self != x:
|
||||
first = ZeroMatrix(x.shape[0], self.shape[0]) if self.shape[0] != 1 else S.Zero
|
||||
second = ZeroMatrix(x.shape[1], self.shape[1]) if self.shape[1] != 1 else S.Zero
|
||||
return [_LeftRightArgs(
|
||||
[first, second],
|
||||
)]
|
||||
else:
|
||||
first = Identity(self.shape[0]) if self.shape[0] != 1 else S.One
|
||||
second = Identity(self.shape[1]) if self.shape[1] != 1 else S.One
|
||||
return [_LeftRightArgs(
|
||||
[first, second],
|
||||
)]
|
||||
|
||||
|
||||
def matrix_symbols(expr):
|
||||
return [sym for sym in expr.free_symbols if sym.is_Matrix]
|
||||
|
||||
|
||||
class _LeftRightArgs:
|
||||
r"""
|
||||
Helper class to compute matrix derivatives.
|
||||
|
||||
The logic: when an expression is derived by a matrix `X_{mn}`, two lines of
|
||||
matrix multiplications are created: the one contracted to `m` (first line),
|
||||
and the one contracted to `n` (second line).
|
||||
|
||||
Transposition flips the side by which new matrices are connected to the
|
||||
lines.
|
||||
|
||||
The trace connects the end of the two lines.
|
||||
"""
|
||||
|
||||
def __init__(self, lines, higher=S.One):
|
||||
self._lines = list(lines)
|
||||
self._first_pointer_parent = self._lines
|
||||
self._first_pointer_index = 0
|
||||
self._first_line_index = 0
|
||||
self._second_pointer_parent = self._lines
|
||||
self._second_pointer_index = 1
|
||||
self._second_line_index = 1
|
||||
self.higher = higher
|
||||
|
||||
@property
|
||||
def first_pointer(self):
|
||||
return self._first_pointer_parent[self._first_pointer_index]
|
||||
|
||||
@first_pointer.setter
|
||||
def first_pointer(self, value):
|
||||
self._first_pointer_parent[self._first_pointer_index] = value
|
||||
|
||||
@property
|
||||
def second_pointer(self):
|
||||
return self._second_pointer_parent[self._second_pointer_index]
|
||||
|
||||
@second_pointer.setter
|
||||
def second_pointer(self, value):
|
||||
self._second_pointer_parent[self._second_pointer_index] = value
|
||||
|
||||
def __repr__(self):
|
||||
built = [self._build(i) for i in self._lines]
|
||||
return "_LeftRightArgs(lines=%s, higher=%s)" % (
|
||||
built,
|
||||
self.higher,
|
||||
)
|
||||
|
||||
def transpose(self):
|
||||
self._first_pointer_parent, self._second_pointer_parent = self._second_pointer_parent, self._first_pointer_parent
|
||||
self._first_pointer_index, self._second_pointer_index = self._second_pointer_index, self._first_pointer_index
|
||||
self._first_line_index, self._second_line_index = self._second_line_index, self._first_line_index
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def _build(expr):
|
||||
if isinstance(expr, ExprBuilder):
|
||||
return expr.build()
|
||||
if isinstance(expr, list):
|
||||
if len(expr) == 1:
|
||||
return expr[0]
|
||||
else:
|
||||
return expr[0](*[_LeftRightArgs._build(i) for i in expr[1]])
|
||||
else:
|
||||
return expr
|
||||
|
||||
def build(self):
|
||||
data = [self._build(i) for i in self._lines]
|
||||
if self.higher != 1:
|
||||
data += [self._build(self.higher)]
|
||||
data = list(data)
|
||||
return data
|
||||
|
||||
def matrix_form(self):
|
||||
if self.first != 1 and self.higher != 1:
|
||||
raise ValueError("higher dimensional array cannot be represented")
|
||||
|
||||
def _get_shape(elem):
|
||||
if isinstance(elem, MatrixExpr):
|
||||
return elem.shape
|
||||
return (None, None)
|
||||
|
||||
if _get_shape(self.first)[1] != _get_shape(self.second)[1]:
|
||||
# Remove one-dimensional identity matrices:
|
||||
# (this is needed by `a.diff(a)` where `a` is a vector)
|
||||
if _get_shape(self.second) == (1, 1):
|
||||
return self.first*self.second[0, 0]
|
||||
if _get_shape(self.first) == (1, 1):
|
||||
return self.first[1, 1]*self.second.T
|
||||
raise ValueError("incompatible shapes")
|
||||
if self.first != 1:
|
||||
return self.first*self.second.T
|
||||
else:
|
||||
return self.higher
|
||||
|
||||
def rank(self):
|
||||
"""
|
||||
Number of dimensions different from trivial (warning: not related to
|
||||
matrix rank).
|
||||
"""
|
||||
rank = 0
|
||||
if self.first != 1:
|
||||
rank += sum(i != 1 for i in self.first.shape)
|
||||
if self.second != 1:
|
||||
rank += sum(i != 1 for i in self.second.shape)
|
||||
if self.higher != 1:
|
||||
rank += 2
|
||||
return rank
|
||||
|
||||
def _multiply_pointer(self, pointer, other):
|
||||
from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from ...tensor.array.expressions.array_expressions import ArrayContraction
|
||||
|
||||
subexpr = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
pointer,
|
||||
other
|
||||
]
|
||||
),
|
||||
(1, 2)
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
|
||||
return subexpr
|
||||
|
||||
def append_first(self, other):
|
||||
self.first_pointer *= other
|
||||
|
||||
def append_second(self, other):
|
||||
self.second_pointer *= other
|
||||
|
||||
|
||||
def _make_matrix(x):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
if isinstance(x, MatrixExpr):
|
||||
return x
|
||||
return ImmutableDenseMatrix([[x]])
|
||||
|
||||
|
||||
from .matmul import MatMul
|
||||
from .matadd import MatAdd
|
||||
from .matpow import MatPow
|
||||
from .transpose import Transpose
|
||||
from .inverse import Inverse
|
||||
from .special import ZeroMatrix, Identity
|
||||
from .determinant import Determinant
|
||||
@@ -0,0 +1,496 @@
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
from sympy.core import Basic, sympify, S
|
||||
from sympy.core.mul import mul, Mul
|
||||
from sympy.core.numbers import Number, Integer
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions import adjoint
|
||||
from sympy.strategies import (rm_id, unpack, typed, flatten, exhaust,
|
||||
do_one, new)
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
from sympy.matrices.expressions._shape import validate_matmul_integer as validate
|
||||
|
||||
from .inverse import Inverse
|
||||
from .matexpr import MatrixExpr
|
||||
from .matpow import MatPow
|
||||
from .transpose import transpose
|
||||
from .permutation import PermutationMatrix
|
||||
from .special import ZeroMatrix, Identity, GenericIdentity, OneMatrix
|
||||
|
||||
|
||||
# XXX: MatMul should perhaps not subclass directly from Mul
|
||||
class MatMul(MatrixExpr, Mul):
|
||||
"""
|
||||
A product of matrix expressions
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatMul, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 4)
|
||||
>>> B = MatrixSymbol('B', 4, 3)
|
||||
>>> C = MatrixSymbol('C', 3, 6)
|
||||
>>> MatMul(A, B, C)
|
||||
A*B*C
|
||||
"""
|
||||
is_MatMul = True
|
||||
|
||||
identity = GenericIdentity()
|
||||
|
||||
def __new__(cls, *args, evaluate=False, check=None, _sympify=True):
|
||||
if not args:
|
||||
return cls.identity
|
||||
|
||||
# This must be removed aggressively in the constructor to avoid
|
||||
# TypeErrors from GenericIdentity().shape
|
||||
args = list(filter(lambda i: cls.identity != i, args))
|
||||
if _sympify:
|
||||
args = list(map(sympify, args))
|
||||
obj = Basic.__new__(cls, *args)
|
||||
factor, matrices = obj.as_coeff_matrices()
|
||||
|
||||
if check is not None:
|
||||
sympy_deprecation_warning(
|
||||
"Passing check to MatMul is deprecated and the check argument will be removed in a future version.",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target='remove-check-argument-from-matrix-operations')
|
||||
|
||||
if check is not False:
|
||||
validate(*matrices)
|
||||
|
||||
if not matrices:
|
||||
# Should it be
|
||||
#
|
||||
# return Basic.__neq__(cls, factor, GenericIdentity()) ?
|
||||
return factor
|
||||
|
||||
if evaluate:
|
||||
return cls._evaluate(obj)
|
||||
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def _evaluate(cls, expr):
|
||||
return canonicalize(expr)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
matrices = [arg for arg in self.args if arg.is_Matrix]
|
||||
return (matrices[0].rows, matrices[-1].cols)
|
||||
|
||||
def _entry(self, i, j, expand=True, **kwargs):
|
||||
# Avoid cyclic imports
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
|
||||
coeff, matrices = self.as_coeff_matrices()
|
||||
|
||||
if len(matrices) == 1: # situation like 2*X, matmul is just X
|
||||
return coeff * matrices[0][i, j]
|
||||
|
||||
indices = [None]*(len(matrices) + 1)
|
||||
ind_ranges = [None]*(len(matrices) - 1)
|
||||
indices[0] = i
|
||||
indices[-1] = j
|
||||
|
||||
def f():
|
||||
counter = 1
|
||||
while True:
|
||||
yield Dummy("i_%i" % counter)
|
||||
counter += 1
|
||||
|
||||
dummy_generator = kwargs.get("dummy_generator", f())
|
||||
|
||||
for i in range(1, len(matrices)):
|
||||
indices[i] = next(dummy_generator)
|
||||
|
||||
for i, arg in enumerate(matrices[:-1]):
|
||||
ind_ranges[i] = arg.shape[1] - 1
|
||||
matrices = [arg._entry(indices[i], indices[i+1], dummy_generator=dummy_generator) for i, arg in enumerate(matrices)]
|
||||
expr_in_sum = Mul.fromiter(matrices)
|
||||
if any(v.has(ImmutableMatrix) for v in matrices):
|
||||
expand = True
|
||||
result = coeff*Sum(
|
||||
expr_in_sum,
|
||||
*zip(indices[1:-1], [0]*len(ind_ranges), ind_ranges)
|
||||
)
|
||||
|
||||
# Don't waste time in result.doit() if the sum bounds are symbolic
|
||||
if not any(isinstance(v, (Integer, int)) for v in ind_ranges):
|
||||
expand = False
|
||||
return result.doit() if expand else result
|
||||
|
||||
def as_coeff_matrices(self):
|
||||
scalars = [x for x in self.args if not x.is_Matrix]
|
||||
matrices = [x for x in self.args if x.is_Matrix]
|
||||
coeff = Mul(*scalars)
|
||||
if coeff.is_commutative is False:
|
||||
raise NotImplementedError("noncommutative scalars in MatMul are not supported.")
|
||||
|
||||
return coeff, matrices
|
||||
|
||||
def as_coeff_mmul(self):
|
||||
coeff, matrices = self.as_coeff_matrices()
|
||||
return coeff, MatMul(*matrices)
|
||||
|
||||
def expand(self, **kwargs):
|
||||
expanded = super(MatMul, self).expand(**kwargs)
|
||||
return self._evaluate(expanded)
|
||||
|
||||
def _eval_transpose(self):
|
||||
"""Transposition of matrix multiplication.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The following rules are applied.
|
||||
|
||||
Transposition for matrix multiplied with another matrix:
|
||||
`\\left(A B\\right)^{T} = B^{T} A^{T}`
|
||||
|
||||
Transposition for matrix multiplied with scalar:
|
||||
`\\left(c A\\right)^{T} = c A^{T}`
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Transpose
|
||||
"""
|
||||
coeff, matrices = self.as_coeff_matrices()
|
||||
return MatMul(
|
||||
coeff, *[transpose(arg) for arg in matrices[::-1]]).doit()
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return MatMul(*[adjoint(arg) for arg in self.args[::-1]]).doit()
|
||||
|
||||
def _eval_trace(self):
|
||||
factor, mmul = self.as_coeff_mmul()
|
||||
if factor != 1:
|
||||
from .trace import trace
|
||||
return factor * trace(mmul.doit())
|
||||
|
||||
def _eval_determinant(self):
|
||||
from sympy.matrices.expressions.determinant import Determinant
|
||||
factor, matrices = self.as_coeff_matrices()
|
||||
square_matrices = only_squares(*matrices)
|
||||
return factor**self.rows * Mul(*list(map(Determinant, square_matrices)))
|
||||
|
||||
def _eval_inverse(self):
|
||||
if all(arg.is_square for arg in self.args if isinstance(arg, MatrixExpr)):
|
||||
return MatMul(*(
|
||||
arg.inverse() if isinstance(arg, MatrixExpr) else arg**-1
|
||||
for arg in self.args[::-1]
|
||||
)
|
||||
).doit()
|
||||
return Inverse(self)
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = tuple(arg.doit(**hints) for arg in self.args)
|
||||
else:
|
||||
args = self.args
|
||||
|
||||
# treat scalar*MatrixSymbol or scalar*MatPow separately
|
||||
expr = canonicalize(MatMul(*args))
|
||||
return expr
|
||||
|
||||
# Needed for partial compatibility with Mul
|
||||
def args_cnc(self, cset=False, warn=True, **kwargs):
|
||||
coeff_c = [x for x in self.args if x.is_commutative]
|
||||
coeff_nc = [x for x in self.args if not x.is_commutative]
|
||||
if cset:
|
||||
clen = len(coeff_c)
|
||||
coeff_c = set(coeff_c)
|
||||
if clen and warn and len(coeff_c) != clen:
|
||||
raise ValueError('repeated commutative arguments: %s' %
|
||||
[ci for ci in coeff_c if list(self.args).count(ci) > 1])
|
||||
return [coeff_c, coeff_nc]
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from .transpose import Transpose
|
||||
with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)]
|
||||
lines = []
|
||||
for ind in with_x_ind:
|
||||
left_args = self.args[:ind]
|
||||
right_args = self.args[ind+1:]
|
||||
|
||||
if right_args:
|
||||
right_mat = MatMul.fromiter(right_args)
|
||||
else:
|
||||
right_mat = Identity(self.shape[1])
|
||||
if left_args:
|
||||
left_rev = MatMul.fromiter([Transpose(i).doit() if i.is_Matrix else i for i in reversed(left_args)])
|
||||
else:
|
||||
left_rev = Identity(self.shape[0])
|
||||
|
||||
d = self.args[ind]._eval_derivative_matrix_lines(x)
|
||||
for i in d:
|
||||
i.append_first(left_rev)
|
||||
i.append_second(right_mat)
|
||||
lines.append(i)
|
||||
|
||||
return lines
|
||||
|
||||
mul.register_handlerclass((Mul, MatMul), MatMul)
|
||||
|
||||
|
||||
# Rules
|
||||
def newmul(*args):
|
||||
if args[0] == 1:
|
||||
args = args[1:]
|
||||
return new(MatMul, *args)
|
||||
|
||||
def any_zeros(mul):
|
||||
if any(arg.is_zero or (arg.is_Matrix and arg.is_ZeroMatrix)
|
||||
for arg in mul.args):
|
||||
matrices = [arg for arg in mul.args if arg.is_Matrix]
|
||||
return ZeroMatrix(matrices[0].rows, matrices[-1].cols)
|
||||
return mul
|
||||
|
||||
def merge_explicit(matmul):
|
||||
""" Merge explicit MatrixBase arguments
|
||||
|
||||
>>> from sympy import MatrixSymbol, Matrix, MatMul, pprint
|
||||
>>> from sympy.matrices.expressions.matmul import merge_explicit
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = Matrix([[1, 1], [1, 1]])
|
||||
>>> C = Matrix([[1, 2], [3, 4]])
|
||||
>>> X = MatMul(A, B, C)
|
||||
>>> pprint(X)
|
||||
[1 1] [1 2]
|
||||
A*[ ]*[ ]
|
||||
[1 1] [3 4]
|
||||
>>> pprint(merge_explicit(X))
|
||||
[4 6]
|
||||
A*[ ]
|
||||
[4 6]
|
||||
|
||||
>>> X = MatMul(B, A, C)
|
||||
>>> pprint(X)
|
||||
[1 1] [1 2]
|
||||
[ ]*A*[ ]
|
||||
[1 1] [3 4]
|
||||
>>> pprint(merge_explicit(X))
|
||||
[1 1] [1 2]
|
||||
[ ]*A*[ ]
|
||||
[1 1] [3 4]
|
||||
"""
|
||||
if not any(isinstance(arg, MatrixBase) for arg in matmul.args):
|
||||
return matmul
|
||||
newargs = []
|
||||
last = matmul.args[0]
|
||||
for arg in matmul.args[1:]:
|
||||
if isinstance(arg, (MatrixBase, Number)) and isinstance(last, (MatrixBase, Number)):
|
||||
last = last * arg
|
||||
else:
|
||||
newargs.append(last)
|
||||
last = arg
|
||||
newargs.append(last)
|
||||
|
||||
return MatMul(*newargs)
|
||||
|
||||
def remove_ids(mul):
|
||||
""" Remove Identities from a MatMul
|
||||
|
||||
This is a modified version of sympy.strategies.rm_id.
|
||||
This is necessary because MatMul may contain both MatrixExprs and Exprs
|
||||
as args.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.strategies.rm_id
|
||||
"""
|
||||
# Separate Exprs from MatrixExprs in args
|
||||
factor, mmul = mul.as_coeff_mmul()
|
||||
# Apply standard rm_id for MatMuls
|
||||
result = rm_id(lambda x: x.is_Identity is True)(mmul)
|
||||
if result != mmul:
|
||||
return newmul(factor, *result.args) # Recombine and return
|
||||
else:
|
||||
return mul
|
||||
|
||||
def factor_in_front(mul):
|
||||
factor, matrices = mul.as_coeff_matrices()
|
||||
if factor != 1:
|
||||
return newmul(factor, *matrices)
|
||||
return mul
|
||||
|
||||
def combine_powers(mul):
|
||||
r"""Combine consecutive powers with the same base into one, e.g.
|
||||
$$A \times A^2 \Rightarrow A^3$$
|
||||
|
||||
This also cancels out the possible matrix inverses using the
|
||||
knowledgebase of :class:`~.Inverse`, e.g.,
|
||||
$$ Y \times X \times X^{-1} \Rightarrow Y $$
|
||||
"""
|
||||
factor, args = mul.as_coeff_matrices()
|
||||
new_args = [args[0]]
|
||||
|
||||
for i in range(1, len(args)):
|
||||
A = new_args[-1]
|
||||
B = args[i]
|
||||
|
||||
if isinstance(B, Inverse) and isinstance(B.arg, MatMul):
|
||||
Bargs = B.arg.args
|
||||
l = len(Bargs)
|
||||
if list(Bargs) == new_args[-l:]:
|
||||
new_args = new_args[:-l] + [Identity(B.shape[0])]
|
||||
continue
|
||||
|
||||
if isinstance(A, Inverse) and isinstance(A.arg, MatMul):
|
||||
Aargs = A.arg.args
|
||||
l = len(Aargs)
|
||||
if list(Aargs) == args[i:i+l]:
|
||||
identity = Identity(A.shape[0])
|
||||
new_args[-1] = identity
|
||||
for j in range(i, i+l):
|
||||
args[j] = identity
|
||||
continue
|
||||
|
||||
if A.is_square == False or B.is_square == False:
|
||||
new_args.append(B)
|
||||
continue
|
||||
|
||||
if isinstance(A, MatPow):
|
||||
A_base, A_exp = A.args
|
||||
else:
|
||||
A_base, A_exp = A, S.One
|
||||
|
||||
if isinstance(B, MatPow):
|
||||
B_base, B_exp = B.args
|
||||
else:
|
||||
B_base, B_exp = B, S.One
|
||||
|
||||
if A_base == B_base:
|
||||
new_exp = A_exp + B_exp
|
||||
new_args[-1] = MatPow(A_base, new_exp).doit(deep=False)
|
||||
continue
|
||||
elif not isinstance(B_base, MatrixBase):
|
||||
try:
|
||||
B_base_inv = B_base.inverse()
|
||||
except NonInvertibleMatrixError:
|
||||
B_base_inv = None
|
||||
if B_base_inv is not None and A_base == B_base_inv:
|
||||
new_exp = A_exp - B_exp
|
||||
new_args[-1] = MatPow(A_base, new_exp).doit(deep=False)
|
||||
continue
|
||||
new_args.append(B)
|
||||
|
||||
return newmul(factor, *new_args)
|
||||
|
||||
def combine_permutations(mul):
|
||||
"""Refine products of permutation matrices as the products of cycles.
|
||||
"""
|
||||
args = mul.args
|
||||
l = len(args)
|
||||
if l < 2:
|
||||
return mul
|
||||
|
||||
result = [args[0]]
|
||||
for i in range(1, l):
|
||||
A = result[-1]
|
||||
B = args[i]
|
||||
if isinstance(A, PermutationMatrix) and \
|
||||
isinstance(B, PermutationMatrix):
|
||||
cycle_1 = A.args[0]
|
||||
cycle_2 = B.args[0]
|
||||
result[-1] = PermutationMatrix(cycle_1 * cycle_2)
|
||||
else:
|
||||
result.append(B)
|
||||
|
||||
return MatMul(*result)
|
||||
|
||||
def combine_one_matrices(mul):
|
||||
"""
|
||||
Combine products of OneMatrix
|
||||
|
||||
e.g. OneMatrix(2, 3) * OneMatrix(3, 4) -> 3 * OneMatrix(2, 4)
|
||||
"""
|
||||
factor, args = mul.as_coeff_matrices()
|
||||
new_args = [args[0]]
|
||||
|
||||
for B in args[1:]:
|
||||
A = new_args[-1]
|
||||
if not isinstance(A, OneMatrix) or not isinstance(B, OneMatrix):
|
||||
new_args.append(B)
|
||||
continue
|
||||
new_args.pop()
|
||||
new_args.append(OneMatrix(A.shape[0], B.shape[1]))
|
||||
factor *= A.shape[1]
|
||||
|
||||
return newmul(factor, *new_args)
|
||||
|
||||
def distribute_monom(mul):
|
||||
"""
|
||||
Simplify MatMul expressions but distributing
|
||||
rational term to MatMul.
|
||||
|
||||
e.g. 2*(A+B) -> 2*A + 2*B
|
||||
"""
|
||||
args = mul.args
|
||||
if len(args) == 2:
|
||||
from .matadd import MatAdd
|
||||
if args[0].is_MatAdd and args[1].is_Rational:
|
||||
return MatAdd(*[MatMul(mat, args[1]).doit() for mat in args[0].args])
|
||||
if args[1].is_MatAdd and args[0].is_Rational:
|
||||
return MatAdd(*[MatMul(args[0], mat).doit() for mat in args[1].args])
|
||||
return mul
|
||||
|
||||
rules = (
|
||||
distribute_monom, any_zeros, remove_ids, combine_one_matrices, combine_powers, unpack, rm_id(lambda x: x == 1),
|
||||
merge_explicit, factor_in_front, flatten, combine_permutations)
|
||||
|
||||
canonicalize = exhaust(typed({MatMul: do_one(*rules)}))
|
||||
|
||||
def only_squares(*matrices):
|
||||
"""factor matrices only if they are square"""
|
||||
if matrices[0].rows != matrices[-1].cols:
|
||||
raise RuntimeError("Invalid matrices being multiplied")
|
||||
out = []
|
||||
start = 0
|
||||
for i, M in enumerate(matrices):
|
||||
if M.cols == matrices[start].rows:
|
||||
out.append(MatMul(*matrices[start:i+1]).doit())
|
||||
start = i+1
|
||||
return out
|
||||
|
||||
|
||||
def refine_MatMul(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> expr = X * X.T
|
||||
>>> print(expr)
|
||||
X*X.T
|
||||
>>> with assuming(Q.orthogonal(X)):
|
||||
... print(refine(expr))
|
||||
I
|
||||
"""
|
||||
newargs = []
|
||||
exprargs = []
|
||||
|
||||
for args in expr.args:
|
||||
if args.is_Matrix:
|
||||
exprargs.append(args)
|
||||
else:
|
||||
newargs.append(args)
|
||||
|
||||
last = exprargs[0]
|
||||
for arg in exprargs[1:]:
|
||||
if arg == last.T and ask(Q.orthogonal(arg), assumptions):
|
||||
last = Identity(arg.shape[0])
|
||||
elif arg == last.conjugate() and ask(Q.unitary(arg), assumptions):
|
||||
last = Identity(arg.shape[0])
|
||||
else:
|
||||
newargs.append(last)
|
||||
last = arg
|
||||
newargs.append(last)
|
||||
|
||||
return MatMul(*newargs)
|
||||
|
||||
|
||||
handlers_dict['MatMul'] = refine_MatMul
|
||||
@@ -0,0 +1,150 @@
|
||||
from .matexpr import MatrixExpr
|
||||
from .special import Identity
|
||||
from sympy.core import S
|
||||
from sympy.core.expr import ExprBuilder
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices import MatrixBase
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
|
||||
|
||||
class MatPow(MatrixExpr):
|
||||
def __new__(cls, base, exp, evaluate=False, **options):
|
||||
base = _sympify(base)
|
||||
if not base.is_Matrix:
|
||||
raise TypeError("MatPow base should be a matrix")
|
||||
|
||||
if base.is_square is False:
|
||||
raise NonSquareMatrixError("Power of non-square matrix %s" % base)
|
||||
|
||||
exp = _sympify(exp)
|
||||
obj = super().__new__(cls, base, exp)
|
||||
|
||||
if evaluate:
|
||||
obj = obj.doit(deep=False)
|
||||
|
||||
return obj
|
||||
|
||||
@property
|
||||
def base(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def exp(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.base.shape
|
||||
|
||||
@cacheit
|
||||
def _get_explicit_matrix(self):
|
||||
return self.base.as_explicit()**self.exp
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
from sympy.matrices.expressions import MatMul
|
||||
A = self.doit()
|
||||
if isinstance(A, MatPow):
|
||||
# We still have a MatPow, make an explicit MatMul out of it.
|
||||
if A.exp.is_Integer and A.exp.is_positive:
|
||||
A = MatMul(*[A.base for k in range(A.exp)])
|
||||
elif not self._is_shape_symbolic():
|
||||
return A._get_explicit_matrix()[i, j]
|
||||
else:
|
||||
# Leave the expression unevaluated:
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
return MatrixElement(self, i, j)
|
||||
return A[i, j]
|
||||
|
||||
def doit(self, **hints):
|
||||
if hints.get('deep', True):
|
||||
base, exp = (arg.doit(**hints) for arg in self.args)
|
||||
else:
|
||||
base, exp = self.args
|
||||
|
||||
# combine all powers, e.g. (A ** 2) ** 3 -> A ** 6
|
||||
while isinstance(base, MatPow):
|
||||
exp *= base.args[1]
|
||||
base = base.args[0]
|
||||
|
||||
if isinstance(base, MatrixBase):
|
||||
# Delegate
|
||||
return base ** exp
|
||||
|
||||
# Handle simple cases so that _eval_power() in MatrixExpr sub-classes can ignore them
|
||||
if exp == S.One:
|
||||
return base
|
||||
if exp == S.Zero:
|
||||
return Identity(base.rows)
|
||||
if exp == S.NegativeOne:
|
||||
from sympy.matrices.expressions import Inverse
|
||||
return Inverse(base).doit(**hints)
|
||||
|
||||
eval_power = getattr(base, '_eval_power', None)
|
||||
if eval_power is not None:
|
||||
return eval_power(exp)
|
||||
|
||||
return MatPow(base, exp)
|
||||
|
||||
def _eval_transpose(self):
|
||||
base, exp = self.args
|
||||
return MatPow(base.transpose(), exp)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
base, exp = self.args
|
||||
return MatPow(base.adjoint(), exp)
|
||||
|
||||
def _eval_conjugate(self):
|
||||
base, exp = self.args
|
||||
return MatPow(base.conjugate(), exp)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
return Pow._eval_derivative(self, x)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayContraction
|
||||
from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from .matmul import MatMul
|
||||
from .inverse import Inverse
|
||||
exp = self.exp
|
||||
if self.base.shape == (1, 1) and not exp.has(x):
|
||||
lr = self.base._eval_derivative_matrix_lines(x)
|
||||
for i in lr:
|
||||
subexpr = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
Identity(1),
|
||||
i._lines[0],
|
||||
exp*self.base**(exp-1),
|
||||
i._lines[1],
|
||||
Identity(1),
|
||||
]
|
||||
),
|
||||
(0, 3, 4), (5, 7, 8)
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args
|
||||
i._first_pointer_index = 0
|
||||
i._second_pointer_parent = subexpr.args[0].args
|
||||
i._second_pointer_index = 4
|
||||
i._lines = [subexpr]
|
||||
return lr
|
||||
if (exp > 0) == True:
|
||||
newexpr = MatMul.fromiter([self.base for i in range(exp)])
|
||||
elif (exp == -1) == True:
|
||||
return Inverse(self.base)._eval_derivative_matrix_lines(x)
|
||||
elif (exp < 0) == True:
|
||||
newexpr = MatMul.fromiter([Inverse(self.base) for i in range(-exp)])
|
||||
elif (exp == 0) == True:
|
||||
return self.doit()._eval_derivative_matrix_lines(x)
|
||||
else:
|
||||
raise NotImplementedError("cannot evaluate %s derived by %s" % (self, x))
|
||||
return newexpr._eval_derivative_matrix_lines(x)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return MatPow(self.base, -self.exp)
|
||||
@@ -0,0 +1,303 @@
|
||||
from sympy.core import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.functions import KroneckerDelta
|
||||
|
||||
from .matexpr import MatrixExpr
|
||||
from .special import ZeroMatrix, Identity, OneMatrix
|
||||
|
||||
|
||||
class PermutationMatrix(MatrixExpr):
|
||||
"""A Permutation Matrix
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
perm : Permutation
|
||||
The permutation the matrix uses.
|
||||
|
||||
The size of the permutation determines the matrix size.
|
||||
|
||||
See the documentation of
|
||||
:class:`sympy.combinatorics.permutations.Permutation` for
|
||||
the further information of how to create a permutation object.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, PermutationMatrix
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
|
||||
Creating a permutation matrix:
|
||||
|
||||
>>> p = Permutation(1, 2, 0)
|
||||
>>> P = PermutationMatrix(p)
|
||||
>>> P = P.as_explicit()
|
||||
>>> P
|
||||
Matrix([
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[1, 0, 0]])
|
||||
|
||||
Permuting a matrix row and column:
|
||||
|
||||
>>> M = Matrix([0, 1, 2])
|
||||
>>> Matrix(P*M)
|
||||
Matrix([
|
||||
[1],
|
||||
[2],
|
||||
[0]])
|
||||
|
||||
>>> Matrix(M.T*P)
|
||||
Matrix([[2, 0, 1]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.permutations.Permutation
|
||||
"""
|
||||
|
||||
def __new__(cls, perm):
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
|
||||
perm = _sympify(perm)
|
||||
if not isinstance(perm, Permutation):
|
||||
raise ValueError(
|
||||
"{} must be a SymPy Permutation instance.".format(perm))
|
||||
|
||||
return super().__new__(cls, perm)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
size = self.args[0].size
|
||||
return (size, size)
|
||||
|
||||
@property
|
||||
def is_Identity(self):
|
||||
return self.args[0].is_Identity
|
||||
|
||||
def doit(self, **hints):
|
||||
if self.is_Identity:
|
||||
return Identity(self.rows)
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
perm = self.args[0]
|
||||
return KroneckerDelta(perm.apply(i), j)
|
||||
|
||||
def _eval_power(self, exp):
|
||||
return PermutationMatrix(self.args[0] ** exp).doit()
|
||||
|
||||
def _eval_inverse(self):
|
||||
return PermutationMatrix(self.args[0] ** -1)
|
||||
|
||||
_eval_transpose = _eval_adjoint = _eval_inverse
|
||||
|
||||
def _eval_determinant(self):
|
||||
sign = self.args[0].signature()
|
||||
if sign == 1:
|
||||
return S.One
|
||||
elif sign == -1:
|
||||
return S.NegativeOne
|
||||
raise NotImplementedError
|
||||
|
||||
def _eval_rewrite_as_BlockDiagMatrix(self, *args, **kwargs):
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from .blockmatrix import BlockDiagMatrix
|
||||
|
||||
perm = self.args[0]
|
||||
full_cyclic_form = perm.full_cyclic_form
|
||||
|
||||
cycles_picks = []
|
||||
|
||||
# Stage 1. Decompose the cycles into the blockable form.
|
||||
a, b, c = 0, 0, 0
|
||||
flag = False
|
||||
for cycle in full_cyclic_form:
|
||||
l = len(cycle)
|
||||
m = max(cycle)
|
||||
|
||||
if not flag:
|
||||
if m + 1 > a + l:
|
||||
flag = True
|
||||
temp = [cycle]
|
||||
b = m
|
||||
c = l
|
||||
else:
|
||||
cycles_picks.append([cycle])
|
||||
a += l
|
||||
|
||||
else:
|
||||
if m > b:
|
||||
if m + 1 == a + c + l:
|
||||
temp.append(cycle)
|
||||
cycles_picks.append(temp)
|
||||
flag = False
|
||||
a = m+1
|
||||
else:
|
||||
b = m
|
||||
temp.append(cycle)
|
||||
c += l
|
||||
else:
|
||||
if b + 1 == a + c + l:
|
||||
temp.append(cycle)
|
||||
cycles_picks.append(temp)
|
||||
flag = False
|
||||
a = b+1
|
||||
else:
|
||||
temp.append(cycle)
|
||||
c += l
|
||||
|
||||
# Stage 2. Normalize each decomposed cycles and build matrix.
|
||||
p = 0
|
||||
args = []
|
||||
for pick in cycles_picks:
|
||||
new_cycles = []
|
||||
l = 0
|
||||
for cycle in pick:
|
||||
new_cycle = [i - p for i in cycle]
|
||||
new_cycles.append(new_cycle)
|
||||
l += len(cycle)
|
||||
p += l
|
||||
perm = Permutation(new_cycles)
|
||||
mat = PermutationMatrix(perm)
|
||||
args.append(mat)
|
||||
|
||||
return BlockDiagMatrix(*args)
|
||||
|
||||
|
||||
class MatrixPermute(MatrixExpr):
|
||||
r"""Symbolic representation for permuting matrix rows or columns.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
perm : Permutation, PermutationMatrix
|
||||
The permutation to use for permuting the matrix.
|
||||
The permutation can be resized to the suitable one,
|
||||
|
||||
axis : 0 or 1
|
||||
The axis to permute alongside.
|
||||
If `0`, it will permute the matrix rows.
|
||||
If `1`, it will permute the matrix columns.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This follows the same notation used in
|
||||
:meth:`sympy.matrices.matrixbase.MatrixBase.permute`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, MatrixPermute
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
|
||||
Permuting the matrix rows:
|
||||
|
||||
>>> p = Permutation(1, 2, 0)
|
||||
>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
>>> B = MatrixPermute(A, p, axis=0)
|
||||
>>> B.as_explicit()
|
||||
Matrix([
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[1, 2, 3]])
|
||||
|
||||
Permuting the matrix columns:
|
||||
|
||||
>>> B = MatrixPermute(A, p, axis=1)
|
||||
>>> B.as_explicit()
|
||||
Matrix([
|
||||
[2, 3, 1],
|
||||
[5, 6, 4],
|
||||
[8, 9, 7]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.matrixbase.MatrixBase.permute
|
||||
"""
|
||||
def __new__(cls, mat, perm, axis=S.Zero):
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
|
||||
mat = _sympify(mat)
|
||||
if not mat.is_Matrix:
|
||||
raise ValueError(
|
||||
"{} must be a SymPy matrix instance.".format(perm))
|
||||
|
||||
perm = _sympify(perm)
|
||||
if isinstance(perm, PermutationMatrix):
|
||||
perm = perm.args[0]
|
||||
|
||||
if not isinstance(perm, Permutation):
|
||||
raise ValueError(
|
||||
"{} must be a SymPy Permutation or a PermutationMatrix " \
|
||||
"instance".format(perm))
|
||||
|
||||
axis = _sympify(axis)
|
||||
if axis not in (0, 1):
|
||||
raise ValueError("The axis must be 0 or 1.")
|
||||
|
||||
mat_size = mat.shape[axis]
|
||||
if mat_size != perm.size:
|
||||
try:
|
||||
perm = perm.resize(mat_size)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
"Size does not match between the permutation {} "
|
||||
"and the matrix {} threaded over the axis {} "
|
||||
"and cannot be converted."
|
||||
.format(perm, mat, axis))
|
||||
|
||||
return super().__new__(cls, mat, perm, axis)
|
||||
|
||||
def doit(self, deep=True, **hints):
|
||||
mat, perm, axis = self.args
|
||||
|
||||
if deep:
|
||||
mat = mat.doit(deep=deep, **hints)
|
||||
perm = perm.doit(deep=deep, **hints)
|
||||
|
||||
if perm.is_Identity:
|
||||
return mat
|
||||
|
||||
if mat.is_Identity:
|
||||
if axis is S.Zero:
|
||||
return PermutationMatrix(perm)
|
||||
elif axis is S.One:
|
||||
return PermutationMatrix(perm**-1)
|
||||
|
||||
if isinstance(mat, (ZeroMatrix, OneMatrix)):
|
||||
return mat
|
||||
|
||||
if isinstance(mat, MatrixPermute) and mat.args[2] == axis:
|
||||
return MatrixPermute(mat.args[0], perm * mat.args[1], axis)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0].shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
mat, perm, axis = self.args
|
||||
|
||||
if axis == 0:
|
||||
return mat[perm.apply(i), j]
|
||||
elif axis == 1:
|
||||
return mat[i, perm.apply(j)]
|
||||
|
||||
def _eval_rewrite_as_MatMul(self, *args, **kwargs):
|
||||
from .matmul import MatMul
|
||||
|
||||
mat, perm, axis = self.args
|
||||
|
||||
deep = kwargs.get("deep", True)
|
||||
|
||||
if deep:
|
||||
mat = mat.rewrite(MatMul)
|
||||
|
||||
if axis == 0:
|
||||
return MatMul(PermutationMatrix(perm), mat)
|
||||
elif axis == 1:
|
||||
return MatMul(mat, PermutationMatrix(perm**-1))
|
||||
@@ -0,0 +1,68 @@
|
||||
from sympy.core.assumptions import check_assumptions
|
||||
from sympy.core.logic import fuzzy_and
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices.kind import MatrixKind
|
||||
from sympy.sets.sets import Set, SetKind
|
||||
from sympy.core.kind import NumberKind
|
||||
from .matexpr import MatrixExpr
|
||||
|
||||
|
||||
class MatrixSet(Set):
|
||||
"""
|
||||
MatrixSet represents the set of matrices with ``shape = (n, m)`` over the
|
||||
given set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices import MatrixSet
|
||||
>>> from sympy import S, I, Matrix
|
||||
>>> M = MatrixSet(2, 2, set=S.Reals)
|
||||
>>> X = Matrix([[1, 2], [3, 4]])
|
||||
>>> X in M
|
||||
True
|
||||
>>> X = Matrix([[1, 2], [I, 4]])
|
||||
>>> X in M
|
||||
False
|
||||
|
||||
"""
|
||||
is_empty = False
|
||||
|
||||
def __new__(cls, n, m, set):
|
||||
n, m, set = _sympify(n), _sympify(m), _sympify(set)
|
||||
cls._check_dim(n)
|
||||
cls._check_dim(m)
|
||||
if not isinstance(set, Set):
|
||||
raise TypeError("{} should be an instance of Set.".format(set))
|
||||
return Set.__new__(cls, n, m, set)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[:2]
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.args[2]
|
||||
|
||||
def _contains(self, other):
|
||||
if not isinstance(other, MatrixExpr):
|
||||
raise TypeError("{} should be an instance of MatrixExpr.".format(other))
|
||||
if other.shape != self.shape:
|
||||
are_symbolic = any(_sympify(x).is_Symbol for x in other.shape + self.shape)
|
||||
if are_symbolic:
|
||||
return None
|
||||
return False
|
||||
return fuzzy_and(self.set.contains(x) for x in other)
|
||||
|
||||
@classmethod
|
||||
def _check_dim(cls, dim):
|
||||
"""Helper function to check invalid matrix dimensions"""
|
||||
ok = not dim.is_Float and check_assumptions(
|
||||
dim, integer=True, nonnegative=True)
|
||||
if ok is False:
|
||||
raise ValueError(
|
||||
"The dimension specification {} should be "
|
||||
"a nonnegative integer.".format(dim))
|
||||
|
||||
def _kind(self):
|
||||
return SetKind(MatrixKind(NumberKind))
|
||||
@@ -0,0 +1,114 @@
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.functions.elementary.integers import floor
|
||||
|
||||
def normalize(i, parentsize):
|
||||
if isinstance(i, slice):
|
||||
i = (i.start, i.stop, i.step)
|
||||
if not isinstance(i, (tuple, list, Tuple)):
|
||||
if (i < 0) == True:
|
||||
i += parentsize
|
||||
i = (i, i+1, 1)
|
||||
i = list(i)
|
||||
if len(i) == 2:
|
||||
i.append(1)
|
||||
start, stop, step = i
|
||||
start = start or 0
|
||||
if stop is None:
|
||||
stop = parentsize
|
||||
if (start < 0) == True:
|
||||
start += parentsize
|
||||
if (stop < 0) == True:
|
||||
stop += parentsize
|
||||
step = step or 1
|
||||
|
||||
if ((stop - start) * step < 1) == True:
|
||||
raise IndexError()
|
||||
|
||||
return (start, stop, step)
|
||||
|
||||
class MatrixSlice(MatrixExpr):
|
||||
""" A MatrixSlice of a Matrix Expression
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSlice, ImmutableMatrix
|
||||
>>> M = ImmutableMatrix(4, 4, range(16))
|
||||
>>> M
|
||||
Matrix([
|
||||
[ 0, 1, 2, 3],
|
||||
[ 4, 5, 6, 7],
|
||||
[ 8, 9, 10, 11],
|
||||
[12, 13, 14, 15]])
|
||||
|
||||
>>> B = MatrixSlice(M, (0, 2), (2, 4))
|
||||
>>> ImmutableMatrix(B)
|
||||
Matrix([
|
||||
[2, 3],
|
||||
[6, 7]])
|
||||
"""
|
||||
parent = property(lambda self: self.args[0])
|
||||
rowslice = property(lambda self: self.args[1])
|
||||
colslice = property(lambda self: self.args[2])
|
||||
|
||||
def __new__(cls, parent, rowslice, colslice):
|
||||
rowslice = normalize(rowslice, parent.shape[0])
|
||||
colslice = normalize(colslice, parent.shape[1])
|
||||
if not (len(rowslice) == len(colslice) == 3):
|
||||
raise IndexError()
|
||||
if ((0 > rowslice[0]) == True or
|
||||
(parent.shape[0] < rowslice[1]) == True or
|
||||
(0 > colslice[0]) == True or
|
||||
(parent.shape[1] < colslice[1]) == True):
|
||||
raise IndexError()
|
||||
if isinstance(parent, MatrixSlice):
|
||||
return mat_slice_of_slice(parent, rowslice, colslice)
|
||||
return Basic.__new__(cls, parent, Tuple(*rowslice), Tuple(*colslice))
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
rows = self.rowslice[1] - self.rowslice[0]
|
||||
rows = rows if self.rowslice[2] == 1 else floor(rows/self.rowslice[2])
|
||||
cols = self.colslice[1] - self.colslice[0]
|
||||
cols = cols if self.colslice[2] == 1 else floor(cols/self.colslice[2])
|
||||
return rows, cols
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.parent._entry(i*self.rowslice[2] + self.rowslice[0],
|
||||
j*self.colslice[2] + self.colslice[0],
|
||||
**kwargs)
|
||||
|
||||
@property
|
||||
def on_diag(self):
|
||||
return self.rowslice == self.colslice
|
||||
|
||||
|
||||
def slice_of_slice(s, t):
|
||||
start1, stop1, step1 = s
|
||||
start2, stop2, step2 = t
|
||||
|
||||
start = start1 + start2*step1
|
||||
step = step1 * step2
|
||||
stop = start1 + step1*stop2
|
||||
|
||||
if stop > stop1:
|
||||
raise IndexError()
|
||||
|
||||
return start, stop, step
|
||||
|
||||
|
||||
def mat_slice_of_slice(parent, rowslice, colslice):
|
||||
""" Collapse nested matrix slices
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> X = MatrixSymbol('X', 10, 10)
|
||||
>>> X[:, 1:5][5:8, :]
|
||||
X[5:8, 1:5]
|
||||
>>> X[1:9:2, 2:6][1:3, 2]
|
||||
X[3:7:2, 4:5]
|
||||
"""
|
||||
row = slice_of_slice(parent.rowslice, rowslice)
|
||||
col = slice_of_slice(parent.colslice, colslice)
|
||||
return MatrixSlice(parent.parent, row, col)
|
||||
@@ -0,0 +1,299 @@
|
||||
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.exceptions 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
|
||||
@@ -0,0 +1,34 @@
|
||||
from sympy.core import symbols, S
|
||||
from sympy.functions import adjoint, conjugate, transpose
|
||||
from sympy.matrices.expressions import MatrixSymbol, Adjoint, trace, Transpose
|
||||
from sympy.matrices import eye, Matrix
|
||||
|
||||
n, m, l, k, p = symbols('n m l k p', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
|
||||
def test_adjoint():
|
||||
Sq = MatrixSymbol('Sq', n, n)
|
||||
|
||||
assert Adjoint(A).shape == (m, n)
|
||||
assert Adjoint(A*B).shape == (l, n)
|
||||
assert adjoint(Adjoint(A)) == A
|
||||
assert isinstance(Adjoint(Adjoint(A)), Adjoint)
|
||||
|
||||
assert conjugate(Adjoint(A)) == Transpose(A)
|
||||
assert transpose(Adjoint(A)) == Adjoint(Transpose(A))
|
||||
|
||||
assert Adjoint(eye(3)).doit() == eye(3)
|
||||
|
||||
assert Adjoint(S(5)).doit() == S(5)
|
||||
|
||||
assert Adjoint(Matrix([[1, 2], [3, 4]])).doit() == Matrix([[1, 3], [2, 4]])
|
||||
|
||||
assert adjoint(trace(Sq)) == conjugate(trace(Sq))
|
||||
assert trace(adjoint(Sq)) == conjugate(trace(Sq))
|
||||
|
||||
assert Adjoint(Sq)[0, 1] == conjugate(Sq[1, 0])
|
||||
|
||||
assert Adjoint(A*B).doit() == Adjoint(B) * Adjoint(A)
|
||||
@@ -0,0 +1,118 @@
|
||||
from sympy.core.symbol import symbols, Dummy
|
||||
from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.simplify.simplify import simplify
|
||||
|
||||
|
||||
X = MatrixSymbol("X", 3, 3)
|
||||
Y = MatrixSymbol("Y", 3, 3)
|
||||
|
||||
k = symbols("k")
|
||||
Xk = MatrixSymbol("X", k, k)
|
||||
|
||||
Xd = X.as_explicit()
|
||||
|
||||
x, y, z, t = symbols("x y z t")
|
||||
|
||||
|
||||
def test_applyfunc_matrix():
|
||||
x = Dummy('x')
|
||||
double = Lambda(x, x**2)
|
||||
|
||||
expr = ElementwiseApplyFunction(double, Xd)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
assert expr.doit() == Xd.applyfunc(lambda x: x**2)
|
||||
assert expr.shape == (3, 3)
|
||||
assert expr.func(*expr.args) == expr
|
||||
assert simplify(expr) == expr
|
||||
assert expr[0, 0] == double(Xd[0, 0])
|
||||
|
||||
expr = ElementwiseApplyFunction(double, X)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
assert isinstance(expr.doit(), ElementwiseApplyFunction)
|
||||
assert expr == X.applyfunc(double)
|
||||
assert expr.func(*expr.args) == expr
|
||||
|
||||
expr = ElementwiseApplyFunction(exp, X*Y)
|
||||
assert expr.expr == X*Y
|
||||
assert expr.function.dummy_eq(Lambda(x, exp(x)))
|
||||
assert expr.dummy_eq((X*Y).applyfunc(exp))
|
||||
assert expr.func(*expr.args) == expr
|
||||
|
||||
assert isinstance(X*expr, MatMul)
|
||||
assert (X*expr).shape == (3, 3)
|
||||
Z = MatrixSymbol("Z", 2, 3)
|
||||
assert (Z*expr).shape == (2, 3)
|
||||
|
||||
expr = ElementwiseApplyFunction(exp, Z.T)*ElementwiseApplyFunction(exp, Z)
|
||||
assert expr.shape == (3, 3)
|
||||
expr = ElementwiseApplyFunction(exp, Z)*ElementwiseApplyFunction(exp, Z.T)
|
||||
assert expr.shape == (2, 2)
|
||||
|
||||
M = Matrix([[x, y], [z, t]])
|
||||
expr = ElementwiseApplyFunction(sin, M)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
assert expr.function.dummy_eq(Lambda(x, sin(x)))
|
||||
assert expr.expr == M
|
||||
assert expr.doit() == M.applyfunc(sin)
|
||||
assert expr.doit() == Matrix([[sin(x), sin(y)], [sin(z), sin(t)]])
|
||||
assert expr.func(*expr.args) == expr
|
||||
|
||||
expr = ElementwiseApplyFunction(double, Xk)
|
||||
assert expr.doit() == expr
|
||||
assert expr.subs(k, 2).shape == (2, 2)
|
||||
assert (expr*expr).shape == (k, k)
|
||||
M = MatrixSymbol("M", k, t)
|
||||
expr2 = M.T*expr*M
|
||||
assert isinstance(expr2, MatMul)
|
||||
assert expr2.args[1] == expr
|
||||
assert expr2.shape == (t, t)
|
||||
expr3 = expr*M
|
||||
assert expr3.shape == (k, t)
|
||||
|
||||
expr1 = ElementwiseApplyFunction(lambda x: x+1, Xk)
|
||||
expr2 = ElementwiseApplyFunction(lambda x: x, Xk)
|
||||
assert expr1 != expr2
|
||||
|
||||
|
||||
def test_applyfunc_entry():
|
||||
|
||||
af = X.applyfunc(sin)
|
||||
assert af[0, 0] == sin(X[0, 0])
|
||||
|
||||
af = Xd.applyfunc(sin)
|
||||
assert af[0, 0] == sin(X[0, 0])
|
||||
|
||||
|
||||
def test_applyfunc_as_explicit():
|
||||
|
||||
af = X.applyfunc(sin)
|
||||
assert af.as_explicit() == Matrix([
|
||||
[sin(X[0, 0]), sin(X[0, 1]), sin(X[0, 2])],
|
||||
[sin(X[1, 0]), sin(X[1, 1]), sin(X[1, 2])],
|
||||
[sin(X[2, 0]), sin(X[2, 1]), sin(X[2, 2])],
|
||||
])
|
||||
|
||||
|
||||
def test_applyfunc_transpose():
|
||||
|
||||
af = Xk.applyfunc(sin)
|
||||
assert af.T.dummy_eq(Xk.T.applyfunc(sin))
|
||||
|
||||
|
||||
def test_applyfunc_shape_11_matrices():
|
||||
M = MatrixSymbol("M", 1, 1)
|
||||
|
||||
double = Lambda(x, x*2)
|
||||
|
||||
expr = M.applyfunc(sin)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
|
||||
expr = M.applyfunc(double)
|
||||
assert isinstance(expr, MatMul)
|
||||
assert expr == 2*M
|
||||
@@ -0,0 +1,469 @@
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.testing.pytest import raises, slow
|
||||
from sympy.matrices.expressions.blockmatrix import (
|
||||
block_collapse, bc_matmul, bc_block_plus_ident, BlockDiagMatrix,
|
||||
BlockMatrix, bc_dist, bc_matadd, bc_transpose, bc_inverse,
|
||||
blockcut, reblock_2x2, deblock)
|
||||
from sympy.matrices.expressions import (
|
||||
MatrixSymbol, Identity, trace, det, ZeroMatrix, OneMatrix)
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.matrices import (
|
||||
Matrix, ImmutableMatrix, ImmutableSparseMatrix, zeros)
|
||||
from sympy.core import Tuple, Expr, S, Function
|
||||
from sympy.core.symbol import Symbol, symbols
|
||||
from sympy.functions import transpose, im, re
|
||||
|
||||
i, j, k, l, m, n, p = symbols('i:n, p', integer=True)
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
G = MatrixSymbol('G', n, n)
|
||||
H = MatrixSymbol('H', n, n)
|
||||
b1 = BlockMatrix([[G, H]])
|
||||
b2 = BlockMatrix([[G], [H]])
|
||||
|
||||
def test_bc_matmul():
|
||||
assert bc_matmul(H*b1*b2*G) == BlockMatrix([[(H*G*G + H*H*H)*G]])
|
||||
|
||||
def test_bc_matadd():
|
||||
assert bc_matadd(BlockMatrix([[G, H]]) + BlockMatrix([[H, H]])) == \
|
||||
BlockMatrix([[G+H, H+H]])
|
||||
|
||||
def test_bc_transpose():
|
||||
assert bc_transpose(Transpose(BlockMatrix([[A, B], [C, D]]))) == \
|
||||
BlockMatrix([[A.T, C.T], [B.T, D.T]])
|
||||
|
||||
def test_bc_dist_diag():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
C = MatrixSymbol('C', l, l)
|
||||
X = BlockDiagMatrix(A, B, C)
|
||||
|
||||
assert bc_dist(X+X).equals(BlockDiagMatrix(2*A, 2*B, 2*C))
|
||||
|
||||
def test_block_plus_ident():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
Z = MatrixSymbol('Z', n + m, n + m)
|
||||
assert bc_block_plus_ident(X + Identity(m + n) + Z) == \
|
||||
BlockDiagMatrix(Identity(n), Identity(m)) + X + Z
|
||||
|
||||
def test_BlockMatrix():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, k)
|
||||
C = MatrixSymbol('C', l, m)
|
||||
D = MatrixSymbol('D', l, k)
|
||||
M = MatrixSymbol('M', m + k, p)
|
||||
N = MatrixSymbol('N', l + n, k + m)
|
||||
X = BlockMatrix(Matrix([[A, B], [C, D]]))
|
||||
|
||||
assert X.__class__(*X.args) == X
|
||||
|
||||
# block_collapse does nothing on normal inputs
|
||||
E = MatrixSymbol('E', n, m)
|
||||
assert block_collapse(A + 2*E) == A + 2*E
|
||||
F = MatrixSymbol('F', m, m)
|
||||
assert block_collapse(E.T*A*F) == E.T*A*F
|
||||
|
||||
assert X.shape == (l + n, k + m)
|
||||
assert X.blockshape == (2, 2)
|
||||
assert transpose(X) == BlockMatrix(Matrix([[A.T, C.T], [B.T, D.T]]))
|
||||
assert transpose(X).shape == X.shape[::-1]
|
||||
|
||||
# Test that BlockMatrices and MatrixSymbols can still mix
|
||||
assert (X*M).is_MatMul
|
||||
assert X._blockmul(M).is_MatMul
|
||||
assert (X*M).shape == (n + l, p)
|
||||
assert (X + N).is_MatAdd
|
||||
assert X._blockadd(N).is_MatAdd
|
||||
assert (X + N).shape == X.shape
|
||||
|
||||
E = MatrixSymbol('E', m, 1)
|
||||
F = MatrixSymbol('F', k, 1)
|
||||
|
||||
Y = BlockMatrix(Matrix([[E], [F]]))
|
||||
|
||||
assert (X*Y).shape == (l + n, 1)
|
||||
assert block_collapse(X*Y).blocks[0, 0] == A*E + B*F
|
||||
assert block_collapse(X*Y).blocks[1, 0] == C*E + D*F
|
||||
|
||||
# block_collapse passes down into container objects, transposes, and inverse
|
||||
assert block_collapse(transpose(X*Y)) == transpose(block_collapse(X*Y))
|
||||
assert block_collapse(Tuple(X*Y, 2*X)) == (
|
||||
block_collapse(X*Y), block_collapse(2*X))
|
||||
|
||||
# Make sure that MatrixSymbols will enter 1x1 BlockMatrix if it simplifies
|
||||
Ab = BlockMatrix([[A]])
|
||||
Z = MatrixSymbol('Z', *A.shape)
|
||||
assert block_collapse(Ab + Z) == A + Z
|
||||
|
||||
def test_block_collapse_explicit_matrices():
|
||||
A = Matrix([[1, 2], [3, 4]])
|
||||
assert block_collapse(BlockMatrix([[A]])) == A
|
||||
|
||||
A = ImmutableSparseMatrix([[1, 2], [3, 4]])
|
||||
assert block_collapse(BlockMatrix([[A]])) == A
|
||||
|
||||
def test_issue_17624():
|
||||
a = MatrixSymbol("a", 2, 2)
|
||||
z = ZeroMatrix(2, 2)
|
||||
b = BlockMatrix([[a, z], [z, z]])
|
||||
assert block_collapse(b * b) == BlockMatrix([[a**2, z], [z, z]])
|
||||
assert block_collapse(b * b * b) == BlockMatrix([[a**3, z], [z, z]])
|
||||
|
||||
def test_issue_18618():
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
assert A == Matrix(BlockDiagMatrix(A))
|
||||
|
||||
def test_BlockMatrix_trace():
|
||||
A, B, C, D = [MatrixSymbol(s, 3, 3) for s in 'ABCD']
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert trace(X) == trace(A) + trace(D)
|
||||
assert trace(BlockMatrix([ZeroMatrix(n, n)])) == 0
|
||||
|
||||
def test_BlockMatrix_Determinant():
|
||||
A, B, C, D = [MatrixSymbol(s, 3, 3) for s in 'ABCD']
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.assume import assuming
|
||||
with assuming(Q.invertible(A)):
|
||||
assert det(X) == det(A) * det(X.schur('A'))
|
||||
|
||||
assert isinstance(det(X), Expr)
|
||||
assert det(BlockMatrix([A])) == det(A)
|
||||
assert det(BlockMatrix([ZeroMatrix(n, n)])) == 0
|
||||
|
||||
def test_squareBlockMatrix():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
Y = BlockMatrix([[A]])
|
||||
|
||||
assert X.is_square
|
||||
|
||||
Q = X + Identity(m + n)
|
||||
assert (block_collapse(Q) ==
|
||||
BlockMatrix([[A + Identity(n), B], [C, D + Identity(m)]]))
|
||||
|
||||
assert (X + MatrixSymbol('Q', n + m, n + m)).is_MatAdd
|
||||
assert (X * MatrixSymbol('Q', n + m, n + m)).is_MatMul
|
||||
|
||||
assert block_collapse(Y.I) == A.I
|
||||
|
||||
assert isinstance(X.inverse(), Inverse)
|
||||
|
||||
assert not X.is_Identity
|
||||
|
||||
Z = BlockMatrix([[Identity(n), B], [C, D]])
|
||||
assert not Z.is_Identity
|
||||
|
||||
|
||||
def test_BlockMatrix_2x2_inverse_symbolic():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, k - m)
|
||||
C = MatrixSymbol('C', k - n, m)
|
||||
D = MatrixSymbol('D', k - n, k - m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert X.is_square and X.shape == (k, k)
|
||||
assert isinstance(block_collapse(X.I), Inverse) # Can't invert when none of the blocks is square
|
||||
|
||||
# test code path where only A is invertible
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = ZeroMatrix(m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[A.I + A.I * B * X.schur('A').I * C * A.I, -A.I * B * X.schur('A').I],
|
||||
[-X.schur('A').I * C * A.I, X.schur('A').I],
|
||||
])
|
||||
|
||||
# test code path where only B is invertible
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = ZeroMatrix(m, m)
|
||||
D = MatrixSymbol('D', m, n)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[-X.schur('B').I * D * B.I, X.schur('B').I],
|
||||
[B.I + B.I * A * X.schur('B').I * D * B.I, -B.I * A * X.schur('B').I],
|
||||
])
|
||||
|
||||
# test code path where only C is invertible
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = ZeroMatrix(n, n)
|
||||
C = MatrixSymbol('C', m, m)
|
||||
D = MatrixSymbol('D', m, n)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[-C.I * D * X.schur('C').I, C.I + C.I * D * X.schur('C').I * A * C.I],
|
||||
[X.schur('C').I, -X.schur('C').I * A * C.I],
|
||||
])
|
||||
|
||||
# test code path where only D is invertible
|
||||
A = ZeroMatrix(n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[X.schur('D').I, -X.schur('D').I * B * D.I],
|
||||
[-D.I * C * X.schur('D').I, D.I + D.I * C * X.schur('D').I * B * D.I],
|
||||
])
|
||||
|
||||
|
||||
def test_BlockMatrix_2x2_inverse_numeric():
|
||||
"""Test 2x2 block matrix inversion numerically for all 4 formulas"""
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
# rank deficient matrices that have full rank when two of them combined
|
||||
D1 = Matrix([[1, 2], [2, 4]])
|
||||
D2 = Matrix([[1, 3], [3, 9]])
|
||||
D3 = Matrix([[1, 4], [4, 16]])
|
||||
assert D1.rank() == D2.rank() == D3.rank() == 1
|
||||
assert (D1 + D2).rank() == (D2 + D3).rank() == (D3 + D1).rank() == 2
|
||||
|
||||
# Only A is invertible
|
||||
K = BlockMatrix([[M, D1], [D2, D3]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
# Only B is invertible
|
||||
K = BlockMatrix([[D1, M], [D2, D3]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
# Only C is invertible
|
||||
K = BlockMatrix([[D1, D2], [M, D3]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
# Only D is invertible
|
||||
K = BlockMatrix([[D1, D2], [D3, M]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
|
||||
|
||||
@slow
|
||||
def test_BlockMatrix_3x3_symbolic():
|
||||
# Only test one of these, instead of all permutations, because it's slow
|
||||
rowblocksizes = (n, m, k)
|
||||
colblocksizes = (m, k, n)
|
||||
K = BlockMatrix([
|
||||
[MatrixSymbol('M%s%s' % (rows, cols), rows, cols) for cols in colblocksizes]
|
||||
for rows in rowblocksizes
|
||||
])
|
||||
collapse = block_collapse(K.I)
|
||||
assert isinstance(collapse, BlockMatrix)
|
||||
|
||||
|
||||
def test_BlockDiagMatrix():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
C = MatrixSymbol('C', l, l)
|
||||
M = MatrixSymbol('M', n + m + l, n + m + l)
|
||||
|
||||
X = BlockDiagMatrix(A, B, C)
|
||||
Y = BlockDiagMatrix(A, 2*B, 3*C)
|
||||
|
||||
assert X.blocks[1, 1] == B
|
||||
assert X.shape == (n + m + l, n + m + l)
|
||||
assert all(X.blocks[i, j].is_ZeroMatrix if i != j else X.blocks[i, j] in [A, B, C]
|
||||
for i in range(3) for j in range(3))
|
||||
assert X.__class__(*X.args) == X
|
||||
assert X.get_diag_blocks() == (A, B, C)
|
||||
|
||||
assert isinstance(block_collapse(X.I * X), Identity)
|
||||
|
||||
assert bc_matmul(X*X) == BlockDiagMatrix(A*A, B*B, C*C)
|
||||
assert block_collapse(X*X) == BlockDiagMatrix(A*A, B*B, C*C)
|
||||
#XXX: should be == ??
|
||||
assert block_collapse(X + X).equals(BlockDiagMatrix(2*A, 2*B, 2*C))
|
||||
assert block_collapse(X*Y) == BlockDiagMatrix(A*A, 2*B*B, 3*C*C)
|
||||
assert block_collapse(X + Y) == BlockDiagMatrix(2*A, 3*B, 4*C)
|
||||
|
||||
# Ensure that BlockDiagMatrices can still interact with normal MatrixExprs
|
||||
assert (X*(2*M)).is_MatMul
|
||||
assert (X + (2*M)).is_MatAdd
|
||||
|
||||
assert (X._blockmul(M)).is_MatMul
|
||||
assert (X._blockadd(M)).is_MatAdd
|
||||
|
||||
def test_BlockDiagMatrix_nonsquare():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', k, l)
|
||||
X = BlockDiagMatrix(A, B)
|
||||
assert X.shape == (n + k, m + l)
|
||||
assert X.shape == (n + k, m + l)
|
||||
assert X.rowblocksizes == [n, k]
|
||||
assert X.colblocksizes == [m, l]
|
||||
C = MatrixSymbol('C', n, m)
|
||||
D = MatrixSymbol('D', k, l)
|
||||
Y = BlockDiagMatrix(C, D)
|
||||
assert block_collapse(X + Y) == BlockDiagMatrix(A + C, B + D)
|
||||
assert block_collapse(X * Y.T) == BlockDiagMatrix(A * C.T, B * D.T)
|
||||
raises(NonInvertibleMatrixError, lambda: BlockDiagMatrix(A, C.T).inverse())
|
||||
|
||||
def test_BlockDiagMatrix_determinant():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
assert det(BlockDiagMatrix()) == 1
|
||||
assert det(BlockDiagMatrix(A)) == det(A)
|
||||
assert det(BlockDiagMatrix(A, B)) == det(A) * det(B)
|
||||
|
||||
# non-square blocks
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', n, m)
|
||||
assert det(BlockDiagMatrix(C, D)) == 0
|
||||
|
||||
def test_BlockDiagMatrix_trace():
|
||||
assert trace(BlockDiagMatrix()) == 0
|
||||
assert trace(BlockDiagMatrix(ZeroMatrix(n, n))) == 0
|
||||
A = MatrixSymbol('A', n, n)
|
||||
assert trace(BlockDiagMatrix(A)) == trace(A)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
assert trace(BlockDiagMatrix(A, B)) == trace(A) + trace(B)
|
||||
|
||||
# non-square blocks
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', n, m)
|
||||
assert isinstance(trace(BlockDiagMatrix(C, D)), Trace)
|
||||
|
||||
def test_BlockDiagMatrix_transpose():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', k, l)
|
||||
assert transpose(BlockDiagMatrix()) == BlockDiagMatrix()
|
||||
assert transpose(BlockDiagMatrix(A)) == BlockDiagMatrix(A.T)
|
||||
assert transpose(BlockDiagMatrix(A, B)) == BlockDiagMatrix(A.T, B.T)
|
||||
|
||||
def test_issue_2460():
|
||||
bdm1 = BlockDiagMatrix(Matrix([i]), Matrix([j]))
|
||||
bdm2 = BlockDiagMatrix(Matrix([k]), Matrix([l]))
|
||||
assert block_collapse(bdm1 + bdm2) == BlockDiagMatrix(Matrix([i + k]), Matrix([j + l]))
|
||||
|
||||
def test_blockcut():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = blockcut(A, (n/2, n/2), (m/2, m/2))
|
||||
assert B == BlockMatrix([[A[:n/2, :m/2], A[:n/2, m/2:]],
|
||||
[A[n/2:, :m/2], A[n/2:, m/2:]]])
|
||||
|
||||
M = ImmutableMatrix(4, 4, range(16))
|
||||
B = blockcut(M, (2, 2), (2, 2))
|
||||
assert M == ImmutableMatrix(B)
|
||||
|
||||
B = blockcut(M, (1, 3), (2, 2))
|
||||
assert ImmutableMatrix(B.blocks[0, 1]) == ImmutableMatrix([[2, 3]])
|
||||
|
||||
def test_reblock_2x2():
|
||||
B = BlockMatrix([[MatrixSymbol('A_%d%d'%(i,j), 2, 2)
|
||||
for j in range(3)]
|
||||
for i in range(3)])
|
||||
assert B.blocks.shape == (3, 3)
|
||||
|
||||
BB = reblock_2x2(B)
|
||||
assert BB.blocks.shape == (2, 2)
|
||||
|
||||
assert B.shape == BB.shape
|
||||
assert B.as_explicit() == BB.as_explicit()
|
||||
|
||||
def test_deblock():
|
||||
B = BlockMatrix([[MatrixSymbol('A_%d%d'%(i,j), n, n)
|
||||
for j in range(4)]
|
||||
for i in range(4)])
|
||||
|
||||
assert deblock(reblock_2x2(B)) == B
|
||||
|
||||
def test_block_collapse_type():
|
||||
bm1 = BlockDiagMatrix(ImmutableMatrix([1]), ImmutableMatrix([2]))
|
||||
bm2 = BlockDiagMatrix(ImmutableMatrix([3]), ImmutableMatrix([4]))
|
||||
|
||||
assert bm1.T.__class__ == BlockDiagMatrix
|
||||
assert block_collapse(bm1 - bm2).__class__ == BlockDiagMatrix
|
||||
assert block_collapse(Inverse(bm1)).__class__ == BlockDiagMatrix
|
||||
assert block_collapse(Transpose(bm1)).__class__ == BlockDiagMatrix
|
||||
assert bc_transpose(Transpose(bm1)).__class__ == BlockDiagMatrix
|
||||
assert bc_inverse(Inverse(bm1)).__class__ == BlockDiagMatrix
|
||||
|
||||
def test_invalid_block_matrix():
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[Identity(2), Identity(5)],
|
||||
]))
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[Identity(n), Identity(m)],
|
||||
]))
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[ZeroMatrix(n, n), ZeroMatrix(n, n)],
|
||||
[ZeroMatrix(n, n - 1), ZeroMatrix(n, n + 1)],
|
||||
]))
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[ZeroMatrix(n - 1, n), ZeroMatrix(n, n)],
|
||||
[ZeroMatrix(n + 1, n), ZeroMatrix(n, n)],
|
||||
]))
|
||||
|
||||
def test_block_lu_decomposition():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
|
||||
#LDU decomposition
|
||||
L, D, U = X.LDUdecomposition()
|
||||
assert block_collapse(L*D*U) == X
|
||||
|
||||
#UDL decomposition
|
||||
U, D, L = X.UDLdecomposition()
|
||||
assert block_collapse(U*D*L) == X
|
||||
|
||||
#LU decomposition
|
||||
L, U = X.LUdecomposition()
|
||||
assert block_collapse(L*U) == X
|
||||
|
||||
def test_issue_21866():
|
||||
n = 10
|
||||
I = Identity(n)
|
||||
O = ZeroMatrix(n, n)
|
||||
A = BlockMatrix([[ I, O, O, O ],
|
||||
[ O, I, O, O ],
|
||||
[ O, O, I, O ],
|
||||
[ I, O, O, I ]])
|
||||
Ainv = block_collapse(A.inv())
|
||||
AinvT = BlockMatrix([[ I, O, O, O ],
|
||||
[ O, I, O, O ],
|
||||
[ O, O, I, O ],
|
||||
[ -I, O, O, I ]])
|
||||
assert Ainv == AinvT
|
||||
|
||||
|
||||
def test_adjoint_and_special_matrices():
|
||||
A = Identity(3)
|
||||
B = OneMatrix(3, 2)
|
||||
C = ZeroMatrix(2, 3)
|
||||
D = Identity(2)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
X2 = BlockMatrix([[A, S.ImaginaryUnit*B], [C, D]])
|
||||
assert X.adjoint() == BlockMatrix([[A, ZeroMatrix(3, 2)], [OneMatrix(2, 3), D]])
|
||||
assert re(X) == X
|
||||
assert X2.adjoint() == BlockMatrix([[A, ZeroMatrix(3, 2)], [-S.ImaginaryUnit*OneMatrix(2, 3), D]])
|
||||
assert im(X2) == BlockMatrix([[ZeroMatrix(3, 3), OneMatrix(3, 2)], [ZeroMatrix(2, 3), ZeroMatrix(2, 2)]])
|
||||
|
||||
|
||||
def test_block_matrix_derivative():
|
||||
x = symbols('x')
|
||||
A = Matrix(3, 3, [Function(f'a{i}')(x) for i in range(9)])
|
||||
bc = BlockMatrix([[A[:2, :2], A[:2, 2]], [A[2, :2], A[2:, 2]]])
|
||||
assert Matrix(bc.diff(x)) - A.diff(x) == zeros(3, 3)
|
||||
|
||||
|
||||
def test_transpose_inverse_commute():
|
||||
n = Symbol('n')
|
||||
I = Identity(n)
|
||||
Z = ZeroMatrix(n, n)
|
||||
A = BlockMatrix([[I, Z], [Z, I]])
|
||||
|
||||
assert block_collapse(A.transpose().inverse()) == A
|
||||
assert block_collapse(A.inverse().transpose()) == A
|
||||
|
||||
assert block_collapse(MatPow(A.transpose(), -2)) == MatPow(A, -2)
|
||||
assert block_collapse(MatPow(A, -2).transpose()) == MatPow(A, -2)
|
||||
@@ -0,0 +1,48 @@
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.symbol import Symbol, symbols
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
from sympy.matrices.expressions.companion import CompanionMatrix
|
||||
from sympy.polys.polytools import Poly
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_creation():
|
||||
x = Symbol('x')
|
||||
y = Symbol('y')
|
||||
raises(ValueError, lambda: CompanionMatrix(1))
|
||||
raises(ValueError, lambda: CompanionMatrix(Poly([1], x)))
|
||||
raises(ValueError, lambda: CompanionMatrix(Poly([2, 1], x)))
|
||||
raises(ValueError, lambda: CompanionMatrix(Poly(x*y, [x, y])))
|
||||
assert unchanged(CompanionMatrix, Poly([1, 2, 3], x))
|
||||
|
||||
|
||||
def test_shape():
|
||||
c0, c1, c2 = symbols('c0:3')
|
||||
x = Symbol('x')
|
||||
assert CompanionMatrix(Poly([1, c0], x)).shape == (1, 1)
|
||||
assert CompanionMatrix(Poly([1, c1, c0], x)).shape == (2, 2)
|
||||
assert CompanionMatrix(Poly([1, c2, c1, c0], x)).shape == (3, 3)
|
||||
|
||||
|
||||
def test_entry():
|
||||
c0, c1, c2 = symbols('c0:3')
|
||||
x = Symbol('x')
|
||||
A = CompanionMatrix(Poly([1, c2, c1, c0], x))
|
||||
assert A[0, 0] == 0
|
||||
assert A[1, 0] == 1
|
||||
assert A[1, 1] == 0
|
||||
assert A[2, 1] == 1
|
||||
assert A[0, 2] == -c0
|
||||
assert A[1, 2] == -c1
|
||||
assert A[2, 2] == -c2
|
||||
|
||||
|
||||
def test_as_explicit():
|
||||
c0, c1, c2 = symbols('c0:3')
|
||||
x = Symbol('x')
|
||||
assert CompanionMatrix(Poly([1, c0], x)).as_explicit() == \
|
||||
ImmutableDenseMatrix([-c0])
|
||||
assert CompanionMatrix(Poly([1, c1, c0], x)).as_explicit() == \
|
||||
ImmutableDenseMatrix([[0, -c0], [1, -c1]])
|
||||
assert CompanionMatrix(Poly([1, c2, c1, c0], x)).as_explicit() == \
|
||||
ImmutableDenseMatrix([[0, 0, -c0], [1, 0, -c1], [0, 1, -c2]])
|
||||
@@ -0,0 +1,477 @@
|
||||
"""
|
||||
Some examples have been taken from:
|
||||
|
||||
http://www.math.uwaterloo.ca/~hwolkowi//matrixcookbook.pdf
|
||||
"""
|
||||
from sympy import KroneckerProduct
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.exponential import (exp, log)
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin, tan)
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.expressions.determinant import Determinant
|
||||
from sympy.matrices.expressions.diagonal import DiagMatrix
|
||||
from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct, hadamard_product)
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import OneMatrix
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.special import (Identity, ZeroMatrix)
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
from sympy.matrices.expressions import hadamard_power
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayAdd, ArrayTensorProduct, PermuteDims
|
||||
|
||||
i, j, k = symbols("i j k")
|
||||
m, n = symbols("m n")
|
||||
|
||||
X = MatrixSymbol("X", k, k)
|
||||
x = MatrixSymbol("x", k, 1)
|
||||
y = MatrixSymbol("y", k, 1)
|
||||
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
D = MatrixSymbol("D", k, k)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
c = MatrixSymbol("c", k, 1)
|
||||
d = MatrixSymbol("d", k, 1)
|
||||
|
||||
|
||||
KDelta = lambda i, j: KroneckerDelta(i, j, (0, k-1))
|
||||
|
||||
|
||||
def _check_derivative_with_explicit_matrix(expr, x, diffexpr, dim=2):
|
||||
# TODO: this is commented because it slows down the tests.
|
||||
return
|
||||
|
||||
expr = expr.xreplace({k: dim})
|
||||
x = x.xreplace({k: dim})
|
||||
diffexpr = diffexpr.xreplace({k: dim})
|
||||
|
||||
expr = expr.as_explicit()
|
||||
x = x.as_explicit()
|
||||
diffexpr = diffexpr.as_explicit()
|
||||
|
||||
assert expr.diff(x).reshape(*diffexpr.shape).tomatrix() == diffexpr
|
||||
|
||||
|
||||
def test_matrix_derivative_by_scalar():
|
||||
assert A.diff(i) == ZeroMatrix(k, k)
|
||||
assert (A*(X + B)*c).diff(i) == ZeroMatrix(k, 1)
|
||||
assert x.diff(i) == ZeroMatrix(k, 1)
|
||||
assert (x.T*y).diff(i) == ZeroMatrix(1, 1)
|
||||
assert (x*x.T).diff(i) == ZeroMatrix(k, k)
|
||||
assert (x + y).diff(i) == ZeroMatrix(k, 1)
|
||||
assert hadamard_power(x, 2).diff(i) == ZeroMatrix(k, 1)
|
||||
assert hadamard_power(x, i).diff(i).dummy_eq(
|
||||
HadamardProduct(x.applyfunc(log), HadamardPower(x, i)))
|
||||
assert hadamard_product(x, y).diff(i) == ZeroMatrix(k, 1)
|
||||
assert hadamard_product(i*OneMatrix(k, 1), x, y).diff(i) == hadamard_product(x, y)
|
||||
assert (i*x).diff(i) == x
|
||||
assert (sin(i)*A*B*x).diff(i) == cos(i)*A*B*x
|
||||
assert x.applyfunc(sin).diff(i) == ZeroMatrix(k, 1)
|
||||
assert Trace(i**2*X).diff(i) == 2*i*Trace(X)
|
||||
|
||||
mu = symbols("mu")
|
||||
expr = (2*mu*x)
|
||||
assert expr.diff(x) == 2*mu*Identity(k)
|
||||
|
||||
|
||||
def test_one_matrix():
|
||||
assert MatMul(x.T, OneMatrix(k, 1)).diff(x) == OneMatrix(k, 1)
|
||||
|
||||
|
||||
def test_matrix_derivative_non_matrix_result():
|
||||
# This is a 4-dimensional array:
|
||||
I = Identity(k)
|
||||
AdA = PermuteDims(ArrayTensorProduct(I, I), Permutation(3)(1, 2))
|
||||
assert A.diff(A) == AdA
|
||||
assert A.T.diff(A) == PermuteDims(ArrayTensorProduct(I, I), Permutation(3)(1, 2, 3))
|
||||
assert (2*A).diff(A) == PermuteDims(ArrayTensorProduct(2*I, I), Permutation(3)(1, 2))
|
||||
assert MatAdd(A, A).diff(A) == ArrayAdd(AdA, AdA)
|
||||
assert (A + B).diff(A) == AdA
|
||||
|
||||
|
||||
def test_matrix_derivative_trivial_cases():
|
||||
# Cookbook example 33:
|
||||
# TODO: find a way to represent a four-dimensional zero-array:
|
||||
assert X.diff(A) == ArrayDerivative(X, A)
|
||||
|
||||
|
||||
def test_matrix_derivative_with_inverse():
|
||||
|
||||
# Cookbook example 61:
|
||||
expr = a.T*Inverse(X)*b
|
||||
assert expr.diff(X) == -Inverse(X).T*a*b.T*Inverse(X).T
|
||||
|
||||
# Cookbook example 62:
|
||||
expr = Determinant(Inverse(X))
|
||||
# Not implemented yet:
|
||||
# assert expr.diff(X) == -Determinant(X.inv())*(X.inv()).T
|
||||
|
||||
# Cookbook example 63:
|
||||
expr = Trace(A*Inverse(X)*B)
|
||||
assert expr.diff(X) == -(X**(-1)*B*A*X**(-1)).T
|
||||
|
||||
# Cookbook example 64:
|
||||
expr = Trace(Inverse(X + A))
|
||||
assert expr.diff(X) == -(Inverse(X + A)).T**2
|
||||
|
||||
|
||||
def test_matrix_derivative_vectors_and_scalars():
|
||||
|
||||
assert x.diff(x) == Identity(k)
|
||||
assert x[i, 0].diff(x[m, 0]).doit() == KDelta(m, i)
|
||||
|
||||
assert x.T.diff(x) == Identity(k)
|
||||
|
||||
# Cookbook example 69:
|
||||
expr = x.T*a
|
||||
assert expr.diff(x) == a
|
||||
assert expr[0, 0].diff(x[m, 0]).doit() == a[m, 0]
|
||||
expr = a.T*x
|
||||
assert expr.diff(x) == a
|
||||
|
||||
# Cookbook example 70:
|
||||
expr = a.T*X*b
|
||||
assert expr.diff(X) == a*b.T
|
||||
|
||||
# Cookbook example 71:
|
||||
expr = a.T*X.T*b
|
||||
assert expr.diff(X) == b*a.T
|
||||
|
||||
# Cookbook example 72:
|
||||
expr = a.T*X*a
|
||||
assert expr.diff(X) == a*a.T
|
||||
expr = a.T*X.T*a
|
||||
assert expr.diff(X) == a*a.T
|
||||
|
||||
# Cookbook example 77:
|
||||
expr = b.T*X.T*X*c
|
||||
assert expr.diff(X) == X*b*c.T + X*c*b.T
|
||||
|
||||
# Cookbook example 78:
|
||||
expr = (B*x + b).T*C*(D*x + d)
|
||||
assert expr.diff(x) == B.T*C*(D*x + d) + D.T*C.T*(B*x + b)
|
||||
|
||||
# Cookbook example 81:
|
||||
expr = x.T*B*x
|
||||
assert expr.diff(x) == B*x + B.T*x
|
||||
|
||||
# Cookbook example 82:
|
||||
expr = b.T*X.T*D*X*c
|
||||
assert expr.diff(X) == D.T*X*b*c.T + D*X*c*b.T
|
||||
|
||||
# Cookbook example 83:
|
||||
expr = (X*b + c).T*D*(X*b + c)
|
||||
assert expr.diff(X) == D*(X*b + c)*b.T + D.T*(X*b + c)*b.T
|
||||
assert str(expr[0, 0].diff(X[m, n]).doit()) == \
|
||||
'b[n, 0]*Sum((c[_i_1, 0] + Sum(X[_i_1, _i_3]*b[_i_3, 0], (_i_3, 0, k - 1)))*D[_i_1, m], (_i_1, 0, k - 1)) + Sum((c[_i_2, 0] + Sum(X[_i_2, _i_4]*b[_i_4, 0], (_i_4, 0, k - 1)))*D[m, _i_2]*b[n, 0], (_i_2, 0, k - 1))'
|
||||
|
||||
# See https://github.com/sympy/sympy/issues/16504#issuecomment-1018339957
|
||||
expr = x*x.T*x
|
||||
I = Identity(k)
|
||||
assert expr.diff(x) == KroneckerProduct(I, x.T*x) + 2*x*x.T
|
||||
|
||||
|
||||
def test_matrix_derivatives_of_traces():
|
||||
|
||||
expr = Trace(A)*A
|
||||
I = Identity(k)
|
||||
assert expr.diff(A) == ArrayAdd(ArrayTensorProduct(I, A), PermuteDims(ArrayTensorProduct(Trace(A)*I, I), Permutation(3)(1, 2)))
|
||||
assert expr[i, j].diff(A[m, n]).doit() == (
|
||||
KDelta(i, m)*KDelta(j, n)*Trace(A) +
|
||||
KDelta(m, n)*A[i, j]
|
||||
)
|
||||
|
||||
## First order:
|
||||
|
||||
# Cookbook example 99:
|
||||
expr = Trace(X)
|
||||
assert expr.diff(X) == Identity(k)
|
||||
assert expr.rewrite(Sum).diff(X[m, n]).doit() == KDelta(m, n)
|
||||
|
||||
# Cookbook example 100:
|
||||
expr = Trace(X*A)
|
||||
assert expr.diff(X) == A.T
|
||||
assert expr.rewrite(Sum).diff(X[m, n]).doit() == A[n, m]
|
||||
|
||||
# Cookbook example 101:
|
||||
expr = Trace(A*X*B)
|
||||
assert expr.diff(X) == A.T*B.T
|
||||
assert expr.rewrite(Sum).diff(X[m, n]).doit().dummy_eq((A.T*B.T)[m, n])
|
||||
|
||||
# Cookbook example 102:
|
||||
expr = Trace(A*X.T*B)
|
||||
assert expr.diff(X) == B*A
|
||||
|
||||
# Cookbook example 103:
|
||||
expr = Trace(X.T*A)
|
||||
assert expr.diff(X) == A
|
||||
|
||||
# Cookbook example 104:
|
||||
expr = Trace(A*X.T)
|
||||
assert expr.diff(X) == A
|
||||
|
||||
# Cookbook example 105:
|
||||
# TODO: TensorProduct is not supported
|
||||
#expr = Trace(TensorProduct(A, X))
|
||||
#assert expr.diff(X) == Trace(A)*Identity(k)
|
||||
|
||||
## Second order:
|
||||
|
||||
# Cookbook example 106:
|
||||
expr = Trace(X**2)
|
||||
assert expr.diff(X) == 2*X.T
|
||||
|
||||
# Cookbook example 107:
|
||||
expr = Trace(X**2*B)
|
||||
assert expr.diff(X) == (X*B + B*X).T
|
||||
expr = Trace(MatMul(X, X, B))
|
||||
assert expr.diff(X) == (X*B + B*X).T
|
||||
|
||||
# Cookbook example 108:
|
||||
expr = Trace(X.T*B*X)
|
||||
assert expr.diff(X) == B*X + B.T*X
|
||||
|
||||
# Cookbook example 109:
|
||||
expr = Trace(B*X*X.T)
|
||||
assert expr.diff(X) == B*X + B.T*X
|
||||
|
||||
# Cookbook example 110:
|
||||
expr = Trace(X*X.T*B)
|
||||
assert expr.diff(X) == B*X + B.T*X
|
||||
|
||||
# Cookbook example 111:
|
||||
expr = Trace(X*B*X.T)
|
||||
assert expr.diff(X) == X*B.T + X*B
|
||||
|
||||
# Cookbook example 112:
|
||||
expr = Trace(B*X.T*X)
|
||||
assert expr.diff(X) == X*B.T + X*B
|
||||
|
||||
# Cookbook example 113:
|
||||
expr = Trace(X.T*X*B)
|
||||
assert expr.diff(X) == X*B.T + X*B
|
||||
|
||||
# Cookbook example 114:
|
||||
expr = Trace(A*X*B*X)
|
||||
assert expr.diff(X) == A.T*X.T*B.T + B.T*X.T*A.T
|
||||
|
||||
# Cookbook example 115:
|
||||
expr = Trace(X.T*X)
|
||||
assert expr.diff(X) == 2*X
|
||||
expr = Trace(X*X.T)
|
||||
assert expr.diff(X) == 2*X
|
||||
|
||||
# Cookbook example 116:
|
||||
expr = Trace(B.T*X.T*C*X*B)
|
||||
assert expr.diff(X) == C.T*X*B*B.T + C*X*B*B.T
|
||||
|
||||
# Cookbook example 117:
|
||||
expr = Trace(X.T*B*X*C)
|
||||
assert expr.diff(X) == B*X*C + B.T*X*C.T
|
||||
|
||||
# Cookbook example 118:
|
||||
expr = Trace(A*X*B*X.T*C)
|
||||
assert expr.diff(X) == A.T*C.T*X*B.T + C*A*X*B
|
||||
|
||||
# Cookbook example 119:
|
||||
expr = Trace((A*X*B + C)*(A*X*B + C).T)
|
||||
assert expr.diff(X) == 2*A.T*(A*X*B + C)*B.T
|
||||
|
||||
# Cookbook example 120:
|
||||
# TODO: no support for TensorProduct.
|
||||
# expr = Trace(TensorProduct(X, X))
|
||||
# expr = Trace(X)*Trace(X)
|
||||
# expr.diff(X) == 2*Trace(X)*Identity(k)
|
||||
|
||||
# Higher Order
|
||||
|
||||
# Cookbook example 121:
|
||||
expr = Trace(X**k)
|
||||
#assert expr.diff(X) == k*(X**(k-1)).T
|
||||
|
||||
# Cookbook example 122:
|
||||
expr = Trace(A*X**k)
|
||||
#assert expr.diff(X) == # Needs indices
|
||||
|
||||
# Cookbook example 123:
|
||||
expr = Trace(B.T*X.T*C*X*X.T*C*X*B)
|
||||
assert expr.diff(X) == C*X*X.T*C*X*B*B.T + C.T*X*B*B.T*X.T*C.T*X + C*X*B*B.T*X.T*C*X + C.T*X*X.T*C.T*X*B*B.T
|
||||
|
||||
# Other
|
||||
|
||||
# Cookbook example 124:
|
||||
expr = Trace(A*X**(-1)*B)
|
||||
assert expr.diff(X) == -Inverse(X).T*A.T*B.T*Inverse(X).T
|
||||
|
||||
# Cookbook example 125:
|
||||
expr = Trace(Inverse(X.T*C*X)*A)
|
||||
# Warning: result in the cookbook is equivalent if B and C are symmetric:
|
||||
assert expr.diff(X) == - X.inv().T*A.T*X.inv()*C.inv().T*X.inv().T - X.inv().T*A*X.inv()*C.inv()*X.inv().T
|
||||
|
||||
# Cookbook example 126:
|
||||
expr = Trace((X.T*C*X).inv()*(X.T*B*X))
|
||||
assert expr.diff(X) == -2*C*X*(X.T*C*X).inv()*X.T*B*X*(X.T*C*X).inv() + 2*B*X*(X.T*C*X).inv()
|
||||
|
||||
# Cookbook example 127:
|
||||
expr = Trace((A + X.T*C*X).inv()*(X.T*B*X))
|
||||
# Warning: result in the cookbook is equivalent if B and C are symmetric:
|
||||
assert expr.diff(X) == B*X*Inverse(A + X.T*C*X) - C*X*Inverse(A + X.T*C*X)*X.T*B*X*Inverse(A + X.T*C*X) - C.T*X*Inverse(A.T + (C*X).T*X)*X.T*B.T*X*Inverse(A.T + (C*X).T*X) + B.T*X*Inverse(A.T + (C*X).T*X)
|
||||
|
||||
|
||||
def test_derivatives_of_complicated_matrix_expr():
|
||||
expr = a.T*(A*X*(X.T*B + X*A) + B.T*X.T*(a*b.T*(X*D*X.T + X*(X.T*B + A*X)*D*B - X.T*C.T*A)*B + B*(X*D.T + B*A*X*A.T - 3*X*D))*B + 42*X*B*X.T*A.T*(X + X.T))*b
|
||||
result = (B*(B*A*X*A.T - 3*X*D + X*D.T) + a*b.T*(X*(A*X + X.T*B)*D*B + X*D*X.T - X.T*C.T*A)*B)*B*b*a.T*B.T + B**2*b*a.T*B.T*X.T*a*b.T*X*D + 42*A*X*B.T*X.T*a*b.T + B*D*B**3*b*a.T*B.T*X.T*a*b.T*X + B*b*a.T*A*X + a*b.T*(42*X + 42*X.T)*A*X*B.T + b*a.T*X*B*a*b.T*B.T**2*X*D.T + b*a.T*X*B*a*b.T*B.T**3*D.T*(B.T*X + X.T*A.T) + 42*b*a.T*X*B*X.T*A.T + A.T*(42*X + 42*X.T)*b*a.T*X*B + A.T*B.T**2*X*B*a*b.T*B.T*A + A.T*a*b.T*(A.T*X.T + B.T*X) + A.T*X.T*b*a.T*X*B*a*b.T*B.T**3*D.T + B.T*X*B*a*b.T*B.T*D - 3*B.T*X*B*a*b.T*B.T*D.T - C.T*A*B**2*b*a.T*B.T*X.T*a*b.T + X.T*A.T*a*b.T*A.T
|
||||
assert expr.diff(X) == result
|
||||
|
||||
|
||||
def test_mixed_deriv_mixed_expressions():
|
||||
|
||||
expr = 3*Trace(A)
|
||||
assert expr.diff(A) == 3*Identity(k)
|
||||
|
||||
expr = k
|
||||
deriv = expr.diff(A)
|
||||
assert isinstance(deriv, ZeroMatrix)
|
||||
assert deriv == ZeroMatrix(k, k)
|
||||
|
||||
expr = Trace(A)**2
|
||||
assert expr.diff(A) == (2*Trace(A))*Identity(k)
|
||||
|
||||
expr = Trace(A)*A
|
||||
I = Identity(k)
|
||||
assert expr.diff(A) == ArrayAdd(ArrayTensorProduct(I, A), PermuteDims(ArrayTensorProduct(Trace(A)*I, I), Permutation(3)(1, 2)))
|
||||
|
||||
expr = Trace(Trace(A)*A)
|
||||
assert expr.diff(A) == (2*Trace(A))*Identity(k)
|
||||
|
||||
expr = Trace(Trace(Trace(A)*A)*A)
|
||||
assert expr.diff(A) == (3*Trace(A)**2)*Identity(k)
|
||||
|
||||
|
||||
def test_derivatives_matrix_norms():
|
||||
|
||||
expr = x.T*y
|
||||
assert expr.diff(x) == y
|
||||
assert expr[0, 0].diff(x[m, 0]).doit() == y[m, 0]
|
||||
|
||||
expr = (x.T*y)**S.Half
|
||||
assert expr.diff(x) == y/(2*sqrt(x.T*y))
|
||||
|
||||
expr = (x.T*x)**S.Half
|
||||
assert expr.diff(x) == x*(x.T*x)**Rational(-1, 2)
|
||||
|
||||
expr = (c.T*a*x.T*b)**S.Half
|
||||
assert expr.diff(x) == b*a.T*c/sqrt(c.T*a*x.T*b)/2
|
||||
|
||||
expr = (c.T*a*x.T*b)**Rational(1, 3)
|
||||
assert expr.diff(x) == b*a.T*c*(c.T*a*x.T*b)**Rational(-2, 3)/3
|
||||
|
||||
expr = (a.T*X*b)**S.Half
|
||||
assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*b.T
|
||||
|
||||
expr = d.T*x*(a.T*X*b)**S.Half*y.T*c
|
||||
assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*x.T*d*y.T*c*b.T
|
||||
|
||||
|
||||
def test_derivatives_elementwise_applyfunc():
|
||||
|
||||
expr = x.applyfunc(tan)
|
||||
assert expr.diff(x).dummy_eq(
|
||||
DiagMatrix(x.applyfunc(lambda x: tan(x)**2 + 1)))
|
||||
assert expr[i, 0].diff(x[m, 0]).doit() == (tan(x[i, 0])**2 + 1)*KDelta(i, m)
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = (i**2*x).applyfunc(sin)
|
||||
assert expr.diff(i).dummy_eq(
|
||||
HadamardProduct((2*i)*x, (i**2*x).applyfunc(cos)))
|
||||
assert expr[i, 0].diff(i).doit() == 2*i*x[i, 0]*cos(i**2*x[i, 0])
|
||||
_check_derivative_with_explicit_matrix(expr, i, expr.diff(i))
|
||||
|
||||
expr = (log(i)*A*B).applyfunc(sin)
|
||||
assert expr.diff(i).dummy_eq(
|
||||
HadamardProduct(A*B/i, (log(i)*A*B).applyfunc(cos)))
|
||||
_check_derivative_with_explicit_matrix(expr, i, expr.diff(i))
|
||||
|
||||
expr = A*x.applyfunc(exp)
|
||||
# TODO: restore this result (currently returning the transpose):
|
||||
# assert expr.diff(x).dummy_eq(DiagMatrix(x.applyfunc(exp))*A.T)
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = x.T*A*x + k*y.applyfunc(sin).T*x
|
||||
assert expr.diff(x).dummy_eq(A.T*x + A*x + k*y.applyfunc(sin))
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = x.applyfunc(sin).T*y
|
||||
# TODO: restore (currently returning the transpose):
|
||||
# assert expr.diff(x).dummy_eq(DiagMatrix(x.applyfunc(cos))*y)
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = (a.T * X * b).applyfunc(sin)
|
||||
assert expr.diff(X).dummy_eq(a*(a.T*X*b).applyfunc(cos)*b.T)
|
||||
_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T * X.applyfunc(sin) * b
|
||||
assert expr.diff(X).dummy_eq(
|
||||
DiagMatrix(a)*X.applyfunc(cos)*DiagMatrix(b))
|
||||
_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T * (A*X*B).applyfunc(sin) * b
|
||||
assert expr.diff(X).dummy_eq(
|
||||
A.T*DiagMatrix(a)*(A*X*B).applyfunc(cos)*DiagMatrix(b)*B.T)
|
||||
_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T * (A*X*b).applyfunc(sin) * b.T
|
||||
# TODO: not implemented
|
||||
#assert expr.diff(X) == ...
|
||||
#_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T*A*X.applyfunc(sin)*B*b
|
||||
assert expr.diff(X).dummy_eq(
|
||||
HadamardProduct(A.T * a * b.T * B.T, X.applyfunc(cos)))
|
||||
|
||||
expr = a.T * (A*X.applyfunc(sin)*B).applyfunc(log) * b
|
||||
# TODO: wrong
|
||||
# assert expr.diff(X) == A.T*DiagMatrix(a)*(A*X.applyfunc(sin)*B).applyfunc(Lambda(k, 1/k))*DiagMatrix(b)*B.T
|
||||
|
||||
expr = a.T * (X.applyfunc(sin)).applyfunc(log) * b
|
||||
# TODO: wrong
|
||||
# assert expr.diff(X) == DiagMatrix(a)*X.applyfunc(sin).applyfunc(Lambda(k, 1/k))*DiagMatrix(b)
|
||||
|
||||
|
||||
def test_derivatives_of_hadamard_expressions():
|
||||
|
||||
# Hadamard Product
|
||||
|
||||
expr = hadamard_product(a, x, b)
|
||||
assert expr.diff(x) == DiagMatrix(hadamard_product(b, a))
|
||||
|
||||
expr = a.T*hadamard_product(A, X, B)*b
|
||||
assert expr.diff(X) == HadamardProduct(a*b.T, A, B)
|
||||
|
||||
# Hadamard Power
|
||||
|
||||
expr = hadamard_power(x, 2)
|
||||
assert expr.diff(x).doit() == 2*DiagMatrix(x)
|
||||
|
||||
expr = hadamard_power(x.T, 2)
|
||||
assert expr.diff(x).doit() == 2*DiagMatrix(x)
|
||||
|
||||
expr = hadamard_power(x, S.Half)
|
||||
assert expr.diff(x) == S.Half*DiagMatrix(hadamard_power(x, Rational(-1, 2)))
|
||||
|
||||
expr = hadamard_power(a.T*X*b, 2)
|
||||
assert expr.diff(X) == 2*a*a.T*X*b*b.T
|
||||
|
||||
expr = hadamard_power(a.T*X*b, S.Half)
|
||||
assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*b.T
|
||||
@@ -0,0 +1,65 @@
|
||||
from sympy.core import S, symbols
|
||||
from sympy.matrices import eye, ones, Matrix, ShapeError
|
||||
from sympy.matrices.expressions import (
|
||||
Identity, MatrixExpr, MatrixSymbol, Determinant,
|
||||
det, per, ZeroMatrix, Transpose,
|
||||
Permanent, MatMul
|
||||
)
|
||||
from sympy.matrices.expressions.special import OneMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = MatrixSymbol('C', 3, 4)
|
||||
|
||||
|
||||
def test_det():
|
||||
assert isinstance(Determinant(A), Determinant)
|
||||
assert not isinstance(Determinant(A), MatrixExpr)
|
||||
raises(ShapeError, lambda: Determinant(C))
|
||||
assert det(eye(3)) == 1
|
||||
assert det(Matrix(3, 3, [1, 3, 2, 4, 1, 3, 2, 5, 2])) == 17
|
||||
_ = A / det(A) # Make sure this is possible
|
||||
|
||||
raises(TypeError, lambda: Determinant(S.One))
|
||||
|
||||
assert Determinant(A).arg is A
|
||||
|
||||
|
||||
def test_eval_determinant():
|
||||
assert det(Identity(n)) == 1
|
||||
assert det(ZeroMatrix(n, n)) == 0
|
||||
assert det(OneMatrix(n, n)) == Determinant(OneMatrix(n, n))
|
||||
assert det(OneMatrix(1, 1)) == 1
|
||||
assert det(OneMatrix(2, 2)) == 0
|
||||
assert det(Transpose(A)) == det(A)
|
||||
assert Determinant(MatMul(eye(2), eye(2))).doit(deep=True) == 1
|
||||
|
||||
|
||||
def test_refine():
|
||||
assert refine(det(A), Q.orthogonal(A)) == 1
|
||||
assert refine(det(A), Q.singular(A)) == 0
|
||||
assert refine(det(A), Q.unit_triangular(A)) == 1
|
||||
assert refine(det(A), Q.normal(A)) == det(A)
|
||||
|
||||
|
||||
def test_commutative():
|
||||
det_a = Determinant(A)
|
||||
det_b = Determinant(B)
|
||||
assert det_a.is_commutative
|
||||
assert det_b.is_commutative
|
||||
assert det_a * det_b == det_b * det_a
|
||||
|
||||
|
||||
def test_permanent():
|
||||
assert isinstance(Permanent(A), Permanent)
|
||||
assert not isinstance(Permanent(A), MatrixExpr)
|
||||
assert isinstance(Permanent(C), Permanent)
|
||||
assert Permanent(ones(3, 3)).doit() == 6
|
||||
_ = C / per(C)
|
||||
assert per(Matrix(3, 3, [1, 3, 2, 4, 1, 3, 2, 5, 2])) == 103
|
||||
raises(TypeError, lambda: Permanent(S.One))
|
||||
assert Permanent(A).arg is A
|
||||
@@ -0,0 +1,156 @@
|
||||
from sympy.matrices.expressions import MatrixSymbol
|
||||
from sympy.matrices.expressions.diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
n = Symbol('n')
|
||||
m = Symbol('m')
|
||||
|
||||
|
||||
def test_DiagonalMatrix():
|
||||
x = MatrixSymbol('x', n, m)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length is None
|
||||
assert D.shape == (n, m)
|
||||
|
||||
x = MatrixSymbol('x', n, n)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length == n
|
||||
assert D.shape == (n, n)
|
||||
assert D[1, 2] == 0
|
||||
assert D[1, 1] == x[1, 1]
|
||||
i = Symbol('i')
|
||||
j = Symbol('j')
|
||||
x = MatrixSymbol('x', 3, 3)
|
||||
ij = DiagonalMatrix(x)[i, j]
|
||||
assert ij != 0
|
||||
assert ij.subs({i:0, j:0}) == x[0, 0]
|
||||
assert ij.subs({i:0, j:1}) == 0
|
||||
assert ij.subs({i:1, j:1}) == x[1, 1]
|
||||
assert ask(Q.diagonal(D)) # affirm that D is diagonal
|
||||
|
||||
x = MatrixSymbol('x', n, 3)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length == 3
|
||||
assert D.shape == (n, 3)
|
||||
assert D[2, m] == KroneckerDelta(2, m)*x[2, m]
|
||||
assert D[3, m] == 0
|
||||
raises(IndexError, lambda: D[m, 3])
|
||||
|
||||
x = MatrixSymbol('x', 3, n)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length == 3
|
||||
assert D.shape == (3, n)
|
||||
assert D[m, 2] == KroneckerDelta(m, 2)*x[m, 2]
|
||||
assert D[m, 3] == 0
|
||||
raises(IndexError, lambda: D[3, m])
|
||||
|
||||
x = MatrixSymbol('x', n, m)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length is None
|
||||
assert D.shape == (n, m)
|
||||
assert D[m, 4] != 0
|
||||
|
||||
x = MatrixSymbol('x', 3, 4)
|
||||
assert [DiagonalMatrix(x)[i] for i in range(12)] == [
|
||||
x[0, 0], 0, 0, 0, 0, x[1, 1], 0, 0, 0, 0, x[2, 2], 0]
|
||||
|
||||
# shape is retained, issue 12427
|
||||
assert (
|
||||
DiagonalMatrix(MatrixSymbol('x', 3, 4))*
|
||||
DiagonalMatrix(MatrixSymbol('x', 4, 2))).shape == (3, 2)
|
||||
|
||||
|
||||
def test_DiagonalOf():
|
||||
x = MatrixSymbol('x', n, n)
|
||||
d = DiagonalOf(x)
|
||||
assert d.shape == (n, 1)
|
||||
assert d.diagonal_length == n
|
||||
assert d[2, 0] == d[2] == x[2, 2]
|
||||
|
||||
x = MatrixSymbol('x', n, m)
|
||||
d = DiagonalOf(x)
|
||||
assert d.shape == (None, 1)
|
||||
assert d.diagonal_length is None
|
||||
assert d[2, 0] == d[2] == x[2, 2]
|
||||
|
||||
d = DiagonalOf(MatrixSymbol('x', 4, 3))
|
||||
assert d.shape == (3, 1)
|
||||
d = DiagonalOf(MatrixSymbol('x', n, 3))
|
||||
assert d.shape == (3, 1)
|
||||
d = DiagonalOf(MatrixSymbol('x', 3, n))
|
||||
assert d.shape == (3, 1)
|
||||
x = MatrixSymbol('x', n, m)
|
||||
assert [DiagonalOf(x)[i] for i in range(4)] ==[
|
||||
x[0, 0], x[1, 1], x[2, 2], x[3, 3]]
|
||||
|
||||
|
||||
def test_DiagMatrix():
|
||||
x = MatrixSymbol('x', n, 1)
|
||||
d = DiagMatrix(x)
|
||||
assert d.shape == (n, n)
|
||||
assert d[0, 1] == 0
|
||||
assert d[0, 0] == x[0, 0]
|
||||
|
||||
a = MatrixSymbol('a', 1, 1)
|
||||
d = diagonalize_vector(a)
|
||||
assert isinstance(d, MatrixSymbol)
|
||||
assert a == d
|
||||
assert diagonalize_vector(Identity(3)) == Identity(3)
|
||||
assert DiagMatrix(Identity(3)).doit() == Identity(3)
|
||||
assert isinstance(DiagMatrix(Identity(3)), DiagMatrix)
|
||||
|
||||
# A diagonal matrix is equal to its transpose:
|
||||
assert DiagMatrix(x).T == DiagMatrix(x)
|
||||
assert diagonalize_vector(x.T) == DiagMatrix(x)
|
||||
|
||||
dx = DiagMatrix(x)
|
||||
assert dx[0, 0] == x[0, 0]
|
||||
assert dx[1, 1] == x[1, 0]
|
||||
assert dx[0, 1] == 0
|
||||
assert dx[0, m] == x[0, 0]*KroneckerDelta(0, m)
|
||||
|
||||
z = MatrixSymbol('z', 1, n)
|
||||
dz = DiagMatrix(z)
|
||||
assert dz[0, 0] == z[0, 0]
|
||||
assert dz[1, 1] == z[0, 1]
|
||||
assert dz[0, 1] == 0
|
||||
assert dz[0, m] == z[0, m]*KroneckerDelta(0, m)
|
||||
|
||||
v = MatrixSymbol('v', 3, 1)
|
||||
dv = DiagMatrix(v)
|
||||
assert dv.as_explicit() == Matrix([
|
||||
[v[0, 0], 0, 0],
|
||||
[0, v[1, 0], 0],
|
||||
[0, 0, v[2, 0]],
|
||||
])
|
||||
|
||||
v = MatrixSymbol('v', 1, 3)
|
||||
dv = DiagMatrix(v)
|
||||
assert dv.as_explicit() == Matrix([
|
||||
[v[0, 0], 0, 0],
|
||||
[0, v[0, 1], 0],
|
||||
[0, 0, v[0, 2]],
|
||||
])
|
||||
|
||||
dv = DiagMatrix(3*v)
|
||||
assert dv.args == (3*v,)
|
||||
assert dv.doit() == 3*DiagMatrix(v)
|
||||
assert isinstance(dv.doit(), MatMul)
|
||||
|
||||
a = MatrixSymbol("a", 3, 1).as_explicit()
|
||||
expr = DiagMatrix(a)
|
||||
result = Matrix([
|
||||
[a[0, 0], 0, 0],
|
||||
[0, a[1, 0], 0],
|
||||
[0, 0, a[2, 0]],
|
||||
])
|
||||
assert expr.doit() == result
|
||||
expr = DiagMatrix(a.T)
|
||||
assert expr.doit() == result
|
||||
@@ -0,0 +1,35 @@
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.dotproduct import DotProduct
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
A = Matrix(3, 1, [1, 2, 3])
|
||||
B = Matrix(3, 1, [1, 3, 5])
|
||||
C = Matrix(4, 1, [1, 2, 4, 5])
|
||||
D = Matrix(2, 2, [1, 2, 3, 4])
|
||||
|
||||
def test_docproduct():
|
||||
assert DotProduct(A, B).doit() == 22
|
||||
assert DotProduct(A.T, B).doit() == 22
|
||||
assert DotProduct(A, B.T).doit() == 22
|
||||
assert DotProduct(A.T, B.T).doit() == 22
|
||||
|
||||
raises(TypeError, lambda: DotProduct(1, A))
|
||||
raises(TypeError, lambda: DotProduct(A, 1))
|
||||
raises(TypeError, lambda: DotProduct(A, D))
|
||||
raises(TypeError, lambda: DotProduct(D, A))
|
||||
|
||||
raises(TypeError, lambda: DotProduct(B, C).doit())
|
||||
|
||||
def test_dotproduct_symbolic():
|
||||
A = MatrixSymbol('A', 3, 1)
|
||||
B = MatrixSymbol('B', 3, 1)
|
||||
|
||||
dot = DotProduct(A, B)
|
||||
assert dot.is_scalar == True
|
||||
assert unchanged(Mul, 2, dot)
|
||||
# XXX Fix forced evaluation for arithmetics with matrix expressions
|
||||
assert dot * A == (A[0, 0]*B[0, 0] + A[1, 0]*B[1, 0] + A[2, 0]*B[2, 0])*A
|
||||
@@ -0,0 +1,29 @@
|
||||
from sympy.matrices.expressions.factorizations import lu, LofCholesky, qr, svd
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
|
||||
n = Symbol('n')
|
||||
X = MatrixSymbol('X', n, n)
|
||||
|
||||
def test_LU():
|
||||
L, U = lu(X)
|
||||
assert L.shape == U.shape == X.shape
|
||||
assert ask(Q.lower_triangular(L))
|
||||
assert ask(Q.upper_triangular(U))
|
||||
|
||||
def test_Cholesky():
|
||||
LofCholesky(X)
|
||||
|
||||
def test_QR():
|
||||
Q_, R = qr(X)
|
||||
assert Q_.shape == R.shape == X.shape
|
||||
assert ask(Q.orthogonal(Q_))
|
||||
assert ask(Q.upper_triangular(R))
|
||||
|
||||
def test_svd():
|
||||
U, S, V = svd(X)
|
||||
assert U.shape == S.shape == V.shape == X.shape
|
||||
assert ask(Q.orthogonal(U))
|
||||
assert ask(Q.orthogonal(V))
|
||||
assert ask(Q.diagonal(S))
|
||||
@@ -0,0 +1,44 @@
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core.numbers import (I, Rational)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices.expressions.fourier import DFT, IDFT
|
||||
from sympy.matrices import det, Matrix, Identity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_dft_creation():
|
||||
assert DFT(2)
|
||||
assert DFT(0)
|
||||
raises(ValueError, lambda: DFT(-1))
|
||||
raises(ValueError, lambda: DFT(2.0))
|
||||
raises(ValueError, lambda: DFT(2 + 1j))
|
||||
|
||||
n = symbols('n')
|
||||
assert DFT(n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: DFT(n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: DFT(n))
|
||||
|
||||
|
||||
def test_dft():
|
||||
n, i, j = symbols('n i j')
|
||||
assert DFT(4).shape == (4, 4)
|
||||
assert ask(Q.unitary(DFT(4)))
|
||||
assert Abs(simplify(det(Matrix(DFT(4))))) == 1
|
||||
assert DFT(n)*IDFT(n) == Identity(n)
|
||||
assert DFT(n)[i, j] == exp(-2*S.Pi*I/n)**(i*j) / sqrt(n)
|
||||
|
||||
|
||||
def test_dft2():
|
||||
assert DFT(1).as_explicit() == Matrix([[1]])
|
||||
assert DFT(2).as_explicit() == 1/sqrt(2)*Matrix([[1,1],[1,-1]])
|
||||
assert DFT(4).as_explicit() == Matrix([[S.Half, S.Half, S.Half, S.Half],
|
||||
[S.Half, -I/2, Rational(-1,2), I/2],
|
||||
[S.Half, Rational(-1,2), S.Half, Rational(-1,2)],
|
||||
[S.Half, I/2, Rational(-1,2), -I/2]])
|
||||
@@ -0,0 +1,54 @@
|
||||
from sympy.core import symbols, Lambda
|
||||
from sympy.core.sympify import SympifyError
|
||||
from sympy.functions import KroneckerDelta
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions import FunctionMatrix, MatrixExpr, Identity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_funcmatrix_creation():
|
||||
i, j, k = symbols('i j k')
|
||||
assert FunctionMatrix(2, 2, Lambda((i, j), 0))
|
||||
assert FunctionMatrix(0, 0, Lambda((i, j), 0))
|
||||
|
||||
raises(ValueError, lambda: FunctionMatrix(-1, 0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2.0, 0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2j, 0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(0, -1, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(0, 2.0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(0, 2j, Lambda((i, j), 0)))
|
||||
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda(i, 0)))
|
||||
raises(SympifyError, lambda: FunctionMatrix(2, 2, lambda i, j: 0))
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda((i,), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda((i, j, k), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, i+j))
|
||||
assert FunctionMatrix(2, 2, "lambda i, j: 0") == \
|
||||
FunctionMatrix(2, 2, Lambda((i, j), 0))
|
||||
|
||||
m = FunctionMatrix(2, 2, KroneckerDelta)
|
||||
assert m.as_explicit() == Identity(2).as_explicit()
|
||||
assert m.args[2].dummy_eq(Lambda((i, j), KroneckerDelta(i, j)))
|
||||
|
||||
n = symbols('n')
|
||||
assert FunctionMatrix(n, n, Lambda((i, j), 0))
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: FunctionMatrix(n, n, Lambda((i, j), 0)))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: FunctionMatrix(n, n, Lambda((i, j), 0)))
|
||||
|
||||
|
||||
def test_funcmatrix():
|
||||
i, j = symbols('i,j')
|
||||
X = FunctionMatrix(3, 3, Lambda((i, j), i - j))
|
||||
assert X[1, 1] == 0
|
||||
assert X[1, 2] == -1
|
||||
assert X.shape == (3, 3)
|
||||
assert X.rows == X.cols == 3
|
||||
assert Matrix(X) == Matrix(3, 3, lambda i, j: i - j)
|
||||
assert isinstance(X*X + X, MatrixExpr)
|
||||
|
||||
|
||||
def test_replace_issue():
|
||||
X = FunctionMatrix(3, 3, KroneckerDelta)
|
||||
assert X.replace(lambda x: True, lambda x: x) == X
|
||||
@@ -0,0 +1,141 @@
|
||||
from sympy.matrices.dense import Matrix, eye
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.special import Identity, OneMatrix, ZeroMatrix
|
||||
from sympy.core import symbols
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
from sympy.matrices import MatrixSymbol
|
||||
from sympy.matrices.expressions import (HadamardProduct, hadamard_product, HadamardPower, hadamard_power)
|
||||
|
||||
n, m, k = symbols('n,m,k')
|
||||
Z = MatrixSymbol('Z', n, n)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, k)
|
||||
|
||||
|
||||
def test_HadamardProduct():
|
||||
assert HadamardProduct(A, B, A).shape == A.shape
|
||||
|
||||
raises(TypeError, lambda: HadamardProduct(A, n))
|
||||
raises(TypeError, lambda: HadamardProduct(A, 1))
|
||||
|
||||
assert HadamardProduct(A, 2*B, -A)[1, 1] == \
|
||||
-2 * A[1, 1] * B[1, 1] * A[1, 1]
|
||||
|
||||
mix = HadamardProduct(Z*A, B)*C
|
||||
assert mix.shape == (n, k)
|
||||
|
||||
assert set(HadamardProduct(A, B, A).T.args) == {A.T, A.T, B.T}
|
||||
|
||||
|
||||
def test_HadamardProduct_isnt_commutative():
|
||||
assert HadamardProduct(A, B) != HadamardProduct(B, A)
|
||||
|
||||
|
||||
def test_mixed_indexing():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
Z = MatrixSymbol('Z', 2, 2)
|
||||
|
||||
assert (X*HadamardProduct(Y, Z))[0, 0] == \
|
||||
X[0, 0]*Y[0, 0]*Z[0, 0] + X[0, 1]*Y[1, 0]*Z[1, 0]
|
||||
|
||||
|
||||
def test_canonicalize():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
with warns_deprecated_sympy():
|
||||
expr = HadamardProduct(X, check=False)
|
||||
assert isinstance(expr, HadamardProduct)
|
||||
expr2 = expr.doit() # unpack is called
|
||||
assert isinstance(expr2, MatrixSymbol)
|
||||
Z = ZeroMatrix(2, 2)
|
||||
U = OneMatrix(2, 2)
|
||||
assert HadamardProduct(Z, X).doit() == Z
|
||||
assert HadamardProduct(U, X, X, U).doit() == HadamardPower(X, 2)
|
||||
assert HadamardProduct(X, U, Y).doit() == HadamardProduct(X, Y)
|
||||
assert HadamardProduct(X, Z, U, Y).doit() == Z
|
||||
|
||||
|
||||
def test_hadamard():
|
||||
m, n, p = symbols('m, n, p', integer=True)
|
||||
A = MatrixSymbol('A', m, n)
|
||||
B = MatrixSymbol('B', m, n)
|
||||
X = MatrixSymbol('X', m, m)
|
||||
I = Identity(m)
|
||||
|
||||
raises(TypeError, lambda: hadamard_product())
|
||||
assert hadamard_product(A) == A
|
||||
assert isinstance(hadamard_product(A, B), HadamardProduct)
|
||||
assert hadamard_product(A, B).doit() == hadamard_product(A, B)
|
||||
assert hadamard_product(X, I) == HadamardProduct(I, X)
|
||||
assert isinstance(hadamard_product(X, I), HadamardProduct)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
expr = MatAdd(ZeroMatrix(k, 1), OneMatrix(k, 1))
|
||||
expr = HadamardProduct(expr, a)
|
||||
assert expr.doit() == a
|
||||
|
||||
raises(ValueError, lambda: HadamardProduct())
|
||||
|
||||
|
||||
def test_hadamard_product_with_explicit_mat():
|
||||
A = MatrixSymbol("A", 3, 3).as_explicit()
|
||||
B = MatrixSymbol("B", 3, 3).as_explicit()
|
||||
X = MatrixSymbol("X", 3, 3)
|
||||
expr = hadamard_product(A, B)
|
||||
ret = Matrix([i*j for i, j in zip(A, B)]).reshape(3, 3)
|
||||
assert expr == ret
|
||||
expr = hadamard_product(A, X, B)
|
||||
assert expr == HadamardProduct(ret, X)
|
||||
expr = hadamard_product(eye(3), A)
|
||||
assert expr == Matrix([[A[0, 0], 0, 0], [0, A[1, 1], 0], [0, 0, A[2, 2]]])
|
||||
expr = hadamard_product(eye(3), eye(3))
|
||||
assert expr == eye(3)
|
||||
|
||||
|
||||
def test_hadamard_power():
|
||||
m, n, p = symbols('m, n, p', integer=True)
|
||||
A = MatrixSymbol('A', m, n)
|
||||
|
||||
assert hadamard_power(A, 1) == A
|
||||
assert isinstance(hadamard_power(A, 2), HadamardPower)
|
||||
assert hadamard_power(A, n).T == hadamard_power(A.T, n)
|
||||
assert hadamard_power(A, n)[0, 0] == A[0, 0]**n
|
||||
assert hadamard_power(m, n) == m**n
|
||||
raises(ValueError, lambda: hadamard_power(A, A))
|
||||
|
||||
|
||||
def test_hadamard_power_explicit():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
a, b = symbols('a b')
|
||||
|
||||
assert HadamardPower(a, b) == a**b
|
||||
|
||||
assert HadamardPower(a, B).as_explicit() == \
|
||||
Matrix([
|
||||
[a**B[0, 0], a**B[0, 1]],
|
||||
[a**B[1, 0], a**B[1, 1]]])
|
||||
|
||||
assert HadamardPower(A, b).as_explicit() == \
|
||||
Matrix([
|
||||
[A[0, 0]**b, A[0, 1]**b],
|
||||
[A[1, 0]**b, A[1, 1]**b]])
|
||||
|
||||
assert HadamardPower(A, B).as_explicit() == \
|
||||
Matrix([
|
||||
[A[0, 0]**B[0, 0], A[0, 1]**B[0, 1]],
|
||||
[A[1, 0]**B[1, 0], A[1, 1]**B[1, 1]]])
|
||||
|
||||
|
||||
def test_shape_error():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
raises(ShapeError, lambda: HadamardProduct(A, B))
|
||||
raises(ShapeError, lambda: HadamardPower(A, B))
|
||||
A = MatrixSymbol('A', 3, 2)
|
||||
raises(ShapeError, lambda: HadamardProduct(A, B))
|
||||
raises(ShapeError, lambda: HadamardPower(A, B))
|
||||
@@ -0,0 +1,299 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.symbol import symbols, Symbol, Dummy
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.dense import eye
|
||||
from sympy.matrices.expressions.blockmatrix import BlockMatrix
|
||||
from sympy.matrices.expressions.hadamard import HadamardPower
|
||||
from sympy.matrices.expressions.matexpr import (MatrixSymbol,
|
||||
MatrixExpr, MatrixElement)
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.special import (ZeroMatrix, Identity,
|
||||
OneMatrix)
|
||||
from sympy.matrices.expressions.trace import Trace, trace
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.testing.pytest import XFAIL, raises
|
||||
|
||||
k, l, m, n = symbols('k l m n', integer=True)
|
||||
i, j = symbols('i j', integer=True)
|
||||
|
||||
W = MatrixSymbol('W', k, l)
|
||||
X = MatrixSymbol('X', l, m)
|
||||
Y = MatrixSymbol('Y', l, m)
|
||||
Z = MatrixSymbol('Z', m, n)
|
||||
|
||||
X1 = MatrixSymbol('X1', m, m)
|
||||
X2 = MatrixSymbol('X2', m, m)
|
||||
X3 = MatrixSymbol('X3', m, m)
|
||||
X4 = MatrixSymbol('X4', m, m)
|
||||
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
x = MatrixSymbol('x', 1, 2)
|
||||
y = MatrixSymbol('x', 2, 1)
|
||||
|
||||
|
||||
def test_symbolic_indexing():
|
||||
x12 = X[1, 2]
|
||||
assert all(s in str(x12) for s in ['1', '2', X.name])
|
||||
# We don't care about the exact form of this. We do want to make sure
|
||||
# that all of these features are present
|
||||
|
||||
|
||||
def test_add_index():
|
||||
assert (X + Y)[i, j] == X[i, j] + Y[i, j]
|
||||
|
||||
|
||||
def test_mul_index():
|
||||
assert (A*y)[0, 0] == A[0, 0]*y[0, 0] + A[0, 1]*y[1, 0]
|
||||
assert (A*B).as_mutable() == (A.as_mutable() * B.as_mutable())
|
||||
X = MatrixSymbol('X', n, m)
|
||||
Y = MatrixSymbol('Y', m, k)
|
||||
|
||||
result = (X*Y)[4,2]
|
||||
expected = Sum(X[4, i]*Y[i, 2], (i, 0, m - 1))
|
||||
assert result.args[0].dummy_eq(expected.args[0], i)
|
||||
assert result.args[1][1:] == expected.args[1][1:]
|
||||
|
||||
|
||||
def test_pow_index():
|
||||
Q = MatPow(A, 2)
|
||||
assert Q[0, 0] == A[0, 0]**2 + A[0, 1]*A[1, 0]
|
||||
n = symbols("n")
|
||||
Q2 = A**n
|
||||
assert Q2[0, 0] == 2*(
|
||||
-sqrt((A[0, 0] + A[1, 1])**2 - 4*A[0, 0]*A[1, 1] +
|
||||
4*A[0, 1]*A[1, 0])/2 + A[0, 0]/2 + A[1, 1]/2
|
||||
)**n * \
|
||||
A[0, 1]*A[1, 0]/(
|
||||
(sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] +
|
||||
A[1, 1]**2) + A[0, 0] - A[1, 1])*
|
||||
sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)
|
||||
) - 2*(
|
||||
sqrt((A[0, 0] + A[1, 1])**2 - 4*A[0, 0]*A[1, 1] +
|
||||
4*A[0, 1]*A[1, 0])/2 + A[0, 0]/2 + A[1, 1]/2
|
||||
)**n * A[0, 1]*A[1, 0]/(
|
||||
(-sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] +
|
||||
A[1, 1]**2) + A[0, 0] - A[1, 1])*
|
||||
sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)
|
||||
)
|
||||
|
||||
|
||||
def test_transpose_index():
|
||||
assert X.T[i, j] == X[j, i]
|
||||
|
||||
|
||||
def test_Identity_index():
|
||||
I = Identity(3)
|
||||
assert I[0, 0] == I[1, 1] == I[2, 2] == 1
|
||||
assert I[1, 0] == I[0, 1] == I[2, 1] == 0
|
||||
assert I[i, 0].delta_range == (0, 2)
|
||||
raises(IndexError, lambda: I[3, 3])
|
||||
|
||||
|
||||
def test_block_index():
|
||||
I = Identity(3)
|
||||
Z = ZeroMatrix(3, 3)
|
||||
B = BlockMatrix([[I, I], [I, I]])
|
||||
e3 = ImmutableMatrix(eye(3))
|
||||
BB = BlockMatrix([[e3, e3], [e3, e3]])
|
||||
assert B[0, 0] == B[3, 0] == B[0, 3] == B[3, 3] == 1
|
||||
assert B[4, 3] == B[5, 1] == 0
|
||||
|
||||
BB = BlockMatrix([[e3, e3], [e3, e3]])
|
||||
assert B.as_explicit() == BB.as_explicit()
|
||||
|
||||
BI = BlockMatrix([[I, Z], [Z, I]])
|
||||
|
||||
assert BI.as_explicit().equals(eye(6))
|
||||
|
||||
|
||||
def test_block_index_symbolic():
|
||||
# Note that these matrices may be zero-sized and indices may be negative, which causes
|
||||
# all naive simplifications given in the comments to be invalid
|
||||
A1 = MatrixSymbol('A1', n, k)
|
||||
A2 = MatrixSymbol('A2', n, l)
|
||||
A3 = MatrixSymbol('A3', m, k)
|
||||
A4 = MatrixSymbol('A4', m, l)
|
||||
A = BlockMatrix([[A1, A2], [A3, A4]])
|
||||
assert A[0, 0] == MatrixElement(A, 0, 0) # Cannot be A1[0, 0]
|
||||
assert A[n - 1, k - 1] == A1[n - 1, k - 1]
|
||||
assert A[n, k] == A4[0, 0]
|
||||
assert A[n + m - 1, 0] == MatrixElement(A, n + m - 1, 0) # Cannot be A3[m - 1, 0]
|
||||
assert A[0, k + l - 1] == MatrixElement(A, 0, k + l - 1) # Cannot be A2[0, l - 1]
|
||||
assert A[n + m - 1, k + l - 1] == MatrixElement(A, n + m - 1, k + l - 1) # Cannot be A4[m - 1, l - 1]
|
||||
assert A[i, j] == MatrixElement(A, i, j)
|
||||
assert A[n + i, k + j] == MatrixElement(A, n + i, k + j) # Cannot be A4[i, j]
|
||||
assert A[n - i - 1, k - j - 1] == MatrixElement(A, n - i - 1, k - j - 1) # Cannot be A1[n - i - 1, k - j - 1]
|
||||
|
||||
|
||||
def test_block_index_symbolic_nonzero():
|
||||
# All invalid simplifications from test_block_index_symbolic() that become valid if all
|
||||
# matrices have nonzero size and all indices are nonnegative
|
||||
k, l, m, n = symbols('k l m n', integer=True, positive=True)
|
||||
i, j = symbols('i j', integer=True, nonnegative=True)
|
||||
A1 = MatrixSymbol('A1', n, k)
|
||||
A2 = MatrixSymbol('A2', n, l)
|
||||
A3 = MatrixSymbol('A3', m, k)
|
||||
A4 = MatrixSymbol('A4', m, l)
|
||||
A = BlockMatrix([[A1, A2], [A3, A4]])
|
||||
assert A[0, 0] == A1[0, 0]
|
||||
assert A[n + m - 1, 0] == A3[m - 1, 0]
|
||||
assert A[0, k + l - 1] == A2[0, l - 1]
|
||||
assert A[n + m - 1, k + l - 1] == A4[m - 1, l - 1]
|
||||
assert A[i, j] == MatrixElement(A, i, j)
|
||||
assert A[n + i, k + j] == A4[i, j]
|
||||
assert A[n - i - 1, k - j - 1] == A1[n - i - 1, k - j - 1]
|
||||
assert A[2 * n, 2 * k] == A4[n, k]
|
||||
|
||||
|
||||
def test_block_index_large():
|
||||
n, m, k = symbols('n m k', integer=True, positive=True)
|
||||
i = symbols('i', integer=True, nonnegative=True)
|
||||
A1 = MatrixSymbol('A1', n, n)
|
||||
A2 = MatrixSymbol('A2', n, m)
|
||||
A3 = MatrixSymbol('A3', n, k)
|
||||
A4 = MatrixSymbol('A4', m, n)
|
||||
A5 = MatrixSymbol('A5', m, m)
|
||||
A6 = MatrixSymbol('A6', m, k)
|
||||
A7 = MatrixSymbol('A7', k, n)
|
||||
A8 = MatrixSymbol('A8', k, m)
|
||||
A9 = MatrixSymbol('A9', k, k)
|
||||
A = BlockMatrix([[A1, A2, A3], [A4, A5, A6], [A7, A8, A9]])
|
||||
assert A[n + i, n + i] == MatrixElement(A, n + i, n + i)
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_block_index_symbolic_fail():
|
||||
# To make this work, symbolic matrix dimensions would need to be somehow assumed nonnegative
|
||||
# even if the symbols aren't specified as such. Then 2 * n < n would correctly evaluate to
|
||||
# False in BlockMatrix._entry()
|
||||
A1 = MatrixSymbol('A1', n, 1)
|
||||
A2 = MatrixSymbol('A2', m, 1)
|
||||
A = BlockMatrix([[A1], [A2]])
|
||||
assert A[2 * n, 0] == A2[n, 0]
|
||||
|
||||
|
||||
def test_slicing():
|
||||
A.as_explicit()[0, :] # does not raise an error
|
||||
|
||||
|
||||
def test_errors():
|
||||
raises(IndexError, lambda: Identity(2)[1, 2, 3, 4, 5])
|
||||
raises(IndexError, lambda: Identity(2)[[1, 2, 3, 4, 5]])
|
||||
|
||||
|
||||
def test_matrix_expression_to_indices():
|
||||
i, j = symbols("i, j")
|
||||
i1, i2, i3 = symbols("i_1:4")
|
||||
|
||||
def replace_dummies(expr):
|
||||
repl = {i: Symbol(i.name) for i in expr.atoms(Dummy)}
|
||||
return expr.xreplace(repl)
|
||||
|
||||
expr = W*X*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = Z.T*X.T*W.T
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[j, i2]*X[i2, i1]*Z[i1, i], (i1, 0, m-1), (i2, 0, l-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j), i) == expr
|
||||
|
||||
expr = W*X*Z + W*Y*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) +\
|
||||
Sum(W[i, i1]*Y[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = 2*W*X*Z + 3*W*Y*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
2*Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) +\
|
||||
3*Sum(W[i, i1]*Y[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = W*(X + Y)*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[i, i1]*(X[i1, i2] + Y[i1, i2])*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = A*B**2*A
|
||||
#assert replace_dummies(expr._entry(i, j)) == \
|
||||
# Sum(A[i, i1]*B[i1, i2]*B[i2, i3]*A[i3, j], (i1, 0, 1), (i2, 0, 1), (i3, 0, 1))
|
||||
|
||||
# Check that different dummies are used in sub-multiplications:
|
||||
expr = (X1*X2 + X2*X1)*X3
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum((Sum(X1[i, i2] * X2[i2, i1], (i2, 0, m - 1)) + Sum(X1[i3, i1] * X2[i, i3], (i3, 0, m - 1))) * X3[
|
||||
i1, j], (i1, 0, m - 1))
|
||||
|
||||
|
||||
def test_matrix_expression_from_index_summation():
|
||||
from sympy.abc import a,b,c,d
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
w1 = MatrixSymbol("w1", k, 1)
|
||||
|
||||
i0, i1, i2, i3, i4 = symbols("i0:5", cls=Dummy)
|
||||
|
||||
expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == W*X*Z
|
||||
expr = Sum(W.T[b,a]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == W*X*Z
|
||||
expr = Sum(A[b, a]*B[b, c]*C[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixSymbol.from_index_summation(expr, a) == A.T*B*C
|
||||
expr = Sum(A[b, a]*B[c, b]*C[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixSymbol.from_index_summation(expr, a) == A.T*B.T*C
|
||||
expr = Sum(C[c, d]*A[b, a]*B[c, b], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixSymbol.from_index_summation(expr, a) == A.T*B.T*C
|
||||
expr = Sum(A[a, b] + B[a, b], (a, 0, k-1), (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == OneMatrix(1, k)*A*OneMatrix(k, 1) + OneMatrix(1, k)*B*OneMatrix(k, 1)
|
||||
expr = Sum(A[a, b]**2, (a, 0, k - 1), (b, 0, k - 1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == Trace(A * A.T)
|
||||
expr = Sum(A[a, b]**3, (a, 0, k - 1), (b, 0, k - 1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == Trace(HadamardPower(A.T, 2) * A)
|
||||
expr = Sum((A[a, b] + B[a, b])*C[b, c], (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == (A+B)*C
|
||||
expr = Sum((A[a, b] + B[b, a])*C[b, c], (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == (A+B.T)*C
|
||||
expr = Sum(A[a, b]*A[b, c]*A[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A**3
|
||||
expr = Sum(A[a, b]*A[b, c]*B[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A**2*B
|
||||
|
||||
# Parse the trace of a matrix:
|
||||
|
||||
expr = Sum(A[a, a], (a, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, None) == trace(A)
|
||||
expr = Sum(A[a, a]*B[b, c]*C[c, d], (a, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, b) == trace(A)*B*C
|
||||
|
||||
# Check wrong sum ranges (should raise an exception):
|
||||
|
||||
## Case 1: 0 to m instead of 0 to m-1
|
||||
expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m))
|
||||
raises(ValueError, lambda: MatrixExpr.from_index_summation(expr, a))
|
||||
## Case 2: 1 to m-1 instead of 0 to m-1
|
||||
expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 1, m-1))
|
||||
raises(ValueError, lambda: MatrixExpr.from_index_summation(expr, a))
|
||||
|
||||
# Parse nested sums:
|
||||
expr = Sum(A[a, b]*Sum(B[b, c]*C[c, d], (c, 0, k-1)), (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A*B*C
|
||||
|
||||
# Test Kronecker delta:
|
||||
expr = Sum(A[a, b]*KroneckerDelta(b, c)*B[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A*B
|
||||
|
||||
expr = Sum(KroneckerDelta(i1, m)*KroneckerDelta(i2, n)*A[i, i1]*A[j, i2], (i1, 0, k-1), (i2, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, m) == ArrayTensorProduct(A.T, A)
|
||||
|
||||
# Test numbered indices:
|
||||
expr = Sum(A[i1, i2]*w1[i2, 0], (i2, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, i1) == MatrixElement(A*w1, i1, 0)
|
||||
|
||||
expr = Sum(A[i1, i2]*B[i2, 0], (i2, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, i1) == MatrixElement(A*B, i1, 0)
|
||||
@@ -0,0 +1,69 @@
|
||||
from sympy.core import symbols, S
|
||||
from sympy.matrices.expressions import MatrixSymbol, Inverse, MatPow, ZeroMatrix, OneMatrix
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError, NonSquareMatrixError
|
||||
from sympy.matrices import eye, Identity
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
|
||||
n, m, l = symbols('n m l', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
|
||||
|
||||
def test_inverse():
|
||||
assert Inverse(C).args == (C, S.NegativeOne)
|
||||
assert Inverse(C).shape == (n, n)
|
||||
assert Inverse(A*E).shape == (n, n)
|
||||
assert Inverse(E*A).shape == (m, m)
|
||||
assert Inverse(C).inverse() == C
|
||||
assert Inverse(Inverse(C)).doit() == C
|
||||
assert isinstance(Inverse(Inverse(C)), Inverse)
|
||||
|
||||
assert Inverse(*Inverse(E*A).args) == Inverse(E*A)
|
||||
|
||||
assert C.inverse().inverse() == C
|
||||
|
||||
assert C.inverse()*C == Identity(C.rows)
|
||||
|
||||
assert Identity(n).inverse() == Identity(n)
|
||||
assert (3*Identity(n)).inverse() == Identity(n)/3
|
||||
|
||||
# Simplifies Muls if possible (i.e. submatrices are square)
|
||||
assert (C*D).inverse() == D.I*C.I
|
||||
# But still works when not possible
|
||||
assert isinstance((A*E).inverse(), Inverse)
|
||||
assert Inverse(C*D).doit(inv_expand=False) == Inverse(C*D)
|
||||
|
||||
assert Inverse(eye(3)).doit() == eye(3)
|
||||
assert Inverse(eye(3)).doit(deep=False) == eye(3)
|
||||
|
||||
assert OneMatrix(1, 1).I == Identity(1)
|
||||
assert isinstance(OneMatrix(n, n).I, Inverse)
|
||||
|
||||
def test_inverse_non_invertible():
|
||||
raises(NonInvertibleMatrixError, lambda: ZeroMatrix(n, n).I)
|
||||
raises(NonInvertibleMatrixError, lambda: OneMatrix(2, 2).I)
|
||||
|
||||
def test_refine():
|
||||
assert refine(C.I, Q.orthogonal(C)) == C.T
|
||||
|
||||
|
||||
def test_inverse_matpow_canonicalization():
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
assert Inverse(MatPow(A, 3)).doit() == MatPow(Inverse(A), 3).doit()
|
||||
|
||||
|
||||
def test_nonsquare_error():
|
||||
A = MatrixSymbol('A', 3, 4)
|
||||
raises(NonSquareMatrixError, lambda: Inverse(A))
|
||||
|
||||
|
||||
def test_adjoint_trnaspose_conjugate():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
assert A.transpose().inverse() == A.inverse().transpose()
|
||||
assert A.conjugate().inverse() == A.inverse().conjugate()
|
||||
assert A.adjoint().inverse() == A.inverse().adjoint()
|
||||
@@ -0,0 +1,150 @@
|
||||
from sympy.core.mod import Mod
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.matrices.dense import (Matrix, eye)
|
||||
from sympy.matrices import MatrixSymbol, Identity
|
||||
from sympy.matrices.expressions import det, trace
|
||||
|
||||
from sympy.matrices.expressions.kronecker import (KroneckerProduct,
|
||||
kronecker_product,
|
||||
combine_kronecker)
|
||||
|
||||
|
||||
mat1 = Matrix([[1, 2 * I], [1 + I, 3]])
|
||||
mat2 = Matrix([[2 * I, 3], [4 * I, 2]])
|
||||
|
||||
i, j, k, n, m, o, p, x = symbols('i,j,k,n,m,o,p,x')
|
||||
Z = MatrixSymbol('Z', n, n)
|
||||
W = MatrixSymbol('W', m, m)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, k)
|
||||
|
||||
|
||||
def test_KroneckerProduct():
|
||||
assert isinstance(KroneckerProduct(A, B), KroneckerProduct)
|
||||
assert KroneckerProduct(A, B).subs(A, C) == KroneckerProduct(C, B)
|
||||
assert KroneckerProduct(A, C).shape == (n*m, m*k)
|
||||
assert (KroneckerProduct(A, C) + KroneckerProduct(-A, C)).is_ZeroMatrix
|
||||
assert (KroneckerProduct(W, Z) * KroneckerProduct(W.I, Z.I)).is_Identity
|
||||
|
||||
|
||||
def test_KroneckerProduct_identity():
|
||||
assert KroneckerProduct(Identity(m), Identity(n)) == Identity(m*n)
|
||||
assert KroneckerProduct(eye(2), eye(3)) == eye(6)
|
||||
|
||||
|
||||
def test_KroneckerProduct_explicit():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
kp = KroneckerProduct(X, Y)
|
||||
assert kp.shape == (4, 4)
|
||||
assert kp.as_explicit() == Matrix(
|
||||
[
|
||||
[X[0, 0]*Y[0, 0], X[0, 0]*Y[0, 1], X[0, 1]*Y[0, 0], X[0, 1]*Y[0, 1]],
|
||||
[X[0, 0]*Y[1, 0], X[0, 0]*Y[1, 1], X[0, 1]*Y[1, 0], X[0, 1]*Y[1, 1]],
|
||||
[X[1, 0]*Y[0, 0], X[1, 0]*Y[0, 1], X[1, 1]*Y[0, 0], X[1, 1]*Y[0, 1]],
|
||||
[X[1, 0]*Y[1, 0], X[1, 0]*Y[1, 1], X[1, 1]*Y[1, 0], X[1, 1]*Y[1, 1]]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_tensor_product_adjoint():
|
||||
assert KroneckerProduct(I*A, B).adjoint() == \
|
||||
-I*KroneckerProduct(A.adjoint(), B.adjoint())
|
||||
assert KroneckerProduct(mat1, mat2).adjoint() == \
|
||||
kronecker_product(mat1.adjoint(), mat2.adjoint())
|
||||
|
||||
|
||||
def test_tensor_product_conjugate():
|
||||
assert KroneckerProduct(I*A, B).conjugate() == \
|
||||
-I*KroneckerProduct(A.conjugate(), B.conjugate())
|
||||
assert KroneckerProduct(mat1, mat2).conjugate() == \
|
||||
kronecker_product(mat1.conjugate(), mat2.conjugate())
|
||||
|
||||
|
||||
def test_tensor_product_transpose():
|
||||
assert KroneckerProduct(I*A, B).transpose() == \
|
||||
I*KroneckerProduct(A.transpose(), B.transpose())
|
||||
assert KroneckerProduct(mat1, mat2).transpose() == \
|
||||
kronecker_product(mat1.transpose(), mat2.transpose())
|
||||
|
||||
|
||||
def test_KroneckerProduct_is_associative():
|
||||
assert kronecker_product(A, kronecker_product(
|
||||
B, C)) == kronecker_product(kronecker_product(A, B), C)
|
||||
assert kronecker_product(A, kronecker_product(
|
||||
B, C)) == KroneckerProduct(A, B, C)
|
||||
|
||||
|
||||
def test_KroneckerProduct_is_bilinear():
|
||||
assert kronecker_product(x*A, B) == x*kronecker_product(A, B)
|
||||
assert kronecker_product(A, x*B) == x*kronecker_product(A, B)
|
||||
|
||||
|
||||
def test_KroneckerProduct_determinant():
|
||||
kp = kronecker_product(W, Z)
|
||||
assert det(kp) == det(W)**n * det(Z)**m
|
||||
|
||||
|
||||
def test_KroneckerProduct_trace():
|
||||
kp = kronecker_product(W, Z)
|
||||
assert trace(kp) == trace(W)*trace(Z)
|
||||
|
||||
|
||||
def test_KroneckerProduct_isnt_commutative():
|
||||
assert KroneckerProduct(A, B) != KroneckerProduct(B, A)
|
||||
assert KroneckerProduct(A, B).is_commutative is False
|
||||
|
||||
|
||||
def test_KroneckerProduct_extracts_commutative_part():
|
||||
assert kronecker_product(x * A, 2 * B) == x * \
|
||||
2 * KroneckerProduct(A, B)
|
||||
|
||||
|
||||
def test_KroneckerProduct_inverse():
|
||||
kp = kronecker_product(W, Z)
|
||||
assert kp.inverse() == kronecker_product(W.inverse(), Z.inverse())
|
||||
|
||||
|
||||
def test_KroneckerProduct_combine_add():
|
||||
kp1 = kronecker_product(A, B)
|
||||
kp2 = kronecker_product(C, W)
|
||||
assert combine_kronecker(kp1*kp2) == kronecker_product(A*C, B*W)
|
||||
|
||||
|
||||
def test_KroneckerProduct_combine_mul():
|
||||
X = MatrixSymbol('X', m, n)
|
||||
Y = MatrixSymbol('Y', m, n)
|
||||
kp1 = kronecker_product(A, X)
|
||||
kp2 = kronecker_product(B, Y)
|
||||
assert combine_kronecker(kp1+kp2) == kronecker_product(A+B, X+Y)
|
||||
|
||||
|
||||
def test_KroneckerProduct_combine_pow():
|
||||
X = MatrixSymbol('X', n, n)
|
||||
Y = MatrixSymbol('Y', n, n)
|
||||
assert combine_kronecker(KroneckerProduct(
|
||||
X, Y)**x) == KroneckerProduct(X**x, Y**x)
|
||||
assert combine_kronecker(x * KroneckerProduct(X, Y)
|
||||
** 2) == x * KroneckerProduct(X**2, Y**2)
|
||||
assert combine_kronecker(
|
||||
x * (KroneckerProduct(X, Y)**2) * KroneckerProduct(A, B)) == x * KroneckerProduct(X**2 * A, Y**2 * B)
|
||||
# cannot simplify because of non-square arguments to kronecker product:
|
||||
assert combine_kronecker(KroneckerProduct(A, B.T) ** m) == KroneckerProduct(A, B.T) ** m
|
||||
|
||||
|
||||
def test_KroneckerProduct_expand():
|
||||
X = MatrixSymbol('X', n, n)
|
||||
Y = MatrixSymbol('Y', n, n)
|
||||
|
||||
assert KroneckerProduct(X + Y, Y + Z).expand(kroneckerproduct=True) == \
|
||||
KroneckerProduct(X, Y) + KroneckerProduct(X, Z) + \
|
||||
KroneckerProduct(Y, Y) + KroneckerProduct(Y, Z)
|
||||
|
||||
def test_KroneckerProduct_entry():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', o, p)
|
||||
|
||||
assert KroneckerProduct(A, B)._entry(i, j) == A[Mod(floor(i/o), n), Mod(floor(j/p), m)]*B[Mod(i, o), Mod(j, p)]
|
||||
@@ -0,0 +1,58 @@
|
||||
from sympy.matrices.expressions import MatrixSymbol, MatAdd, MatPow, MatMul
|
||||
from sympy.matrices.expressions.special import GenericZeroMatrix, ZeroMatrix
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices import eye, ImmutableMatrix
|
||||
from sympy.core import Add, Basic, S
|
||||
from sympy.core.add import add
|
||||
from sympy.testing.pytest import XFAIL, raises
|
||||
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
|
||||
def test_evaluate():
|
||||
assert MatAdd(X, X, evaluate=True) == add(X, X, evaluate=True) == MatAdd(X, X).doit()
|
||||
|
||||
def test_sort_key():
|
||||
assert MatAdd(Y, X).doit().args == add(Y, X).doit().args == (X, Y)
|
||||
|
||||
|
||||
def test_matadd_sympify():
|
||||
assert isinstance(MatAdd(eye(1), eye(1)).args[0], Basic)
|
||||
assert isinstance(add(eye(1), eye(1)).args[0], Basic)
|
||||
|
||||
|
||||
def test_matadd_of_matrices():
|
||||
assert MatAdd(eye(2), 4*eye(2), eye(2)).doit() == ImmutableMatrix(6*eye(2))
|
||||
assert add(eye(2), 4*eye(2), eye(2)).doit() == ImmutableMatrix(6*eye(2))
|
||||
|
||||
|
||||
def test_doit_args():
|
||||
A = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
B = ImmutableMatrix([[2, 3], [4, 5]])
|
||||
assert MatAdd(A, MatPow(B, 2)).doit() == A + B**2
|
||||
assert MatAdd(A, MatMul(A, B)).doit() == A + A*B
|
||||
assert (MatAdd(A, X, MatMul(A, B), Y, MatAdd(2*A, B)).doit() ==
|
||||
add(A, X, MatMul(A, B), Y, add(2*A, B)).doit() ==
|
||||
MatAdd(3*A + A*B + B, X, Y))
|
||||
|
||||
|
||||
def test_generic_identity():
|
||||
assert MatAdd.identity == GenericZeroMatrix()
|
||||
assert MatAdd.identity != S.Zero
|
||||
|
||||
|
||||
def test_zero_matrix_add():
|
||||
assert Add(ZeroMatrix(2, 2), ZeroMatrix(2, 2)) == ZeroMatrix(2, 2)
|
||||
|
||||
@XFAIL
|
||||
def test_matrix_Add_with_scalar():
|
||||
raises(TypeError, lambda: Add(0, ZeroMatrix(2, 2)))
|
||||
|
||||
|
||||
def test_shape_error():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
raises(ShapeError, lambda: MatAdd(A, B))
|
||||
|
||||
A = MatrixSymbol('A', 3, 2)
|
||||
raises(ShapeError, lambda: MatAdd(A, B))
|
||||
@@ -0,0 +1,592 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.exprtools import gcd_terms
|
||||
from sympy.core.function import (diff, expand)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.symbol import (Dummy, Symbol, Str)
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.dense import zeros
|
||||
from sympy.polys.polytools import factor
|
||||
|
||||
from sympy.core import (S, symbols, Add, Mul, SympifyError, Rational,
|
||||
Function)
|
||||
from sympy.functions import sin, cos, tan, sqrt, cbrt, exp
|
||||
from sympy.simplify import simplify
|
||||
from sympy.matrices import (ImmutableMatrix, Inverse, MatAdd, MatMul,
|
||||
MatPow, Matrix, MatrixExpr, MatrixSymbol,
|
||||
SparseMatrix, Transpose, Adjoint, MatrixSet)
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.expressions.determinant import Determinant, det
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, Identity
|
||||
from sympy.testing.pytest import raises, XFAIL, skip
|
||||
from importlib.metadata import version
|
||||
|
||||
n, m, l, k, p = symbols('n m l k p', integer=True)
|
||||
x = symbols('x')
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
w = MatrixSymbol('w', n, 1)
|
||||
|
||||
|
||||
def test_matrix_symbol_creation():
|
||||
assert MatrixSymbol('A', 2, 2)
|
||||
assert MatrixSymbol('A', 0, 0)
|
||||
raises(ValueError, lambda: MatrixSymbol('A', -1, 2))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2.0, 2))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2j, 2))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2, -1))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2, 2.0))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2, 2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert MatrixSymbol('A', n, n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: MatrixSymbol('A', n, n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: MatrixSymbol('A', n, n))
|
||||
|
||||
|
||||
def test_matexpr_properties():
|
||||
assert A.shape == (n, m)
|
||||
assert (A * B).shape == (n, l)
|
||||
assert A[0, 1].indices == (0, 1)
|
||||
assert A[0, 0].symbol == A
|
||||
assert A[0, 0].symbol.name == 'A'
|
||||
|
||||
|
||||
def test_matexpr():
|
||||
assert (x*A).shape == A.shape
|
||||
assert (x*A).__class__ == MatMul
|
||||
assert 2*A - A - A == ZeroMatrix(*A.shape)
|
||||
assert (A*B).shape == (n, l)
|
||||
|
||||
|
||||
def test_matexpr_subs():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', m, l)
|
||||
|
||||
assert A.subs(n, m).shape == (m, m)
|
||||
assert (A*B).subs(B, C) == A*C
|
||||
assert (A*B).subs(l, n).is_square
|
||||
|
||||
W = MatrixSymbol("W", 3, 3)
|
||||
X = MatrixSymbol("X", 2, 2)
|
||||
Y = MatrixSymbol("Y", 1, 2)
|
||||
Z = MatrixSymbol("Z", n, 2)
|
||||
# no restrictions on Symbol replacement
|
||||
assert X.subs(X, Y) == Y
|
||||
# it might be better to just change the name
|
||||
y = Str('y')
|
||||
assert X.subs(Str("X"), y).args == (y, 2, 2)
|
||||
# it's ok to introduce a wider matrix
|
||||
assert X[1, 1].subs(X, W) == W[1, 1]
|
||||
# but for a given MatrixExpression, only change
|
||||
# name if indexing on the new shape is valid.
|
||||
# Here, X is 2,2; Y is 1,2 and Y[1, 1] is out
|
||||
# of range so an error is raised
|
||||
raises(IndexError, lambda: X[1, 1].subs(X, Y))
|
||||
# here, [0, 1] is in range so the subs succeeds
|
||||
assert X[0, 1].subs(X, Y) == Y[0, 1]
|
||||
# and here the size of n will accept any index
|
||||
# in the first position
|
||||
assert W[2, 1].subs(W, Z) == Z[2, 1]
|
||||
# but not in the second position
|
||||
raises(IndexError, lambda: W[2, 2].subs(W, Z))
|
||||
# any matrix should raise if invalid
|
||||
raises(IndexError, lambda: W[2, 2].subs(W, zeros(2)))
|
||||
|
||||
A = SparseMatrix([[1, 2], [3, 4]])
|
||||
B = Matrix([[1, 2], [3, 4]])
|
||||
C, D = MatrixSymbol('C', 2, 2), MatrixSymbol('D', 2, 2)
|
||||
|
||||
assert (C*D).subs({C: A, D: B}) == MatMul(A, B)
|
||||
|
||||
|
||||
def test_addition():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
|
||||
assert isinstance(A + B, MatAdd)
|
||||
assert (A + B).shape == A.shape
|
||||
assert isinstance(A - A + 2*B, MatMul)
|
||||
|
||||
raises(TypeError, lambda: A + 1)
|
||||
raises(TypeError, lambda: 5 + A)
|
||||
raises(TypeError, lambda: 5 - A)
|
||||
|
||||
assert A + ZeroMatrix(n, m) - A == ZeroMatrix(n, m)
|
||||
raises(TypeError, lambda: ZeroMatrix(n, m) + S.Zero)
|
||||
|
||||
|
||||
def test_multiplication():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
assert (2*A*B).shape == (n, l)
|
||||
assert (A*0*B) == ZeroMatrix(n, l)
|
||||
assert (2*A).shape == A.shape
|
||||
|
||||
assert A * ZeroMatrix(m, m) * B == ZeroMatrix(n, l)
|
||||
|
||||
assert C * Identity(n) * C.I == Identity(n)
|
||||
|
||||
assert B/2 == S.Half*B
|
||||
raises(NotImplementedError, lambda: 2/B)
|
||||
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
assert Identity(n) * (A + B) == A + B
|
||||
|
||||
assert A**2*A == A**3
|
||||
assert A**2*(A.I)**3 == A.I
|
||||
assert A**3*(A.I)**2 == A
|
||||
|
||||
|
||||
def test_MatPow():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
|
||||
AA = MatPow(A, 2)
|
||||
assert AA.exp == 2
|
||||
assert AA.base == A
|
||||
assert (A**n).exp == n
|
||||
|
||||
assert A**0 == Identity(n)
|
||||
assert A**1 == A
|
||||
assert A**2 == AA
|
||||
assert A**-1 == Inverse(A)
|
||||
assert (A**-1)**-1 == A
|
||||
assert (A**2)**3 == A**6
|
||||
assert A**S.Half == sqrt(A)
|
||||
assert A**Rational(1, 3) == cbrt(A)
|
||||
raises(NonSquareMatrixError, lambda: MatrixSymbol('B', 3, 2)**2)
|
||||
|
||||
|
||||
def test_MatrixSymbol():
|
||||
n, m, t = symbols('n,m,t')
|
||||
X = MatrixSymbol('X', n, m)
|
||||
assert X.shape == (n, m)
|
||||
raises(TypeError, lambda: MatrixSymbol('X', n, m)(t)) # issue 5855
|
||||
assert X.doit() == X
|
||||
|
||||
|
||||
def test_dense_conversion():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
assert ImmutableMatrix(X) == ImmutableMatrix(2, 2, lambda i, j: X[i, j])
|
||||
assert Matrix(X) == Matrix(2, 2, lambda i, j: X[i, j])
|
||||
|
||||
|
||||
def test_free_symbols():
|
||||
assert (C*D).free_symbols == {C, D}
|
||||
|
||||
|
||||
def test_zero_matmul():
|
||||
assert isinstance(S.Zero * MatrixSymbol('X', 2, 2), MatrixExpr)
|
||||
|
||||
|
||||
def test_matadd_simplify():
|
||||
A = MatrixSymbol('A', 1, 1)
|
||||
assert simplify(MatAdd(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \
|
||||
MatAdd(A, Matrix([[1]]))
|
||||
|
||||
|
||||
def test_matmul_simplify():
|
||||
A = MatrixSymbol('A', 1, 1)
|
||||
assert simplify(MatMul(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \
|
||||
MatMul(A, Matrix([[1]]))
|
||||
|
||||
|
||||
def test_invariants():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
X = MatrixSymbol('X', n, n)
|
||||
objs = [Identity(n), ZeroMatrix(m, n), A, MatMul(A, B), MatAdd(A, A),
|
||||
Transpose(A), Adjoint(A), Inverse(X), MatPow(X, 2), MatPow(X, -1),
|
||||
MatPow(X, 0)]
|
||||
for obj in objs:
|
||||
assert obj == obj.__class__(*obj.args)
|
||||
|
||||
|
||||
def test_matexpr_indexing():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
A[1, 2]
|
||||
A[l, k]
|
||||
A[l + 1, k + 1]
|
||||
A = MatrixSymbol('A', 2, 1)
|
||||
for i in range(-2, 2):
|
||||
for j in range(-1, 1):
|
||||
A[i, j]
|
||||
|
||||
|
||||
def test_single_indexing():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
assert A[1] == A[0, 1]
|
||||
assert A[int(1)] == A[0, 1]
|
||||
assert A[3] == A[1, 0]
|
||||
assert list(A[:2, :2]) == [A[0, 0], A[0, 1], A[1, 0], A[1, 1]]
|
||||
raises(IndexError, lambda: A[6])
|
||||
raises(IndexError, lambda: A[n])
|
||||
B = MatrixSymbol('B', n, m)
|
||||
raises(IndexError, lambda: B[1])
|
||||
B = MatrixSymbol('B', n, 3)
|
||||
assert B[3] == B[1, 0]
|
||||
|
||||
|
||||
def test_MatrixElement_commutative():
|
||||
assert A[0, 1]*A[1, 0] == A[1, 0]*A[0, 1]
|
||||
|
||||
|
||||
def test_MatrixSymbol_determinant():
|
||||
A = MatrixSymbol('A', 4, 4)
|
||||
assert A.as_explicit().det() == A[0, 0]*A[1, 1]*A[2, 2]*A[3, 3] - \
|
||||
A[0, 0]*A[1, 1]*A[2, 3]*A[3, 2] - A[0, 0]*A[1, 2]*A[2, 1]*A[3, 3] + \
|
||||
A[0, 0]*A[1, 2]*A[2, 3]*A[3, 1] + A[0, 0]*A[1, 3]*A[2, 1]*A[3, 2] - \
|
||||
A[0, 0]*A[1, 3]*A[2, 2]*A[3, 1] - A[0, 1]*A[1, 0]*A[2, 2]*A[3, 3] + \
|
||||
A[0, 1]*A[1, 0]*A[2, 3]*A[3, 2] + A[0, 1]*A[1, 2]*A[2, 0]*A[3, 3] - \
|
||||
A[0, 1]*A[1, 2]*A[2, 3]*A[3, 0] - A[0, 1]*A[1, 3]*A[2, 0]*A[3, 2] + \
|
||||
A[0, 1]*A[1, 3]*A[2, 2]*A[3, 0] + A[0, 2]*A[1, 0]*A[2, 1]*A[3, 3] - \
|
||||
A[0, 2]*A[1, 0]*A[2, 3]*A[3, 1] - A[0, 2]*A[1, 1]*A[2, 0]*A[3, 3] + \
|
||||
A[0, 2]*A[1, 1]*A[2, 3]*A[3, 0] + A[0, 2]*A[1, 3]*A[2, 0]*A[3, 1] - \
|
||||
A[0, 2]*A[1, 3]*A[2, 1]*A[3, 0] - A[0, 3]*A[1, 0]*A[2, 1]*A[3, 2] + \
|
||||
A[0, 3]*A[1, 0]*A[2, 2]*A[3, 1] + A[0, 3]*A[1, 1]*A[2, 0]*A[3, 2] - \
|
||||
A[0, 3]*A[1, 1]*A[2, 2]*A[3, 0] - A[0, 3]*A[1, 2]*A[2, 0]*A[3, 1] + \
|
||||
A[0, 3]*A[1, 2]*A[2, 1]*A[3, 0]
|
||||
|
||||
B = MatrixSymbol('B', 4, 4)
|
||||
assert Determinant(A + B).doit() == det(A + B) == (A + B).det()
|
||||
|
||||
|
||||
def test_MatrixElement_diff():
|
||||
assert (A[3, 0]*A[0, 0]).diff(A[0, 0]) == A[3, 0]
|
||||
|
||||
|
||||
def test_MatrixElement_doit():
|
||||
u = MatrixSymbol('u', 2, 1)
|
||||
v = ImmutableMatrix([3, 5])
|
||||
assert u[0, 0].subs(u, v).doit() == v[0, 0]
|
||||
|
||||
|
||||
def test_identity_powers():
|
||||
M = Identity(n)
|
||||
assert MatPow(M, 3).doit() == M**3
|
||||
assert M**n == M
|
||||
assert MatPow(M, 0).doit() == M**2
|
||||
assert M**-2 == M
|
||||
assert MatPow(M, -2).doit() == M**0
|
||||
N = Identity(3)
|
||||
assert MatPow(N, 2).doit() == N**n
|
||||
assert MatPow(N, 3).doit() == N
|
||||
assert MatPow(N, -2).doit() == N**4
|
||||
assert MatPow(N, 2).doit() == N**0
|
||||
|
||||
|
||||
def test_Zero_power():
|
||||
z1 = ZeroMatrix(n, n)
|
||||
assert z1**4 == z1
|
||||
raises(ValueError, lambda:z1**-2)
|
||||
assert z1**0 == Identity(n)
|
||||
assert MatPow(z1, 2).doit() == z1**2
|
||||
raises(ValueError, lambda:MatPow(z1, -2).doit())
|
||||
z2 = ZeroMatrix(3, 3)
|
||||
assert MatPow(z2, 4).doit() == z2**4
|
||||
raises(ValueError, lambda:z2**-3)
|
||||
assert z2**3 == MatPow(z2, 3).doit()
|
||||
assert z2**0 == Identity(3)
|
||||
raises(ValueError, lambda:MatPow(z2, -1).doit())
|
||||
|
||||
|
||||
def test_matrixelement_diff():
|
||||
dexpr = diff((D*w)[k,0], w[p,0])
|
||||
|
||||
assert w[k, p].diff(w[k, p]) == 1
|
||||
assert w[k, p].diff(w[0, 0]) == KroneckerDelta(0, k, (0, n-1))*KroneckerDelta(0, p, (0, 0))
|
||||
_i_1 = Dummy("_i_1")
|
||||
assert dexpr.dummy_eq(Sum(KroneckerDelta(_i_1, p, (0, n-1))*D[k, _i_1], (_i_1, 0, n - 1)))
|
||||
assert dexpr.doit() == D[k, p]
|
||||
|
||||
|
||||
def test_MatrixElement_with_values():
|
||||
x, y, z, w = symbols("x y z w")
|
||||
M = Matrix([[x, y], [z, w]])
|
||||
i, j = symbols("i, j")
|
||||
Mij = M[i, j]
|
||||
assert isinstance(Mij, MatrixElement)
|
||||
Ms = SparseMatrix([[2, 3], [4, 5]])
|
||||
msij = Ms[i, j]
|
||||
assert isinstance(msij, MatrixElement)
|
||||
for oi, oj in [(0, 0), (0, 1), (1, 0), (1, 1)]:
|
||||
assert Mij.subs({i: oi, j: oj}) == M[oi, oj]
|
||||
assert msij.subs({i: oi, j: oj}) == Ms[oi, oj]
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
assert A[0, 0].subs(A, M) == x
|
||||
assert A[i, j].subs(A, M) == M[i, j]
|
||||
assert M[i, j].subs(M, A) == A[i, j]
|
||||
|
||||
assert isinstance(M[3*i - 2, j], MatrixElement)
|
||||
assert M[3*i - 2, j].subs({i: 1, j: 0}) == M[1, 0]
|
||||
assert isinstance(M[i, 0], MatrixElement)
|
||||
assert M[i, 0].subs(i, 0) == M[0, 0]
|
||||
assert M[0, i].subs(i, 1) == M[0, 1]
|
||||
|
||||
assert M[i, j].diff(x) == Matrix([[1, 0], [0, 0]])[i, j]
|
||||
|
||||
raises(ValueError, lambda: M[i, 2])
|
||||
raises(ValueError, lambda: M[i, -1])
|
||||
raises(ValueError, lambda: M[2, i])
|
||||
raises(ValueError, lambda: M[-1, i])
|
||||
|
||||
|
||||
def test_inv():
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
assert B.inv() == B**-1
|
||||
|
||||
# https://github.com/sympy/sympy/issues/19162
|
||||
X = MatrixSymbol('X', 1, 1).as_explicit()
|
||||
assert X.inv() == Matrix([[1/X[0, 0]]])
|
||||
|
||||
X = MatrixSymbol('X', 2, 2).as_explicit()
|
||||
detX = X[0, 0]*X[1, 1] - X[0, 1]*X[1, 0]
|
||||
invX = Matrix([[ X[1, 1], -X[0, 1]],
|
||||
[-X[1, 0], X[0, 0]]]) / detX
|
||||
assert X.inv() == invX
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_factor_expand():
|
||||
A = MatrixSymbol("A", n, n)
|
||||
B = MatrixSymbol("B", n, n)
|
||||
expr1 = (A + B)*(C + D)
|
||||
expr2 = A*C + B*C + A*D + B*D
|
||||
assert expr1 != expr2
|
||||
assert expand(expr1) == expr2
|
||||
assert factor(expr2) == expr1
|
||||
|
||||
expr = B**(-1)*(A**(-1)*B**(-1) - A**(-1)*C*B**(-1))**(-1)*A**(-1)
|
||||
I = Identity(n)
|
||||
# Ideally we get the first, but we at least don't want a wrong answer
|
||||
assert factor(expr) in [I - C, B**-1*(A**-1*(I - C)*B**-1)**-1*A**-1]
|
||||
|
||||
def test_numpy_conversion():
|
||||
try:
|
||||
from numpy import array, array_equal
|
||||
except ImportError:
|
||||
skip('NumPy must be available to test creating matrices from ndarrays')
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
np_array = array([[MatrixElement(A, 0, 0), MatrixElement(A, 0, 1)],
|
||||
[MatrixElement(A, 1, 0), MatrixElement(A, 1, 1)]])
|
||||
assert array_equal(array(A), np_array)
|
||||
assert array_equal(array(A, copy=True), np_array)
|
||||
if(int(version('numpy').split('.')[0]) >= 2): #run this test only if numpy is new enough that copy variable is passed properly.
|
||||
raises(TypeError, lambda: array(A, copy=False))
|
||||
|
||||
def test_issue_2749():
|
||||
A = MatrixSymbol("A", 5, 2)
|
||||
assert (A.T * A).I.as_explicit() == Matrix([[(A.T * A).I[0, 0], (A.T * A).I[0, 1]], \
|
||||
[(A.T * A).I[1, 0], (A.T * A).I[1, 1]]])
|
||||
|
||||
|
||||
def test_issue_2750():
|
||||
x = MatrixSymbol('x', 1, 1)
|
||||
assert (x.T*x).as_explicit()**-1 == Matrix([[x[0, 0]**(-2)]])
|
||||
|
||||
|
||||
def test_issue_7842():
|
||||
A = MatrixSymbol('A', 3, 1)
|
||||
B = MatrixSymbol('B', 2, 1)
|
||||
assert Eq(A, B) == False
|
||||
assert Eq(A[1,0], B[1, 0]).func is Eq
|
||||
A = ZeroMatrix(2, 3)
|
||||
B = ZeroMatrix(2, 3)
|
||||
assert Eq(A, B) == True
|
||||
|
||||
|
||||
def test_issue_21195():
|
||||
t = symbols('t')
|
||||
x = Function('x')(t)
|
||||
dx = x.diff(t)
|
||||
exp1 = cos(x) + cos(x)*dx
|
||||
exp2 = sin(x) + tan(x)*(dx.diff(t))
|
||||
exp3 = sin(x)*sin(t)*(dx.diff(t)).diff(t)
|
||||
A = Matrix([[exp1], [exp2], [exp3]])
|
||||
B = Matrix([[exp1.diff(x)], [exp2.diff(x)], [exp3.diff(x)]])
|
||||
assert A.diff(x) == B
|
||||
|
||||
|
||||
def test_issue_24859():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = MatrixSymbol('B', 3, 2)
|
||||
J = A*B
|
||||
Jinv = Matrix(J).adjugate()
|
||||
u = MatrixSymbol('u', 2, 3)
|
||||
Jk = Jinv.subs(A, A + x*u)
|
||||
|
||||
expected = B[0, 1]*u[1, 0] + B[1, 1]*u[1, 1] + B[2, 1]*u[1, 2]
|
||||
assert Jk[0, 0].diff(x) == expected
|
||||
assert diff(Jk[0, 0], x).doit() == expected
|
||||
|
||||
|
||||
def test_MatMul_postprocessor():
|
||||
z = zeros(2)
|
||||
z1 = ZeroMatrix(2, 2)
|
||||
assert Mul(0, z) == Mul(z, 0) in [z, z1]
|
||||
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
Mx = Matrix([[x, 2*x], [3*x, 4*x]])
|
||||
assert Mul(x, M) == Mul(M, x) == Mx
|
||||
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
assert Mul(A, M) == MatMul(A, M)
|
||||
assert Mul(M, A) == MatMul(M, A)
|
||||
# Scalars should be absorbed into constant matrices
|
||||
a = Mul(x, M, A)
|
||||
b = Mul(M, x, A)
|
||||
c = Mul(M, A, x)
|
||||
assert a == b == c == MatMul(Mx, A)
|
||||
a = Mul(x, A, M)
|
||||
b = Mul(A, x, M)
|
||||
c = Mul(A, M, x)
|
||||
assert a == b == c == MatMul(A, Mx)
|
||||
assert Mul(M, M) == M**2
|
||||
assert Mul(A, M, M) == MatMul(A, M**2)
|
||||
assert Mul(M, M, A) == MatMul(M**2, A)
|
||||
assert Mul(M, A, M) == MatMul(M, A, M)
|
||||
|
||||
assert Mul(A, x, M, M, x) == MatMul(A, Mx**2)
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_MatAdd_postprocessor_xfail():
|
||||
# This is difficult to get working because of the way that Add processes
|
||||
# its args.
|
||||
z = zeros(2)
|
||||
assert Add(z, S.NaN) == Add(S.NaN, z)
|
||||
|
||||
|
||||
def test_MatAdd_postprocessor():
|
||||
# Some of these are nonsensical, but we do not raise errors for Add
|
||||
# because that breaks algorithms that want to replace matrices with dummy
|
||||
# symbols.
|
||||
|
||||
z = zeros(2)
|
||||
|
||||
assert Add(0, z) == Add(z, 0) == z
|
||||
|
||||
a = Add(S.Infinity, z)
|
||||
assert a == Add(z, S.Infinity)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (S.Infinity, z)
|
||||
|
||||
a = Add(S.ComplexInfinity, z)
|
||||
assert a == Add(z, S.ComplexInfinity)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (S.ComplexInfinity, z)
|
||||
|
||||
a = Add(z, S.NaN)
|
||||
# assert a == Add(S.NaN, z) # See the XFAIL above
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (S.NaN, z)
|
||||
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
a = Add(x, M)
|
||||
assert a == Add(M, x)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (x, M)
|
||||
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
assert Add(A, M) == Add(M, A) == A + M
|
||||
|
||||
# Scalars should be absorbed into constant matrices (producing an error)
|
||||
a = Add(x, M, A)
|
||||
assert a == Add(M, x, A) == Add(M, A, x) == Add(x, A, M) == Add(A, x, M) == Add(A, M, x)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (x, A + M)
|
||||
|
||||
assert Add(M, M) == 2*M
|
||||
assert Add(M, A, M) == Add(M, M, A) == Add(A, M, M) == A + 2*M
|
||||
|
||||
a = Add(A, x, M, M, x)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (2*x, A + 2*M)
|
||||
|
||||
|
||||
def test_simplify_matrix_expressions():
|
||||
# Various simplification functions
|
||||
assert type(gcd_terms(C*D + D*C)) == MatAdd
|
||||
a = gcd_terms(2*C*D + 4*D*C)
|
||||
assert type(a) == MatAdd
|
||||
assert a.args == (2*C*D, 4*D*C)
|
||||
|
||||
|
||||
def test_exp():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
expr1 = exp(A)*exp(B)
|
||||
expr2 = exp(B)*exp(A)
|
||||
assert expr1 != expr2
|
||||
assert expr1 - expr2 != 0
|
||||
assert not isinstance(expr1, exp)
|
||||
assert not isinstance(expr2, exp)
|
||||
|
||||
|
||||
def test_invalid_args():
|
||||
raises(SympifyError, lambda: MatrixSymbol(1, 2, 'A'))
|
||||
|
||||
|
||||
def test_matrixsymbol_from_symbol():
|
||||
# The label should be preserved during doit and subs
|
||||
A_label = Symbol('A', complex=True)
|
||||
A = MatrixSymbol(A_label, 2, 2)
|
||||
|
||||
A_1 = A.doit()
|
||||
A_2 = A.subs(2, 3)
|
||||
assert A_1.args == A.args
|
||||
assert A_2.args[0] == A.args[0]
|
||||
|
||||
|
||||
def test_as_explicit():
|
||||
Z = MatrixSymbol('Z', 2, 3)
|
||||
assert Z.as_explicit() == ImmutableMatrix([
|
||||
[Z[0, 0], Z[0, 1], Z[0, 2]],
|
||||
[Z[1, 0], Z[1, 1], Z[1, 2]],
|
||||
])
|
||||
raises(ValueError, lambda: A.as_explicit())
|
||||
|
||||
|
||||
def test_MatrixSet():
|
||||
M = MatrixSet(2, 2, set=S.Reals)
|
||||
assert M.shape == (2, 2)
|
||||
assert M.set == S.Reals
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert X in M
|
||||
X = ZeroMatrix(2, 2)
|
||||
assert X in M
|
||||
raises(TypeError, lambda: A in M)
|
||||
raises(TypeError, lambda: 1 in M)
|
||||
M = MatrixSet(n, m, set=S.Reals)
|
||||
assert A in M
|
||||
raises(TypeError, lambda: C in M)
|
||||
raises(TypeError, lambda: X in M)
|
||||
M = MatrixSet(2, 2, set={1, 2, 3})
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
Y = Matrix([[1, 2]])
|
||||
assert (X in M) == S.false
|
||||
assert (Y in M) == S.false
|
||||
raises(ValueError, lambda: MatrixSet(2, -2, S.Reals))
|
||||
raises(ValueError, lambda: MatrixSet(2.4, -1, S.Reals))
|
||||
raises(TypeError, lambda: MatrixSet(2, 2, (1, 2, 3)))
|
||||
|
||||
|
||||
def test_matrixsymbol_solving():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
Z = ZeroMatrix(2, 2)
|
||||
assert -(-A + B) - A + B == Z
|
||||
assert (-(-A + B) - A + B).simplify() == Z
|
||||
assert (-(-A + B) - A + B).expand() == Z
|
||||
assert (-(-A + B) - A + B - Z).simplify() == Z
|
||||
assert (-(-A + B) - A + B - Z).expand() == Z
|
||||
assert (A*(A + B) + B*(A.T + B.T)).expand() == A**2 + A*B + B*A.T + B*B.T
|
||||
@@ -0,0 +1,193 @@
|
||||
from sympy.core import I, symbols, Basic, Mul, S
|
||||
from sympy.core.mul import mul
|
||||
from sympy.functions import adjoint, transpose
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices import (Identity, Inverse, Matrix, MatrixSymbol, ZeroMatrix,
|
||||
eye, ImmutableMatrix)
|
||||
from sympy.matrices.expressions import Adjoint, Transpose, det, MatPow
|
||||
from sympy.matrices.expressions.special import GenericIdentity
|
||||
from sympy.matrices.expressions.matmul import (factor_in_front, remove_ids,
|
||||
MatMul, combine_powers, any_zeros, unpack, only_squares)
|
||||
from sympy.strategies import null_safe
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
from sympy.core.symbol import Symbol
|
||||
|
||||
from sympy.testing.pytest import XFAIL, raises
|
||||
|
||||
n, m, l, k = symbols('n m l k', integer=True)
|
||||
x = symbols('x')
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
|
||||
def test_evaluate():
|
||||
assert MatMul(C, C, evaluate=True) == MatMul(C, C).doit()
|
||||
|
||||
def test_adjoint():
|
||||
assert adjoint(A*B) == Adjoint(B)*Adjoint(A)
|
||||
assert adjoint(2*A*B) == 2*Adjoint(B)*Adjoint(A)
|
||||
assert adjoint(2*I*C) == -2*I*Adjoint(C)
|
||||
|
||||
M = Matrix(2, 2, [1, 2 + I, 3, 4])
|
||||
MA = Matrix(2, 2, [1, 3, 2 - I, 4])
|
||||
assert adjoint(M) == MA
|
||||
assert adjoint(2*M) == 2*MA
|
||||
assert adjoint(MatMul(2, M)) == MatMul(2, MA).doit()
|
||||
|
||||
|
||||
def test_transpose():
|
||||
assert transpose(A*B) == Transpose(B)*Transpose(A)
|
||||
assert transpose(2*A*B) == 2*Transpose(B)*Transpose(A)
|
||||
assert transpose(2*I*C) == 2*I*Transpose(C)
|
||||
|
||||
M = Matrix(2, 2, [1, 2 + I, 3, 4])
|
||||
MT = Matrix(2, 2, [1, 3, 2 + I, 4])
|
||||
assert transpose(M) == MT
|
||||
assert transpose(2*M) == 2*MT
|
||||
assert transpose(x*M) == x*MT
|
||||
assert transpose(MatMul(2, M)) == MatMul(2, MT).doit()
|
||||
|
||||
|
||||
def test_factor_in_front():
|
||||
assert factor_in_front(MatMul(A, 2, B, evaluate=False)) ==\
|
||||
MatMul(2, A, B, evaluate=False)
|
||||
|
||||
|
||||
def test_remove_ids():
|
||||
assert remove_ids(MatMul(A, Identity(m), B, evaluate=False)) == \
|
||||
MatMul(A, B, evaluate=False)
|
||||
assert null_safe(remove_ids)(MatMul(Identity(n), evaluate=False)) == \
|
||||
MatMul(Identity(n), evaluate=False)
|
||||
|
||||
|
||||
def test_combine_powers():
|
||||
assert combine_powers(MatMul(D, Inverse(D), D, evaluate=False)) == \
|
||||
MatMul(Identity(n), D, evaluate=False)
|
||||
assert combine_powers(MatMul(B.T, Inverse(E*A), E, A, B, evaluate=False)) == \
|
||||
MatMul(B.T, Identity(m), B, evaluate=False)
|
||||
assert combine_powers(MatMul(A, E, Inverse(A*E), D, evaluate=False)) == \
|
||||
MatMul(Identity(n), D, evaluate=False)
|
||||
|
||||
|
||||
def test_any_zeros():
|
||||
assert any_zeros(MatMul(A, ZeroMatrix(m, k), evaluate=False)) == \
|
||||
ZeroMatrix(n, k)
|
||||
|
||||
|
||||
def test_unpack():
|
||||
assert unpack(MatMul(A, evaluate=False)) == A
|
||||
x = MatMul(A, B)
|
||||
assert unpack(x) == x
|
||||
|
||||
|
||||
def test_only_squares():
|
||||
assert only_squares(C) == [C]
|
||||
assert only_squares(C, D) == [C, D]
|
||||
assert only_squares(C, A, A.T, D) == [C, A*A.T, D]
|
||||
|
||||
|
||||
def test_determinant():
|
||||
assert det(2*C) == 2**n*det(C)
|
||||
assert det(2*C*D) == 2**n*det(C)*det(D)
|
||||
assert det(3*C*A*A.T*D) == 3**n*det(C)*det(A*A.T)*det(D)
|
||||
|
||||
|
||||
def test_doit():
|
||||
assert MatMul(C, 2, D).args == (C, 2, D)
|
||||
assert MatMul(C, 2, D).doit().args == (2, C, D)
|
||||
assert MatMul(C, Transpose(D*C)).args == (C, Transpose(D*C))
|
||||
assert MatMul(C, Transpose(D*C)).doit(deep=True).args == (C, C.T, D.T)
|
||||
|
||||
|
||||
def test_doit_drills_down():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
Y = ImmutableMatrix([[2, 3], [4, 5]])
|
||||
assert MatMul(X, MatPow(Y, 2)).doit() == X*Y**2
|
||||
assert MatMul(C, Transpose(D*C)).doit().args == (C, C.T, D.T)
|
||||
|
||||
|
||||
def test_doit_deep_false_still_canonical():
|
||||
assert (MatMul(C, Transpose(D*C), 2).doit(deep=False).args ==
|
||||
(2, C, Transpose(D*C)))
|
||||
|
||||
|
||||
def test_matmul_scalar_Matrix_doit():
|
||||
# Issue 9053
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert MatMul(2, X).doit() == 2*X
|
||||
|
||||
|
||||
def test_matmul_sympify():
|
||||
assert isinstance(MatMul(eye(1), eye(1)).args[0], Basic)
|
||||
|
||||
|
||||
def test_collapse_MatrixBase():
|
||||
A = Matrix([[1, 1], [1, 1]])
|
||||
B = Matrix([[1, 2], [3, 4]])
|
||||
assert MatMul(A, B).doit() == ImmutableMatrix([[4, 6], [4, 6]])
|
||||
|
||||
|
||||
def test_refine():
|
||||
assert refine(C*C.T*D, Q.orthogonal(C)).doit() == D
|
||||
|
||||
kC = k*C
|
||||
assert refine(kC*C.T, Q.orthogonal(C)).doit() == k*Identity(n)
|
||||
assert refine(kC* kC.T, Q.orthogonal(C)).doit() == (k**2)*Identity(n)
|
||||
|
||||
def test_matmul_no_matrices():
|
||||
assert MatMul(1) == 1
|
||||
assert MatMul(n, m) == n*m
|
||||
assert not isinstance(MatMul(n, m), MatMul)
|
||||
|
||||
def test_matmul_args_cnc():
|
||||
assert MatMul(n, A, A.T).args_cnc() == [[n], [A, A.T]]
|
||||
assert MatMul(A, A.T).args_cnc() == [[], [A, A.T]]
|
||||
|
||||
@XFAIL
|
||||
def test_matmul_args_cnc_symbols():
|
||||
# Not currently supported
|
||||
a, b = symbols('a b', commutative=False)
|
||||
assert MatMul(n, a, b, A, A.T).args_cnc() == [[n], [a, b, A, A.T]]
|
||||
assert MatMul(n, a, A, b, A.T).args_cnc() == [[n], [a, A, b, A.T]]
|
||||
|
||||
def test_issue_12950():
|
||||
M = Matrix([[Symbol("x")]]) * MatrixSymbol("A", 1, 1)
|
||||
assert MatrixSymbol("A", 1, 1).as_explicit()[0]*Symbol('x') == M.as_explicit()[0]
|
||||
|
||||
def test_construction_with_Mul():
|
||||
assert Mul(C, D) == MatMul(C, D)
|
||||
assert Mul(D, C) == MatMul(D, C)
|
||||
|
||||
def test_construction_with_mul():
|
||||
assert mul(C, D) == MatMul(C, D)
|
||||
assert mul(D, C) == MatMul(D, C)
|
||||
assert mul(C, D) != MatMul(D, C)
|
||||
|
||||
def test_generic_identity():
|
||||
assert MatMul.identity == GenericIdentity()
|
||||
assert MatMul.identity != S.One
|
||||
|
||||
|
||||
def test_issue_23519():
|
||||
N = Symbol("N", integer=True)
|
||||
M1 = MatrixSymbol("M1", N, N)
|
||||
M2 = MatrixSymbol("M2", N, N)
|
||||
I = Identity(N)
|
||||
z = (M2 + 2 * (M2 + I) * M1 + I)
|
||||
assert z.coeff(M1) == 2*I + 2*M2
|
||||
|
||||
|
||||
def test_shape_error():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
raises(ShapeError, lambda: MatMul(A, B))
|
||||
|
||||
|
||||
def test_matmul_transpose():
|
||||
# https://github.com/sympy/sympy/issues/9503
|
||||
M = Matrix(2, 2, [1, 2 + I, 3, 4])
|
||||
a = Symbol('a')
|
||||
assert (MatMul(a, M).T).expand() == (a*Matrix([[1, 3],[2 + I, 4]])).expand()
|
||||
@@ -0,0 +1,217 @@
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.simplify.powsimp import powsimp
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core import symbols, S
|
||||
from sympy.matrices import Identity, MatrixSymbol, ImmutableMatrix, ZeroMatrix, OneMatrix, Matrix
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.expressions import MatPow, MatAdd, MatMul
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
|
||||
n, m, l, k = symbols('n m l k', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
|
||||
|
||||
def test_entry_matrix():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert MatPow(X, 0)[0, 0] == 1
|
||||
assert MatPow(X, 0)[0, 1] == 0
|
||||
assert MatPow(X, 1)[0, 0] == 1
|
||||
assert MatPow(X, 1)[0, 1] == 2
|
||||
assert MatPow(X, 2)[0, 0] == 7
|
||||
|
||||
|
||||
def test_entry_symbol():
|
||||
from sympy.concrete import Sum
|
||||
assert MatPow(C, 0)[0, 0] == 1
|
||||
assert MatPow(C, 0)[0, 1] == 0
|
||||
assert MatPow(C, 1)[0, 0] == C[0, 0]
|
||||
assert isinstance(MatPow(C, 2)[0, 0], Sum)
|
||||
assert isinstance(MatPow(C, n)[0, 0], MatrixElement)
|
||||
|
||||
|
||||
def test_as_explicit_symbol():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
assert MatPow(X, 0).as_explicit() == ImmutableMatrix(Identity(2))
|
||||
assert MatPow(X, 1).as_explicit() == X.as_explicit()
|
||||
assert MatPow(X, 2).as_explicit() == (X.as_explicit())**2
|
||||
assert MatPow(X, n).as_explicit() == ImmutableMatrix([
|
||||
[(X ** n)[0, 0], (X ** n)[0, 1]],
|
||||
[(X ** n)[1, 0], (X ** n)[1, 1]],
|
||||
])
|
||||
|
||||
a = MatrixSymbol("a", 3, 1)
|
||||
b = MatrixSymbol("b", 3, 1)
|
||||
c = MatrixSymbol("c", 3, 1)
|
||||
|
||||
expr = (a.T*b)**S.Half
|
||||
assert expr.as_explicit() == Matrix([[sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])]])
|
||||
|
||||
expr = c*(a.T*b)**S.Half
|
||||
m = sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])
|
||||
assert expr.as_explicit() == Matrix([[c[0, 0]*m], [c[1, 0]*m], [c[2, 0]*m]])
|
||||
|
||||
expr = (a*b.T)**S.Half
|
||||
denom = sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])
|
||||
expected = (a*b.T).as_explicit()/denom
|
||||
assert expr.as_explicit() == expected
|
||||
|
||||
expr = X**-1
|
||||
det = X[0, 0]*X[1, 1] - X[1, 0]*X[0, 1]
|
||||
expected = Matrix([[X[1, 1], -X[0, 1]], [-X[1, 0], X[0, 0]]])/det
|
||||
assert expr.as_explicit() == expected
|
||||
|
||||
expr = X**m
|
||||
assert expr.as_explicit() == X.as_explicit()**m
|
||||
|
||||
|
||||
def test_as_explicit_matrix():
|
||||
A = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert MatPow(A, 0).as_explicit() == ImmutableMatrix(Identity(2))
|
||||
assert MatPow(A, 1).as_explicit() == A
|
||||
assert MatPow(A, 2).as_explicit() == A**2
|
||||
assert MatPow(A, -1).as_explicit() == A.inv()
|
||||
assert MatPow(A, -2).as_explicit() == (A.inv())**2
|
||||
# less expensive than testing on a 2x2
|
||||
A = ImmutableMatrix([4])
|
||||
assert MatPow(A, S.Half).as_explicit() == A**S.Half
|
||||
|
||||
|
||||
def test_doit_symbol():
|
||||
assert MatPow(C, 0).doit() == Identity(n)
|
||||
assert MatPow(C, 1).doit() == C
|
||||
assert MatPow(C, -1).doit() == C.I
|
||||
for r in [2, S.Half, S.Pi, n]:
|
||||
assert MatPow(C, r).doit() == MatPow(C, r)
|
||||
|
||||
|
||||
def test_doit_matrix():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert MatPow(X, 0).doit() == ImmutableMatrix(Identity(2))
|
||||
assert MatPow(X, 1).doit() == X
|
||||
assert MatPow(X, 2).doit() == X**2
|
||||
assert MatPow(X, -1).doit() == X.inv()
|
||||
assert MatPow(X, -2).doit() == (X.inv())**2
|
||||
# less expensive than testing on a 2x2
|
||||
assert MatPow(ImmutableMatrix([4]), S.Half).doit() == ImmutableMatrix([2])
|
||||
X = ImmutableMatrix([[0, 2], [0, 4]]) # det() == 0
|
||||
raises(ValueError, lambda: MatPow(X,-1).doit())
|
||||
raises(ValueError, lambda: MatPow(X,-2).doit())
|
||||
|
||||
|
||||
def test_nonsquare():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = ImmutableMatrix([[1, 2, 3], [4, 5, 6]])
|
||||
for r in [-1, 0, 1, 2, S.Half, S.Pi, n]:
|
||||
raises(NonSquareMatrixError, lambda: MatPow(A, r))
|
||||
raises(NonSquareMatrixError, lambda: MatPow(B, r))
|
||||
|
||||
|
||||
def test_doit_equals_pow(): #17179
|
||||
X = ImmutableMatrix ([[1,0],[0,1]])
|
||||
assert MatPow(X, n).doit() == X**n == X
|
||||
|
||||
|
||||
def test_doit_nested_MatrixExpr():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
Y = ImmutableMatrix([[2, 3], [4, 5]])
|
||||
assert MatPow(MatMul(X, Y), 2).doit() == (X*Y)**2
|
||||
assert MatPow(MatAdd(X, Y), 2).doit() == (X + Y)**2
|
||||
|
||||
|
||||
def test_identity_power():
|
||||
k = Identity(n)
|
||||
assert MatPow(k, 4).doit() == k
|
||||
assert MatPow(k, n).doit() == k
|
||||
assert MatPow(k, -3).doit() == k
|
||||
assert MatPow(k, 0).doit() == k
|
||||
l = Identity(3)
|
||||
assert MatPow(l, n).doit() == l
|
||||
assert MatPow(l, -1).doit() == l
|
||||
assert MatPow(l, 0).doit() == l
|
||||
|
||||
|
||||
def test_zero_power():
|
||||
z1 = ZeroMatrix(n, n)
|
||||
assert MatPow(z1, 3).doit() == z1
|
||||
raises(ValueError, lambda:MatPow(z1, -1).doit())
|
||||
assert MatPow(z1, 0).doit() == Identity(n)
|
||||
assert MatPow(z1, n).doit() == z1
|
||||
raises(ValueError, lambda:MatPow(z1, -2).doit())
|
||||
z2 = ZeroMatrix(4, 4)
|
||||
assert MatPow(z2, n).doit() == z2
|
||||
raises(ValueError, lambda:MatPow(z2, -3).doit())
|
||||
assert MatPow(z2, 2).doit() == z2
|
||||
assert MatPow(z2, 0).doit() == Identity(4)
|
||||
raises(ValueError, lambda:MatPow(z2, -1).doit())
|
||||
|
||||
|
||||
def test_OneMatrix_power():
|
||||
o = OneMatrix(3, 3)
|
||||
assert o ** 0 == Identity(3)
|
||||
assert o ** 1 == o
|
||||
assert o * o == o ** 2 == 3 * o
|
||||
assert o * o * o == o ** 3 == 9 * o
|
||||
|
||||
o = OneMatrix(n, n)
|
||||
assert o * o == o ** 2 == n * o
|
||||
# powsimp necessary as n ** (n - 2) * n does not produce n ** (n - 1)
|
||||
assert powsimp(o ** (n - 1) * o) == o ** n == n ** (n - 1) * o
|
||||
|
||||
|
||||
def test_transpose_power():
|
||||
from sympy.matrices.expressions.transpose import Transpose as TP
|
||||
|
||||
assert (C*D).T**5 == ((C*D)**5).T == (D.T * C.T)**5
|
||||
assert ((C*D).T**5).T == (C*D)**5
|
||||
|
||||
assert (C.T.I.T)**7 == C**-7
|
||||
assert (C.T**l).T**k == C**(l*k)
|
||||
|
||||
assert ((E.T * A.T)**5).T == (A*E)**5
|
||||
assert ((A*E).T**5).T**7 == (A*E)**35
|
||||
assert TP(TP(C**2 * D**3)**5).doit() == (C**2 * D**3)**5
|
||||
|
||||
assert ((D*C)**-5).T**-5 == ((D*C)**25).T
|
||||
assert (((D*C)**l).T**k).T == (D*C)**(l*k)
|
||||
|
||||
|
||||
def test_Inverse():
|
||||
assert Inverse(MatPow(C, 0)).doit() == Identity(n)
|
||||
assert Inverse(MatPow(C, 1)).doit() == Inverse(C)
|
||||
assert Inverse(MatPow(C, 2)).doit() == MatPow(C, -2)
|
||||
assert Inverse(MatPow(C, -1)).doit() == C
|
||||
|
||||
assert MatPow(Inverse(C), 0).doit() == Identity(n)
|
||||
assert MatPow(Inverse(C), 1).doit() == Inverse(C)
|
||||
assert MatPow(Inverse(C), 2).doit() == MatPow(C, -2)
|
||||
assert MatPow(Inverse(C), -1).doit() == C
|
||||
|
||||
|
||||
def test_combine_powers():
|
||||
assert (C ** 1) ** 1 == C
|
||||
assert (C ** 2) ** 3 == MatPow(C, 6)
|
||||
assert (C ** -2) ** -3 == MatPow(C, 6)
|
||||
assert (C ** -1) ** -1 == C
|
||||
assert (((C ** 2) ** 3) ** 4) ** 5 == MatPow(C, 120)
|
||||
assert (C ** n) ** n == C ** (n ** 2)
|
||||
|
||||
|
||||
def test_unchanged():
|
||||
assert unchanged(MatPow, C, 0)
|
||||
assert unchanged(MatPow, C, 1)
|
||||
assert unchanged(MatPow, Inverse(C), -1)
|
||||
assert unchanged(Inverse, MatPow(C, -1), -1)
|
||||
assert unchanged(MatPow, MatPow(C, -1), -1)
|
||||
assert unchanged(MatPow, MatPow(C, 1), 1)
|
||||
|
||||
|
||||
def test_no_exponentiation():
|
||||
# if this passes, Pow.as_numer_denom should recognize
|
||||
# MatAdd as exponent
|
||||
raises(NotImplementedError, lambda: 3**(-2*C))
|
||||
@@ -0,0 +1,166 @@
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions import \
|
||||
MatMul, BlockDiagMatrix, Determinant, Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix, Identity
|
||||
from sympy.matrices.expressions.permutation import \
|
||||
MatrixPermute, PermutationMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.symbol import Symbol
|
||||
|
||||
|
||||
def test_PermutationMatrix_basic():
|
||||
p = Permutation([1, 0])
|
||||
assert unchanged(PermutationMatrix, p)
|
||||
raises(ValueError, lambda: PermutationMatrix((0, 1, 2)))
|
||||
assert PermutationMatrix(p).as_explicit() == Matrix([[0, 1], [1, 0]])
|
||||
assert isinstance(PermutationMatrix(p)*MatrixSymbol('A', 2, 2), MatMul)
|
||||
|
||||
|
||||
def test_PermutationMatrix_matmul():
|
||||
p = Permutation([1, 2, 0])
|
||||
P = PermutationMatrix(p)
|
||||
M = Matrix([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
|
||||
assert (P*M).as_explicit() == P.as_explicit()*M
|
||||
assert (M*P).as_explicit() == M*P.as_explicit()
|
||||
|
||||
P1 = PermutationMatrix(Permutation([1, 2, 0]))
|
||||
P2 = PermutationMatrix(Permutation([2, 1, 0]))
|
||||
P3 = PermutationMatrix(Permutation([1, 0, 2]))
|
||||
assert P1*P2 == P3
|
||||
|
||||
|
||||
def test_PermutationMatrix_matpow():
|
||||
p1 = Permutation([1, 2, 0])
|
||||
P1 = PermutationMatrix(p1)
|
||||
p2 = Permutation([2, 0, 1])
|
||||
P2 = PermutationMatrix(p2)
|
||||
assert P1**2 == P2
|
||||
assert P1**3 == Identity(3)
|
||||
|
||||
|
||||
def test_PermutationMatrix_identity():
|
||||
p = Permutation([0, 1])
|
||||
assert PermutationMatrix(p).is_Identity
|
||||
|
||||
p = Permutation([1, 0])
|
||||
assert not PermutationMatrix(p).is_Identity
|
||||
|
||||
|
||||
def test_PermutationMatrix_determinant():
|
||||
P = PermutationMatrix(Permutation([0, 1, 2]))
|
||||
assert Determinant(P).doit() == 1
|
||||
P = PermutationMatrix(Permutation([0, 2, 1]))
|
||||
assert Determinant(P).doit() == -1
|
||||
P = PermutationMatrix(Permutation([2, 0, 1]))
|
||||
assert Determinant(P).doit() == 1
|
||||
|
||||
|
||||
def test_PermutationMatrix_inverse():
|
||||
P = PermutationMatrix(Permutation(0, 1, 2))
|
||||
assert Inverse(P).doit() == PermutationMatrix(Permutation(0, 2, 1))
|
||||
|
||||
|
||||
def test_PermutationMatrix_rewrite_BlockDiagMatrix():
|
||||
P = PermutationMatrix(Permutation([0, 1, 2, 3, 4, 5]))
|
||||
P0 = PermutationMatrix(Permutation([0]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P0, P0, P0, P0, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation([0, 1, 3, 2, 4, 5]))
|
||||
P10 = PermutationMatrix(Permutation(0, 1))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P0, P10, P0, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation([1, 0, 3, 2, 5, 4]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P10, P10, P10)
|
||||
|
||||
P = PermutationMatrix(Permutation([0, 4, 3, 2, 1, 5]))
|
||||
P3210 = PermutationMatrix(Permutation([3, 2, 1, 0]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P3210, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation([0, 4, 2, 3, 1, 5]))
|
||||
P3120 = PermutationMatrix(Permutation([3, 1, 2, 0]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P3120, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation(0, 3)(1, 4)(2, 5))
|
||||
assert P.rewrite(BlockDiagMatrix) == BlockDiagMatrix(P)
|
||||
|
||||
|
||||
def test_MartrixPermute_basic():
|
||||
p = Permutation(0, 1)
|
||||
P = PermutationMatrix(p)
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
|
||||
raises(ValueError, lambda: MatrixPermute(Symbol('x'), p))
|
||||
raises(ValueError, lambda: MatrixPermute(A, Symbol('x')))
|
||||
|
||||
assert MatrixPermute(A, P) == MatrixPermute(A, p)
|
||||
raises(ValueError, lambda: MatrixPermute(A, p, 2))
|
||||
|
||||
pp = Permutation(0, 1, size=3)
|
||||
assert MatrixPermute(A, pp) == MatrixPermute(A, p)
|
||||
pp = Permutation(0, 1, 2)
|
||||
raises(ValueError, lambda: MatrixPermute(A, pp))
|
||||
|
||||
|
||||
def test_MatrixPermute_shape():
|
||||
p = Permutation(0, 1)
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
assert MatrixPermute(A, p).shape == (2, 3)
|
||||
|
||||
|
||||
def test_MatrixPermute_explicit():
|
||||
p = Permutation(0, 1, 2)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
AA = A.as_explicit()
|
||||
assert MatrixPermute(A, p, 0).as_explicit() == \
|
||||
AA.permute(p, orientation='rows')
|
||||
assert MatrixPermute(A, p, 1).as_explicit() == \
|
||||
AA.permute(p, orientation='cols')
|
||||
|
||||
|
||||
def test_MatrixPermute_rewrite_MatMul():
|
||||
p = Permutation(0, 1, 2)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
|
||||
assert MatrixPermute(A, p, 0).rewrite(MatMul).as_explicit() == \
|
||||
MatrixPermute(A, p, 0).as_explicit()
|
||||
assert MatrixPermute(A, p, 1).rewrite(MatMul).as_explicit() == \
|
||||
MatrixPermute(A, p, 1).as_explicit()
|
||||
|
||||
|
||||
def test_MatrixPermute_doit():
|
||||
p = Permutation(0, 1, 2)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
assert MatrixPermute(A, p).doit() == MatrixPermute(A, p)
|
||||
|
||||
p = Permutation(0, size=3)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
assert MatrixPermute(A, p).doit().as_explicit() == \
|
||||
MatrixPermute(A, p).as_explicit()
|
||||
|
||||
p = Permutation(0, 1, 2)
|
||||
A = Identity(3)
|
||||
assert MatrixPermute(A, p, 0).doit().as_explicit() == \
|
||||
MatrixPermute(A, p, 0).as_explicit()
|
||||
assert MatrixPermute(A, p, 1).doit().as_explicit() == \
|
||||
MatrixPermute(A, p, 1).as_explicit()
|
||||
|
||||
A = ZeroMatrix(3, 3)
|
||||
assert MatrixPermute(A, p).doit() == A
|
||||
A = OneMatrix(3, 3)
|
||||
assert MatrixPermute(A, p).doit() == A
|
||||
|
||||
A = MatrixSymbol('A', 4, 4)
|
||||
p1 = Permutation(0, 1, 2, 3)
|
||||
p2 = Permutation(0, 2, 3, 1)
|
||||
expr = MatrixPermute(MatrixPermute(A, p1, 0), p2, 0)
|
||||
assert expr.as_explicit() == expr.doit().as_explicit()
|
||||
expr = MatrixPermute(MatrixPermute(A, p1, 1), p2, 1)
|
||||
assert expr.as_explicit() == expr.doit().as_explicit()
|
||||
@@ -0,0 +1,42 @@
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.sets import MatrixSet
|
||||
from sympy.matrices.expressions.special import ZeroMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.sets.sets import SetKind
|
||||
from sympy.matrices.kind import MatrixKind
|
||||
from sympy.core.kind import NumberKind
|
||||
|
||||
|
||||
def test_MatrixSet():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
M = MatrixSet(2, 2, set=S.Reals)
|
||||
assert M.shape == (2, 2)
|
||||
assert M.set == S.Reals
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert X in M
|
||||
X = ZeroMatrix(2, 2)
|
||||
assert X in M
|
||||
raises(TypeError, lambda: A in M)
|
||||
raises(TypeError, lambda: 1 in M)
|
||||
M = MatrixSet(n, m, set=S.Reals)
|
||||
assert A in M
|
||||
raises(TypeError, lambda: C in M)
|
||||
raises(TypeError, lambda: X in M)
|
||||
M = MatrixSet(2, 2, set={1, 2, 3})
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
Y = Matrix([[1, 2]])
|
||||
assert (X in M) == S.false
|
||||
assert (Y in M) == S.false
|
||||
raises(ValueError, lambda: MatrixSet(2, -2, S.Reals))
|
||||
raises(ValueError, lambda: MatrixSet(2.4, -1, S.Reals))
|
||||
raises(TypeError, lambda: MatrixSet(2, 2, (1, 2, 3)))
|
||||
|
||||
|
||||
def test_SetKind_MatrixSet():
|
||||
assert MatrixSet(2, 2, set=S.Reals).kind is SetKind(MatrixKind(NumberKind))
|
||||
@@ -0,0 +1,65 @@
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
from sympy.matrices.expressions import MatrixSymbol
|
||||
from sympy.abc import a, b, c, d, k, l, m, n
|
||||
from sympy.testing.pytest import raises, XFAIL
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.assumptions import assuming, Q
|
||||
|
||||
|
||||
X = MatrixSymbol('X', n, m)
|
||||
Y = MatrixSymbol('Y', m, k)
|
||||
|
||||
def test_shape():
|
||||
B = MatrixSlice(X, (a, b), (c, d))
|
||||
assert B.shape == (b - a, d - c)
|
||||
|
||||
def test_entry():
|
||||
B = MatrixSlice(X, (a, b), (c, d))
|
||||
assert B[0,0] == X[a, c]
|
||||
assert B[k,l] == X[a+k, c+l]
|
||||
raises(IndexError, lambda : MatrixSlice(X, 1, (2, 5))[1, 0])
|
||||
|
||||
assert X[1::2, :][1, 3] == X[1+2, 3]
|
||||
assert X[:, 1::2][3, 1] == X[3, 1+2]
|
||||
|
||||
def test_on_diag():
|
||||
assert not MatrixSlice(X, (a, b), (c, d)).on_diag
|
||||
assert MatrixSlice(X, (a, b), (a, b)).on_diag
|
||||
|
||||
def test_inputs():
|
||||
assert MatrixSlice(X, 1, (2, 5)) == MatrixSlice(X, (1, 2), (2, 5))
|
||||
assert MatrixSlice(X, 1, (2, 5)).shape == (1, 3)
|
||||
|
||||
def test_slicing():
|
||||
assert X[1:5, 2:4] == MatrixSlice(X, (1, 5), (2, 4))
|
||||
assert X[1, 2:4] == MatrixSlice(X, 1, (2, 4))
|
||||
assert X[1:5, :].shape == (4, X.shape[1])
|
||||
assert X[:, 1:5].shape == (X.shape[0], 4)
|
||||
|
||||
assert X[::2, ::2].shape == (floor(n/2), floor(m/2))
|
||||
assert X[2, :] == MatrixSlice(X, 2, (0, m))
|
||||
assert X[k, :] == MatrixSlice(X, k, (0, m))
|
||||
|
||||
def test_exceptions():
|
||||
X = MatrixSymbol('x', 10, 20)
|
||||
raises(IndexError, lambda: X[0:12, 2])
|
||||
raises(IndexError, lambda: X[0:9, 22])
|
||||
raises(IndexError, lambda: X[-1:5, 2])
|
||||
|
||||
@XFAIL
|
||||
def test_symmetry():
|
||||
X = MatrixSymbol('x', 10, 10)
|
||||
Y = X[:5, 5:]
|
||||
with assuming(Q.symmetric(X)):
|
||||
assert Y.T == X[5:, :5]
|
||||
|
||||
def test_slice_of_slice():
|
||||
X = MatrixSymbol('x', 10, 10)
|
||||
assert X[2, :][:, 3][0, 0] == X[2, 3]
|
||||
assert X[:5, :5][:4, :4] == X[:4, :4]
|
||||
assert X[1:5, 2:6][1:3, 2] == X[2:4, 4]
|
||||
assert X[1:9:2, 2:6][1:3, 2] == X[3:7:2, 4]
|
||||
|
||||
def test_negative_index():
|
||||
X = MatrixSymbol('x', 10, 10)
|
||||
assert X[-1, :] == X[9, :]
|
||||
@@ -0,0 +1,228 @@
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.functions.elementary.complexes import im, re
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.special import (
|
||||
ZeroMatrix, GenericZeroMatrix, Identity, GenericIdentity, OneMatrix)
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_zero_matrix_creation():
|
||||
assert unchanged(ZeroMatrix, 2, 2)
|
||||
assert unchanged(ZeroMatrix, 0, 0)
|
||||
raises(ValueError, lambda: ZeroMatrix(-1, 2))
|
||||
raises(ValueError, lambda: ZeroMatrix(2.0, 2))
|
||||
raises(ValueError, lambda: ZeroMatrix(2j, 2))
|
||||
raises(ValueError, lambda: ZeroMatrix(2, -1))
|
||||
raises(ValueError, lambda: ZeroMatrix(2, 2.0))
|
||||
raises(ValueError, lambda: ZeroMatrix(2, 2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert unchanged(ZeroMatrix, n, n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: ZeroMatrix(n, n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: ZeroMatrix(n, n))
|
||||
|
||||
|
||||
def test_generic_zero_matrix():
|
||||
z = GenericZeroMatrix()
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol("A", n, n)
|
||||
|
||||
assert z == z
|
||||
assert z != A
|
||||
assert A != z
|
||||
|
||||
assert z.is_ZeroMatrix
|
||||
|
||||
raises(TypeError, lambda: z.shape)
|
||||
raises(TypeError, lambda: z.rows)
|
||||
raises(TypeError, lambda: z.cols)
|
||||
|
||||
assert MatAdd() == z
|
||||
assert MatAdd(z, A) == MatAdd(A)
|
||||
# Make sure it is hashable
|
||||
hash(z)
|
||||
|
||||
|
||||
def test_identity_matrix_creation():
|
||||
assert Identity(2)
|
||||
assert Identity(0)
|
||||
raises(ValueError, lambda: Identity(-1))
|
||||
raises(ValueError, lambda: Identity(2.0))
|
||||
raises(ValueError, lambda: Identity(2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert Identity(n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: Identity(n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: Identity(n))
|
||||
|
||||
|
||||
def test_generic_identity():
|
||||
I = GenericIdentity()
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol("A", n, n)
|
||||
|
||||
assert I == I
|
||||
assert I != A
|
||||
assert A != I
|
||||
|
||||
assert I.is_Identity
|
||||
assert I**-1 == I
|
||||
|
||||
raises(TypeError, lambda: I.shape)
|
||||
raises(TypeError, lambda: I.rows)
|
||||
raises(TypeError, lambda: I.cols)
|
||||
|
||||
assert MatMul() == I
|
||||
assert MatMul(I, A) == MatMul(A)
|
||||
# Make sure it is hashable
|
||||
hash(I)
|
||||
|
||||
|
||||
def test_one_matrix_creation():
|
||||
assert OneMatrix(2, 2)
|
||||
assert OneMatrix(0, 0)
|
||||
assert Eq(OneMatrix(1, 1), Identity(1))
|
||||
raises(ValueError, lambda: OneMatrix(-1, 2))
|
||||
raises(ValueError, lambda: OneMatrix(2.0, 2))
|
||||
raises(ValueError, lambda: OneMatrix(2j, 2))
|
||||
raises(ValueError, lambda: OneMatrix(2, -1))
|
||||
raises(ValueError, lambda: OneMatrix(2, 2.0))
|
||||
raises(ValueError, lambda: OneMatrix(2, 2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert OneMatrix(n, n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: OneMatrix(n, n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: OneMatrix(n, n))
|
||||
|
||||
|
||||
def test_ZeroMatrix():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
Z = ZeroMatrix(n, m)
|
||||
|
||||
assert A + Z == A
|
||||
assert A*Z.T == ZeroMatrix(n, n)
|
||||
assert Z*A.T == ZeroMatrix(n, n)
|
||||
assert A - A == ZeroMatrix(*A.shape)
|
||||
|
||||
assert Z
|
||||
|
||||
assert Z.transpose() == ZeroMatrix(m, n)
|
||||
assert Z.conjugate() == Z
|
||||
assert Z.adjoint() == ZeroMatrix(m, n)
|
||||
assert re(Z) == Z
|
||||
assert im(Z) == Z
|
||||
|
||||
assert ZeroMatrix(n, n)**0 == Identity(n)
|
||||
assert ZeroMatrix(3, 3).as_explicit() == ImmutableDenseMatrix.zeros(3, 3)
|
||||
|
||||
|
||||
def test_ZeroMatrix_doit():
|
||||
n = symbols('n', integer=True)
|
||||
Znn = ZeroMatrix(Add(n, n, evaluate=False), n)
|
||||
assert isinstance(Znn.rows, Add)
|
||||
assert Znn.doit() == ZeroMatrix(2*n, n)
|
||||
assert isinstance(Znn.doit().rows, Mul)
|
||||
|
||||
|
||||
def test_OneMatrix():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
U = OneMatrix(n, m)
|
||||
|
||||
assert U.shape == (n, m)
|
||||
assert isinstance(A + U, Add)
|
||||
assert U.transpose() == OneMatrix(m, n)
|
||||
assert U.conjugate() == U
|
||||
assert U.adjoint() == OneMatrix(m, n)
|
||||
assert re(U) == U
|
||||
assert im(U) == ZeroMatrix(n, m)
|
||||
|
||||
assert OneMatrix(n, n) ** 0 == Identity(n)
|
||||
|
||||
U = OneMatrix(n, n)
|
||||
assert U[1, 2] == 1
|
||||
|
||||
U = OneMatrix(2, 3)
|
||||
assert U.as_explicit() == ImmutableDenseMatrix.ones(2, 3)
|
||||
|
||||
|
||||
def test_OneMatrix_doit():
|
||||
n = symbols('n', integer=True)
|
||||
Unn = OneMatrix(Add(n, n, evaluate=False), n)
|
||||
assert isinstance(Unn.rows, Add)
|
||||
assert Unn.doit() == OneMatrix(2 * n, n)
|
||||
assert isinstance(Unn.doit().rows, Mul)
|
||||
|
||||
|
||||
def test_OneMatrix_mul():
|
||||
n, m, k = symbols('n m k', integer=True)
|
||||
w = MatrixSymbol('w', n, 1)
|
||||
assert OneMatrix(n, m) * OneMatrix(m, k) == OneMatrix(n, k) * m
|
||||
assert w * OneMatrix(1, 1) == w
|
||||
assert OneMatrix(1, 1) * w.T == w.T
|
||||
|
||||
|
||||
def test_Identity():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
i, j = symbols('i j')
|
||||
|
||||
In = Identity(n)
|
||||
Im = Identity(m)
|
||||
|
||||
assert A*Im == A
|
||||
assert In*A == A
|
||||
|
||||
assert In.transpose() == In
|
||||
assert In.inverse() == In
|
||||
assert In.conjugate() == In
|
||||
assert In.adjoint() == In
|
||||
assert re(In) == In
|
||||
assert im(In) == ZeroMatrix(n, n)
|
||||
|
||||
assert In[i, j] != 0
|
||||
assert Sum(In[i, j], (i, 0, n-1), (j, 0, n-1)).subs(n,3).doit() == 3
|
||||
assert Sum(Sum(In[i, j], (i, 0, n-1)), (j, 0, n-1)).subs(n,3).doit() == 3
|
||||
|
||||
# If range exceeds the limit `(0, n-1)`, do not remove `Piecewise`:
|
||||
expr = Sum(In[i, j], (i, 0, n-1))
|
||||
assert expr.doit() == 1
|
||||
expr = Sum(In[i, j], (i, 0, n-2))
|
||||
assert expr.doit().dummy_eq(
|
||||
Piecewise(
|
||||
(1, (j >= 0) & (j <= n-2)),
|
||||
(0, True)
|
||||
)
|
||||
)
|
||||
expr = Sum(In[i, j], (i, 1, n-1))
|
||||
assert expr.doit().dummy_eq(
|
||||
Piecewise(
|
||||
(1, (j >= 1) & (j <= n-1)),
|
||||
(0, True)
|
||||
)
|
||||
)
|
||||
assert Identity(3).as_explicit() == ImmutableDenseMatrix.eye(3)
|
||||
|
||||
|
||||
def test_Identity_doit():
|
||||
n = symbols('n', integer=True)
|
||||
Inn = Identity(Add(n, n, evaluate=False))
|
||||
assert isinstance(Inn.rows, Add)
|
||||
assert Inn.doit() == Identity(2*n)
|
||||
assert isinstance(Inn.doit().rows, Mul)
|
||||
@@ -0,0 +1,116 @@
|
||||
from sympy.core import Lambda, S, symbols
|
||||
from sympy.concrete import Sum
|
||||
from sympy.functions import adjoint, conjugate, transpose
|
||||
from sympy.matrices import eye, Matrix, ShapeError, ImmutableMatrix
|
||||
from sympy.matrices.expressions import (
|
||||
Adjoint, Identity, FunctionMatrix, MatrixExpr, MatrixSymbol, Trace,
|
||||
ZeroMatrix, trace, MatPow, MatAdd, MatMul
|
||||
)
|
||||
from sympy.matrices.expressions.special import OneMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.abc import i
|
||||
|
||||
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = MatrixSymbol('C', 3, 4)
|
||||
|
||||
|
||||
def test_Trace():
|
||||
assert isinstance(Trace(A), Trace)
|
||||
assert not isinstance(Trace(A), MatrixExpr)
|
||||
raises(ShapeError, lambda: Trace(C))
|
||||
assert trace(eye(3)) == 3
|
||||
assert trace(Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])) == 15
|
||||
|
||||
assert adjoint(Trace(A)) == trace(Adjoint(A))
|
||||
assert conjugate(Trace(A)) == trace(Adjoint(A))
|
||||
assert transpose(Trace(A)) == Trace(A)
|
||||
|
||||
_ = A / Trace(A) # Make sure this is possible
|
||||
|
||||
# Some easy simplifications
|
||||
assert trace(Identity(5)) == 5
|
||||
assert trace(ZeroMatrix(5, 5)) == 0
|
||||
assert trace(OneMatrix(1, 1)) == 1
|
||||
assert trace(OneMatrix(2, 2)) == 2
|
||||
assert trace(OneMatrix(n, n)) == n
|
||||
assert trace(2*A*B) == 2*Trace(A*B)
|
||||
assert trace(A.T) == trace(A)
|
||||
|
||||
i, j = symbols('i j')
|
||||
F = FunctionMatrix(3, 3, Lambda((i, j), i + j))
|
||||
assert trace(F) == (0 + 0) + (1 + 1) + (2 + 2)
|
||||
|
||||
raises(TypeError, lambda: Trace(S.One))
|
||||
|
||||
assert Trace(A).arg is A
|
||||
|
||||
assert str(trace(A)) == str(Trace(A).doit())
|
||||
|
||||
assert Trace(A).is_commutative is True
|
||||
|
||||
def test_Trace_A_plus_B():
|
||||
assert trace(A + B) == Trace(A) + Trace(B)
|
||||
assert Trace(A + B).arg == MatAdd(A, B)
|
||||
assert Trace(A + B).doit() == Trace(A) + Trace(B)
|
||||
|
||||
|
||||
def test_Trace_MatAdd_doit():
|
||||
# See issue #9028
|
||||
X = ImmutableMatrix([[1, 2, 3]]*3)
|
||||
Y = MatrixSymbol('Y', 3, 3)
|
||||
q = MatAdd(X, 2*X, Y, -3*Y)
|
||||
assert Trace(q).arg == q
|
||||
assert Trace(q).doit() == 18 - 2*Trace(Y)
|
||||
|
||||
|
||||
def test_Trace_MatPow_doit():
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert Trace(X).doit() == 5
|
||||
q = MatPow(X, 2)
|
||||
assert Trace(q).arg == q
|
||||
assert Trace(q).doit() == 29
|
||||
|
||||
|
||||
def test_Trace_MutableMatrix_plus():
|
||||
# See issue #9043
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert Trace(X) + Trace(X) == 2*Trace(X)
|
||||
|
||||
|
||||
def test_Trace_doit_deep_False():
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
q = MatPow(X, 2)
|
||||
assert Trace(q).doit(deep=False).arg == q
|
||||
q = MatAdd(X, 2*X)
|
||||
assert Trace(q).doit(deep=False).arg == q
|
||||
q = MatMul(X, 2*X)
|
||||
assert Trace(q).doit(deep=False).arg == q
|
||||
|
||||
|
||||
def test_trace_constant_factor():
|
||||
# Issue 9052: gave 2*Trace(MatMul(A)) instead of 2*Trace(A)
|
||||
assert trace(2*A) == 2*Trace(A)
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert trace(MatMul(2, X)) == 10
|
||||
|
||||
|
||||
def test_trace_rewrite():
|
||||
assert trace(A).rewrite(Sum) == Sum(A[i, i], (i, 0, n - 1))
|
||||
assert trace(eye(3)).rewrite(Sum) == 3
|
||||
|
||||
|
||||
def test_trace_normalize():
|
||||
assert Trace(B*A) != Trace(A*B)
|
||||
assert Trace(B*A)._normalize() == Trace(A*B)
|
||||
assert Trace(B*A.T)._normalize() == Trace(A*B.T)
|
||||
|
||||
|
||||
def test_trace_as_explicit():
|
||||
raises(ValueError, lambda: Trace(A).as_explicit())
|
||||
|
||||
X = MatrixSymbol("X", 3, 3)
|
||||
assert Trace(X).as_explicit() == X[0, 0] + X[1, 1] + X[2, 2]
|
||||
assert Trace(eye(3)).as_explicit() == 3
|
||||
@@ -0,0 +1,69 @@
|
||||
from sympy.functions import adjoint, conjugate, transpose
|
||||
from sympy.matrices.expressions import MatrixSymbol, Adjoint, trace, Transpose
|
||||
from sympy.matrices import eye, Matrix
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
|
||||
n, m, l, k, p = symbols('n m l k p', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
|
||||
def test_transpose():
|
||||
Sq = MatrixSymbol('Sq', n, n)
|
||||
|
||||
assert transpose(A) == Transpose(A)
|
||||
assert Transpose(A).shape == (m, n)
|
||||
assert Transpose(A*B).shape == (l, n)
|
||||
assert transpose(Transpose(A)) == A
|
||||
assert isinstance(Transpose(Transpose(A)), Transpose)
|
||||
|
||||
assert adjoint(Transpose(A)) == Adjoint(Transpose(A))
|
||||
assert conjugate(Transpose(A)) == Adjoint(A)
|
||||
|
||||
assert Transpose(eye(3)).doit() == eye(3)
|
||||
|
||||
assert Transpose(S(5)).doit() == S(5)
|
||||
|
||||
assert Transpose(Matrix([[1, 2], [3, 4]])).doit() == Matrix([[1, 3], [2, 4]])
|
||||
|
||||
assert transpose(trace(Sq)) == trace(Sq)
|
||||
assert trace(Transpose(Sq)) == trace(Sq)
|
||||
|
||||
assert Transpose(Sq)[0, 1] == Sq[1, 0]
|
||||
|
||||
assert Transpose(A*B).doit() == Transpose(B) * Transpose(A)
|
||||
|
||||
|
||||
def test_transpose_MatAdd_MatMul():
|
||||
# Issue 16807
|
||||
from sympy.functions.elementary.trigonometric import cos
|
||||
|
||||
x = symbols('x')
|
||||
M = MatrixSymbol('M', 3, 3)
|
||||
N = MatrixSymbol('N', 3, 3)
|
||||
|
||||
assert (N + (cos(x) * M)).T == cos(x)*M.T + N.T
|
||||
|
||||
|
||||
def test_refine():
|
||||
assert refine(C.T, Q.symmetric(C)) == C
|
||||
|
||||
|
||||
def test_transpose1x1():
|
||||
m = MatrixSymbol('m', 1, 1)
|
||||
assert m == refine(m.T)
|
||||
assert m == refine(m.T.T)
|
||||
|
||||
def test_issue_9817():
|
||||
from sympy.matrices.expressions import Identity
|
||||
v = MatrixSymbol('v', 3, 1)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
x = Matrix([i + 1 for i in range(3)])
|
||||
X = Identity(3)
|
||||
quadratic = v.T * A * v
|
||||
subbed = quadratic.xreplace({v:x, A:X})
|
||||
assert subbed.as_explicit() == Matrix([[14]])
|
||||
@@ -0,0 +1,167 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.expr import Expr, ExprBuilder
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.core.symbol import uniquely_named_symbol
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
|
||||
|
||||
class Trace(Expr):
|
||||
"""Matrix Trace
|
||||
|
||||
Represents the trace of a matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Trace, eye
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> Trace(A)
|
||||
Trace(A)
|
||||
>>> Trace(eye(3))
|
||||
Trace(Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]]))
|
||||
>>> Trace(eye(3)).simplify()
|
||||
3
|
||||
"""
|
||||
is_Trace = True
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, mat):
|
||||
mat = sympify(mat)
|
||||
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("input to Trace, %s, is not a matrix" % str(mat))
|
||||
|
||||
if mat.is_square is False:
|
||||
raise NonSquareMatrixError("Trace of a non-square matrix")
|
||||
|
||||
return Basic.__new__(cls, mat)
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self
|
||||
|
||||
def _eval_derivative(self, v):
|
||||
from sympy.concrete.summations import Sum
|
||||
from .matexpr import MatrixElement
|
||||
if isinstance(v, MatrixElement):
|
||||
return self.rewrite(Sum).diff(v)
|
||||
expr = self.doit()
|
||||
if isinstance(expr, Trace):
|
||||
# Avoid looping infinitely:
|
||||
raise NotImplementedError
|
||||
return expr._eval_derivative(v)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayContraction
|
||||
r = self.args[0]._eval_derivative_matrix_lines(x)
|
||||
for lr in r:
|
||||
if lr.higher == 1:
|
||||
lr.higher = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
lr._lines[0],
|
||||
lr._lines[1],
|
||||
]
|
||||
),
|
||||
(1, 3),
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
else:
|
||||
# This is not a matrix line:
|
||||
lr.higher = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
lr._lines[0],
|
||||
lr._lines[1],
|
||||
lr.higher,
|
||||
]
|
||||
),
|
||||
(1, 3), (0, 2)
|
||||
]
|
||||
)
|
||||
lr._lines = [S.One, S.One]
|
||||
lr._first_pointer_parent = lr._lines
|
||||
lr._second_pointer_parent = lr._lines
|
||||
lr._first_pointer_index = 0
|
||||
lr._second_pointer_index = 1
|
||||
return r
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
def doit(self, **hints):
|
||||
if hints.get('deep', True):
|
||||
arg = self.arg.doit(**hints)
|
||||
result = arg._eval_trace()
|
||||
if result is not None:
|
||||
return result
|
||||
else:
|
||||
return Trace(arg)
|
||||
else:
|
||||
# _eval_trace would go too deep here
|
||||
if isinstance(self.arg, MatrixBase):
|
||||
return trace(self.arg)
|
||||
else:
|
||||
return Trace(self.arg)
|
||||
|
||||
def as_explicit(self):
|
||||
return Trace(self.arg.as_explicit()).doit()
|
||||
|
||||
def _normalize(self):
|
||||
# Normalization of trace of matrix products. Use transposition and
|
||||
# cyclic properties of traces to make sure the arguments of the matrix
|
||||
# product are sorted and the first argument is not a transposition.
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
trace_arg = self.arg
|
||||
if isinstance(trace_arg, MatMul):
|
||||
|
||||
def get_arg_key(x):
|
||||
a = trace_arg.args[x]
|
||||
if isinstance(a, Transpose):
|
||||
a = a.arg
|
||||
return default_sort_key(a)
|
||||
|
||||
indmin = min(range(len(trace_arg.args)), key=get_arg_key)
|
||||
if isinstance(trace_arg.args[indmin], Transpose):
|
||||
trace_arg = Transpose(trace_arg).doit()
|
||||
indmin = min(range(len(trace_arg.args)), key=lambda x: default_sort_key(trace_arg.args[x]))
|
||||
trace_arg = MatMul.fromiter(trace_arg.args[indmin:] + trace_arg.args[:indmin])
|
||||
return Trace(trace_arg)
|
||||
return self
|
||||
|
||||
def _eval_rewrite_as_Sum(self, expr, **kwargs):
|
||||
from sympy.concrete.summations import Sum
|
||||
i = uniquely_named_symbol('i', [expr])
|
||||
s = Sum(self.arg[i, i], (i, 0, self.arg.rows - 1))
|
||||
return s.doit()
|
||||
|
||||
|
||||
def trace(expr):
|
||||
"""Trace of a Matrix. Sum of the diagonal elements.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import trace, Symbol, MatrixSymbol, eye
|
||||
>>> n = Symbol('n')
|
||||
>>> X = MatrixSymbol('X', n, n) # A square matrix
|
||||
>>> trace(2*X)
|
||||
2*Trace(X)
|
||||
>>> trace(eye(3))
|
||||
3
|
||||
"""
|
||||
return Trace(expr).doit()
|
||||
@@ -0,0 +1,103 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
|
||||
|
||||
class Transpose(MatrixExpr):
|
||||
"""
|
||||
The transpose of a matrix expression.
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the transpose, use the ``transpose()``
|
||||
function, or the ``.T`` attribute of matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Transpose, transpose
|
||||
>>> A = MatrixSymbol('A', 3, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 3)
|
||||
>>> Transpose(A)
|
||||
A.T
|
||||
>>> A.T == transpose(A) == Transpose(A)
|
||||
True
|
||||
>>> Transpose(A*B)
|
||||
(A*B).T
|
||||
>>> transpose(A*B)
|
||||
B.T*A.T
|
||||
|
||||
"""
|
||||
is_Transpose = True
|
||||
|
||||
def doit(self, **hints):
|
||||
arg = self.arg
|
||||
if hints.get('deep', True) and isinstance(arg, Basic):
|
||||
arg = arg.doit(**hints)
|
||||
_eval_transpose = getattr(arg, '_eval_transpose', None)
|
||||
if _eval_transpose is not None:
|
||||
result = _eval_transpose()
|
||||
return result if result is not None else Transpose(arg)
|
||||
else:
|
||||
return Transpose(arg)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.arg.shape[::-1]
|
||||
|
||||
def _entry(self, i, j, expand=False, **kwargs):
|
||||
return self.arg._entry(j, i, expand=expand, **kwargs)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self.arg.conjugate()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self.arg.adjoint()
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self.arg
|
||||
|
||||
def _eval_trace(self):
|
||||
from .trace import Trace
|
||||
return Trace(self.arg) # Trace(X.T) => Trace(X)
|
||||
|
||||
def _eval_determinant(self):
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
return det(self.arg)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
# x is a scalar:
|
||||
return self.arg._eval_derivative(x)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
lines = self.args[0]._eval_derivative_matrix_lines(x)
|
||||
return [i.transpose() for i in lines]
|
||||
|
||||
|
||||
def transpose(expr):
|
||||
"""Matrix transpose"""
|
||||
return Transpose(expr).doit(deep=False)
|
||||
|
||||
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
|
||||
|
||||
def refine_Transpose(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> X.T
|
||||
X.T
|
||||
>>> with assuming(Q.symmetric(X)):
|
||||
... print(refine(X.T))
|
||||
X
|
||||
"""
|
||||
if ask(Q.symmetric(expr), assumptions):
|
||||
return expr.arg
|
||||
|
||||
return expr
|
||||
|
||||
handlers_dict['Transpose'] = refine_Transpose
|
||||
Reference in New Issue
Block a user