I am done

This commit is contained in:
2024-10-30 22:14:35 +01:00
parent 720dc28c09
commit 40e2a747cf
36901 changed files with 5011519 additions and 0 deletions

View File

@ -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'
]

View File

@ -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)

View File

@ -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))

View File

@ -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())

View File

@ -0,0 +1,980 @@
from sympy.assumptions.ask import (Q, ask)
from sympy.core import Basic, Add, Mul, S
from sympy.core.sympify import _sympify
from sympy.functions import adjoint
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):
# Adjoint all the individual matrices
matrices = [adjoint(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_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])

View File

@ -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])

View File

@ -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

View File

@ -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()

View File

@ -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]

View File

@ -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)

View File

@ -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)

View File

@ -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)))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)))

View File

@ -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

View File

@ -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 necesssary 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

View File

@ -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)

View File

@ -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))

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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]])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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]])

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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()

View File

@ -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)]

View File

@ -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))

View File

@ -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

View File

@ -0,0 +1,186 @@
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))

View File

@ -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))

View File

@ -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()

View File

@ -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))

View File

@ -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, :]

View File

@ -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)

View File

@ -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

View File

@ -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]])

Some files were not shown because too many files have changed in this diff Show More