chore: 添加虚拟环境到仓库
- 添加 backend_service/venv 虚拟环境 - 包含所有Python依赖包 - 注意:虚拟环境约393MB,包含12655个文件
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
"""A module that handles matrices.
|
||||
|
||||
Includes functions for fast creating matrices like zero, one/eye, random
|
||||
matrix, etc.
|
||||
"""
|
||||
from .exceptions import ShapeError, NonSquareMatrixError
|
||||
from .kind import MatrixKind
|
||||
from .dense import (
|
||||
GramSchmidt, casoratian, diag, eye, hessian, jordan_cell,
|
||||
list2numpy, matrix2numpy, matrix_multiply_elementwise, ones,
|
||||
randMatrix, rot_axis1, rot_axis2, rot_axis3, rot_ccw_axis1,
|
||||
rot_ccw_axis2, rot_ccw_axis3, rot_givens,
|
||||
symarray, wronskian, zeros)
|
||||
from .dense import MutableDenseMatrix
|
||||
from .matrixbase import DeferredVector, MatrixBase
|
||||
|
||||
MutableMatrix = MutableDenseMatrix
|
||||
Matrix = MutableMatrix
|
||||
|
||||
from .sparse import MutableSparseMatrix
|
||||
from .sparsetools import banded
|
||||
from .immutable import ImmutableDenseMatrix, ImmutableSparseMatrix
|
||||
|
||||
ImmutableMatrix = ImmutableDenseMatrix
|
||||
SparseMatrix = MutableSparseMatrix
|
||||
|
||||
from .expressions import (
|
||||
MatrixSlice, BlockDiagMatrix, BlockMatrix, FunctionMatrix, Identity,
|
||||
Inverse, MatAdd, MatMul, MatPow, MatrixExpr, MatrixSymbol, Trace,
|
||||
Transpose, ZeroMatrix, OneMatrix, blockcut, block_collapse, matrix_symbols, Adjoint,
|
||||
hadamard_product, HadamardProduct, HadamardPower, Determinant, det,
|
||||
diagonalize_vector, DiagMatrix, DiagonalMatrix, DiagonalOf, trace,
|
||||
DotProduct, kronecker_product, KroneckerProduct,
|
||||
PermutationMatrix, MatrixPermute, MatrixSet, Permanent, per)
|
||||
|
||||
from .utilities import dotprodsimp
|
||||
|
||||
__all__ = [
|
||||
'ShapeError', 'NonSquareMatrixError', 'MatrixKind',
|
||||
|
||||
'GramSchmidt', 'casoratian', 'diag', 'eye', 'hessian', 'jordan_cell',
|
||||
'list2numpy', 'matrix2numpy', 'matrix_multiply_elementwise', 'ones',
|
||||
'randMatrix', 'rot_axis1', 'rot_axis2', 'rot_axis3', 'symarray',
|
||||
'wronskian', 'zeros', 'rot_ccw_axis1', 'rot_ccw_axis2', 'rot_ccw_axis3',
|
||||
'rot_givens',
|
||||
|
||||
'MutableDenseMatrix',
|
||||
|
||||
'DeferredVector', 'MatrixBase',
|
||||
|
||||
'Matrix', 'MutableMatrix',
|
||||
|
||||
'MutableSparseMatrix',
|
||||
|
||||
'banded',
|
||||
|
||||
'ImmutableDenseMatrix', 'ImmutableSparseMatrix',
|
||||
|
||||
'ImmutableMatrix', 'SparseMatrix',
|
||||
|
||||
'MatrixSlice', 'BlockDiagMatrix', 'BlockMatrix', 'FunctionMatrix',
|
||||
'Identity', 'Inverse', 'MatAdd', 'MatMul', 'MatPow', 'MatrixExpr',
|
||||
'MatrixSymbol', 'Trace', 'Transpose', 'ZeroMatrix', 'OneMatrix',
|
||||
'blockcut', 'block_collapse', 'matrix_symbols', 'Adjoint',
|
||||
'hadamard_product', 'HadamardProduct', 'HadamardPower', 'Determinant',
|
||||
'det', 'diagonalize_vector', 'DiagMatrix', 'DiagonalMatrix',
|
||||
'DiagonalOf', 'trace', 'DotProduct', 'kronecker_product',
|
||||
'KroneckerProduct', 'PermutationMatrix', 'MatrixPermute', 'MatrixSet',
|
||||
'Permanent', 'per',
|
||||
|
||||
'dotprodsimp',
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.matrices.dense import (eye, zeros)
|
||||
|
||||
i3 = Integer(3)
|
||||
M = eye(100)
|
||||
|
||||
|
||||
def timeit_Matrix__getitem_ii():
|
||||
M[3, 3]
|
||||
|
||||
|
||||
def timeit_Matrix__getitem_II():
|
||||
M[i3, i3]
|
||||
|
||||
|
||||
def timeit_Matrix__getslice():
|
||||
M[:, :]
|
||||
|
||||
|
||||
def timeit_Matrix_zeronm():
|
||||
zeros(100, 100)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
"""
|
||||
Exceptions raised by the matrix module.
|
||||
"""
|
||||
|
||||
|
||||
class MatrixError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ShapeError(ValueError, MatrixError):
|
||||
"""Wrong matrix shape"""
|
||||
pass
|
||||
|
||||
|
||||
class NonSquareMatrixError(ShapeError):
|
||||
pass
|
||||
|
||||
|
||||
class NonInvertibleMatrixError(ValueError, MatrixError):
|
||||
"""The matrix in not invertible (division by multidimensional zero error)."""
|
||||
pass
|
||||
|
||||
|
||||
class NonPositiveDefiniteMatrixError(ValueError, MatrixError):
|
||||
"""The matrix is not a positive-definite matrix."""
|
||||
pass
|
||||
@@ -0,0 +1,62 @@
|
||||
""" A module which handles Matrix Expressions """
|
||||
|
||||
from .slice import MatrixSlice
|
||||
from .blockmatrix import BlockMatrix, BlockDiagMatrix, block_collapse, blockcut
|
||||
from .companion import CompanionMatrix
|
||||
from .funcmatrix import FunctionMatrix
|
||||
from .inverse import Inverse
|
||||
from .matadd import MatAdd
|
||||
from .matexpr import MatrixExpr, MatrixSymbol, matrix_symbols
|
||||
from .matmul import MatMul
|
||||
from .matpow import MatPow
|
||||
from .trace import Trace, trace
|
||||
from .determinant import Determinant, det, Permanent, per
|
||||
from .transpose import Transpose
|
||||
from .adjoint import Adjoint
|
||||
from .hadamard import hadamard_product, HadamardProduct, hadamard_power, HadamardPower
|
||||
from .diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector
|
||||
from .dotproduct import DotProduct
|
||||
from .kronecker import kronecker_product, KroneckerProduct, combine_kronecker
|
||||
from .permutation import PermutationMatrix, MatrixPermute
|
||||
from .sets import MatrixSet
|
||||
from .special import ZeroMatrix, Identity, OneMatrix
|
||||
|
||||
__all__ = [
|
||||
'MatrixSlice',
|
||||
|
||||
'BlockMatrix', 'BlockDiagMatrix', 'block_collapse', 'blockcut',
|
||||
'FunctionMatrix',
|
||||
|
||||
'CompanionMatrix',
|
||||
|
||||
'Inverse',
|
||||
|
||||
'MatAdd',
|
||||
|
||||
'Identity', 'MatrixExpr', 'MatrixSymbol', 'ZeroMatrix', 'OneMatrix',
|
||||
'matrix_symbols', 'MatrixSet',
|
||||
|
||||
'MatMul',
|
||||
|
||||
'MatPow',
|
||||
|
||||
'Trace', 'trace',
|
||||
|
||||
'Determinant', 'det',
|
||||
|
||||
'Transpose',
|
||||
|
||||
'Adjoint',
|
||||
|
||||
'hadamard_product', 'HadamardProduct', 'hadamard_power', 'HadamardPower',
|
||||
|
||||
'DiagonalMatrix', 'DiagonalOf', 'DiagMatrix', 'diagonalize_vector',
|
||||
|
||||
'DotProduct',
|
||||
|
||||
'kronecker_product', 'KroneckerProduct', 'combine_kronecker',
|
||||
|
||||
'PermutationMatrix', 'MatrixPermute',
|
||||
|
||||
'Permanent', 'per'
|
||||
]
|
||||
@@ -0,0 +1,102 @@
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.logic.boolalg import Boolean, And
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from typing import Union
|
||||
|
||||
|
||||
def is_matadd_valid(*args: MatrixExpr) -> Boolean:
|
||||
"""Return the symbolic condition how ``MatAdd``, ``HadamardProduct``
|
||||
makes sense.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args
|
||||
The list of arguments of matrices to be tested for.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> from sympy.matrices.expressions._shape import is_matadd_valid
|
||||
|
||||
>>> m, n, p, q = symbols('m n p q')
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', p, q)
|
||||
>>> is_matadd_valid(A, B)
|
||||
Eq(m, p) & Eq(n, q)
|
||||
"""
|
||||
rows, cols = zip(*(arg.shape for arg in args))
|
||||
return And(
|
||||
*(Eq(i, j) for i, j in zip(rows[:-1], rows[1:])),
|
||||
*(Eq(i, j) for i, j in zip(cols[:-1], cols[1:])),
|
||||
)
|
||||
|
||||
|
||||
def is_matmul_valid(*args: Union[MatrixExpr, Expr]) -> Boolean:
|
||||
"""Return the symbolic condition how ``MatMul`` makes sense
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
args
|
||||
The list of arguments of matrices and scalar expressions to be tested
|
||||
for.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> from sympy.matrices.expressions._shape import is_matmul_valid
|
||||
|
||||
>>> m, n, p, q = symbols('m n p q')
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', p, q)
|
||||
>>> is_matmul_valid(A, B)
|
||||
Eq(n, p)
|
||||
"""
|
||||
rows, cols = zip(*(arg.shape for arg in args if isinstance(arg, MatrixExpr)))
|
||||
return And(*(Eq(i, j) for i, j in zip(cols[:-1], rows[1:])))
|
||||
|
||||
|
||||
def is_square(arg: MatrixExpr, /) -> Boolean:
|
||||
"""Return the symbolic condition how the matrix is assumed to be square
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
arg
|
||||
The matrix to be tested for.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, symbols
|
||||
>>> from sympy.matrices.expressions._shape import is_square
|
||||
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> is_square(A)
|
||||
Eq(m, n)
|
||||
"""
|
||||
return Eq(arg.rows, arg.cols)
|
||||
|
||||
|
||||
def validate_matadd_integer(*args: MatrixExpr) -> None:
|
||||
"""Validate matrix shape for addition only for integer values"""
|
||||
rows, cols = zip(*(x.shape for x in args))
|
||||
if len(set(filter(lambda x: isinstance(x, (int, Integer)), rows))) > 1:
|
||||
raise ShapeError(f"Matrices have mismatching shape: {rows}")
|
||||
if len(set(filter(lambda x: isinstance(x, (int, Integer)), cols))) > 1:
|
||||
raise ShapeError(f"Matrices have mismatching shape: {cols}")
|
||||
|
||||
|
||||
def validate_matmul_integer(*args: MatrixExpr) -> None:
|
||||
"""Validate matrix shape for multiplication only for integer values"""
|
||||
for A, B in zip(args[:-1], args[1:]):
|
||||
i, j = A.cols, B.rows
|
||||
if isinstance(i, (int, Integer)) and isinstance(j, (int, Integer)) and i != j:
|
||||
raise ShapeError("Matrices are not aligned", i, j)
|
||||
@@ -0,0 +1,60 @@
|
||||
from sympy.core import Basic
|
||||
from sympy.functions import adjoint, conjugate
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
|
||||
|
||||
class Adjoint(MatrixExpr):
|
||||
"""
|
||||
The Hermitian adjoint of a matrix expression.
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the adjoint, use the ``adjoint()``
|
||||
function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Adjoint, adjoint
|
||||
>>> A = MatrixSymbol('A', 3, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 3)
|
||||
>>> Adjoint(A*B)
|
||||
Adjoint(A*B)
|
||||
>>> adjoint(A*B)
|
||||
Adjoint(B)*Adjoint(A)
|
||||
>>> adjoint(A*B) == Adjoint(A*B)
|
||||
False
|
||||
>>> adjoint(A*B) == Adjoint(A*B).doit()
|
||||
True
|
||||
"""
|
||||
is_Adjoint = True
|
||||
|
||||
def doit(self, **hints):
|
||||
arg = self.arg
|
||||
if hints.get('deep', True) and isinstance(arg, Basic):
|
||||
return adjoint(arg.doit(**hints))
|
||||
else:
|
||||
return adjoint(self.arg)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.arg.shape[::-1]
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return conjugate(self.arg._entry(j, i, **kwargs))
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self.arg
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self.arg.conjugate()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self.arg.transpose()
|
||||
|
||||
def _eval_trace(self):
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
return conjugate(Trace(self.arg))
|
||||
@@ -0,0 +1,204 @@
|
||||
from sympy.core.expr import ExprBuilder
|
||||
from sympy.core.function import (Function, FunctionClass, Lambda)
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.core.sympify import sympify, _sympify
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
|
||||
|
||||
class ElementwiseApplyFunction(MatrixExpr):
|
||||
r"""
|
||||
Apply function to a matrix elementwise without evaluating.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
It can be created by calling ``.applyfunc(<function>)`` on a matrix
|
||||
expression:
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
||||
>>> from sympy import exp
|
||||
>>> X = MatrixSymbol("X", 3, 3)
|
||||
>>> X.applyfunc(exp)
|
||||
Lambda(_d, exp(_d)).(X)
|
||||
|
||||
Otherwise using the class constructor:
|
||||
|
||||
>>> from sympy import eye
|
||||
>>> expr = ElementwiseApplyFunction(exp, eye(3))
|
||||
>>> expr
|
||||
Lambda(_d, exp(_d)).(Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]]))
|
||||
>>> expr.doit()
|
||||
Matrix([
|
||||
[E, 1, 1],
|
||||
[1, E, 1],
|
||||
[1, 1, E]])
|
||||
|
||||
Notice the difference with the real mathematical functions:
|
||||
|
||||
>>> exp(eye(3))
|
||||
Matrix([
|
||||
[E, 0, 0],
|
||||
[0, E, 0],
|
||||
[0, 0, E]])
|
||||
"""
|
||||
|
||||
def __new__(cls, function, expr):
|
||||
expr = _sympify(expr)
|
||||
if not expr.is_Matrix:
|
||||
raise ValueError("{} must be a matrix instance.".format(expr))
|
||||
|
||||
if expr.shape == (1, 1):
|
||||
# Check if the function returns a matrix, in that case, just apply
|
||||
# the function instead of creating an ElementwiseApplyFunc object:
|
||||
ret = function(expr)
|
||||
if isinstance(ret, MatrixExpr):
|
||||
return ret
|
||||
|
||||
if not isinstance(function, (FunctionClass, Lambda)):
|
||||
d = Dummy('d')
|
||||
function = Lambda(d, function(d))
|
||||
|
||||
function = sympify(function)
|
||||
if not isinstance(function, (FunctionClass, Lambda)):
|
||||
raise ValueError(
|
||||
"{} should be compatible with SymPy function classes."
|
||||
.format(function))
|
||||
|
||||
if 1 not in function.nargs:
|
||||
raise ValueError(
|
||||
'{} should be able to accept 1 arguments.'.format(function))
|
||||
|
||||
if not isinstance(function, Lambda):
|
||||
d = Dummy('d')
|
||||
function = Lambda(d, function(d))
|
||||
|
||||
obj = MatrixExpr.__new__(cls, function, expr)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def function(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def expr(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.expr.shape
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get("deep", True)
|
||||
expr = self.expr
|
||||
if deep:
|
||||
expr = expr.doit(**hints)
|
||||
function = self.function
|
||||
if isinstance(function, Lambda) and function.is_identity:
|
||||
# This is a Lambda containing the identity function.
|
||||
return expr
|
||||
if isinstance(expr, MatrixBase):
|
||||
return expr.applyfunc(self.function)
|
||||
elif isinstance(expr, ElementwiseApplyFunction):
|
||||
return ElementwiseApplyFunction(
|
||||
lambda x: self.function(expr.function(x)),
|
||||
expr.expr
|
||||
).doit(**hints)
|
||||
else:
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.function(self.expr._entry(i, j, **kwargs))
|
||||
|
||||
def _get_function_fdiff(self):
|
||||
d = Dummy("d")
|
||||
function = self.function(d)
|
||||
fdiff = function.diff(d)
|
||||
if isinstance(fdiff, Function):
|
||||
fdiff = type(fdiff)
|
||||
else:
|
||||
fdiff = Lambda(d, fdiff)
|
||||
return fdiff
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
from sympy.matrices.expressions.hadamard import hadamard_product
|
||||
dexpr = self.expr.diff(x)
|
||||
fdiff = self._get_function_fdiff()
|
||||
return hadamard_product(
|
||||
dexpr,
|
||||
ElementwiseApplyFunction(fdiff, self.expr)
|
||||
)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayContraction
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
|
||||
fdiff = self._get_function_fdiff()
|
||||
lr = self.expr._eval_derivative_matrix_lines(x)
|
||||
ewdiff = ElementwiseApplyFunction(fdiff, self.expr)
|
||||
if 1 in x.shape:
|
||||
# Vector:
|
||||
iscolumn = self.shape[1] == 1
|
||||
for i in lr:
|
||||
if iscolumn:
|
||||
ptr1 = i.first_pointer
|
||||
ptr2 = Identity(self.shape[1])
|
||||
else:
|
||||
ptr1 = Identity(self.shape[0])
|
||||
ptr2 = i.second_pointer
|
||||
|
||||
subexpr = ExprBuilder(
|
||||
ArrayDiagonal,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
ewdiff,
|
||||
ptr1,
|
||||
ptr2,
|
||||
]
|
||||
),
|
||||
(0, 2) if iscolumn else (1, 4)
|
||||
],
|
||||
validator=ArrayDiagonal._validate
|
||||
)
|
||||
i._lines = [subexpr]
|
||||
i._first_pointer_parent = subexpr.args[0].args
|
||||
i._first_pointer_index = 1
|
||||
i._second_pointer_parent = subexpr.args[0].args
|
||||
i._second_pointer_index = 2
|
||||
else:
|
||||
# Matrix case:
|
||||
for i in lr:
|
||||
ptr1 = i.first_pointer
|
||||
ptr2 = i.second_pointer
|
||||
newptr1 = Identity(ptr1.shape[1])
|
||||
newptr2 = Identity(ptr2.shape[1])
|
||||
subexpr = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[ptr1, newptr1, ewdiff, ptr2, newptr2]
|
||||
),
|
||||
(1, 2, 4),
|
||||
(5, 7, 8),
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args
|
||||
i._first_pointer_index = 1
|
||||
i._second_pointer_parent = subexpr.args[0].args
|
||||
i._second_pointer_index = 4
|
||||
i._lines = [subexpr]
|
||||
return lr
|
||||
|
||||
def _eval_transpose(self):
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
return self.func(self.function, Transpose(self.expr).doit())
|
||||
@@ -0,0 +1,975 @@
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core import Basic, Add, Mul, S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.functions.elementary.complexes import re, im
|
||||
from sympy.strategies import typed, exhaust, condition, do_one, unpack
|
||||
from sympy.strategies.traverse import bottom_up
|
||||
from sympy.utilities.iterables import is_sequence, sift
|
||||
from sympy.utilities.misc import filldedent
|
||||
|
||||
from sympy.matrices import Matrix, ShapeError
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.matrices.expressions.determinant import det, Determinant
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr, MatrixElement
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, Identity
|
||||
from sympy.matrices.expressions.trace import trace
|
||||
from sympy.matrices.expressions.transpose import Transpose, transpose
|
||||
|
||||
|
||||
class BlockMatrix(MatrixExpr):
|
||||
"""A BlockMatrix is a Matrix comprised of other matrices.
|
||||
|
||||
The submatrices are stored in a SymPy Matrix object but accessed as part of
|
||||
a Matrix Expression
|
||||
|
||||
>>> from sympy import (MatrixSymbol, BlockMatrix, symbols,
|
||||
... Identity, ZeroMatrix, block_collapse)
|
||||
>>> n,m,l = symbols('n m l')
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> Z = MatrixSymbol('Z', n, m)
|
||||
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]])
|
||||
>>> print(B)
|
||||
Matrix([
|
||||
[X, Z],
|
||||
[0, Y]])
|
||||
|
||||
>>> C = BlockMatrix([[Identity(n), Z]])
|
||||
>>> print(C)
|
||||
Matrix([[I, Z]])
|
||||
|
||||
>>> print(block_collapse(C*B))
|
||||
Matrix([[X, Z + Z*Y]])
|
||||
|
||||
Some matrices might be comprised of rows of blocks with
|
||||
the matrices in each row having the same height and the
|
||||
rows all having the same total number of columns but
|
||||
not having the same number of columns for each matrix
|
||||
in each row. In this case, the matrix is not a block
|
||||
matrix and should be instantiated by Matrix.
|
||||
|
||||
>>> from sympy import ones, Matrix
|
||||
>>> dat = [
|
||||
... [ones(3,2), ones(3,3)*2],
|
||||
... [ones(2,3)*3, ones(2,2)*4]]
|
||||
...
|
||||
>>> BlockMatrix(dat)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError:
|
||||
Although this matrix is comprised of blocks, the blocks do not fill
|
||||
the matrix in a size-symmetric fashion. To create a full matrix from
|
||||
these arguments, pass them directly to Matrix.
|
||||
>>> Matrix(dat)
|
||||
Matrix([
|
||||
[1, 1, 2, 2, 2],
|
||||
[1, 1, 2, 2, 2],
|
||||
[1, 1, 2, 2, 2],
|
||||
[3, 3, 3, 4, 4],
|
||||
[3, 3, 3, 4, 4]])
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.matrixbase.MatrixBase.irregular
|
||||
"""
|
||||
def __new__(cls, *args, **kwargs):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
isMat = lambda i: getattr(i, 'is_Matrix', False)
|
||||
if len(args) != 1 or \
|
||||
not is_sequence(args[0]) or \
|
||||
len({isMat(r) for r in args[0]}) != 1:
|
||||
raise ValueError(filldedent('''
|
||||
expecting a sequence of 1 or more rows
|
||||
containing Matrices.'''))
|
||||
rows = args[0] if args else []
|
||||
if not isMat(rows):
|
||||
if rows and isMat(rows[0]):
|
||||
rows = [rows] # rows is not list of lists or []
|
||||
# regularity check
|
||||
# same number of matrices in each row
|
||||
blocky = ok = len({len(r) for r in rows}) == 1
|
||||
if ok:
|
||||
# same number of rows for each matrix in a row
|
||||
for r in rows:
|
||||
ok = len({i.rows for i in r}) == 1
|
||||
if not ok:
|
||||
break
|
||||
blocky = ok
|
||||
if ok:
|
||||
# same number of cols for each matrix in each col
|
||||
for c in range(len(rows[0])):
|
||||
ok = len({rows[i][c].cols
|
||||
for i in range(len(rows))}) == 1
|
||||
if not ok:
|
||||
break
|
||||
if not ok:
|
||||
# same total cols in each row
|
||||
ok = len({
|
||||
sum(i.cols for i in r) for r in rows}) == 1
|
||||
if blocky and ok:
|
||||
raise ValueError(filldedent('''
|
||||
Although this matrix is comprised of blocks,
|
||||
the blocks do not fill the matrix in a
|
||||
size-symmetric fashion. To create a full matrix
|
||||
from these arguments, pass them directly to
|
||||
Matrix.'''))
|
||||
raise ValueError(filldedent('''
|
||||
When there are not the same number of rows in each
|
||||
row's matrices or there are not the same number of
|
||||
total columns in each row, the matrix is not a
|
||||
block matrix. If this matrix is known to consist of
|
||||
blocks fully filling a 2-D space then see
|
||||
Matrix.irregular.'''))
|
||||
mat = ImmutableDenseMatrix(rows, evaluate=False)
|
||||
obj = Basic.__new__(cls, mat)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
numrows = numcols = 0
|
||||
M = self.blocks
|
||||
for i in range(M.shape[0]):
|
||||
numrows += M[i, 0].shape[0]
|
||||
for i in range(M.shape[1]):
|
||||
numcols += M[0, i].shape[1]
|
||||
return (numrows, numcols)
|
||||
|
||||
@property
|
||||
def blockshape(self):
|
||||
return self.blocks.shape
|
||||
|
||||
@property
|
||||
def blocks(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def rowblocksizes(self):
|
||||
return [self.blocks[i, 0].rows for i in range(self.blockshape[0])]
|
||||
|
||||
@property
|
||||
def colblocksizes(self):
|
||||
return [self.blocks[0, i].cols for i in range(self.blockshape[1])]
|
||||
|
||||
def structurally_equal(self, other):
|
||||
return (isinstance(other, BlockMatrix)
|
||||
and self.shape == other.shape
|
||||
and self.blockshape == other.blockshape
|
||||
and self.rowblocksizes == other.rowblocksizes
|
||||
and self.colblocksizes == other.colblocksizes)
|
||||
|
||||
def _blockmul(self, other):
|
||||
if (isinstance(other, BlockMatrix) and
|
||||
self.colblocksizes == other.rowblocksizes):
|
||||
return BlockMatrix(self.blocks*other.blocks)
|
||||
|
||||
return self * other
|
||||
|
||||
def _blockadd(self, other):
|
||||
if (isinstance(other, BlockMatrix)
|
||||
and self.structurally_equal(other)):
|
||||
return BlockMatrix(self.blocks + other.blocks)
|
||||
|
||||
return self + other
|
||||
|
||||
def _eval_transpose(self):
|
||||
# Flip all the individual matrices
|
||||
matrices = [transpose(matrix) for matrix in self.blocks]
|
||||
# Make a copy
|
||||
M = Matrix(self.blockshape[0], self.blockshape[1], matrices)
|
||||
# Transpose the block structure
|
||||
M = M.transpose()
|
||||
return BlockMatrix(M)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return BlockMatrix(
|
||||
Matrix(self.blockshape[0], self.blockshape[1], self.blocks).adjoint()
|
||||
)
|
||||
|
||||
def _eval_trace(self):
|
||||
if self.rowblocksizes == self.colblocksizes:
|
||||
blocks = [self.blocks[i, i] for i in range(self.blockshape[0])]
|
||||
return Add(*[trace(block) for block in blocks])
|
||||
|
||||
def _eval_determinant(self):
|
||||
if self.blockshape == (1, 1):
|
||||
return det(self.blocks[0, 0])
|
||||
if self.blockshape == (2, 2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
if ask(Q.invertible(A)):
|
||||
return det(A)*det(D - C*A.I*B)
|
||||
elif ask(Q.invertible(D)):
|
||||
return det(D)*det(A - B*D.I*C)
|
||||
return Determinant(self)
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
real_matrices = [re(matrix) for matrix in self.blocks]
|
||||
real_matrices = Matrix(self.blockshape[0], self.blockshape[1], real_matrices)
|
||||
|
||||
im_matrices = [im(matrix) for matrix in self.blocks]
|
||||
im_matrices = Matrix(self.blockshape[0], self.blockshape[1], im_matrices)
|
||||
|
||||
return (BlockMatrix(real_matrices), BlockMatrix(im_matrices))
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
return BlockMatrix(self.blocks.diff(x))
|
||||
|
||||
def transpose(self):
|
||||
"""Return transpose of matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, BlockMatrix, ZeroMatrix
|
||||
>>> from sympy.abc import m, n
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> Z = MatrixSymbol('Z', n, m)
|
||||
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m,n), Y]])
|
||||
>>> B.transpose()
|
||||
Matrix([
|
||||
[X.T, 0],
|
||||
[Z.T, Y.T]])
|
||||
>>> _.transpose()
|
||||
Matrix([
|
||||
[X, Z],
|
||||
[0, Y]])
|
||||
"""
|
||||
return self._eval_transpose()
|
||||
|
||||
def schur(self, mat = 'A', generalized = False):
|
||||
"""Return the Schur Complement of the 2x2 BlockMatrix
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mat : String, optional
|
||||
The matrix with respect to which the
|
||||
Schur Complement is calculated. 'A' is
|
||||
used by default
|
||||
|
||||
generalized : bool, optional
|
||||
If True, returns the generalized Schur
|
||||
Component which uses Moore-Penrose Inverse
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
|
||||
The default Schur Complement is evaluated with "A"
|
||||
|
||||
>>> X.schur()
|
||||
-C*A**(-1)*B + D
|
||||
>>> X.schur('D')
|
||||
A - B*D**(-1)*C
|
||||
|
||||
Schur complement with non-invertible matrices is not
|
||||
defined. Instead, the generalized Schur complement can
|
||||
be calculated which uses the Moore-Penrose Inverse. To
|
||||
achieve this, `generalized` must be set to `True`
|
||||
|
||||
>>> X.schur('B', generalized=True)
|
||||
C - D*(B.T*B)**(-1)*B.T*A
|
||||
>>> X.schur('C', generalized=True)
|
||||
-A*(C.T*C)**(-1)*C.T*D + B
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
M : Matrix
|
||||
The Schur Complement Matrix
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If given matrix is non-invertible
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Wikipedia Article on Schur Component : https://en.wikipedia.org/wiki/Schur_complement
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.matrixbase.MatrixBase.pinv
|
||||
"""
|
||||
|
||||
if self.blockshape == (2, 2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
d={'A' : A, 'B' : B, 'C' : C, 'D' : D}
|
||||
try:
|
||||
inv = (d[mat].T*d[mat]).inv()*d[mat].T if generalized else d[mat].inv()
|
||||
if mat == 'A':
|
||||
return D - C * inv * B
|
||||
elif mat == 'B':
|
||||
return C - D * inv * A
|
||||
elif mat == 'C':
|
||||
return B - A * inv * D
|
||||
elif mat == 'D':
|
||||
return A - B * inv * C
|
||||
#For matrices where no sub-matrix is square
|
||||
return self
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('The given matrix is not invertible. Please set generalized=True \
|
||||
to compute the generalized Schur Complement which uses Moore-Penrose Inverse')
|
||||
else:
|
||||
raise ShapeError('Schur Complement can only be calculated for 2x2 block matrices')
|
||||
|
||||
def LDUdecomposition(self):
|
||||
"""Returns the Block LDU decomposition of
|
||||
a 2x2 Block Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(L, D, U) : Matrices
|
||||
L : Lower Diagonal Matrix
|
||||
D : Diagonal Matrix
|
||||
U : Upper Diagonal Matrix
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
>>> L, D, U = X.LDUdecomposition()
|
||||
>>> block_collapse(L*D*U)
|
||||
Matrix([
|
||||
[A, B],
|
||||
[C, D]])
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If the matrix "A" is non-invertible
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition
|
||||
"""
|
||||
if self.blockshape == (2,2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
try:
|
||||
AI = A.I
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('Block LDU decomposition cannot be calculated when\
|
||||
"A" is singular')
|
||||
Ip = Identity(B.shape[0])
|
||||
Iq = Identity(B.shape[1])
|
||||
Z = ZeroMatrix(*B.shape)
|
||||
L = BlockMatrix([[Ip, Z], [C*AI, Iq]])
|
||||
D = BlockDiagMatrix(A, self.schur())
|
||||
U = BlockMatrix([[Ip, AI*B],[Z.T, Iq]])
|
||||
return L, D, U
|
||||
else:
|
||||
raise ShapeError("Block LDU decomposition is supported only for 2x2 block matrices")
|
||||
|
||||
def UDLdecomposition(self):
|
||||
"""Returns the Block UDL decomposition of
|
||||
a 2x2 Block Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(U, D, L) : Matrices
|
||||
U : Upper Diagonal Matrix
|
||||
D : Diagonal Matrix
|
||||
L : Lower Diagonal Matrix
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
>>> U, D, L = X.UDLdecomposition()
|
||||
>>> block_collapse(U*D*L)
|
||||
Matrix([
|
||||
[A, B],
|
||||
[C, D]])
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If the matrix "D" is non-invertible
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LUdecomposition
|
||||
"""
|
||||
if self.blockshape == (2,2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
try:
|
||||
DI = D.I
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('Block UDL decomposition cannot be calculated when\
|
||||
"D" is singular')
|
||||
Ip = Identity(A.shape[0])
|
||||
Iq = Identity(B.shape[1])
|
||||
Z = ZeroMatrix(*B.shape)
|
||||
U = BlockMatrix([[Ip, B*DI], [Z.T, Iq]])
|
||||
D = BlockDiagMatrix(self.schur('D'), D)
|
||||
L = BlockMatrix([[Ip, Z],[DI*C, Iq]])
|
||||
return U, D, L
|
||||
else:
|
||||
raise ShapeError("Block UDL decomposition is supported only for 2x2 block matrices")
|
||||
|
||||
def LUdecomposition(self):
|
||||
"""Returns the Block LU decomposition of
|
||||
a 2x2 Block Matrix
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
(L, U) : Matrices
|
||||
L : Lower Diagonal Matrix
|
||||
U : Upper Diagonal Matrix
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import symbols, MatrixSymbol, BlockMatrix, block_collapse
|
||||
>>> m, n = symbols('m n')
|
||||
>>> A = MatrixSymbol('A', n, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> C = MatrixSymbol('C', m, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> X = BlockMatrix([[A, B], [C, D]])
|
||||
>>> L, U = X.LUdecomposition()
|
||||
>>> block_collapse(L*U)
|
||||
Matrix([
|
||||
[A, B],
|
||||
[C, D]])
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ShapeError
|
||||
If the block matrix is not a 2x2 matrix
|
||||
|
||||
NonInvertibleMatrixError
|
||||
If the matrix "A" is non-invertible
|
||||
|
||||
See Also
|
||||
========
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.UDLdecomposition
|
||||
sympy.matrices.expressions.blockmatrix.BlockMatrix.LDUdecomposition
|
||||
"""
|
||||
if self.blockshape == (2,2):
|
||||
[[A, B],
|
||||
[C, D]] = self.blocks.tolist()
|
||||
try:
|
||||
A = A**S.Half
|
||||
AI = A.I
|
||||
except NonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError('Block LU decomposition cannot be calculated when\
|
||||
"A" is singular')
|
||||
Z = ZeroMatrix(*B.shape)
|
||||
Q = self.schur()**S.Half
|
||||
L = BlockMatrix([[A, Z], [C*AI, Q]])
|
||||
U = BlockMatrix([[A, AI*B],[Z.T, Q]])
|
||||
return L, U
|
||||
else:
|
||||
raise ShapeError("Block LU decomposition is supported only for 2x2 block matrices")
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
# Find row entry
|
||||
orig_i, orig_j = i, j
|
||||
for row_block, numrows in enumerate(self.rowblocksizes):
|
||||
cmp = i < numrows
|
||||
if cmp == True:
|
||||
break
|
||||
elif cmp == False:
|
||||
i -= numrows
|
||||
elif row_block < self.blockshape[0] - 1:
|
||||
# Can't tell which block and it's not the last one, return unevaluated
|
||||
return MatrixElement(self, orig_i, orig_j)
|
||||
for col_block, numcols in enumerate(self.colblocksizes):
|
||||
cmp = j < numcols
|
||||
if cmp == True:
|
||||
break
|
||||
elif cmp == False:
|
||||
j -= numcols
|
||||
elif col_block < self.blockshape[1] - 1:
|
||||
return MatrixElement(self, orig_i, orig_j)
|
||||
return self.blocks[row_block, col_block][i, j]
|
||||
|
||||
@property
|
||||
def is_Identity(self):
|
||||
if self.blockshape[0] != self.blockshape[1]:
|
||||
return False
|
||||
for i in range(self.blockshape[0]):
|
||||
for j in range(self.blockshape[1]):
|
||||
if i==j and not self.blocks[i, j].is_Identity:
|
||||
return False
|
||||
if i!=j and not self.blocks[i, j].is_ZeroMatrix:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def is_structurally_symmetric(self):
|
||||
return self.rowblocksizes == self.colblocksizes
|
||||
|
||||
def equals(self, other):
|
||||
if self == other:
|
||||
return True
|
||||
if (isinstance(other, BlockMatrix) and self.blocks == other.blocks):
|
||||
return True
|
||||
return super().equals(other)
|
||||
|
||||
|
||||
class BlockDiagMatrix(BlockMatrix):
|
||||
"""A sparse matrix with block matrices along its diagonals
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, BlockDiagMatrix, symbols
|
||||
>>> n, m, l = symbols('n m l')
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> BlockDiagMatrix(X, Y)
|
||||
Matrix([
|
||||
[X, 0],
|
||||
[0, Y]])
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
If you want to get the individual diagonal blocks, use
|
||||
:meth:`get_diag_blocks`.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.diag
|
||||
"""
|
||||
def __new__(cls, *mats):
|
||||
return Basic.__new__(BlockDiagMatrix, *[_sympify(m) for m in mats])
|
||||
|
||||
@property
|
||||
def diag(self):
|
||||
return self.args
|
||||
|
||||
@property
|
||||
def blocks(self):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
mats = self.args
|
||||
data = [[mats[i] if i == j else ZeroMatrix(mats[i].rows, mats[j].cols)
|
||||
for j in range(len(mats))]
|
||||
for i in range(len(mats))]
|
||||
return ImmutableDenseMatrix(data, evaluate=False)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (sum(block.rows for block in self.args),
|
||||
sum(block.cols for block in self.args))
|
||||
|
||||
@property
|
||||
def blockshape(self):
|
||||
n = len(self.args)
|
||||
return (n, n)
|
||||
|
||||
@property
|
||||
def rowblocksizes(self):
|
||||
return [block.rows for block in self.args]
|
||||
|
||||
@property
|
||||
def colblocksizes(self):
|
||||
return [block.cols for block in self.args]
|
||||
|
||||
def _all_square_blocks(self):
|
||||
"""Returns true if all blocks are square"""
|
||||
return all(mat.is_square for mat in self.args)
|
||||
|
||||
def _eval_determinant(self):
|
||||
if self._all_square_blocks():
|
||||
return Mul(*[det(mat) for mat in self.args])
|
||||
# At least one block is non-square. Since the entire matrix must be square we know there must
|
||||
# be at least two blocks in this matrix, in which case the entire matrix is necessarily rank-deficient
|
||||
return S.Zero
|
||||
|
||||
def _eval_inverse(self, expand='ignored'):
|
||||
if self._all_square_blocks():
|
||||
return BlockDiagMatrix(*[mat.inverse() for mat in self.args])
|
||||
# See comment in _eval_determinant()
|
||||
raise NonInvertibleMatrixError('Matrix det == 0; not invertible.')
|
||||
|
||||
def _eval_transpose(self):
|
||||
return BlockDiagMatrix(*[mat.transpose() for mat in self.args])
|
||||
|
||||
def _blockmul(self, other):
|
||||
if (isinstance(other, BlockDiagMatrix) and
|
||||
self.colblocksizes == other.rowblocksizes):
|
||||
return BlockDiagMatrix(*[a*b for a, b in zip(self.args, other.args)])
|
||||
else:
|
||||
return BlockMatrix._blockmul(self, other)
|
||||
|
||||
def _blockadd(self, other):
|
||||
if (isinstance(other, BlockDiagMatrix) and
|
||||
self.blockshape == other.blockshape and
|
||||
self.rowblocksizes == other.rowblocksizes and
|
||||
self.colblocksizes == other.colblocksizes):
|
||||
return BlockDiagMatrix(*[a + b for a, b in zip(self.args, other.args)])
|
||||
else:
|
||||
return BlockMatrix._blockadd(self, other)
|
||||
|
||||
def get_diag_blocks(self):
|
||||
"""Return the list of diagonal blocks of the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import BlockDiagMatrix, Matrix
|
||||
|
||||
>>> A = Matrix([[1, 2], [3, 4]])
|
||||
>>> B = Matrix([[5, 6], [7, 8]])
|
||||
>>> M = BlockDiagMatrix(A, B)
|
||||
|
||||
How to get diagonal blocks from the block diagonal matrix:
|
||||
|
||||
>>> diag_blocks = M.get_diag_blocks()
|
||||
>>> diag_blocks[0]
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[3, 4]])
|
||||
>>> diag_blocks[1]
|
||||
Matrix([
|
||||
[5, 6],
|
||||
[7, 8]])
|
||||
"""
|
||||
return self.args
|
||||
|
||||
|
||||
def block_collapse(expr):
|
||||
"""Evaluates a block matrix expression
|
||||
|
||||
>>> from sympy import MatrixSymbol, BlockMatrix, symbols, Identity, ZeroMatrix, block_collapse
|
||||
>>> n,m,l = symbols('n m l')
|
||||
>>> X = MatrixSymbol('X', n, n)
|
||||
>>> Y = MatrixSymbol('Y', m, m)
|
||||
>>> Z = MatrixSymbol('Z', n, m)
|
||||
>>> B = BlockMatrix([[X, Z], [ZeroMatrix(m, n), Y]])
|
||||
>>> print(B)
|
||||
Matrix([
|
||||
[X, Z],
|
||||
[0, Y]])
|
||||
|
||||
>>> C = BlockMatrix([[Identity(n), Z]])
|
||||
>>> print(C)
|
||||
Matrix([[I, Z]])
|
||||
|
||||
>>> print(block_collapse(C*B))
|
||||
Matrix([[X, Z + Z*Y]])
|
||||
"""
|
||||
from sympy.strategies.util import expr_fns
|
||||
|
||||
hasbm = lambda expr: isinstance(expr, MatrixExpr) and expr.has(BlockMatrix)
|
||||
|
||||
conditioned_rl = condition(
|
||||
hasbm,
|
||||
typed(
|
||||
{MatAdd: do_one(bc_matadd, bc_block_plus_ident),
|
||||
MatMul: do_one(bc_matmul, bc_dist),
|
||||
MatPow: bc_matmul,
|
||||
Transpose: bc_transpose,
|
||||
Inverse: bc_inverse,
|
||||
BlockMatrix: do_one(bc_unpack, deblock)}
|
||||
)
|
||||
)
|
||||
|
||||
rule = exhaust(
|
||||
bottom_up(
|
||||
exhaust(conditioned_rl),
|
||||
fns=expr_fns
|
||||
)
|
||||
)
|
||||
|
||||
result = rule(expr)
|
||||
doit = getattr(result, 'doit', None)
|
||||
if doit is not None:
|
||||
return doit()
|
||||
else:
|
||||
return result
|
||||
|
||||
def bc_unpack(expr):
|
||||
if expr.blockshape == (1, 1):
|
||||
return expr.blocks[0, 0]
|
||||
return expr
|
||||
|
||||
def bc_matadd(expr):
|
||||
args = sift(expr.args, lambda M: isinstance(M, BlockMatrix))
|
||||
blocks = args[True]
|
||||
if not blocks:
|
||||
return expr
|
||||
|
||||
nonblocks = args[False]
|
||||
block = blocks[0]
|
||||
for b in blocks[1:]:
|
||||
block = block._blockadd(b)
|
||||
if nonblocks:
|
||||
return MatAdd(*nonblocks) + block
|
||||
else:
|
||||
return block
|
||||
|
||||
def bc_block_plus_ident(expr):
|
||||
idents = [arg for arg in expr.args if arg.is_Identity]
|
||||
if not idents:
|
||||
return expr
|
||||
|
||||
blocks = [arg for arg in expr.args if isinstance(arg, BlockMatrix)]
|
||||
if (blocks and all(b.structurally_equal(blocks[0]) for b in blocks)
|
||||
and blocks[0].is_structurally_symmetric):
|
||||
block_id = BlockDiagMatrix(*[Identity(k)
|
||||
for k in blocks[0].rowblocksizes])
|
||||
rest = [arg for arg in expr.args if not arg.is_Identity and not isinstance(arg, BlockMatrix)]
|
||||
return MatAdd(block_id * len(idents), *blocks, *rest).doit()
|
||||
|
||||
return expr
|
||||
|
||||
def bc_dist(expr):
|
||||
""" Turn a*[X, Y] into [a*X, a*Y] """
|
||||
factor, mat = expr.as_coeff_mmul()
|
||||
if factor == 1:
|
||||
return expr
|
||||
|
||||
unpacked = unpack(mat)
|
||||
|
||||
if isinstance(unpacked, BlockDiagMatrix):
|
||||
B = unpacked.diag
|
||||
new_B = [factor * mat for mat in B]
|
||||
return BlockDiagMatrix(*new_B)
|
||||
elif isinstance(unpacked, BlockMatrix):
|
||||
B = unpacked.blocks
|
||||
new_B = [
|
||||
[factor * B[i, j] for j in range(B.cols)] for i in range(B.rows)]
|
||||
return BlockMatrix(new_B)
|
||||
return expr
|
||||
|
||||
|
||||
def bc_matmul(expr):
|
||||
if isinstance(expr, MatPow):
|
||||
if expr.args[1].is_Integer and expr.args[1] > 0:
|
||||
factor, matrices = 1, [expr.args[0]]*expr.args[1]
|
||||
else:
|
||||
return expr
|
||||
else:
|
||||
factor, matrices = expr.as_coeff_matrices()
|
||||
|
||||
i = 0
|
||||
while (i+1 < len(matrices)):
|
||||
A, B = matrices[i:i+2]
|
||||
if isinstance(A, BlockMatrix) and isinstance(B, BlockMatrix):
|
||||
matrices[i] = A._blockmul(B)
|
||||
matrices.pop(i+1)
|
||||
elif isinstance(A, BlockMatrix):
|
||||
matrices[i] = A._blockmul(BlockMatrix([[B]]))
|
||||
matrices.pop(i+1)
|
||||
elif isinstance(B, BlockMatrix):
|
||||
matrices[i] = BlockMatrix([[A]])._blockmul(B)
|
||||
matrices.pop(i+1)
|
||||
else:
|
||||
i+=1
|
||||
return MatMul(factor, *matrices).doit()
|
||||
|
||||
def bc_transpose(expr):
|
||||
collapse = block_collapse(expr.arg)
|
||||
return collapse._eval_transpose()
|
||||
|
||||
|
||||
def bc_inverse(expr):
|
||||
if isinstance(expr.arg, BlockDiagMatrix):
|
||||
return expr.inverse()
|
||||
|
||||
expr2 = blockinverse_1x1(expr)
|
||||
if expr != expr2:
|
||||
return expr2
|
||||
return blockinverse_2x2(Inverse(reblock_2x2(expr.arg)))
|
||||
|
||||
def blockinverse_1x1(expr):
|
||||
if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (1, 1):
|
||||
mat = Matrix([[expr.arg.blocks[0].inverse()]])
|
||||
return BlockMatrix(mat)
|
||||
return expr
|
||||
|
||||
|
||||
def blockinverse_2x2(expr):
|
||||
if isinstance(expr.arg, BlockMatrix) and expr.arg.blockshape == (2, 2):
|
||||
# See: Inverses of 2x2 Block Matrices, Tzon-Tzer Lu and Sheng-Hua Shiou
|
||||
[[A, B],
|
||||
[C, D]] = expr.arg.blocks.tolist()
|
||||
|
||||
formula = _choose_2x2_inversion_formula(A, B, C, D)
|
||||
if formula != None:
|
||||
MI = expr.arg.schur(formula).I
|
||||
if formula == 'A':
|
||||
AI = A.I
|
||||
return BlockMatrix([[AI + AI * B * MI * C * AI, -AI * B * MI], [-MI * C * AI, MI]])
|
||||
if formula == 'B':
|
||||
BI = B.I
|
||||
return BlockMatrix([[-MI * D * BI, MI], [BI + BI * A * MI * D * BI, -BI * A * MI]])
|
||||
if formula == 'C':
|
||||
CI = C.I
|
||||
return BlockMatrix([[-CI * D * MI, CI + CI * D * MI * A * CI], [MI, -MI * A * CI]])
|
||||
if formula == 'D':
|
||||
DI = D.I
|
||||
return BlockMatrix([[MI, -MI * B * DI], [-DI * C * MI, DI + DI * C * MI * B * DI]])
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
def _choose_2x2_inversion_formula(A, B, C, D):
|
||||
"""
|
||||
Assuming [[A, B], [C, D]] would form a valid square block matrix, find
|
||||
which of the classical 2x2 block matrix inversion formulas would be
|
||||
best suited.
|
||||
|
||||
Returns 'A', 'B', 'C', 'D' to represent the algorithm involving inversion
|
||||
of the given argument or None if the matrix cannot be inverted using
|
||||
any of those formulas.
|
||||
"""
|
||||
# Try to find a known invertible matrix. Note that the Schur complement
|
||||
# is currently not being considered for this
|
||||
A_inv = ask(Q.invertible(A))
|
||||
if A_inv == True:
|
||||
return 'A'
|
||||
B_inv = ask(Q.invertible(B))
|
||||
if B_inv == True:
|
||||
return 'B'
|
||||
C_inv = ask(Q.invertible(C))
|
||||
if C_inv == True:
|
||||
return 'C'
|
||||
D_inv = ask(Q.invertible(D))
|
||||
if D_inv == True:
|
||||
return 'D'
|
||||
# Otherwise try to find a matrix that isn't known to be non-invertible
|
||||
if A_inv != False:
|
||||
return 'A'
|
||||
if B_inv != False:
|
||||
return 'B'
|
||||
if C_inv != False:
|
||||
return 'C'
|
||||
if D_inv != False:
|
||||
return 'D'
|
||||
return None
|
||||
|
||||
|
||||
def deblock(B):
|
||||
""" Flatten a BlockMatrix of BlockMatrices """
|
||||
if not isinstance(B, BlockMatrix) or not B.blocks.has(BlockMatrix):
|
||||
return B
|
||||
wrap = lambda x: x if isinstance(x, BlockMatrix) else BlockMatrix([[x]])
|
||||
bb = B.blocks.applyfunc(wrap) # everything is a block
|
||||
|
||||
try:
|
||||
MM = Matrix(0, sum(bb[0, i].blocks.shape[1] for i in range(bb.shape[1])), [])
|
||||
for row in range(0, bb.shape[0]):
|
||||
M = Matrix(bb[row, 0].blocks)
|
||||
for col in range(1, bb.shape[1]):
|
||||
M = M.row_join(bb[row, col].blocks)
|
||||
MM = MM.col_join(M)
|
||||
|
||||
return BlockMatrix(MM)
|
||||
except ShapeError:
|
||||
return B
|
||||
|
||||
|
||||
def reblock_2x2(expr):
|
||||
"""
|
||||
Reblock a BlockMatrix so that it has 2x2 blocks of block matrices. If
|
||||
possible in such a way that the matrix continues to be invertible using the
|
||||
classical 2x2 block inversion formulas.
|
||||
"""
|
||||
if not isinstance(expr, BlockMatrix) or not all(d > 2 for d in expr.blockshape):
|
||||
return expr
|
||||
|
||||
BM = BlockMatrix # for brevity's sake
|
||||
rowblocks, colblocks = expr.blockshape
|
||||
blocks = expr.blocks
|
||||
for i in range(1, rowblocks):
|
||||
for j in range(1, colblocks):
|
||||
# try to split rows at i and cols at j
|
||||
A = bc_unpack(BM(blocks[:i, :j]))
|
||||
B = bc_unpack(BM(blocks[:i, j:]))
|
||||
C = bc_unpack(BM(blocks[i:, :j]))
|
||||
D = bc_unpack(BM(blocks[i:, j:]))
|
||||
|
||||
formula = _choose_2x2_inversion_formula(A, B, C, D)
|
||||
if formula is not None:
|
||||
return BlockMatrix([[A, B], [C, D]])
|
||||
|
||||
# else: nothing worked, just split upper left corner
|
||||
return BM([[blocks[0, 0], BM(blocks[0, 1:])],
|
||||
[BM(blocks[1:, 0]), BM(blocks[1:, 1:])]])
|
||||
|
||||
|
||||
def bounds(sizes):
|
||||
""" Convert sequence of numbers into pairs of low-high pairs
|
||||
|
||||
>>> from sympy.matrices.expressions.blockmatrix import bounds
|
||||
>>> bounds((1, 10, 50))
|
||||
[(0, 1), (1, 11), (11, 61)]
|
||||
"""
|
||||
low = 0
|
||||
rv = []
|
||||
for size in sizes:
|
||||
rv.append((low, low + size))
|
||||
low += size
|
||||
return rv
|
||||
|
||||
def blockcut(expr, rowsizes, colsizes):
|
||||
""" Cut a matrix expression into Blocks
|
||||
|
||||
>>> from sympy import ImmutableMatrix, blockcut
|
||||
>>> M = ImmutableMatrix(4, 4, range(16))
|
||||
>>> B = blockcut(M, (1, 3), (1, 3))
|
||||
>>> type(B).__name__
|
||||
'BlockMatrix'
|
||||
>>> ImmutableMatrix(B.blocks[0, 1])
|
||||
Matrix([[1, 2, 3]])
|
||||
"""
|
||||
|
||||
rowbounds = bounds(rowsizes)
|
||||
colbounds = bounds(colsizes)
|
||||
return BlockMatrix([[MatrixSlice(expr, rowbound, colbound)
|
||||
for colbound in colbounds]
|
||||
for rowbound in rowbounds])
|
||||
@@ -0,0 +1,56 @@
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.polys.polytools import Poly
|
||||
|
||||
from .matexpr import MatrixExpr
|
||||
|
||||
|
||||
class CompanionMatrix(MatrixExpr):
|
||||
"""A symbolic companion matrix of a polynomial.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Poly, Symbol, symbols
|
||||
>>> from sympy.matrices.expressions import CompanionMatrix
|
||||
>>> x = Symbol('x')
|
||||
>>> c0, c1, c2, c3, c4 = symbols('c0:5')
|
||||
>>> p = Poly(c0 + c1*x + c2*x**2 + c3*x**3 + c4*x**4 + x**5, x)
|
||||
>>> CompanionMatrix(p)
|
||||
CompanionMatrix(Poly(x**5 + c4*x**4 + c3*x**3 + c2*x**2 + c1*x + c0,
|
||||
x, domain='ZZ[c0,c1,c2,c3,c4]'))
|
||||
"""
|
||||
def __new__(cls, poly):
|
||||
poly = _sympify(poly)
|
||||
if not isinstance(poly, Poly):
|
||||
raise ValueError("{} must be a Poly instance.".format(poly))
|
||||
if not poly.is_monic:
|
||||
raise ValueError("{} must be a monic polynomial.".format(poly))
|
||||
if not poly.is_univariate:
|
||||
raise ValueError(
|
||||
"{} must be a univariate polynomial.".format(poly))
|
||||
if not poly.degree() >= 1:
|
||||
raise ValueError(
|
||||
"{} must have degree not less than 1.".format(poly))
|
||||
|
||||
return super().__new__(cls, poly)
|
||||
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
poly = self.args[0]
|
||||
size = poly.degree()
|
||||
return size, size
|
||||
|
||||
|
||||
def _entry(self, i, j):
|
||||
if j == self.cols - 1:
|
||||
return -self.args[0].all_coeffs()[-1 - i]
|
||||
elif i == j + 1:
|
||||
return S.One
|
||||
return S.Zero
|
||||
|
||||
|
||||
def as_explicit(self):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
return ImmutableDenseMatrix.companion(self.args[0])
|
||||
@@ -0,0 +1,148 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.expr import Expr
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
|
||||
|
||||
class Determinant(Expr):
|
||||
"""Matrix Determinant
|
||||
|
||||
Represents the determinant of a matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Determinant, eye
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> Determinant(A)
|
||||
Determinant(A)
|
||||
>>> Determinant(eye(3)).doit()
|
||||
1
|
||||
"""
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, mat):
|
||||
mat = sympify(mat)
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("Input to Determinant, %s, not a matrix" % str(mat))
|
||||
|
||||
if mat.is_square is False:
|
||||
raise NonSquareMatrixError("Det of a non-square matrix")
|
||||
|
||||
return Basic.__new__(cls, mat)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def kind(self):
|
||||
return self.arg.kind.element_kind
|
||||
|
||||
def doit(self, **hints):
|
||||
arg = self.arg
|
||||
if hints.get('deep', True):
|
||||
arg = arg.doit(**hints)
|
||||
|
||||
result = arg._eval_determinant()
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return self
|
||||
|
||||
|
||||
def det(matexpr):
|
||||
""" Matrix Determinant
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, det, eye
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> det(A)
|
||||
Determinant(A)
|
||||
>>> det(eye(3))
|
||||
1
|
||||
"""
|
||||
|
||||
return Determinant(matexpr).doit()
|
||||
|
||||
class Permanent(Expr):
|
||||
"""Matrix Permanent
|
||||
|
||||
Represents the permanent of a matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Permanent, ones
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> Permanent(A)
|
||||
Permanent(A)
|
||||
>>> Permanent(ones(3, 3)).doit()
|
||||
6
|
||||
"""
|
||||
|
||||
def __new__(cls, mat):
|
||||
mat = sympify(mat)
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("Input to Permanent, %s, not a matrix" % str(mat))
|
||||
|
||||
return Basic.__new__(cls, mat)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
def doit(self, expand=False, **hints):
|
||||
if isinstance(self.arg, MatrixBase):
|
||||
return self.arg.per()
|
||||
else:
|
||||
return self
|
||||
|
||||
def per(matexpr):
|
||||
""" Matrix Permanent
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Matrix, per, ones
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> per(A)
|
||||
Permanent(A)
|
||||
>>> per(ones(5, 5))
|
||||
120
|
||||
>>> M = Matrix([1, 2, 5])
|
||||
>>> per(M)
|
||||
8
|
||||
"""
|
||||
|
||||
return Permanent(matexpr).doit()
|
||||
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
|
||||
|
||||
def refine_Determinant(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine, det
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> det(X)
|
||||
Determinant(X)
|
||||
>>> with assuming(Q.orthogonal(X)):
|
||||
... print(refine(det(X)))
|
||||
1
|
||||
"""
|
||||
if ask(Q.orthogonal(expr.arg), assumptions):
|
||||
return S.One
|
||||
elif ask(Q.singular(expr.arg), assumptions):
|
||||
return S.Zero
|
||||
elif ask(Q.unit_triangular(expr.arg), assumptions):
|
||||
return S.One
|
||||
|
||||
return expr
|
||||
|
||||
|
||||
handlers_dict['Determinant'] = refine_Determinant
|
||||
@@ -0,0 +1,220 @@
|
||||
from sympy.core.sympify import _sympify
|
||||
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.core import S, Eq, Ge
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
|
||||
|
||||
class DiagonalMatrix(MatrixExpr):
|
||||
"""DiagonalMatrix(M) will create a matrix expression that
|
||||
behaves as though all off-diagonal elements,
|
||||
`M[i, j]` where `i != j`, are zero.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, DiagonalMatrix, Symbol
|
||||
>>> n = Symbol('n', integer=True)
|
||||
>>> m = Symbol('m', integer=True)
|
||||
>>> D = DiagonalMatrix(MatrixSymbol('x', 2, 3))
|
||||
>>> D[1, 2]
|
||||
0
|
||||
>>> D[1, 1]
|
||||
x[1, 1]
|
||||
|
||||
The length of the diagonal -- the lesser of the two dimensions of `M` --
|
||||
is accessed through the `diagonal_length` property:
|
||||
|
||||
>>> D.diagonal_length
|
||||
2
|
||||
>>> DiagonalMatrix(MatrixSymbol('x', n + 1, n)).diagonal_length
|
||||
n
|
||||
|
||||
When one of the dimensions is symbolic the other will be treated as
|
||||
though it is smaller:
|
||||
|
||||
>>> tall = DiagonalMatrix(MatrixSymbol('x', n, 3))
|
||||
>>> tall.diagonal_length
|
||||
3
|
||||
>>> tall[10, 1]
|
||||
0
|
||||
|
||||
When the size of the diagonal is not known, a value of None will
|
||||
be returned:
|
||||
|
||||
>>> DiagonalMatrix(MatrixSymbol('x', n, m)).diagonal_length is None
|
||||
True
|
||||
|
||||
"""
|
||||
arg = property(lambda self: self.args[0])
|
||||
|
||||
shape = property(lambda self: self.arg.shape) # type:ignore
|
||||
|
||||
@property
|
||||
def diagonal_length(self):
|
||||
r, c = self.shape
|
||||
if r.is_Integer and c.is_Integer:
|
||||
m = min(r, c)
|
||||
elif r.is_Integer and not c.is_Integer:
|
||||
m = r
|
||||
elif c.is_Integer and not r.is_Integer:
|
||||
m = c
|
||||
elif r == c:
|
||||
m = r
|
||||
else:
|
||||
try:
|
||||
m = min(r, c)
|
||||
except TypeError:
|
||||
m = None
|
||||
return m
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
if self.diagonal_length is not None:
|
||||
if Ge(i, self.diagonal_length) is S.true:
|
||||
return S.Zero
|
||||
elif Ge(j, self.diagonal_length) is S.true:
|
||||
return S.Zero
|
||||
eq = Eq(i, j)
|
||||
if eq is S.true:
|
||||
return self.arg[i, i]
|
||||
elif eq is S.false:
|
||||
return S.Zero
|
||||
return self.arg[i, j]*KroneckerDelta(i, j)
|
||||
|
||||
|
||||
class DiagonalOf(MatrixExpr):
|
||||
"""DiagonalOf(M) will create a matrix expression that
|
||||
is equivalent to the diagonal of `M`, represented as
|
||||
a single column matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, DiagonalOf, Symbol
|
||||
>>> n = Symbol('n', integer=True)
|
||||
>>> m = Symbol('m', integer=True)
|
||||
>>> x = MatrixSymbol('x', 2, 3)
|
||||
>>> diag = DiagonalOf(x)
|
||||
>>> diag.shape
|
||||
(2, 1)
|
||||
|
||||
The diagonal can be addressed like a matrix or vector and will
|
||||
return the corresponding element of the original matrix:
|
||||
|
||||
>>> diag[1, 0] == diag[1] == x[1, 1]
|
||||
True
|
||||
|
||||
The length of the diagonal -- the lesser of the two dimensions of `M` --
|
||||
is accessed through the `diagonal_length` property:
|
||||
|
||||
>>> diag.diagonal_length
|
||||
2
|
||||
>>> DiagonalOf(MatrixSymbol('x', n + 1, n)).diagonal_length
|
||||
n
|
||||
|
||||
When only one of the dimensions is symbolic the other will be
|
||||
treated as though it is smaller:
|
||||
|
||||
>>> dtall = DiagonalOf(MatrixSymbol('x', n, 3))
|
||||
>>> dtall.diagonal_length
|
||||
3
|
||||
|
||||
When the size of the diagonal is not known, a value of None will
|
||||
be returned:
|
||||
|
||||
>>> DiagonalOf(MatrixSymbol('x', n, m)).diagonal_length is None
|
||||
True
|
||||
|
||||
"""
|
||||
arg = property(lambda self: self.args[0])
|
||||
@property
|
||||
def shape(self):
|
||||
r, c = self.arg.shape
|
||||
if r.is_Integer and c.is_Integer:
|
||||
m = min(r, c)
|
||||
elif r.is_Integer and not c.is_Integer:
|
||||
m = r
|
||||
elif c.is_Integer and not r.is_Integer:
|
||||
m = c
|
||||
elif r == c:
|
||||
m = r
|
||||
else:
|
||||
try:
|
||||
m = min(r, c)
|
||||
except TypeError:
|
||||
m = None
|
||||
return m, S.One
|
||||
|
||||
@property
|
||||
def diagonal_length(self):
|
||||
return self.shape[0]
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.arg._entry(i, i, **kwargs)
|
||||
|
||||
|
||||
class DiagMatrix(MatrixExpr):
|
||||
"""
|
||||
Turn a vector into a diagonal matrix.
|
||||
"""
|
||||
def __new__(cls, vector):
|
||||
vector = _sympify(vector)
|
||||
obj = MatrixExpr.__new__(cls, vector)
|
||||
shape = vector.shape
|
||||
dim = shape[1] if shape[0] == 1 else shape[0]
|
||||
if vector.shape[0] != 1:
|
||||
obj._iscolumn = True
|
||||
else:
|
||||
obj._iscolumn = False
|
||||
obj._shape = (dim, dim)
|
||||
obj._vector = vector
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
if self._iscolumn:
|
||||
result = self._vector._entry(i, 0, **kwargs)
|
||||
else:
|
||||
result = self._vector._entry(0, j, **kwargs)
|
||||
if i != j:
|
||||
result *= KroneckerDelta(i, j)
|
||||
return result
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self
|
||||
|
||||
def as_explicit(self):
|
||||
from sympy.matrices.dense import diag
|
||||
return diag(*list(self._vector.as_explicit()))
|
||||
|
||||
def doit(self, **hints):
|
||||
from sympy.assumptions import ask, Q
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
from sympy.matrices.dense import eye
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
vector = self._vector
|
||||
# This accounts for shape (1, 1) and identity matrices, among others:
|
||||
if ask(Q.diagonal(vector)):
|
||||
return vector
|
||||
if isinstance(vector, MatrixBase):
|
||||
ret = eye(max(vector.shape))
|
||||
for i in range(ret.shape[0]):
|
||||
ret[i, i] = vector[i]
|
||||
return type(vector)(ret)
|
||||
if vector.is_MatMul:
|
||||
matrices = [arg for arg in vector.args if arg.is_Matrix]
|
||||
scalars = [arg for arg in vector.args if arg not in matrices]
|
||||
if scalars:
|
||||
return Mul.fromiter(scalars)*DiagMatrix(MatMul.fromiter(matrices).doit()).doit()
|
||||
if isinstance(vector, Transpose):
|
||||
vector = vector.arg
|
||||
return DiagMatrix(vector)
|
||||
|
||||
|
||||
def diagonalize_vector(vector):
|
||||
return DiagMatrix(vector).doit()
|
||||
@@ -0,0 +1,55 @@
|
||||
from sympy.core import Basic, Expr
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
|
||||
|
||||
class DotProduct(Expr):
|
||||
"""
|
||||
Dot product of vector matrices
|
||||
|
||||
The input should be two 1 x n or n x 1 matrices. The output represents the
|
||||
scalar dotproduct.
|
||||
|
||||
This is similar to using MatrixElement and MatMul, except DotProduct does
|
||||
not require that one vector to be a row vector and the other vector to be
|
||||
a column vector.
|
||||
|
||||
>>> from sympy import MatrixSymbol, DotProduct
|
||||
>>> A = MatrixSymbol('A', 1, 3)
|
||||
>>> B = MatrixSymbol('B', 1, 3)
|
||||
>>> DotProduct(A, B)
|
||||
DotProduct(A, B)
|
||||
>>> DotProduct(A, B).doit()
|
||||
A[0, 0]*B[0, 0] + A[0, 1]*B[0, 1] + A[0, 2]*B[0, 2]
|
||||
"""
|
||||
|
||||
def __new__(cls, arg1, arg2):
|
||||
arg1, arg2 = _sympify((arg1, arg2))
|
||||
|
||||
if not arg1.is_Matrix:
|
||||
raise TypeError("Argument 1 of DotProduct is not a matrix")
|
||||
if not arg2.is_Matrix:
|
||||
raise TypeError("Argument 2 of DotProduct is not a matrix")
|
||||
if not (1 in arg1.shape):
|
||||
raise TypeError("Argument 1 of DotProduct is not a vector")
|
||||
if not (1 in arg2.shape):
|
||||
raise TypeError("Argument 2 of DotProduct is not a vector")
|
||||
|
||||
if set(arg1.shape) != set(arg2.shape):
|
||||
raise TypeError("DotProduct arguments are not the same length")
|
||||
|
||||
return Basic.__new__(cls, arg1, arg2)
|
||||
|
||||
def doit(self, expand=False, **hints):
|
||||
if self.args[0].shape == self.args[1].shape:
|
||||
if self.args[0].shape[0] == 1:
|
||||
mul = self.args[0]*transpose(self.args[1])
|
||||
else:
|
||||
mul = transpose(self.args[0])*self.args[1]
|
||||
else:
|
||||
if self.args[0].shape[0] == 1:
|
||||
mul = self.args[0]*self.args[1]
|
||||
else:
|
||||
mul = transpose(self.args[0])*transpose(self.args[1])
|
||||
|
||||
return mul[0]
|
||||
@@ -0,0 +1,62 @@
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.assumptions.ask import Q
|
||||
|
||||
class Factorization(MatrixExpr):
|
||||
arg = property(lambda self: self.args[0])
|
||||
shape = property(lambda self: self.arg.shape) # type: ignore
|
||||
|
||||
class LofLU(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.lower_triangular,)
|
||||
class UofLU(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.upper_triangular,)
|
||||
|
||||
class LofCholesky(LofLU): pass
|
||||
class UofCholesky(UofLU): pass
|
||||
|
||||
class QofQR(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
class RofQR(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.upper_triangular,)
|
||||
|
||||
class EigenVectors(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
class EigenValues(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.diagonal,)
|
||||
|
||||
class UofSVD(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
class SofSVD(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.diagonal,)
|
||||
class VofSVD(Factorization):
|
||||
@property
|
||||
def predicates(self):
|
||||
return (Q.orthogonal,)
|
||||
|
||||
|
||||
def lu(expr):
|
||||
return LofLU(expr), UofLU(expr)
|
||||
|
||||
def qr(expr):
|
||||
return QofQR(expr), RofQR(expr)
|
||||
|
||||
def eig(expr):
|
||||
return EigenValues(expr), EigenVectors(expr)
|
||||
|
||||
def svd(expr):
|
||||
return UofSVD(expr), SofSVD(expr), VofSVD(expr)
|
||||
@@ -0,0 +1,91 @@
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
|
||||
|
||||
class DFT(MatrixExpr):
|
||||
r"""
|
||||
Returns a discrete Fourier transform matrix. The matrix is scaled
|
||||
with :math:`\frac{1}{\sqrt{n}}` so that it is unitary.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : integer or Symbol
|
||||
Size of the transform.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.abc import n
|
||||
>>> from sympy.matrices.expressions.fourier import DFT
|
||||
>>> DFT(3)
|
||||
DFT(3)
|
||||
>>> DFT(3).as_explicit()
|
||||
Matrix([
|
||||
[sqrt(3)/3, sqrt(3)/3, sqrt(3)/3],
|
||||
[sqrt(3)/3, sqrt(3)*exp(-2*I*pi/3)/3, sqrt(3)*exp(2*I*pi/3)/3],
|
||||
[sqrt(3)/3, sqrt(3)*exp(2*I*pi/3)/3, sqrt(3)*exp(-2*I*pi/3)/3]])
|
||||
>>> DFT(n).shape
|
||||
(n, n)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/DFT_matrix
|
||||
|
||||
"""
|
||||
|
||||
def __new__(cls, n):
|
||||
n = _sympify(n)
|
||||
cls._check_dim(n)
|
||||
|
||||
obj = super().__new__(cls, n)
|
||||
return obj
|
||||
|
||||
n = property(lambda self: self.args[0]) # type: ignore
|
||||
shape = property(lambda self: (self.n, self.n)) # type: ignore
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
w = exp(-2*S.Pi*I/self.n)
|
||||
return w**(i*j) / sqrt(self.n)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return IDFT(self.n)
|
||||
|
||||
|
||||
class IDFT(DFT):
|
||||
r"""
|
||||
Returns an inverse discrete Fourier transform matrix. The matrix is scaled
|
||||
with :math:`\frac{1}{\sqrt{n}}` so that it is unitary.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
n : integer or Symbol
|
||||
Size of the transform
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices.expressions.fourier import DFT, IDFT
|
||||
>>> IDFT(3)
|
||||
IDFT(3)
|
||||
>>> IDFT(4)*DFT(4)
|
||||
I
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
DFT
|
||||
|
||||
"""
|
||||
def _entry(self, i, j, **kwargs):
|
||||
w = exp(-2*S.Pi*I/self.n)
|
||||
return w**(-i*j) / sqrt(self.n)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return DFT(self.n)
|
||||
@@ -0,0 +1,118 @@
|
||||
from .matexpr import MatrixExpr
|
||||
from sympy.core.function import FunctionClass, Lambda
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.core.sympify import _sympify, sympify
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.functions.elementary.complexes import re, im
|
||||
|
||||
|
||||
class FunctionMatrix(MatrixExpr):
|
||||
"""Represents a matrix using a function (``Lambda``) which gives
|
||||
outputs according to the coordinates of each matrix entries.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
rows : nonnegative integer. Can be symbolic.
|
||||
|
||||
cols : nonnegative integer. Can be symbolic.
|
||||
|
||||
lamda : Function, Lambda or str
|
||||
If it is a SymPy ``Function`` or ``Lambda`` instance,
|
||||
it should be able to accept two arguments which represents the
|
||||
matrix coordinates.
|
||||
|
||||
If it is a pure string containing Python ``lambda`` semantics,
|
||||
it is interpreted by the SymPy parser and casted into a SymPy
|
||||
``Lambda`` instance.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Creating a ``FunctionMatrix`` from ``Lambda``:
|
||||
|
||||
>>> from sympy import FunctionMatrix, symbols, Lambda, MatPow
|
||||
>>> i, j, n, m = symbols('i,j,n,m')
|
||||
>>> FunctionMatrix(n, m, Lambda((i, j), i + j))
|
||||
FunctionMatrix(n, m, Lambda((i, j), i + j))
|
||||
|
||||
Creating a ``FunctionMatrix`` from a SymPy function:
|
||||
|
||||
>>> from sympy import KroneckerDelta
|
||||
>>> X = FunctionMatrix(3, 3, KroneckerDelta)
|
||||
>>> X.as_explicit()
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
Creating a ``FunctionMatrix`` from a SymPy undefined function:
|
||||
|
||||
>>> from sympy import Function
|
||||
>>> f = Function('f')
|
||||
>>> X = FunctionMatrix(3, 3, f)
|
||||
>>> X.as_explicit()
|
||||
Matrix([
|
||||
[f(0, 0), f(0, 1), f(0, 2)],
|
||||
[f(1, 0), f(1, 1), f(1, 2)],
|
||||
[f(2, 0), f(2, 1), f(2, 2)]])
|
||||
|
||||
Creating a ``FunctionMatrix`` from Python ``lambda``:
|
||||
|
||||
>>> FunctionMatrix(n, m, 'lambda i, j: i + j')
|
||||
FunctionMatrix(n, m, Lambda((i, j), i + j))
|
||||
|
||||
Example of lazy evaluation of matrix product:
|
||||
|
||||
>>> Y = FunctionMatrix(1000, 1000, Lambda((i, j), i + j))
|
||||
>>> isinstance(Y*Y, MatPow) # this is an expression object
|
||||
True
|
||||
>>> (Y**2)[10,10] # So this is evaluated lazily
|
||||
342923500
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This class provides an alternative way to represent an extremely
|
||||
dense matrix with entries in some form of a sequence, in a most
|
||||
sparse way.
|
||||
"""
|
||||
def __new__(cls, rows, cols, lamda):
|
||||
rows, cols = _sympify(rows), _sympify(cols)
|
||||
cls._check_dim(rows)
|
||||
cls._check_dim(cols)
|
||||
|
||||
lamda = sympify(lamda)
|
||||
if not isinstance(lamda, (FunctionClass, Lambda)):
|
||||
raise ValueError(
|
||||
"{} should be compatible with SymPy function classes."
|
||||
.format(lamda))
|
||||
|
||||
if 2 not in lamda.nargs:
|
||||
raise ValueError(
|
||||
'{} should be able to accept 2 arguments.'.format(lamda))
|
||||
|
||||
if not isinstance(lamda, Lambda):
|
||||
i, j = Dummy('i'), Dummy('j')
|
||||
lamda = Lambda((i, j), lamda(i, j))
|
||||
|
||||
return super().__new__(cls, rows, cols, lamda)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0:2]
|
||||
|
||||
@property
|
||||
def lamda(self):
|
||||
return self.args[2]
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.lamda(i, j)
|
||||
|
||||
def _eval_trace(self):
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.concrete.summations import Sum
|
||||
return Trace(self).rewrite(Sum).doit()
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
return (re(Matrix(self)), im(Matrix(self)))
|
||||
@@ -0,0 +1,464 @@
|
||||
from collections import Counter
|
||||
|
||||
from sympy.core import Mul, sympify
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import ExprBuilder
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.functions.elementary.exponential import log
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions._shape import validate_matadd_integer as validate
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix
|
||||
from sympy.strategies import (
|
||||
unpack, flatten, condition, exhaust, rm_id, sort
|
||||
)
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
|
||||
def hadamard_product(*matrices):
|
||||
"""
|
||||
Return the elementwise (aka Hadamard) product of matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import hadamard_product, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 2, 3)
|
||||
>>> B = MatrixSymbol('B', 2, 3)
|
||||
>>> hadamard_product(A)
|
||||
A
|
||||
>>> hadamard_product(A, B)
|
||||
HadamardProduct(A, B)
|
||||
>>> hadamard_product(A, B)[0, 1]
|
||||
A[0, 1]*B[0, 1]
|
||||
"""
|
||||
if not matrices:
|
||||
raise TypeError("Empty Hadamard product is undefined")
|
||||
if len(matrices) == 1:
|
||||
return matrices[0]
|
||||
return HadamardProduct(*matrices).doit()
|
||||
|
||||
|
||||
class HadamardProduct(MatrixExpr):
|
||||
"""
|
||||
Elementwise product of matrix expressions
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Hadamard product for matrix symbols:
|
||||
|
||||
>>> from sympy import hadamard_product, HadamardProduct, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 5)
|
||||
>>> isinstance(hadamard_product(A, B), HadamardProduct)
|
||||
True
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the product, use the function
|
||||
``hadamard_product()`` or ``HadamardProduct.doit``
|
||||
"""
|
||||
is_HadamardProduct = True
|
||||
|
||||
def __new__(cls, *args, evaluate=False, check=None):
|
||||
args = list(map(sympify, args))
|
||||
if len(args) == 0:
|
||||
# We currently don't have a way to support one-matrices of generic dimensions:
|
||||
raise ValueError("HadamardProduct needs at least one argument")
|
||||
|
||||
if not all(isinstance(arg, MatrixExpr) for arg in args):
|
||||
raise TypeError("Mix of Matrix and Scalar symbols")
|
||||
|
||||
if check is not None:
|
||||
sympy_deprecation_warning(
|
||||
"Passing check to HadamardProduct is deprecated and the check argument will be removed in a future version.",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target='remove-check-argument-from-matrix-operations')
|
||||
|
||||
if check is not False:
|
||||
validate(*args)
|
||||
|
||||
obj = super().__new__(cls, *args)
|
||||
if evaluate:
|
||||
obj = obj.doit(deep=False)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0].shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return Mul(*[arg._entry(i, j, **kwargs) for arg in self.args])
|
||||
|
||||
def _eval_transpose(self):
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
return HadamardProduct(*list(map(transpose, self.args)))
|
||||
|
||||
def doit(self, **hints):
|
||||
expr = self.func(*(i.doit(**hints) for i in self.args))
|
||||
# Check for explicit matrices:
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
|
||||
explicit = [i for i in expr.args if isinstance(i, MatrixBase)]
|
||||
if explicit:
|
||||
remainder = [i for i in expr.args if i not in explicit]
|
||||
expl_mat = ImmutableMatrix([
|
||||
Mul.fromiter(i) for i in zip(*explicit)
|
||||
]).reshape(*self.shape)
|
||||
expr = HadamardProduct(*([expl_mat] + remainder))
|
||||
|
||||
return canonicalize(expr)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
terms = []
|
||||
args = list(self.args)
|
||||
for i in range(len(args)):
|
||||
factors = args[:i] + [args[i].diff(x)] + args[i+1:]
|
||||
terms.append(hadamard_product(*factors))
|
||||
return Add.fromiter(terms)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.matrices.expressions.matexpr import _make_matrix
|
||||
|
||||
with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)]
|
||||
lines = []
|
||||
for ind in with_x_ind:
|
||||
left_args = self.args[:ind]
|
||||
right_args = self.args[ind+1:]
|
||||
|
||||
d = self.args[ind]._eval_derivative_matrix_lines(x)
|
||||
hadam = hadamard_product(*(right_args + left_args))
|
||||
diagonal = [(0, 2), (3, 4)]
|
||||
diagonal = [e for j, e in enumerate(diagonal) if self.shape[j] != 1]
|
||||
for i in d:
|
||||
l1 = i._lines[i._first_line_index]
|
||||
l2 = i._lines[i._second_line_index]
|
||||
subexpr = ExprBuilder(
|
||||
ArrayDiagonal,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
ExprBuilder(_make_matrix, [l1]),
|
||||
hadam,
|
||||
ExprBuilder(_make_matrix, [l2]),
|
||||
]
|
||||
),
|
||||
*diagonal],
|
||||
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args[0].args
|
||||
i._first_pointer_index = 0
|
||||
i._second_pointer_parent = subexpr.args[0].args[2].args
|
||||
i._second_pointer_index = 0
|
||||
i._lines = [subexpr]
|
||||
lines.append(i)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
# TODO Implement algorithm for rewriting Hadamard product as diagonal matrix
|
||||
# if matmul identy matrix is multiplied.
|
||||
def canonicalize(x):
|
||||
"""Canonicalize the Hadamard product ``x`` with mathematical properties.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, HadamardProduct
|
||||
>>> from sympy import OneMatrix, ZeroMatrix
|
||||
>>> from sympy.matrices.expressions.hadamard import canonicalize
|
||||
>>> from sympy import init_printing
|
||||
>>> init_printing(use_unicode=False)
|
||||
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = MatrixSymbol('B', 2, 2)
|
||||
>>> C = MatrixSymbol('C', 2, 2)
|
||||
|
||||
Hadamard product associativity:
|
||||
|
||||
>>> X = HadamardProduct(A, HadamardProduct(B, C))
|
||||
>>> X
|
||||
A.*(B.*C)
|
||||
>>> canonicalize(X)
|
||||
A.*B.*C
|
||||
|
||||
Hadamard product commutativity:
|
||||
|
||||
>>> X = HadamardProduct(A, B)
|
||||
>>> Y = HadamardProduct(B, A)
|
||||
>>> X
|
||||
A.*B
|
||||
>>> Y
|
||||
B.*A
|
||||
>>> canonicalize(X)
|
||||
A.*B
|
||||
>>> canonicalize(Y)
|
||||
A.*B
|
||||
|
||||
Hadamard product identity:
|
||||
|
||||
>>> X = HadamardProduct(A, OneMatrix(2, 2))
|
||||
>>> X
|
||||
A.*1
|
||||
>>> canonicalize(X)
|
||||
A
|
||||
|
||||
Absorbing element of Hadamard product:
|
||||
|
||||
>>> X = HadamardProduct(A, ZeroMatrix(2, 2))
|
||||
>>> X
|
||||
A.*0
|
||||
>>> canonicalize(X)
|
||||
0
|
||||
|
||||
Rewriting to Hadamard Power
|
||||
|
||||
>>> X = HadamardProduct(A, A, A)
|
||||
>>> X
|
||||
A.*A.*A
|
||||
>>> canonicalize(X)
|
||||
.3
|
||||
A
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
As the Hadamard product is associative, nested products can be flattened.
|
||||
|
||||
The Hadamard product is commutative so that factors can be sorted for
|
||||
canonical form.
|
||||
|
||||
A matrix of only ones is an identity for Hadamard product,
|
||||
so every matrices of only ones can be removed.
|
||||
|
||||
Any zero matrix will make the whole product a zero matrix.
|
||||
|
||||
Duplicate elements can be collected and rewritten as HadamardPower
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Hadamard_product_(matrices)
|
||||
"""
|
||||
# Associativity
|
||||
rule = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
flatten
|
||||
)
|
||||
fun = exhaust(rule)
|
||||
x = fun(x)
|
||||
|
||||
# Identity
|
||||
fun = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
rm_id(lambda x: isinstance(x, OneMatrix))
|
||||
)
|
||||
x = fun(x)
|
||||
|
||||
# Absorbing by Zero Matrix
|
||||
def absorb(x):
|
||||
if any(isinstance(c, ZeroMatrix) for c in x.args):
|
||||
return ZeroMatrix(*x.shape)
|
||||
else:
|
||||
return x
|
||||
fun = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
absorb
|
||||
)
|
||||
x = fun(x)
|
||||
|
||||
# Rewriting with HadamardPower
|
||||
if isinstance(x, HadamardProduct):
|
||||
tally = Counter(x.args)
|
||||
|
||||
new_arg = []
|
||||
for base, exp in tally.items():
|
||||
if exp == 1:
|
||||
new_arg.append(base)
|
||||
else:
|
||||
new_arg.append(HadamardPower(base, exp))
|
||||
|
||||
x = HadamardProduct(*new_arg)
|
||||
|
||||
# Commutativity
|
||||
fun = condition(
|
||||
lambda x: isinstance(x, HadamardProduct),
|
||||
sort(default_sort_key)
|
||||
)
|
||||
x = fun(x)
|
||||
|
||||
# Unpacking
|
||||
x = unpack(x)
|
||||
return x
|
||||
|
||||
|
||||
def hadamard_power(base, exp):
|
||||
base = sympify(base)
|
||||
exp = sympify(exp)
|
||||
if exp == 1:
|
||||
return base
|
||||
if not base.is_Matrix:
|
||||
return base**exp
|
||||
if exp.is_Matrix:
|
||||
raise ValueError("cannot raise expression to a matrix")
|
||||
return HadamardPower(base, exp)
|
||||
|
||||
|
||||
class HadamardPower(MatrixExpr):
|
||||
r"""
|
||||
Elementwise power of matrix expressions
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
base : scalar or matrix
|
||||
|
||||
exp : scalar or matrix
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
There are four definitions for the hadamard power which can be used.
|
||||
Let's consider `A, B` as `(m, n)` matrices, and `a, b` as scalars.
|
||||
|
||||
Matrix raised to a scalar exponent:
|
||||
|
||||
.. math::
|
||||
A^{\circ b} = \begin{bmatrix}
|
||||
A_{0, 0}^b & A_{0, 1}^b & \cdots & A_{0, n-1}^b \\
|
||||
A_{1, 0}^b & A_{1, 1}^b & \cdots & A_{1, n-1}^b \\
|
||||
\vdots & \vdots & \ddots & \vdots \\
|
||||
A_{m-1, 0}^b & A_{m-1, 1}^b & \cdots & A_{m-1, n-1}^b
|
||||
\end{bmatrix}
|
||||
|
||||
Scalar raised to a matrix exponent:
|
||||
|
||||
.. math::
|
||||
a^{\circ B} = \begin{bmatrix}
|
||||
a^{B_{0, 0}} & a^{B_{0, 1}} & \cdots & a^{B_{0, n-1}} \\
|
||||
a^{B_{1, 0}} & a^{B_{1, 1}} & \cdots & a^{B_{1, n-1}} \\
|
||||
\vdots & \vdots & \ddots & \vdots \\
|
||||
a^{B_{m-1, 0}} & a^{B_{m-1, 1}} & \cdots & a^{B_{m-1, n-1}}
|
||||
\end{bmatrix}
|
||||
|
||||
Matrix raised to a matrix exponent:
|
||||
|
||||
.. math::
|
||||
A^{\circ B} = \begin{bmatrix}
|
||||
A_{0, 0}^{B_{0, 0}} & A_{0, 1}^{B_{0, 1}} &
|
||||
\cdots & A_{0, n-1}^{B_{0, n-1}} \\
|
||||
A_{1, 0}^{B_{1, 0}} & A_{1, 1}^{B_{1, 1}} &
|
||||
\cdots & A_{1, n-1}^{B_{1, n-1}} \\
|
||||
\vdots & \vdots &
|
||||
\ddots & \vdots \\
|
||||
A_{m-1, 0}^{B_{m-1, 0}} & A_{m-1, 1}^{B_{m-1, 1}} &
|
||||
\cdots & A_{m-1, n-1}^{B_{m-1, n-1}}
|
||||
\end{bmatrix}
|
||||
|
||||
Scalar raised to a scalar exponent:
|
||||
|
||||
.. math::
|
||||
a^{\circ b} = a^b
|
||||
"""
|
||||
|
||||
def __new__(cls, base, exp):
|
||||
base = sympify(base)
|
||||
exp = sympify(exp)
|
||||
|
||||
if base.is_scalar and exp.is_scalar:
|
||||
return base ** exp
|
||||
|
||||
if isinstance(base, MatrixExpr) and isinstance(exp, MatrixExpr):
|
||||
validate(base, exp)
|
||||
|
||||
obj = super().__new__(cls, base, exp)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def base(self):
|
||||
return self._args[0]
|
||||
|
||||
@property
|
||||
def exp(self):
|
||||
return self._args[1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
if self.base.is_Matrix:
|
||||
return self.base.shape
|
||||
return self.exp.shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
base = self.base
|
||||
exp = self.exp
|
||||
|
||||
if base.is_Matrix:
|
||||
a = base._entry(i, j, **kwargs)
|
||||
elif base.is_scalar:
|
||||
a = base
|
||||
else:
|
||||
raise ValueError(
|
||||
'The base {} must be a scalar or a matrix.'.format(base))
|
||||
|
||||
if exp.is_Matrix:
|
||||
b = exp._entry(i, j, **kwargs)
|
||||
elif exp.is_scalar:
|
||||
b = exp
|
||||
else:
|
||||
raise ValueError(
|
||||
'The exponent {} must be a scalar or a matrix.'.format(exp))
|
||||
|
||||
return a ** b
|
||||
|
||||
def _eval_transpose(self):
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
return HadamardPower(transpose(self.base), self.exp)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
dexp = self.exp.diff(x)
|
||||
logbase = self.base.applyfunc(log)
|
||||
dlbase = logbase.diff(x)
|
||||
return hadamard_product(
|
||||
dexp*logbase + self.exp*dlbase,
|
||||
self
|
||||
)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayDiagonal
|
||||
from sympy.matrices.expressions.matexpr import _make_matrix
|
||||
|
||||
lr = self.base._eval_derivative_matrix_lines(x)
|
||||
for i in lr:
|
||||
diagonal = [(1, 2), (3, 4)]
|
||||
diagonal = [e for j, e in enumerate(diagonal) if self.base.shape[j] != 1]
|
||||
l1 = i._lines[i._first_line_index]
|
||||
l2 = i._lines[i._second_line_index]
|
||||
subexpr = ExprBuilder(
|
||||
ArrayDiagonal,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
ExprBuilder(_make_matrix, [l1]),
|
||||
self.exp*hadamard_power(self.base, self.exp-1),
|
||||
ExprBuilder(_make_matrix, [l2]),
|
||||
]
|
||||
),
|
||||
*diagonal],
|
||||
validator=ArrayDiagonal._validate
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args[0].args
|
||||
i._first_pointer_index = 0
|
||||
i._first_line_index = 0
|
||||
i._second_pointer_parent = subexpr.args[0].args[2].args
|
||||
i._second_pointer_index = 0
|
||||
i._second_line_index = 0
|
||||
i._lines = [subexpr]
|
||||
return lr
|
||||
@@ -0,0 +1,112 @@
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.core import S, Basic
|
||||
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
|
||||
|
||||
class Inverse(MatPow):
|
||||
"""
|
||||
The multiplicative inverse of a matrix expression
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the inverse, use the ``.inverse()``
|
||||
method of matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Inverse
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> B = MatrixSymbol('B', 3, 3)
|
||||
>>> Inverse(A)
|
||||
A**(-1)
|
||||
>>> A.inverse() == Inverse(A)
|
||||
True
|
||||
>>> (A*B).inverse()
|
||||
B**(-1)*A**(-1)
|
||||
>>> Inverse(A*B)
|
||||
(A*B)**(-1)
|
||||
|
||||
"""
|
||||
is_Inverse = True
|
||||
exp = S.NegativeOne
|
||||
|
||||
def __new__(cls, mat, exp=S.NegativeOne):
|
||||
# exp is there to make it consistent with
|
||||
# inverse.func(*inverse.args) == inverse
|
||||
mat = _sympify(mat)
|
||||
exp = _sympify(exp)
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("mat should be a matrix")
|
||||
if mat.is_square is False:
|
||||
raise NonSquareMatrixError("Inverse of non-square matrix %s" % mat)
|
||||
return Basic.__new__(cls, mat, exp)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.arg.shape
|
||||
|
||||
def _eval_inverse(self):
|
||||
return self.arg
|
||||
|
||||
def _eval_transpose(self):
|
||||
return Inverse(self.arg.transpose())
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return Inverse(self.arg.adjoint())
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return Inverse(self.arg.conjugate())
|
||||
|
||||
def _eval_determinant(self):
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
return 1/det(self.arg)
|
||||
|
||||
def doit(self, **hints):
|
||||
if 'inv_expand' in hints and hints['inv_expand'] == False:
|
||||
return self
|
||||
|
||||
arg = self.arg
|
||||
if hints.get('deep', True):
|
||||
arg = arg.doit(**hints)
|
||||
|
||||
return arg.inverse()
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
arg = self.args[0]
|
||||
lines = arg._eval_derivative_matrix_lines(x)
|
||||
for line in lines:
|
||||
line.first_pointer *= -self.T
|
||||
line.second_pointer *= self
|
||||
return lines
|
||||
|
||||
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
|
||||
|
||||
def refine_Inverse(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> X.I
|
||||
X**(-1)
|
||||
>>> with assuming(Q.orthogonal(X)):
|
||||
... print(refine(X.I))
|
||||
X.T
|
||||
"""
|
||||
if ask(Q.orthogonal(expr), assumptions):
|
||||
return expr.arg.T
|
||||
elif ask(Q.unitary(expr), assumptions):
|
||||
return expr.arg.conjugate()
|
||||
elif ask(Q.singular(expr), assumptions):
|
||||
raise ValueError("Inverse of singular matrix %s" % expr.arg)
|
||||
|
||||
return expr
|
||||
|
||||
handlers_dict['Inverse'] = refine_Inverse
|
||||
@@ -0,0 +1,434 @@
|
||||
"""Implementation of the Kronecker product"""
|
||||
from functools import reduce
|
||||
from math import prod
|
||||
|
||||
from sympy.core import Mul, sympify
|
||||
from sympy.functions import adjoint
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.strategies import (
|
||||
canon, condition, distribute, do_one, exhaust, flatten, typed, unpack)
|
||||
from sympy.strategies.traverse import bottom_up
|
||||
from sympy.utilities import sift
|
||||
|
||||
from .matadd import MatAdd
|
||||
from .matmul import MatMul
|
||||
from .matpow import MatPow
|
||||
|
||||
|
||||
def kronecker_product(*matrices):
|
||||
"""
|
||||
The Kronecker product of two or more arguments.
|
||||
|
||||
This computes the explicit Kronecker product for subclasses of
|
||||
``MatrixBase`` i.e. explicit matrices. Otherwise, a symbolic
|
||||
``KroneckerProduct`` object is returned.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
For ``MatrixSymbol`` arguments a ``KroneckerProduct`` object is returned.
|
||||
Elements of this matrix can be obtained by indexing, or for MatrixSymbols
|
||||
with known dimension the explicit matrix can be obtained with
|
||||
``.as_explicit()``
|
||||
|
||||
>>> from sympy import kronecker_product, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = MatrixSymbol('B', 2, 2)
|
||||
>>> kronecker_product(A)
|
||||
A
|
||||
>>> kronecker_product(A, B)
|
||||
KroneckerProduct(A, B)
|
||||
>>> kronecker_product(A, B)[0, 1]
|
||||
A[0, 0]*B[0, 1]
|
||||
>>> kronecker_product(A, B).as_explicit()
|
||||
Matrix([
|
||||
[A[0, 0]*B[0, 0], A[0, 0]*B[0, 1], A[0, 1]*B[0, 0], A[0, 1]*B[0, 1]],
|
||||
[A[0, 0]*B[1, 0], A[0, 0]*B[1, 1], A[0, 1]*B[1, 0], A[0, 1]*B[1, 1]],
|
||||
[A[1, 0]*B[0, 0], A[1, 0]*B[0, 1], A[1, 1]*B[0, 0], A[1, 1]*B[0, 1]],
|
||||
[A[1, 0]*B[1, 0], A[1, 0]*B[1, 1], A[1, 1]*B[1, 0], A[1, 1]*B[1, 1]]])
|
||||
|
||||
For explicit matrices the Kronecker product is returned as a Matrix
|
||||
|
||||
>>> from sympy import Matrix, kronecker_product
|
||||
>>> sigma_x = Matrix([
|
||||
... [0, 1],
|
||||
... [1, 0]])
|
||||
...
|
||||
>>> Isigma_y = Matrix([
|
||||
... [0, 1],
|
||||
... [-1, 0]])
|
||||
...
|
||||
>>> kronecker_product(sigma_x, Isigma_y)
|
||||
Matrix([
|
||||
[ 0, 0, 0, 1],
|
||||
[ 0, 0, -1, 0],
|
||||
[ 0, 1, 0, 0],
|
||||
[-1, 0, 0, 0]])
|
||||
|
||||
See Also
|
||||
========
|
||||
KroneckerProduct
|
||||
|
||||
"""
|
||||
if not matrices:
|
||||
raise TypeError("Empty Kronecker product is undefined")
|
||||
if len(matrices) == 1:
|
||||
return matrices[0]
|
||||
else:
|
||||
return KroneckerProduct(*matrices).doit()
|
||||
|
||||
|
||||
class KroneckerProduct(MatrixExpr):
|
||||
"""
|
||||
The Kronecker product of two or more arguments.
|
||||
|
||||
The Kronecker product is a non-commutative product of matrices.
|
||||
Given two matrices of dimension (m, n) and (s, t) it produces a matrix
|
||||
of dimension (m s, n t).
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the product, use the function
|
||||
``kronecker_product()`` or call the ``.doit()`` or ``.as_explicit()``
|
||||
methods.
|
||||
|
||||
>>> from sympy import KroneckerProduct, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 5)
|
||||
>>> isinstance(KroneckerProduct(A, B), KroneckerProduct)
|
||||
True
|
||||
"""
|
||||
is_KroneckerProduct = True
|
||||
|
||||
def __new__(cls, *args, check=True):
|
||||
args = list(map(sympify, args))
|
||||
if all(a.is_Identity for a in args):
|
||||
ret = Identity(prod(a.rows for a in args))
|
||||
if all(isinstance(a, MatrixBase) for a in args):
|
||||
return ret.as_explicit()
|
||||
else:
|
||||
return ret
|
||||
|
||||
if check:
|
||||
validate(*args)
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
rows, cols = self.args[0].shape
|
||||
for mat in self.args[1:]:
|
||||
rows *= mat.rows
|
||||
cols *= mat.cols
|
||||
return (rows, cols)
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
result = 1
|
||||
for mat in reversed(self.args):
|
||||
i, m = divmod(i, mat.rows)
|
||||
j, n = divmod(j, mat.cols)
|
||||
result *= mat[m, n]
|
||||
return result
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return KroneckerProduct(*list(map(adjoint, self.args))).doit()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return KroneckerProduct(*[a.conjugate() for a in self.args]).doit()
|
||||
|
||||
def _eval_transpose(self):
|
||||
return KroneckerProduct(*list(map(transpose, self.args))).doit()
|
||||
|
||||
def _eval_trace(self):
|
||||
from .trace import trace
|
||||
return Mul(*[trace(a) for a in self.args])
|
||||
|
||||
def _eval_determinant(self):
|
||||
from .determinant import det, Determinant
|
||||
if not all(a.is_square for a in self.args):
|
||||
return Determinant(self)
|
||||
|
||||
m = self.rows
|
||||
return Mul(*[det(a)**(m/a.rows) for a in self.args])
|
||||
|
||||
def _eval_inverse(self):
|
||||
try:
|
||||
return KroneckerProduct(*[a.inverse() for a in self.args])
|
||||
except ShapeError:
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
return Inverse(self)
|
||||
|
||||
def structurally_equal(self, other):
|
||||
'''Determine whether two matrices have the same Kronecker product structure
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import KroneckerProduct, MatrixSymbol, symbols
|
||||
>>> m, n = symbols(r'm, n', integer=True)
|
||||
>>> A = MatrixSymbol('A', m, m)
|
||||
>>> B = MatrixSymbol('B', n, n)
|
||||
>>> C = MatrixSymbol('C', m, m)
|
||||
>>> D = MatrixSymbol('D', n, n)
|
||||
>>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(C, D))
|
||||
True
|
||||
>>> KroneckerProduct(A, B).structurally_equal(KroneckerProduct(D, C))
|
||||
False
|
||||
>>> KroneckerProduct(A, B).structurally_equal(C)
|
||||
False
|
||||
'''
|
||||
# Inspired by BlockMatrix
|
||||
return (isinstance(other, KroneckerProduct)
|
||||
and self.shape == other.shape
|
||||
and len(self.args) == len(other.args)
|
||||
and all(a.shape == b.shape for (a, b) in zip(self.args, other.args)))
|
||||
|
||||
def has_matching_shape(self, other):
|
||||
'''Determine whether two matrices have the appropriate structure to bring matrix
|
||||
multiplication inside the KroneckerProdut
|
||||
|
||||
Examples
|
||||
========
|
||||
>>> from sympy import KroneckerProduct, MatrixSymbol, symbols
|
||||
>>> m, n = symbols(r'm, n', integer=True)
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(B, A))
|
||||
True
|
||||
>>> KroneckerProduct(A, B).has_matching_shape(KroneckerProduct(A, B))
|
||||
False
|
||||
>>> KroneckerProduct(A, B).has_matching_shape(A)
|
||||
False
|
||||
'''
|
||||
return (isinstance(other, KroneckerProduct)
|
||||
and self.cols == other.rows
|
||||
and len(self.args) == len(other.args)
|
||||
and all(a.cols == b.rows for (a, b) in zip(self.args, other.args)))
|
||||
|
||||
def _eval_expand_kroneckerproduct(self, **hints):
|
||||
return flatten(canon(typed({KroneckerProduct: distribute(KroneckerProduct, MatAdd)}))(self))
|
||||
|
||||
def _kronecker_add(self, other):
|
||||
if self.structurally_equal(other):
|
||||
return self.__class__(*[a + b for (a, b) in zip(self.args, other.args)])
|
||||
else:
|
||||
return self + other
|
||||
|
||||
def _kronecker_mul(self, other):
|
||||
if self.has_matching_shape(other):
|
||||
return self.__class__(*[a*b for (a, b) in zip(self.args, other.args)])
|
||||
else:
|
||||
return self * other
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = [arg.doit(**hints) for arg in self.args]
|
||||
else:
|
||||
args = self.args
|
||||
return canonicalize(KroneckerProduct(*args))
|
||||
|
||||
|
||||
def validate(*args):
|
||||
if not all(arg.is_Matrix for arg in args):
|
||||
raise TypeError("Mix of Matrix and Scalar symbols")
|
||||
|
||||
|
||||
# rules
|
||||
|
||||
def extract_commutative(kron):
|
||||
c_part = []
|
||||
nc_part = []
|
||||
for arg in kron.args:
|
||||
c, nc = arg.args_cnc()
|
||||
c_part.extend(c)
|
||||
nc_part.append(Mul._from_args(nc))
|
||||
|
||||
c_part = Mul(*c_part)
|
||||
if c_part != 1:
|
||||
return c_part*KroneckerProduct(*nc_part)
|
||||
return kron
|
||||
|
||||
|
||||
def matrix_kronecker_product(*matrices):
|
||||
"""Compute the Kronecker product of a sequence of SymPy Matrices.
|
||||
|
||||
This is the standard Kronecker product of matrices [1].
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
matrices : tuple of MatrixBase instances
|
||||
The matrices to take the Kronecker product of.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
matrix : MatrixBase
|
||||
The Kronecker product matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.matrices.expressions.kronecker import (
|
||||
... matrix_kronecker_product)
|
||||
|
||||
>>> m1 = Matrix([[1,2],[3,4]])
|
||||
>>> m2 = Matrix([[1,0],[0,1]])
|
||||
>>> matrix_kronecker_product(m1, m2)
|
||||
Matrix([
|
||||
[1, 0, 2, 0],
|
||||
[0, 1, 0, 2],
|
||||
[3, 0, 4, 0],
|
||||
[0, 3, 0, 4]])
|
||||
>>> matrix_kronecker_product(m2, m1)
|
||||
Matrix([
|
||||
[1, 2, 0, 0],
|
||||
[3, 4, 0, 0],
|
||||
[0, 0, 1, 2],
|
||||
[0, 0, 3, 4]])
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Kronecker_product
|
||||
"""
|
||||
# Make sure we have a sequence of Matrices
|
||||
if not all(isinstance(m, MatrixBase) for m in matrices):
|
||||
raise TypeError(
|
||||
'Sequence of Matrices expected, got: %s' % repr(matrices)
|
||||
)
|
||||
|
||||
# Pull out the first element in the product.
|
||||
matrix_expansion = matrices[-1]
|
||||
# Do the kronecker product working from right to left.
|
||||
for mat in reversed(matrices[:-1]):
|
||||
rows = mat.rows
|
||||
cols = mat.cols
|
||||
# Go through each row appending kronecker product to.
|
||||
# running matrix_expansion.
|
||||
for i in range(rows):
|
||||
start = matrix_expansion*mat[i*cols]
|
||||
# Go through each column joining each item
|
||||
for j in range(cols - 1):
|
||||
start = start.row_join(
|
||||
matrix_expansion*mat[i*cols + j + 1]
|
||||
)
|
||||
# If this is the first element, make it the start of the
|
||||
# new row.
|
||||
if i == 0:
|
||||
next = start
|
||||
else:
|
||||
next = next.col_join(start)
|
||||
matrix_expansion = next
|
||||
|
||||
MatrixClass = max(matrices, key=lambda M: M._class_priority).__class__
|
||||
if isinstance(matrix_expansion, MatrixClass):
|
||||
return matrix_expansion
|
||||
else:
|
||||
return MatrixClass(matrix_expansion)
|
||||
|
||||
|
||||
def explicit_kronecker_product(kron):
|
||||
# Make sure we have a sequence of Matrices
|
||||
if not all(isinstance(m, MatrixBase) for m in kron.args):
|
||||
return kron
|
||||
|
||||
return matrix_kronecker_product(*kron.args)
|
||||
|
||||
|
||||
rules = (unpack,
|
||||
explicit_kronecker_product,
|
||||
flatten,
|
||||
extract_commutative)
|
||||
|
||||
canonicalize = exhaust(condition(lambda x: isinstance(x, KroneckerProduct),
|
||||
do_one(*rules)))
|
||||
|
||||
|
||||
def _kronecker_dims_key(expr):
|
||||
if isinstance(expr, KroneckerProduct):
|
||||
return tuple(a.shape for a in expr.args)
|
||||
else:
|
||||
return (0,)
|
||||
|
||||
|
||||
def kronecker_mat_add(expr):
|
||||
args = sift(expr.args, _kronecker_dims_key)
|
||||
nonkrons = args.pop((0,), None)
|
||||
if not args:
|
||||
return expr
|
||||
|
||||
krons = [reduce(lambda x, y: x._kronecker_add(y), group)
|
||||
for group in args.values()]
|
||||
|
||||
if not nonkrons:
|
||||
return MatAdd(*krons)
|
||||
else:
|
||||
return MatAdd(*krons) + nonkrons
|
||||
|
||||
|
||||
def kronecker_mat_mul(expr):
|
||||
# modified from block matrix code
|
||||
factor, matrices = expr.as_coeff_matrices()
|
||||
|
||||
i = 0
|
||||
while i < len(matrices) - 1:
|
||||
A, B = matrices[i:i+2]
|
||||
if isinstance(A, KroneckerProduct) and isinstance(B, KroneckerProduct):
|
||||
matrices[i] = A._kronecker_mul(B)
|
||||
matrices.pop(i+1)
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return factor*MatMul(*matrices)
|
||||
|
||||
|
||||
def kronecker_mat_pow(expr):
|
||||
if isinstance(expr.base, KroneckerProduct) and all(a.is_square for a in expr.base.args):
|
||||
return KroneckerProduct(*[MatPow(a, expr.exp) for a in expr.base.args])
|
||||
else:
|
||||
return expr
|
||||
|
||||
|
||||
def combine_kronecker(expr):
|
||||
"""Combine KronekeckerProduct with expression.
|
||||
|
||||
If possible write operations on KroneckerProducts of compatible shapes
|
||||
as a single KroneckerProduct.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices.expressions import combine_kronecker
|
||||
>>> from sympy import MatrixSymbol, KroneckerProduct, symbols
|
||||
>>> m, n = symbols(r'm, n', integer=True)
|
||||
>>> A = MatrixSymbol('A', m, n)
|
||||
>>> B = MatrixSymbol('B', n, m)
|
||||
>>> combine_kronecker(KroneckerProduct(A, B)*KroneckerProduct(B, A))
|
||||
KroneckerProduct(A*B, B*A)
|
||||
>>> combine_kronecker(KroneckerProduct(A, B)+KroneckerProduct(B.T, A.T))
|
||||
KroneckerProduct(A + B.T, B + A.T)
|
||||
>>> C = MatrixSymbol('C', n, n)
|
||||
>>> D = MatrixSymbol('D', m, m)
|
||||
>>> combine_kronecker(KroneckerProduct(C, D)**m)
|
||||
KroneckerProduct(C**m, D**m)
|
||||
"""
|
||||
def haskron(expr):
|
||||
return isinstance(expr, MatrixExpr) and expr.has(KroneckerProduct)
|
||||
|
||||
rule = exhaust(
|
||||
bottom_up(exhaust(condition(haskron, typed(
|
||||
{MatAdd: kronecker_mat_add,
|
||||
MatMul: kronecker_mat_mul,
|
||||
MatPow: kronecker_mat_pow})))))
|
||||
result = rule(expr)
|
||||
doit = getattr(result, 'doit', None)
|
||||
if doit is not None:
|
||||
return doit()
|
||||
else:
|
||||
return result
|
||||
@@ -0,0 +1,155 @@
|
||||
from functools import reduce
|
||||
import operator
|
||||
|
||||
from sympy.core import Basic, sympify
|
||||
from sympy.core.add import add, Add, _could_extract_minus_sign
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.functions import adjoint
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
from sympy.strategies import (rm_id, unpack, flatten, sort, condition,
|
||||
exhaust, do_one, glom)
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, GenericZeroMatrix
|
||||
from sympy.matrices.expressions._shape import validate_matadd_integer as validate
|
||||
from sympy.utilities.iterables import sift
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
|
||||
# XXX: MatAdd should perhaps not subclass directly from Add
|
||||
class MatAdd(MatrixExpr, Add):
|
||||
"""A Sum of Matrix Expressions
|
||||
|
||||
MatAdd inherits from and operates like SymPy Add
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatAdd, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 5)
|
||||
>>> C = MatrixSymbol('C', 5, 5)
|
||||
>>> MatAdd(A, B, C)
|
||||
A + B + C
|
||||
"""
|
||||
is_MatAdd = True
|
||||
|
||||
identity = GenericZeroMatrix()
|
||||
|
||||
def __new__(cls, *args, evaluate=False, check=None, _sympify=True):
|
||||
if not args:
|
||||
return cls.identity
|
||||
|
||||
# This must be removed aggressively in the constructor to avoid
|
||||
# TypeErrors from GenericZeroMatrix().shape
|
||||
args = list(filter(lambda i: cls.identity != i, args))
|
||||
if _sympify:
|
||||
args = list(map(sympify, args))
|
||||
|
||||
if not all(isinstance(arg, MatrixExpr) for arg in args):
|
||||
raise TypeError("Mix of Matrix and Scalar symbols")
|
||||
|
||||
obj = Basic.__new__(cls, *args)
|
||||
|
||||
if check is not None:
|
||||
sympy_deprecation_warning(
|
||||
"Passing check to MatAdd is deprecated and the check argument will be removed in a future version.",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target='remove-check-argument-from-matrix-operations')
|
||||
|
||||
if check is not False:
|
||||
validate(*args)
|
||||
|
||||
if evaluate:
|
||||
obj = cls._evaluate(obj)
|
||||
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def _evaluate(cls, expr):
|
||||
return canonicalize(expr)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0].shape
|
||||
|
||||
def could_extract_minus_sign(self):
|
||||
return _could_extract_minus_sign(self)
|
||||
|
||||
def expand(self, **kwargs):
|
||||
expanded = super(MatAdd, self).expand(**kwargs)
|
||||
return self._evaluate(expanded)
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return Add(*[arg._entry(i, j, **kwargs) for arg in self.args])
|
||||
|
||||
def _eval_transpose(self):
|
||||
return MatAdd(*[transpose(arg) for arg in self.args]).doit()
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return MatAdd(*[adjoint(arg) for arg in self.args]).doit()
|
||||
|
||||
def _eval_trace(self):
|
||||
from .trace import trace
|
||||
return Add(*[trace(arg) for arg in self.args]).doit()
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = [arg.doit(**hints) for arg in self.args]
|
||||
else:
|
||||
args = self.args
|
||||
return canonicalize(MatAdd(*args))
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
add_lines = [arg._eval_derivative_matrix_lines(x) for arg in self.args]
|
||||
return [j for i in add_lines for j in i]
|
||||
|
||||
add.register_handlerclass((Add, MatAdd), MatAdd)
|
||||
|
||||
|
||||
factor_of = lambda arg: arg.as_coeff_mmul()[0]
|
||||
matrix_of = lambda arg: unpack(arg.as_coeff_mmul()[1])
|
||||
def combine(cnt, mat):
|
||||
if cnt == 1:
|
||||
return mat
|
||||
else:
|
||||
return cnt * mat
|
||||
|
||||
|
||||
def merge_explicit(matadd):
|
||||
""" Merge explicit MatrixBase arguments
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, eye, Matrix, MatAdd, pprint
|
||||
>>> from sympy.matrices.expressions.matadd import merge_explicit
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = eye(2)
|
||||
>>> C = Matrix([[1, 2], [3, 4]])
|
||||
>>> X = MatAdd(A, B, C)
|
||||
>>> pprint(X)
|
||||
[1 0] [1 2]
|
||||
A + [ ] + [ ]
|
||||
[0 1] [3 4]
|
||||
>>> pprint(merge_explicit(X))
|
||||
[2 2]
|
||||
A + [ ]
|
||||
[3 5]
|
||||
"""
|
||||
groups = sift(matadd.args, lambda arg: isinstance(arg, MatrixBase))
|
||||
if len(groups[True]) > 1:
|
||||
return MatAdd(*(groups[False] + [reduce(operator.add, groups[True])]))
|
||||
else:
|
||||
return matadd
|
||||
|
||||
|
||||
rules = (rm_id(lambda x: x == 0 or isinstance(x, ZeroMatrix)),
|
||||
unpack,
|
||||
flatten,
|
||||
glom(matrix_of, factor_of, combine),
|
||||
merge_explicit,
|
||||
sort(default_sort_key))
|
||||
|
||||
canonicalize = exhaust(condition(lambda x: isinstance(x, MatAdd),
|
||||
do_one(*rules)))
|
||||
@@ -0,0 +1,888 @@
|
||||
from __future__ import annotations
|
||||
from functools import wraps
|
||||
|
||||
from sympy.core import S, Integer, Basic, Mul, Add
|
||||
from sympy.core.assumptions import check_assumptions
|
||||
from sympy.core.decorators import call_highest_priority
|
||||
from sympy.core.expr import Expr, ExprBuilder
|
||||
from sympy.core.logic import FuzzyBool
|
||||
from sympy.core.symbol import Str, Dummy, symbols, Symbol
|
||||
from sympy.core.sympify import SympifyError, _sympify
|
||||
from sympy.external.gmpy import SYMPY_INTS
|
||||
from sympy.functions import conjugate, adjoint
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.kind import MatrixKind
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.multipledispatch import dispatch
|
||||
from sympy.utilities.misc import filldedent
|
||||
|
||||
|
||||
def _sympifyit(arg, retval=None):
|
||||
# This version of _sympifyit sympifies MutableMatrix objects
|
||||
def deco(func):
|
||||
@wraps(func)
|
||||
def __sympifyit_wrapper(a, b):
|
||||
try:
|
||||
b = _sympify(b)
|
||||
return func(a, b)
|
||||
except SympifyError:
|
||||
return retval
|
||||
|
||||
return __sympifyit_wrapper
|
||||
|
||||
return deco
|
||||
|
||||
|
||||
class MatrixExpr(Expr):
|
||||
"""Superclass for Matrix Expressions
|
||||
|
||||
MatrixExprs represent abstract matrices, linear transformations represented
|
||||
within a particular basis.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> y = MatrixSymbol('y', 3, 1)
|
||||
>>> x = (A.T*A).I * A * y
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
MatrixSymbol, MatAdd, MatMul, Transpose, Inverse
|
||||
"""
|
||||
__slots__: tuple[str, ...] = ()
|
||||
|
||||
# Should not be considered iterable by the
|
||||
# sympy.utilities.iterables.iterable function. Subclass that actually are
|
||||
# iterable (i.e., explicit matrices) should set this to True.
|
||||
_iterable = False
|
||||
|
||||
_op_priority = 11.0
|
||||
|
||||
is_Matrix: bool = True
|
||||
is_MatrixExpr: bool = True
|
||||
is_Identity: FuzzyBool = None
|
||||
is_Inverse = False
|
||||
is_Transpose = False
|
||||
is_ZeroMatrix = False
|
||||
is_MatAdd = False
|
||||
is_MatMul = False
|
||||
|
||||
is_commutative = False
|
||||
is_number = False
|
||||
is_symbol = False
|
||||
is_scalar = False
|
||||
|
||||
kind: MatrixKind = MatrixKind()
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
args = map(_sympify, args)
|
||||
return Basic.__new__(cls, *args, **kwargs)
|
||||
|
||||
# The following is adapted from the core Expr object
|
||||
|
||||
@property
|
||||
def shape(self) -> tuple[Expr, Expr]:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def _add_handler(self):
|
||||
return MatAdd
|
||||
|
||||
@property
|
||||
def _mul_handler(self):
|
||||
return MatMul
|
||||
|
||||
def __neg__(self):
|
||||
return MatMul(S.NegativeOne, self).doit()
|
||||
|
||||
def __abs__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__radd__')
|
||||
def __add__(self, other):
|
||||
return MatAdd(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__add__')
|
||||
def __radd__(self, other):
|
||||
return MatAdd(other, self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rsub__')
|
||||
def __sub__(self, other):
|
||||
return MatAdd(self, -other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__sub__')
|
||||
def __rsub__(self, other):
|
||||
return MatAdd(other, -self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rmul__')
|
||||
def __mul__(self, other):
|
||||
return MatMul(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rmul__')
|
||||
def __matmul__(self, other):
|
||||
return MatMul(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__mul__')
|
||||
def __rmul__(self, other):
|
||||
return MatMul(other, self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__mul__')
|
||||
def __rmatmul__(self, other):
|
||||
return MatMul(other, self).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rpow__')
|
||||
def __pow__(self, other):
|
||||
return MatPow(self, other).doit()
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__pow__')
|
||||
def __rpow__(self, other):
|
||||
raise NotImplementedError("Matrix Power not defined")
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__rtruediv__')
|
||||
def __truediv__(self, other):
|
||||
return self * other**S.NegativeOne
|
||||
|
||||
@_sympifyit('other', NotImplemented)
|
||||
@call_highest_priority('__truediv__')
|
||||
def __rtruediv__(self, other):
|
||||
raise NotImplementedError()
|
||||
#return MatMul(other, Pow(self, S.NegativeOne))
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
return self.shape[0]
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
return self.shape[1]
|
||||
|
||||
@property
|
||||
def is_square(self) -> bool | None:
|
||||
rows, cols = self.shape
|
||||
if isinstance(rows, Integer) and isinstance(cols, Integer):
|
||||
return rows == cols
|
||||
if rows == cols:
|
||||
return True
|
||||
return None
|
||||
|
||||
def _eval_conjugate(self):
|
||||
from sympy.matrices.expressions.adjoint import Adjoint
|
||||
return Adjoint(Transpose(self))
|
||||
|
||||
def as_real_imag(self, deep=True, **hints):
|
||||
return self._eval_as_real_imag()
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
real = S.Half * (self + self._eval_conjugate())
|
||||
im = (self - self._eval_conjugate())/(2*S.ImaginaryUnit)
|
||||
return (real, im)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return Inverse(self)
|
||||
|
||||
def _eval_determinant(self):
|
||||
return Determinant(self)
|
||||
|
||||
def _eval_transpose(self):
|
||||
return Transpose(self)
|
||||
|
||||
def _eval_trace(self):
|
||||
return None
|
||||
|
||||
def _eval_power(self, exp):
|
||||
"""
|
||||
Override this in sub-classes to implement simplification of powers. The cases where the exponent
|
||||
is -1, 0, 1 are already covered in MatPow.doit(), so implementations can exclude these cases.
|
||||
"""
|
||||
return MatPow(self, exp)
|
||||
|
||||
def _eval_simplify(self, **kwargs):
|
||||
if self.is_Atom:
|
||||
return self
|
||||
else:
|
||||
from sympy.simplify import simplify
|
||||
return self.func(*[simplify(x, **kwargs) for x in self.args])
|
||||
|
||||
def _eval_adjoint(self):
|
||||
from sympy.matrices.expressions.adjoint import Adjoint
|
||||
return Adjoint(self)
|
||||
|
||||
def _eval_derivative_n_times(self, x, n):
|
||||
return Basic._eval_derivative_n_times(self, x, n)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
# `x` is a scalar:
|
||||
if self.has(x):
|
||||
# See if there are other methods using it:
|
||||
return super()._eval_derivative(x)
|
||||
else:
|
||||
return ZeroMatrix(*self.shape)
|
||||
|
||||
@classmethod
|
||||
def _check_dim(cls, dim):
|
||||
"""Helper function to check invalid matrix dimensions"""
|
||||
ok = not dim.is_Float and check_assumptions(
|
||||
dim, integer=True, nonnegative=True)
|
||||
if ok is False:
|
||||
raise ValueError(
|
||||
"The dimension specification {} should be "
|
||||
"a nonnegative integer.".format(dim))
|
||||
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
raise NotImplementedError(
|
||||
"Indexing not implemented for %s" % self.__class__.__name__)
|
||||
|
||||
def adjoint(self):
|
||||
return adjoint(self)
|
||||
|
||||
def as_coeff_Mul(self, rational=False):
|
||||
"""Efficiently extract the coefficient of a product."""
|
||||
return S.One, self
|
||||
|
||||
def conjugate(self):
|
||||
return conjugate(self)
|
||||
|
||||
def transpose(self):
|
||||
from sympy.matrices.expressions.transpose import transpose
|
||||
return transpose(self)
|
||||
|
||||
@property
|
||||
def T(self):
|
||||
'''Matrix transposition'''
|
||||
return self.transpose()
|
||||
|
||||
def inverse(self):
|
||||
if self.is_square is False:
|
||||
raise NonSquareMatrixError('Inverse of non-square matrix')
|
||||
return self._eval_inverse()
|
||||
|
||||
def inv(self):
|
||||
return self.inverse()
|
||||
|
||||
def det(self):
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
return det(self)
|
||||
|
||||
@property
|
||||
def I(self):
|
||||
return self.inverse()
|
||||
|
||||
def valid_index(self, i, j):
|
||||
def is_valid(idx):
|
||||
return isinstance(idx, (int, Integer, Symbol, Expr))
|
||||
return (is_valid(i) and is_valid(j) and
|
||||
(self.rows is None or
|
||||
(i >= -self.rows) != False and (i < self.rows) != False) and
|
||||
(j >= -self.cols) != False and (j < self.cols) != False)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if not isinstance(key, tuple) and isinstance(key, slice):
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
return MatrixSlice(self, key, (0, None, 1))
|
||||
if isinstance(key, tuple) and len(key) == 2:
|
||||
i, j = key
|
||||
if isinstance(i, slice) or isinstance(j, slice):
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
return MatrixSlice(self, i, j)
|
||||
i, j = _sympify(i), _sympify(j)
|
||||
if self.valid_index(i, j) != False:
|
||||
return self._entry(i, j)
|
||||
else:
|
||||
raise IndexError("Invalid indices (%s, %s)" % (i, j))
|
||||
elif isinstance(key, (SYMPY_INTS, Integer)):
|
||||
# row-wise decomposition of matrix
|
||||
rows, cols = self.shape
|
||||
# allow single indexing if number of columns is known
|
||||
if not isinstance(cols, Integer):
|
||||
raise IndexError(filldedent('''
|
||||
Single indexing is only supported when the number
|
||||
of columns is known.'''))
|
||||
key = _sympify(key)
|
||||
i = key // cols
|
||||
j = key % cols
|
||||
if self.valid_index(i, j) != False:
|
||||
return self._entry(i, j)
|
||||
else:
|
||||
raise IndexError("Invalid index %s" % key)
|
||||
elif isinstance(key, (Symbol, Expr)):
|
||||
raise IndexError(filldedent('''
|
||||
Only integers may be used when addressing the matrix
|
||||
with a single index.'''))
|
||||
raise IndexError("Invalid index, wanted %s[i,j]" % self)
|
||||
|
||||
def _is_shape_symbolic(self) -> bool:
|
||||
return (not isinstance(self.rows, (SYMPY_INTS, Integer))
|
||||
or not isinstance(self.cols, (SYMPY_INTS, Integer)))
|
||||
|
||||
def as_explicit(self):
|
||||
"""
|
||||
Returns a dense Matrix with elements represented explicitly
|
||||
|
||||
Returns an object of type ImmutableDenseMatrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Identity
|
||||
>>> I = Identity(3)
|
||||
>>> I
|
||||
I
|
||||
>>> I.as_explicit()
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
See Also
|
||||
========
|
||||
as_mutable: returns mutable Matrix type
|
||||
|
||||
"""
|
||||
if self._is_shape_symbolic():
|
||||
raise ValueError(
|
||||
'Matrix with symbolic shape '
|
||||
'cannot be represented explicitly.')
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
return ImmutableDenseMatrix([[self[i, j]
|
||||
for j in range(self.cols)]
|
||||
for i in range(self.rows)])
|
||||
|
||||
def as_mutable(self):
|
||||
"""
|
||||
Returns a dense, mutable matrix with elements represented explicitly
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Identity
|
||||
>>> I = Identity(3)
|
||||
>>> I
|
||||
I
|
||||
>>> I.shape
|
||||
(3, 3)
|
||||
>>> I.as_mutable()
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
|
||||
See Also
|
||||
========
|
||||
as_explicit: returns ImmutableDenseMatrix
|
||||
"""
|
||||
return self.as_explicit().as_mutable()
|
||||
|
||||
def __array__(self, dtype=object, copy=None):
|
||||
if copy is not None and not copy:
|
||||
raise TypeError("Cannot implement copy=False when converting Matrix to ndarray")
|
||||
from numpy import empty
|
||||
a = empty(self.shape, dtype=object)
|
||||
for i in range(self.rows):
|
||||
for j in range(self.cols):
|
||||
a[i, j] = self[i, j]
|
||||
return a
|
||||
|
||||
def equals(self, other):
|
||||
"""
|
||||
Test elementwise equality between matrices, potentially of different
|
||||
types
|
||||
|
||||
>>> from sympy import Identity, eye
|
||||
>>> Identity(3).equals(eye(3))
|
||||
True
|
||||
"""
|
||||
return self.as_explicit().equals(other)
|
||||
|
||||
def canonicalize(self):
|
||||
return self
|
||||
|
||||
def as_coeff_mmul(self):
|
||||
return S.One, MatMul(self)
|
||||
|
||||
@staticmethod
|
||||
def from_index_summation(expr, first_index=None, last_index=None, dimensions=None):
|
||||
r"""
|
||||
Parse expression of matrices with explicitly summed indices into a
|
||||
matrix expression without indices, if possible.
|
||||
|
||||
This transformation expressed in mathematical notation:
|
||||
|
||||
`\sum_{j=0}^{N-1} A_{i,j} B_{j,k} \Longrightarrow \mathbf{A}\cdot \mathbf{B}`
|
||||
|
||||
Optional parameter ``first_index``: specify which free index to use as
|
||||
the index starting the expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, MatrixExpr, Sum
|
||||
>>> from sympy.abc import i, j, k, l, N
|
||||
>>> A = MatrixSymbol("A", N, N)
|
||||
>>> B = MatrixSymbol("B", N, N)
|
||||
>>> expr = Sum(A[i, j]*B[j, k], (j, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
A*B
|
||||
|
||||
Transposition is detected:
|
||||
|
||||
>>> expr = Sum(A[j, i]*B[j, k], (j, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
A.T*B
|
||||
|
||||
Detect the trace:
|
||||
|
||||
>>> expr = Sum(A[i, i], (i, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
Trace(A)
|
||||
|
||||
More complicated expressions:
|
||||
|
||||
>>> expr = Sum(A[i, j]*B[k, j]*A[l, k], (j, 0, N-1), (k, 0, N-1))
|
||||
>>> MatrixExpr.from_index_summation(expr)
|
||||
A*B.T*A.T
|
||||
"""
|
||||
from sympy.tensor.array.expressions.from_indexed_to_array import convert_indexed_to_array
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
first_indices = []
|
||||
if first_index is not None:
|
||||
first_indices.append(first_index)
|
||||
if last_index is not None:
|
||||
first_indices.append(last_index)
|
||||
arr = convert_indexed_to_array(expr, first_indices=first_indices)
|
||||
return convert_array_to_matrix(arr)
|
||||
|
||||
def applyfunc(self, func):
|
||||
from .applyfunc import ElementwiseApplyFunction
|
||||
return ElementwiseApplyFunction(func, self)
|
||||
|
||||
|
||||
@dispatch(MatrixExpr, Expr)
|
||||
def _eval_is_eq(lhs, rhs): # noqa:F811
|
||||
return False
|
||||
|
||||
@dispatch(MatrixExpr, MatrixExpr) # type: ignore
|
||||
def _eval_is_eq(lhs, rhs): # noqa:F811
|
||||
if lhs.shape != rhs.shape:
|
||||
return False
|
||||
if (lhs - rhs).is_ZeroMatrix:
|
||||
return True
|
||||
|
||||
def get_postprocessor(cls):
|
||||
def _postprocessor(expr):
|
||||
# To avoid circular imports, we can't have MatMul/MatAdd on the top level
|
||||
mat_class = {Mul: MatMul, Add: MatAdd}[cls]
|
||||
nonmatrices = []
|
||||
matrices = []
|
||||
for term in expr.args:
|
||||
if isinstance(term, MatrixExpr):
|
||||
matrices.append(term)
|
||||
else:
|
||||
nonmatrices.append(term)
|
||||
|
||||
if not matrices:
|
||||
return cls._from_args(nonmatrices)
|
||||
|
||||
if nonmatrices:
|
||||
if cls == Mul:
|
||||
for i in range(len(matrices)):
|
||||
if not matrices[i].is_MatrixExpr:
|
||||
# If one of the matrices explicit, absorb the scalar into it
|
||||
# (doit will combine all explicit matrices into one, so it
|
||||
# doesn't matter which)
|
||||
matrices[i] = matrices[i].__mul__(cls._from_args(nonmatrices))
|
||||
nonmatrices = []
|
||||
break
|
||||
|
||||
else:
|
||||
# Maintain the ability to create Add(scalar, matrix) without
|
||||
# raising an exception. That way different algorithms can
|
||||
# replace matrix expressions with non-commutative symbols to
|
||||
# manipulate them like non-commutative scalars.
|
||||
return cls._from_args(nonmatrices + [mat_class(*matrices).doit(deep=False)])
|
||||
|
||||
if mat_class == MatAdd:
|
||||
return mat_class(*matrices).doit(deep=False)
|
||||
return mat_class(cls._from_args(nonmatrices), *matrices).doit(deep=False)
|
||||
return _postprocessor
|
||||
|
||||
|
||||
Basic._constructor_postprocessor_mapping[MatrixExpr] = {
|
||||
"Mul": [get_postprocessor(Mul)],
|
||||
"Add": [get_postprocessor(Add)],
|
||||
}
|
||||
|
||||
|
||||
def _matrix_derivative(expr, x, old_algorithm=False):
|
||||
|
||||
if isinstance(expr, MatrixBase) or isinstance(x, MatrixBase):
|
||||
# Do not use array expressions for explicit matrices:
|
||||
old_algorithm = True
|
||||
|
||||
if old_algorithm:
|
||||
return _matrix_derivative_old_algorithm(expr, x)
|
||||
|
||||
from sympy.tensor.array.expressions.from_matrix_to_array import convert_matrix_to_array
|
||||
from sympy.tensor.array.expressions.arrayexpr_derivatives import array_derive
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
|
||||
array_expr = convert_matrix_to_array(expr)
|
||||
diff_array_expr = array_derive(array_expr, x)
|
||||
diff_matrix_expr = convert_array_to_matrix(diff_array_expr)
|
||||
return diff_matrix_expr
|
||||
|
||||
|
||||
def _matrix_derivative_old_algorithm(expr, x):
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
lines = expr._eval_derivative_matrix_lines(x)
|
||||
|
||||
parts = [i.build() for i in lines]
|
||||
|
||||
from sympy.tensor.array.expressions.from_array_to_matrix import convert_array_to_matrix
|
||||
|
||||
parts = [[convert_array_to_matrix(j) for j in i] for i in parts]
|
||||
|
||||
def _get_shape(elem):
|
||||
if isinstance(elem, MatrixExpr):
|
||||
return elem.shape
|
||||
return 1, 1
|
||||
|
||||
def get_rank(parts):
|
||||
return sum(j not in (1, None) for i in parts for j in _get_shape(i))
|
||||
|
||||
ranks = [get_rank(i) for i in parts]
|
||||
rank = ranks[0]
|
||||
|
||||
def contract_one_dims(parts):
|
||||
if len(parts) == 1:
|
||||
return parts[0]
|
||||
else:
|
||||
p1, p2 = parts[:2]
|
||||
if p2.is_Matrix:
|
||||
p2 = p2.T
|
||||
if p1 == Identity(1):
|
||||
pbase = p2
|
||||
elif p2 == Identity(1):
|
||||
pbase = p1
|
||||
else:
|
||||
pbase = p1*p2
|
||||
if len(parts) == 2:
|
||||
return pbase
|
||||
else: # len(parts) > 2
|
||||
if pbase.is_Matrix:
|
||||
raise ValueError("")
|
||||
return pbase*Mul.fromiter(parts[2:])
|
||||
|
||||
if rank <= 2:
|
||||
return Add.fromiter([contract_one_dims(i) for i in parts])
|
||||
|
||||
return ArrayDerivative(expr, x)
|
||||
|
||||
|
||||
class MatrixElement(Expr):
|
||||
parent = property(lambda self: self.args[0])
|
||||
i = property(lambda self: self.args[1])
|
||||
j = property(lambda self: self.args[2])
|
||||
_diff_wrt = True
|
||||
is_symbol = True
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, name, n, m):
|
||||
n, m = map(_sympify, (n, m))
|
||||
if isinstance(name, str):
|
||||
name = Symbol(name)
|
||||
else:
|
||||
if isinstance(name, MatrixBase):
|
||||
if n.is_Integer and m.is_Integer:
|
||||
return name[n, m]
|
||||
name = _sympify(name) # change mutable into immutable
|
||||
else:
|
||||
name = _sympify(name)
|
||||
if not isinstance(name.kind, MatrixKind):
|
||||
raise TypeError("First argument of MatrixElement should be a matrix")
|
||||
if not getattr(name, 'valid_index', lambda n, m: True)(n, m):
|
||||
raise IndexError('indices out of range')
|
||||
obj = Expr.__new__(cls, name, n, m)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def symbol(self):
|
||||
return self.args[0]
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = [arg.doit(**hints) for arg in self.args]
|
||||
else:
|
||||
args = self.args
|
||||
return args[0][args[1], args[2]]
|
||||
|
||||
@property
|
||||
def indices(self):
|
||||
return self.args[1:]
|
||||
|
||||
def _eval_derivative(self, v):
|
||||
|
||||
if not isinstance(v, MatrixElement):
|
||||
return self.parent.diff(v)[self.i, self.j]
|
||||
|
||||
M = self.args[0]
|
||||
|
||||
m, n = self.parent.shape
|
||||
|
||||
if M == v.args[0]:
|
||||
return KroneckerDelta(self.args[1], v.args[1], (0, m-1)) * \
|
||||
KroneckerDelta(self.args[2], v.args[2], (0, n-1))
|
||||
|
||||
if isinstance(M, Inverse):
|
||||
from sympy.concrete.summations import Sum
|
||||
i, j = self.args[1:]
|
||||
i1, i2 = symbols("z1, z2", cls=Dummy)
|
||||
Y = M.args[0]
|
||||
r1, r2 = Y.shape
|
||||
return -Sum(M[i, i1]*Y[i1, i2].diff(v)*M[i2, j], (i1, 0, r1-1), (i2, 0, r2-1))
|
||||
|
||||
if self.has(v.args[0]):
|
||||
return None
|
||||
|
||||
return S.Zero
|
||||
|
||||
|
||||
class MatrixSymbol(MatrixExpr):
|
||||
"""Symbolic representation of a Matrix object
|
||||
|
||||
Creates a SymPy Symbol to represent a Matrix. This matrix has a shape and
|
||||
can be included in Matrix Expressions
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Identity
|
||||
>>> A = MatrixSymbol('A', 3, 4) # A 3 by 4 Matrix
|
||||
>>> B = MatrixSymbol('B', 4, 3) # A 4 by 3 Matrix
|
||||
>>> A.shape
|
||||
(3, 4)
|
||||
>>> 2*A*B + Identity(3)
|
||||
I + 2*A*B
|
||||
"""
|
||||
is_commutative = False
|
||||
is_symbol = True
|
||||
_diff_wrt = True
|
||||
|
||||
def __new__(cls, name, n, m):
|
||||
n, m = _sympify(n), _sympify(m)
|
||||
|
||||
cls._check_dim(m)
|
||||
cls._check_dim(n)
|
||||
|
||||
if isinstance(name, str):
|
||||
name = Str(name)
|
||||
obj = Basic.__new__(cls, name, n, m)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[1], self.args[2]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.args[0].name
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return MatrixElement(self, i, j)
|
||||
|
||||
@property
|
||||
def free_symbols(self):
|
||||
return {self}
|
||||
|
||||
def _eval_simplify(self, **kwargs):
|
||||
return self
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
# x is a scalar:
|
||||
return ZeroMatrix(self.shape[0], self.shape[1])
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
if self != x:
|
||||
first = ZeroMatrix(x.shape[0], self.shape[0]) if self.shape[0] != 1 else S.Zero
|
||||
second = ZeroMatrix(x.shape[1], self.shape[1]) if self.shape[1] != 1 else S.Zero
|
||||
return [_LeftRightArgs(
|
||||
[first, second],
|
||||
)]
|
||||
else:
|
||||
first = Identity(self.shape[0]) if self.shape[0] != 1 else S.One
|
||||
second = Identity(self.shape[1]) if self.shape[1] != 1 else S.One
|
||||
return [_LeftRightArgs(
|
||||
[first, second],
|
||||
)]
|
||||
|
||||
|
||||
def matrix_symbols(expr):
|
||||
return [sym for sym in expr.free_symbols if sym.is_Matrix]
|
||||
|
||||
|
||||
class _LeftRightArgs:
|
||||
r"""
|
||||
Helper class to compute matrix derivatives.
|
||||
|
||||
The logic: when an expression is derived by a matrix `X_{mn}`, two lines of
|
||||
matrix multiplications are created: the one contracted to `m` (first line),
|
||||
and the one contracted to `n` (second line).
|
||||
|
||||
Transposition flips the side by which new matrices are connected to the
|
||||
lines.
|
||||
|
||||
The trace connects the end of the two lines.
|
||||
"""
|
||||
|
||||
def __init__(self, lines, higher=S.One):
|
||||
self._lines = list(lines)
|
||||
self._first_pointer_parent = self._lines
|
||||
self._first_pointer_index = 0
|
||||
self._first_line_index = 0
|
||||
self._second_pointer_parent = self._lines
|
||||
self._second_pointer_index = 1
|
||||
self._second_line_index = 1
|
||||
self.higher = higher
|
||||
|
||||
@property
|
||||
def first_pointer(self):
|
||||
return self._first_pointer_parent[self._first_pointer_index]
|
||||
|
||||
@first_pointer.setter
|
||||
def first_pointer(self, value):
|
||||
self._first_pointer_parent[self._first_pointer_index] = value
|
||||
|
||||
@property
|
||||
def second_pointer(self):
|
||||
return self._second_pointer_parent[self._second_pointer_index]
|
||||
|
||||
@second_pointer.setter
|
||||
def second_pointer(self, value):
|
||||
self._second_pointer_parent[self._second_pointer_index] = value
|
||||
|
||||
def __repr__(self):
|
||||
built = [self._build(i) for i in self._lines]
|
||||
return "_LeftRightArgs(lines=%s, higher=%s)" % (
|
||||
built,
|
||||
self.higher,
|
||||
)
|
||||
|
||||
def transpose(self):
|
||||
self._first_pointer_parent, self._second_pointer_parent = self._second_pointer_parent, self._first_pointer_parent
|
||||
self._first_pointer_index, self._second_pointer_index = self._second_pointer_index, self._first_pointer_index
|
||||
self._first_line_index, self._second_line_index = self._second_line_index, self._first_line_index
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def _build(expr):
|
||||
if isinstance(expr, ExprBuilder):
|
||||
return expr.build()
|
||||
if isinstance(expr, list):
|
||||
if len(expr) == 1:
|
||||
return expr[0]
|
||||
else:
|
||||
return expr[0](*[_LeftRightArgs._build(i) for i in expr[1]])
|
||||
else:
|
||||
return expr
|
||||
|
||||
def build(self):
|
||||
data = [self._build(i) for i in self._lines]
|
||||
if self.higher != 1:
|
||||
data += [self._build(self.higher)]
|
||||
data = list(data)
|
||||
return data
|
||||
|
||||
def matrix_form(self):
|
||||
if self.first != 1 and self.higher != 1:
|
||||
raise ValueError("higher dimensional array cannot be represented")
|
||||
|
||||
def _get_shape(elem):
|
||||
if isinstance(elem, MatrixExpr):
|
||||
return elem.shape
|
||||
return (None, None)
|
||||
|
||||
if _get_shape(self.first)[1] != _get_shape(self.second)[1]:
|
||||
# Remove one-dimensional identity matrices:
|
||||
# (this is needed by `a.diff(a)` where `a` is a vector)
|
||||
if _get_shape(self.second) == (1, 1):
|
||||
return self.first*self.second[0, 0]
|
||||
if _get_shape(self.first) == (1, 1):
|
||||
return self.first[1, 1]*self.second.T
|
||||
raise ValueError("incompatible shapes")
|
||||
if self.first != 1:
|
||||
return self.first*self.second.T
|
||||
else:
|
||||
return self.higher
|
||||
|
||||
def rank(self):
|
||||
"""
|
||||
Number of dimensions different from trivial (warning: not related to
|
||||
matrix rank).
|
||||
"""
|
||||
rank = 0
|
||||
if self.first != 1:
|
||||
rank += sum(i != 1 for i in self.first.shape)
|
||||
if self.second != 1:
|
||||
rank += sum(i != 1 for i in self.second.shape)
|
||||
if self.higher != 1:
|
||||
rank += 2
|
||||
return rank
|
||||
|
||||
def _multiply_pointer(self, pointer, other):
|
||||
from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from ...tensor.array.expressions.array_expressions import ArrayContraction
|
||||
|
||||
subexpr = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
pointer,
|
||||
other
|
||||
]
|
||||
),
|
||||
(1, 2)
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
|
||||
return subexpr
|
||||
|
||||
def append_first(self, other):
|
||||
self.first_pointer *= other
|
||||
|
||||
def append_second(self, other):
|
||||
self.second_pointer *= other
|
||||
|
||||
|
||||
def _make_matrix(x):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
if isinstance(x, MatrixExpr):
|
||||
return x
|
||||
return ImmutableDenseMatrix([[x]])
|
||||
|
||||
|
||||
from .matmul import MatMul
|
||||
from .matadd import MatAdd
|
||||
from .matpow import MatPow
|
||||
from .transpose import Transpose
|
||||
from .inverse import Inverse
|
||||
from .special import ZeroMatrix, Identity
|
||||
from .determinant import Determinant
|
||||
@@ -0,0 +1,496 @@
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
from sympy.core import Basic, sympify, S
|
||||
from sympy.core.mul import mul, Mul
|
||||
from sympy.core.numbers import Number, Integer
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.functions import adjoint
|
||||
from sympy.strategies import (rm_id, unpack, typed, flatten, exhaust,
|
||||
do_one, new)
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
from sympy.matrices.expressions._shape import validate_matmul_integer as validate
|
||||
|
||||
from .inverse import Inverse
|
||||
from .matexpr import MatrixExpr
|
||||
from .matpow import MatPow
|
||||
from .transpose import transpose
|
||||
from .permutation import PermutationMatrix
|
||||
from .special import ZeroMatrix, Identity, GenericIdentity, OneMatrix
|
||||
|
||||
|
||||
# XXX: MatMul should perhaps not subclass directly from Mul
|
||||
class MatMul(MatrixExpr, Mul):
|
||||
"""
|
||||
A product of matrix expressions
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatMul, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 5, 4)
|
||||
>>> B = MatrixSymbol('B', 4, 3)
|
||||
>>> C = MatrixSymbol('C', 3, 6)
|
||||
>>> MatMul(A, B, C)
|
||||
A*B*C
|
||||
"""
|
||||
is_MatMul = True
|
||||
|
||||
identity = GenericIdentity()
|
||||
|
||||
def __new__(cls, *args, evaluate=False, check=None, _sympify=True):
|
||||
if not args:
|
||||
return cls.identity
|
||||
|
||||
# This must be removed aggressively in the constructor to avoid
|
||||
# TypeErrors from GenericIdentity().shape
|
||||
args = list(filter(lambda i: cls.identity != i, args))
|
||||
if _sympify:
|
||||
args = list(map(sympify, args))
|
||||
obj = Basic.__new__(cls, *args)
|
||||
factor, matrices = obj.as_coeff_matrices()
|
||||
|
||||
if check is not None:
|
||||
sympy_deprecation_warning(
|
||||
"Passing check to MatMul is deprecated and the check argument will be removed in a future version.",
|
||||
deprecated_since_version="1.11",
|
||||
active_deprecations_target='remove-check-argument-from-matrix-operations')
|
||||
|
||||
if check is not False:
|
||||
validate(*matrices)
|
||||
|
||||
if not matrices:
|
||||
# Should it be
|
||||
#
|
||||
# return Basic.__neq__(cls, factor, GenericIdentity()) ?
|
||||
return factor
|
||||
|
||||
if evaluate:
|
||||
return cls._evaluate(obj)
|
||||
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def _evaluate(cls, expr):
|
||||
return canonicalize(expr)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
matrices = [arg for arg in self.args if arg.is_Matrix]
|
||||
return (matrices[0].rows, matrices[-1].cols)
|
||||
|
||||
def _entry(self, i, j, expand=True, **kwargs):
|
||||
# Avoid cyclic imports
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
|
||||
coeff, matrices = self.as_coeff_matrices()
|
||||
|
||||
if len(matrices) == 1: # situation like 2*X, matmul is just X
|
||||
return coeff * matrices[0][i, j]
|
||||
|
||||
indices = [None]*(len(matrices) + 1)
|
||||
ind_ranges = [None]*(len(matrices) - 1)
|
||||
indices[0] = i
|
||||
indices[-1] = j
|
||||
|
||||
def f():
|
||||
counter = 1
|
||||
while True:
|
||||
yield Dummy("i_%i" % counter)
|
||||
counter += 1
|
||||
|
||||
dummy_generator = kwargs.get("dummy_generator", f())
|
||||
|
||||
for i in range(1, len(matrices)):
|
||||
indices[i] = next(dummy_generator)
|
||||
|
||||
for i, arg in enumerate(matrices[:-1]):
|
||||
ind_ranges[i] = arg.shape[1] - 1
|
||||
matrices = [arg._entry(indices[i], indices[i+1], dummy_generator=dummy_generator) for i, arg in enumerate(matrices)]
|
||||
expr_in_sum = Mul.fromiter(matrices)
|
||||
if any(v.has(ImmutableMatrix) for v in matrices):
|
||||
expand = True
|
||||
result = coeff*Sum(
|
||||
expr_in_sum,
|
||||
*zip(indices[1:-1], [0]*len(ind_ranges), ind_ranges)
|
||||
)
|
||||
|
||||
# Don't waste time in result.doit() if the sum bounds are symbolic
|
||||
if not any(isinstance(v, (Integer, int)) for v in ind_ranges):
|
||||
expand = False
|
||||
return result.doit() if expand else result
|
||||
|
||||
def as_coeff_matrices(self):
|
||||
scalars = [x for x in self.args if not x.is_Matrix]
|
||||
matrices = [x for x in self.args if x.is_Matrix]
|
||||
coeff = Mul(*scalars)
|
||||
if coeff.is_commutative is False:
|
||||
raise NotImplementedError("noncommutative scalars in MatMul are not supported.")
|
||||
|
||||
return coeff, matrices
|
||||
|
||||
def as_coeff_mmul(self):
|
||||
coeff, matrices = self.as_coeff_matrices()
|
||||
return coeff, MatMul(*matrices)
|
||||
|
||||
def expand(self, **kwargs):
|
||||
expanded = super(MatMul, self).expand(**kwargs)
|
||||
return self._evaluate(expanded)
|
||||
|
||||
def _eval_transpose(self):
|
||||
"""Transposition of matrix multiplication.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The following rules are applied.
|
||||
|
||||
Transposition for matrix multiplied with another matrix:
|
||||
`\\left(A B\\right)^{T} = B^{T} A^{T}`
|
||||
|
||||
Transposition for matrix multiplied with scalar:
|
||||
`\\left(c A\\right)^{T} = c A^{T}`
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Transpose
|
||||
"""
|
||||
coeff, matrices = self.as_coeff_matrices()
|
||||
return MatMul(
|
||||
coeff, *[transpose(arg) for arg in matrices[::-1]]).doit()
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return MatMul(*[adjoint(arg) for arg in self.args[::-1]]).doit()
|
||||
|
||||
def _eval_trace(self):
|
||||
factor, mmul = self.as_coeff_mmul()
|
||||
if factor != 1:
|
||||
from .trace import trace
|
||||
return factor * trace(mmul.doit())
|
||||
|
||||
def _eval_determinant(self):
|
||||
from sympy.matrices.expressions.determinant import Determinant
|
||||
factor, matrices = self.as_coeff_matrices()
|
||||
square_matrices = only_squares(*matrices)
|
||||
return factor**self.rows * Mul(*list(map(Determinant, square_matrices)))
|
||||
|
||||
def _eval_inverse(self):
|
||||
if all(arg.is_square for arg in self.args if isinstance(arg, MatrixExpr)):
|
||||
return MatMul(*(
|
||||
arg.inverse() if isinstance(arg, MatrixExpr) else arg**-1
|
||||
for arg in self.args[::-1]
|
||||
)
|
||||
).doit()
|
||||
return Inverse(self)
|
||||
|
||||
def doit(self, **hints):
|
||||
deep = hints.get('deep', True)
|
||||
if deep:
|
||||
args = tuple(arg.doit(**hints) for arg in self.args)
|
||||
else:
|
||||
args = self.args
|
||||
|
||||
# treat scalar*MatrixSymbol or scalar*MatPow separately
|
||||
expr = canonicalize(MatMul(*args))
|
||||
return expr
|
||||
|
||||
# Needed for partial compatibility with Mul
|
||||
def args_cnc(self, cset=False, warn=True, **kwargs):
|
||||
coeff_c = [x for x in self.args if x.is_commutative]
|
||||
coeff_nc = [x for x in self.args if not x.is_commutative]
|
||||
if cset:
|
||||
clen = len(coeff_c)
|
||||
coeff_c = set(coeff_c)
|
||||
if clen and warn and len(coeff_c) != clen:
|
||||
raise ValueError('repeated commutative arguments: %s' %
|
||||
[ci for ci in coeff_c if list(self.args).count(ci) > 1])
|
||||
return [coeff_c, coeff_nc]
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from .transpose import Transpose
|
||||
with_x_ind = [i for i, arg in enumerate(self.args) if arg.has(x)]
|
||||
lines = []
|
||||
for ind in with_x_ind:
|
||||
left_args = self.args[:ind]
|
||||
right_args = self.args[ind+1:]
|
||||
|
||||
if right_args:
|
||||
right_mat = MatMul.fromiter(right_args)
|
||||
else:
|
||||
right_mat = Identity(self.shape[1])
|
||||
if left_args:
|
||||
left_rev = MatMul.fromiter([Transpose(i).doit() if i.is_Matrix else i for i in reversed(left_args)])
|
||||
else:
|
||||
left_rev = Identity(self.shape[0])
|
||||
|
||||
d = self.args[ind]._eval_derivative_matrix_lines(x)
|
||||
for i in d:
|
||||
i.append_first(left_rev)
|
||||
i.append_second(right_mat)
|
||||
lines.append(i)
|
||||
|
||||
return lines
|
||||
|
||||
mul.register_handlerclass((Mul, MatMul), MatMul)
|
||||
|
||||
|
||||
# Rules
|
||||
def newmul(*args):
|
||||
if args[0] == 1:
|
||||
args = args[1:]
|
||||
return new(MatMul, *args)
|
||||
|
||||
def any_zeros(mul):
|
||||
if any(arg.is_zero or (arg.is_Matrix and arg.is_ZeroMatrix)
|
||||
for arg in mul.args):
|
||||
matrices = [arg for arg in mul.args if arg.is_Matrix]
|
||||
return ZeroMatrix(matrices[0].rows, matrices[-1].cols)
|
||||
return mul
|
||||
|
||||
def merge_explicit(matmul):
|
||||
""" Merge explicit MatrixBase arguments
|
||||
|
||||
>>> from sympy import MatrixSymbol, Matrix, MatMul, pprint
|
||||
>>> from sympy.matrices.expressions.matmul import merge_explicit
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> B = Matrix([[1, 1], [1, 1]])
|
||||
>>> C = Matrix([[1, 2], [3, 4]])
|
||||
>>> X = MatMul(A, B, C)
|
||||
>>> pprint(X)
|
||||
[1 1] [1 2]
|
||||
A*[ ]*[ ]
|
||||
[1 1] [3 4]
|
||||
>>> pprint(merge_explicit(X))
|
||||
[4 6]
|
||||
A*[ ]
|
||||
[4 6]
|
||||
|
||||
>>> X = MatMul(B, A, C)
|
||||
>>> pprint(X)
|
||||
[1 1] [1 2]
|
||||
[ ]*A*[ ]
|
||||
[1 1] [3 4]
|
||||
>>> pprint(merge_explicit(X))
|
||||
[1 1] [1 2]
|
||||
[ ]*A*[ ]
|
||||
[1 1] [3 4]
|
||||
"""
|
||||
if not any(isinstance(arg, MatrixBase) for arg in matmul.args):
|
||||
return matmul
|
||||
newargs = []
|
||||
last = matmul.args[0]
|
||||
for arg in matmul.args[1:]:
|
||||
if isinstance(arg, (MatrixBase, Number)) and isinstance(last, (MatrixBase, Number)):
|
||||
last = last * arg
|
||||
else:
|
||||
newargs.append(last)
|
||||
last = arg
|
||||
newargs.append(last)
|
||||
|
||||
return MatMul(*newargs)
|
||||
|
||||
def remove_ids(mul):
|
||||
""" Remove Identities from a MatMul
|
||||
|
||||
This is a modified version of sympy.strategies.rm_id.
|
||||
This is necessary because MatMul may contain both MatrixExprs and Exprs
|
||||
as args.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.strategies.rm_id
|
||||
"""
|
||||
# Separate Exprs from MatrixExprs in args
|
||||
factor, mmul = mul.as_coeff_mmul()
|
||||
# Apply standard rm_id for MatMuls
|
||||
result = rm_id(lambda x: x.is_Identity is True)(mmul)
|
||||
if result != mmul:
|
||||
return newmul(factor, *result.args) # Recombine and return
|
||||
else:
|
||||
return mul
|
||||
|
||||
def factor_in_front(mul):
|
||||
factor, matrices = mul.as_coeff_matrices()
|
||||
if factor != 1:
|
||||
return newmul(factor, *matrices)
|
||||
return mul
|
||||
|
||||
def combine_powers(mul):
|
||||
r"""Combine consecutive powers with the same base into one, e.g.
|
||||
$$A \times A^2 \Rightarrow A^3$$
|
||||
|
||||
This also cancels out the possible matrix inverses using the
|
||||
knowledgebase of :class:`~.Inverse`, e.g.,
|
||||
$$ Y \times X \times X^{-1} \Rightarrow Y $$
|
||||
"""
|
||||
factor, args = mul.as_coeff_matrices()
|
||||
new_args = [args[0]]
|
||||
|
||||
for i in range(1, len(args)):
|
||||
A = new_args[-1]
|
||||
B = args[i]
|
||||
|
||||
if isinstance(B, Inverse) and isinstance(B.arg, MatMul):
|
||||
Bargs = B.arg.args
|
||||
l = len(Bargs)
|
||||
if list(Bargs) == new_args[-l:]:
|
||||
new_args = new_args[:-l] + [Identity(B.shape[0])]
|
||||
continue
|
||||
|
||||
if isinstance(A, Inverse) and isinstance(A.arg, MatMul):
|
||||
Aargs = A.arg.args
|
||||
l = len(Aargs)
|
||||
if list(Aargs) == args[i:i+l]:
|
||||
identity = Identity(A.shape[0])
|
||||
new_args[-1] = identity
|
||||
for j in range(i, i+l):
|
||||
args[j] = identity
|
||||
continue
|
||||
|
||||
if A.is_square == False or B.is_square == False:
|
||||
new_args.append(B)
|
||||
continue
|
||||
|
||||
if isinstance(A, MatPow):
|
||||
A_base, A_exp = A.args
|
||||
else:
|
||||
A_base, A_exp = A, S.One
|
||||
|
||||
if isinstance(B, MatPow):
|
||||
B_base, B_exp = B.args
|
||||
else:
|
||||
B_base, B_exp = B, S.One
|
||||
|
||||
if A_base == B_base:
|
||||
new_exp = A_exp + B_exp
|
||||
new_args[-1] = MatPow(A_base, new_exp).doit(deep=False)
|
||||
continue
|
||||
elif not isinstance(B_base, MatrixBase):
|
||||
try:
|
||||
B_base_inv = B_base.inverse()
|
||||
except NonInvertibleMatrixError:
|
||||
B_base_inv = None
|
||||
if B_base_inv is not None and A_base == B_base_inv:
|
||||
new_exp = A_exp - B_exp
|
||||
new_args[-1] = MatPow(A_base, new_exp).doit(deep=False)
|
||||
continue
|
||||
new_args.append(B)
|
||||
|
||||
return newmul(factor, *new_args)
|
||||
|
||||
def combine_permutations(mul):
|
||||
"""Refine products of permutation matrices as the products of cycles.
|
||||
"""
|
||||
args = mul.args
|
||||
l = len(args)
|
||||
if l < 2:
|
||||
return mul
|
||||
|
||||
result = [args[0]]
|
||||
for i in range(1, l):
|
||||
A = result[-1]
|
||||
B = args[i]
|
||||
if isinstance(A, PermutationMatrix) and \
|
||||
isinstance(B, PermutationMatrix):
|
||||
cycle_1 = A.args[0]
|
||||
cycle_2 = B.args[0]
|
||||
result[-1] = PermutationMatrix(cycle_1 * cycle_2)
|
||||
else:
|
||||
result.append(B)
|
||||
|
||||
return MatMul(*result)
|
||||
|
||||
def combine_one_matrices(mul):
|
||||
"""
|
||||
Combine products of OneMatrix
|
||||
|
||||
e.g. OneMatrix(2, 3) * OneMatrix(3, 4) -> 3 * OneMatrix(2, 4)
|
||||
"""
|
||||
factor, args = mul.as_coeff_matrices()
|
||||
new_args = [args[0]]
|
||||
|
||||
for B in args[1:]:
|
||||
A = new_args[-1]
|
||||
if not isinstance(A, OneMatrix) or not isinstance(B, OneMatrix):
|
||||
new_args.append(B)
|
||||
continue
|
||||
new_args.pop()
|
||||
new_args.append(OneMatrix(A.shape[0], B.shape[1]))
|
||||
factor *= A.shape[1]
|
||||
|
||||
return newmul(factor, *new_args)
|
||||
|
||||
def distribute_monom(mul):
|
||||
"""
|
||||
Simplify MatMul expressions but distributing
|
||||
rational term to MatMul.
|
||||
|
||||
e.g. 2*(A+B) -> 2*A + 2*B
|
||||
"""
|
||||
args = mul.args
|
||||
if len(args) == 2:
|
||||
from .matadd import MatAdd
|
||||
if args[0].is_MatAdd and args[1].is_Rational:
|
||||
return MatAdd(*[MatMul(mat, args[1]).doit() for mat in args[0].args])
|
||||
if args[1].is_MatAdd and args[0].is_Rational:
|
||||
return MatAdd(*[MatMul(args[0], mat).doit() for mat in args[1].args])
|
||||
return mul
|
||||
|
||||
rules = (
|
||||
distribute_monom, any_zeros, remove_ids, combine_one_matrices, combine_powers, unpack, rm_id(lambda x: x == 1),
|
||||
merge_explicit, factor_in_front, flatten, combine_permutations)
|
||||
|
||||
canonicalize = exhaust(typed({MatMul: do_one(*rules)}))
|
||||
|
||||
def only_squares(*matrices):
|
||||
"""factor matrices only if they are square"""
|
||||
if matrices[0].rows != matrices[-1].cols:
|
||||
raise RuntimeError("Invalid matrices being multiplied")
|
||||
out = []
|
||||
start = 0
|
||||
for i, M in enumerate(matrices):
|
||||
if M.cols == matrices[start].rows:
|
||||
out.append(MatMul(*matrices[start:i+1]).doit())
|
||||
start = i+1
|
||||
return out
|
||||
|
||||
|
||||
def refine_MatMul(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> expr = X * X.T
|
||||
>>> print(expr)
|
||||
X*X.T
|
||||
>>> with assuming(Q.orthogonal(X)):
|
||||
... print(refine(expr))
|
||||
I
|
||||
"""
|
||||
newargs = []
|
||||
exprargs = []
|
||||
|
||||
for args in expr.args:
|
||||
if args.is_Matrix:
|
||||
exprargs.append(args)
|
||||
else:
|
||||
newargs.append(args)
|
||||
|
||||
last = exprargs[0]
|
||||
for arg in exprargs[1:]:
|
||||
if arg == last.T and ask(Q.orthogonal(arg), assumptions):
|
||||
last = Identity(arg.shape[0])
|
||||
elif arg == last.conjugate() and ask(Q.unitary(arg), assumptions):
|
||||
last = Identity(arg.shape[0])
|
||||
else:
|
||||
newargs.append(last)
|
||||
last = arg
|
||||
newargs.append(last)
|
||||
|
||||
return MatMul(*newargs)
|
||||
|
||||
|
||||
handlers_dict['MatMul'] = refine_MatMul
|
||||
@@ -0,0 +1,150 @@
|
||||
from .matexpr import MatrixExpr
|
||||
from .special import Identity
|
||||
from sympy.core import S
|
||||
from sympy.core.expr import ExprBuilder
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.power import Pow
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices import MatrixBase
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
|
||||
|
||||
class MatPow(MatrixExpr):
|
||||
def __new__(cls, base, exp, evaluate=False, **options):
|
||||
base = _sympify(base)
|
||||
if not base.is_Matrix:
|
||||
raise TypeError("MatPow base should be a matrix")
|
||||
|
||||
if base.is_square is False:
|
||||
raise NonSquareMatrixError("Power of non-square matrix %s" % base)
|
||||
|
||||
exp = _sympify(exp)
|
||||
obj = super().__new__(cls, base, exp)
|
||||
|
||||
if evaluate:
|
||||
obj = obj.doit(deep=False)
|
||||
|
||||
return obj
|
||||
|
||||
@property
|
||||
def base(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def exp(self):
|
||||
return self.args[1]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.base.shape
|
||||
|
||||
@cacheit
|
||||
def _get_explicit_matrix(self):
|
||||
return self.base.as_explicit()**self.exp
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
from sympy.matrices.expressions import MatMul
|
||||
A = self.doit()
|
||||
if isinstance(A, MatPow):
|
||||
# We still have a MatPow, make an explicit MatMul out of it.
|
||||
if A.exp.is_Integer and A.exp.is_positive:
|
||||
A = MatMul(*[A.base for k in range(A.exp)])
|
||||
elif not self._is_shape_symbolic():
|
||||
return A._get_explicit_matrix()[i, j]
|
||||
else:
|
||||
# Leave the expression unevaluated:
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
return MatrixElement(self, i, j)
|
||||
return A[i, j]
|
||||
|
||||
def doit(self, **hints):
|
||||
if hints.get('deep', True):
|
||||
base, exp = (arg.doit(**hints) for arg in self.args)
|
||||
else:
|
||||
base, exp = self.args
|
||||
|
||||
# combine all powers, e.g. (A ** 2) ** 3 -> A ** 6
|
||||
while isinstance(base, MatPow):
|
||||
exp *= base.args[1]
|
||||
base = base.args[0]
|
||||
|
||||
if isinstance(base, MatrixBase):
|
||||
# Delegate
|
||||
return base ** exp
|
||||
|
||||
# Handle simple cases so that _eval_power() in MatrixExpr sub-classes can ignore them
|
||||
if exp == S.One:
|
||||
return base
|
||||
if exp == S.Zero:
|
||||
return Identity(base.rows)
|
||||
if exp == S.NegativeOne:
|
||||
from sympy.matrices.expressions import Inverse
|
||||
return Inverse(base).doit(**hints)
|
||||
|
||||
eval_power = getattr(base, '_eval_power', None)
|
||||
if eval_power is not None:
|
||||
return eval_power(exp)
|
||||
|
||||
return MatPow(base, exp)
|
||||
|
||||
def _eval_transpose(self):
|
||||
base, exp = self.args
|
||||
return MatPow(base.transpose(), exp)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
base, exp = self.args
|
||||
return MatPow(base.adjoint(), exp)
|
||||
|
||||
def _eval_conjugate(self):
|
||||
base, exp = self.args
|
||||
return MatPow(base.conjugate(), exp)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
return Pow._eval_derivative(self, x)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayContraction
|
||||
from ...tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from .matmul import MatMul
|
||||
from .inverse import Inverse
|
||||
exp = self.exp
|
||||
if self.base.shape == (1, 1) and not exp.has(x):
|
||||
lr = self.base._eval_derivative_matrix_lines(x)
|
||||
for i in lr:
|
||||
subexpr = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
Identity(1),
|
||||
i._lines[0],
|
||||
exp*self.base**(exp-1),
|
||||
i._lines[1],
|
||||
Identity(1),
|
||||
]
|
||||
),
|
||||
(0, 3, 4), (5, 7, 8)
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
i._first_pointer_parent = subexpr.args[0].args
|
||||
i._first_pointer_index = 0
|
||||
i._second_pointer_parent = subexpr.args[0].args
|
||||
i._second_pointer_index = 4
|
||||
i._lines = [subexpr]
|
||||
return lr
|
||||
if (exp > 0) == True:
|
||||
newexpr = MatMul.fromiter([self.base for i in range(exp)])
|
||||
elif (exp == -1) == True:
|
||||
return Inverse(self.base)._eval_derivative_matrix_lines(x)
|
||||
elif (exp < 0) == True:
|
||||
newexpr = MatMul.fromiter([Inverse(self.base) for i in range(-exp)])
|
||||
elif (exp == 0) == True:
|
||||
return self.doit()._eval_derivative_matrix_lines(x)
|
||||
else:
|
||||
raise NotImplementedError("cannot evaluate %s derived by %s" % (self, x))
|
||||
return newexpr._eval_derivative_matrix_lines(x)
|
||||
|
||||
def _eval_inverse(self):
|
||||
return MatPow(self.base, -self.exp)
|
||||
@@ -0,0 +1,303 @@
|
||||
from sympy.core import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.functions import KroneckerDelta
|
||||
|
||||
from .matexpr import MatrixExpr
|
||||
from .special import ZeroMatrix, Identity, OneMatrix
|
||||
|
||||
|
||||
class PermutationMatrix(MatrixExpr):
|
||||
"""A Permutation Matrix
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
perm : Permutation
|
||||
The permutation the matrix uses.
|
||||
|
||||
The size of the permutation determines the matrix size.
|
||||
|
||||
See the documentation of
|
||||
:class:`sympy.combinatorics.permutations.Permutation` for
|
||||
the further information of how to create a permutation object.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, PermutationMatrix
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
|
||||
Creating a permutation matrix:
|
||||
|
||||
>>> p = Permutation(1, 2, 0)
|
||||
>>> P = PermutationMatrix(p)
|
||||
>>> P = P.as_explicit()
|
||||
>>> P
|
||||
Matrix([
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[1, 0, 0]])
|
||||
|
||||
Permuting a matrix row and column:
|
||||
|
||||
>>> M = Matrix([0, 1, 2])
|
||||
>>> Matrix(P*M)
|
||||
Matrix([
|
||||
[1],
|
||||
[2],
|
||||
[0]])
|
||||
|
||||
>>> Matrix(M.T*P)
|
||||
Matrix([[2, 0, 1]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.combinatorics.permutations.Permutation
|
||||
"""
|
||||
|
||||
def __new__(cls, perm):
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
|
||||
perm = _sympify(perm)
|
||||
if not isinstance(perm, Permutation):
|
||||
raise ValueError(
|
||||
"{} must be a SymPy Permutation instance.".format(perm))
|
||||
|
||||
return super().__new__(cls, perm)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
size = self.args[0].size
|
||||
return (size, size)
|
||||
|
||||
@property
|
||||
def is_Identity(self):
|
||||
return self.args[0].is_Identity
|
||||
|
||||
def doit(self, **hints):
|
||||
if self.is_Identity:
|
||||
return Identity(self.rows)
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
perm = self.args[0]
|
||||
return KroneckerDelta(perm.apply(i), j)
|
||||
|
||||
def _eval_power(self, exp):
|
||||
return PermutationMatrix(self.args[0] ** exp).doit()
|
||||
|
||||
def _eval_inverse(self):
|
||||
return PermutationMatrix(self.args[0] ** -1)
|
||||
|
||||
_eval_transpose = _eval_adjoint = _eval_inverse
|
||||
|
||||
def _eval_determinant(self):
|
||||
sign = self.args[0].signature()
|
||||
if sign == 1:
|
||||
return S.One
|
||||
elif sign == -1:
|
||||
return S.NegativeOne
|
||||
raise NotImplementedError
|
||||
|
||||
def _eval_rewrite_as_BlockDiagMatrix(self, *args, **kwargs):
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from .blockmatrix import BlockDiagMatrix
|
||||
|
||||
perm = self.args[0]
|
||||
full_cyclic_form = perm.full_cyclic_form
|
||||
|
||||
cycles_picks = []
|
||||
|
||||
# Stage 1. Decompose the cycles into the blockable form.
|
||||
a, b, c = 0, 0, 0
|
||||
flag = False
|
||||
for cycle in full_cyclic_form:
|
||||
l = len(cycle)
|
||||
m = max(cycle)
|
||||
|
||||
if not flag:
|
||||
if m + 1 > a + l:
|
||||
flag = True
|
||||
temp = [cycle]
|
||||
b = m
|
||||
c = l
|
||||
else:
|
||||
cycles_picks.append([cycle])
|
||||
a += l
|
||||
|
||||
else:
|
||||
if m > b:
|
||||
if m + 1 == a + c + l:
|
||||
temp.append(cycle)
|
||||
cycles_picks.append(temp)
|
||||
flag = False
|
||||
a = m+1
|
||||
else:
|
||||
b = m
|
||||
temp.append(cycle)
|
||||
c += l
|
||||
else:
|
||||
if b + 1 == a + c + l:
|
||||
temp.append(cycle)
|
||||
cycles_picks.append(temp)
|
||||
flag = False
|
||||
a = b+1
|
||||
else:
|
||||
temp.append(cycle)
|
||||
c += l
|
||||
|
||||
# Stage 2. Normalize each decomposed cycles and build matrix.
|
||||
p = 0
|
||||
args = []
|
||||
for pick in cycles_picks:
|
||||
new_cycles = []
|
||||
l = 0
|
||||
for cycle in pick:
|
||||
new_cycle = [i - p for i in cycle]
|
||||
new_cycles.append(new_cycle)
|
||||
l += len(cycle)
|
||||
p += l
|
||||
perm = Permutation(new_cycles)
|
||||
mat = PermutationMatrix(perm)
|
||||
args.append(mat)
|
||||
|
||||
return BlockDiagMatrix(*args)
|
||||
|
||||
|
||||
class MatrixPermute(MatrixExpr):
|
||||
r"""Symbolic representation for permuting matrix rows or columns.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
perm : Permutation, PermutationMatrix
|
||||
The permutation to use for permuting the matrix.
|
||||
The permutation can be resized to the suitable one,
|
||||
|
||||
axis : 0 or 1
|
||||
The axis to permute alongside.
|
||||
If `0`, it will permute the matrix rows.
|
||||
If `1`, it will permute the matrix columns.
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This follows the same notation used in
|
||||
:meth:`sympy.matrices.matrixbase.MatrixBase.permute`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, MatrixPermute
|
||||
>>> from sympy.combinatorics import Permutation
|
||||
|
||||
Permuting the matrix rows:
|
||||
|
||||
>>> p = Permutation(1, 2, 0)
|
||||
>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
>>> B = MatrixPermute(A, p, axis=0)
|
||||
>>> B.as_explicit()
|
||||
Matrix([
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[1, 2, 3]])
|
||||
|
||||
Permuting the matrix columns:
|
||||
|
||||
>>> B = MatrixPermute(A, p, axis=1)
|
||||
>>> B.as_explicit()
|
||||
Matrix([
|
||||
[2, 3, 1],
|
||||
[5, 6, 4],
|
||||
[8, 9, 7]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.matrixbase.MatrixBase.permute
|
||||
"""
|
||||
def __new__(cls, mat, perm, axis=S.Zero):
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
|
||||
mat = _sympify(mat)
|
||||
if not mat.is_Matrix:
|
||||
raise ValueError(
|
||||
"{} must be a SymPy matrix instance.".format(perm))
|
||||
|
||||
perm = _sympify(perm)
|
||||
if isinstance(perm, PermutationMatrix):
|
||||
perm = perm.args[0]
|
||||
|
||||
if not isinstance(perm, Permutation):
|
||||
raise ValueError(
|
||||
"{} must be a SymPy Permutation or a PermutationMatrix " \
|
||||
"instance".format(perm))
|
||||
|
||||
axis = _sympify(axis)
|
||||
if axis not in (0, 1):
|
||||
raise ValueError("The axis must be 0 or 1.")
|
||||
|
||||
mat_size = mat.shape[axis]
|
||||
if mat_size != perm.size:
|
||||
try:
|
||||
perm = perm.resize(mat_size)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
"Size does not match between the permutation {} "
|
||||
"and the matrix {} threaded over the axis {} "
|
||||
"and cannot be converted."
|
||||
.format(perm, mat, axis))
|
||||
|
||||
return super().__new__(cls, mat, perm, axis)
|
||||
|
||||
def doit(self, deep=True, **hints):
|
||||
mat, perm, axis = self.args
|
||||
|
||||
if deep:
|
||||
mat = mat.doit(deep=deep, **hints)
|
||||
perm = perm.doit(deep=deep, **hints)
|
||||
|
||||
if perm.is_Identity:
|
||||
return mat
|
||||
|
||||
if mat.is_Identity:
|
||||
if axis is S.Zero:
|
||||
return PermutationMatrix(perm)
|
||||
elif axis is S.One:
|
||||
return PermutationMatrix(perm**-1)
|
||||
|
||||
if isinstance(mat, (ZeroMatrix, OneMatrix)):
|
||||
return mat
|
||||
|
||||
if isinstance(mat, MatrixPermute) and mat.args[2] == axis:
|
||||
return MatrixPermute(mat.args[0], perm * mat.args[1], axis)
|
||||
|
||||
return self
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[0].shape
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
mat, perm, axis = self.args
|
||||
|
||||
if axis == 0:
|
||||
return mat[perm.apply(i), j]
|
||||
elif axis == 1:
|
||||
return mat[i, perm.apply(j)]
|
||||
|
||||
def _eval_rewrite_as_MatMul(self, *args, **kwargs):
|
||||
from .matmul import MatMul
|
||||
|
||||
mat, perm, axis = self.args
|
||||
|
||||
deep = kwargs.get("deep", True)
|
||||
|
||||
if deep:
|
||||
mat = mat.rewrite(MatMul)
|
||||
|
||||
if axis == 0:
|
||||
return MatMul(PermutationMatrix(perm), mat)
|
||||
elif axis == 1:
|
||||
return MatMul(mat, PermutationMatrix(perm**-1))
|
||||
@@ -0,0 +1,68 @@
|
||||
from sympy.core.assumptions import check_assumptions
|
||||
from sympy.core.logic import fuzzy_and
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.matrices.kind import MatrixKind
|
||||
from sympy.sets.sets import Set, SetKind
|
||||
from sympy.core.kind import NumberKind
|
||||
from .matexpr import MatrixExpr
|
||||
|
||||
|
||||
class MatrixSet(Set):
|
||||
"""
|
||||
MatrixSet represents the set of matrices with ``shape = (n, m)`` over the
|
||||
given set.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices import MatrixSet
|
||||
>>> from sympy import S, I, Matrix
|
||||
>>> M = MatrixSet(2, 2, set=S.Reals)
|
||||
>>> X = Matrix([[1, 2], [3, 4]])
|
||||
>>> X in M
|
||||
True
|
||||
>>> X = Matrix([[1, 2], [I, 4]])
|
||||
>>> X in M
|
||||
False
|
||||
|
||||
"""
|
||||
is_empty = False
|
||||
|
||||
def __new__(cls, n, m, set):
|
||||
n, m, set = _sympify(n), _sympify(m), _sympify(set)
|
||||
cls._check_dim(n)
|
||||
cls._check_dim(m)
|
||||
if not isinstance(set, Set):
|
||||
raise TypeError("{} should be an instance of Set.".format(set))
|
||||
return Set.__new__(cls, n, m, set)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.args[:2]
|
||||
|
||||
@property
|
||||
def set(self):
|
||||
return self.args[2]
|
||||
|
||||
def _contains(self, other):
|
||||
if not isinstance(other, MatrixExpr):
|
||||
raise TypeError("{} should be an instance of MatrixExpr.".format(other))
|
||||
if other.shape != self.shape:
|
||||
are_symbolic = any(_sympify(x).is_Symbol for x in other.shape + self.shape)
|
||||
if are_symbolic:
|
||||
return None
|
||||
return False
|
||||
return fuzzy_and(self.set.contains(x) for x in other)
|
||||
|
||||
@classmethod
|
||||
def _check_dim(cls, dim):
|
||||
"""Helper function to check invalid matrix dimensions"""
|
||||
ok = not dim.is_Float and check_assumptions(
|
||||
dim, integer=True, nonnegative=True)
|
||||
if ok is False:
|
||||
raise ValueError(
|
||||
"The dimension specification {} should be "
|
||||
"a nonnegative integer.".format(dim))
|
||||
|
||||
def _kind(self):
|
||||
return SetKind(MatrixKind(NumberKind))
|
||||
@@ -0,0 +1,114 @@
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.containers import Tuple
|
||||
from sympy.functions.elementary.integers import floor
|
||||
|
||||
def normalize(i, parentsize):
|
||||
if isinstance(i, slice):
|
||||
i = (i.start, i.stop, i.step)
|
||||
if not isinstance(i, (tuple, list, Tuple)):
|
||||
if (i < 0) == True:
|
||||
i += parentsize
|
||||
i = (i, i+1, 1)
|
||||
i = list(i)
|
||||
if len(i) == 2:
|
||||
i.append(1)
|
||||
start, stop, step = i
|
||||
start = start or 0
|
||||
if stop is None:
|
||||
stop = parentsize
|
||||
if (start < 0) == True:
|
||||
start += parentsize
|
||||
if (stop < 0) == True:
|
||||
stop += parentsize
|
||||
step = step or 1
|
||||
|
||||
if ((stop - start) * step < 1) == True:
|
||||
raise IndexError()
|
||||
|
||||
return (start, stop, step)
|
||||
|
||||
class MatrixSlice(MatrixExpr):
|
||||
""" A MatrixSlice of a Matrix Expression
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSlice, ImmutableMatrix
|
||||
>>> M = ImmutableMatrix(4, 4, range(16))
|
||||
>>> M
|
||||
Matrix([
|
||||
[ 0, 1, 2, 3],
|
||||
[ 4, 5, 6, 7],
|
||||
[ 8, 9, 10, 11],
|
||||
[12, 13, 14, 15]])
|
||||
|
||||
>>> B = MatrixSlice(M, (0, 2), (2, 4))
|
||||
>>> ImmutableMatrix(B)
|
||||
Matrix([
|
||||
[2, 3],
|
||||
[6, 7]])
|
||||
"""
|
||||
parent = property(lambda self: self.args[0])
|
||||
rowslice = property(lambda self: self.args[1])
|
||||
colslice = property(lambda self: self.args[2])
|
||||
|
||||
def __new__(cls, parent, rowslice, colslice):
|
||||
rowslice = normalize(rowslice, parent.shape[0])
|
||||
colslice = normalize(colslice, parent.shape[1])
|
||||
if not (len(rowslice) == len(colslice) == 3):
|
||||
raise IndexError()
|
||||
if ((0 > rowslice[0]) == True or
|
||||
(parent.shape[0] < rowslice[1]) == True or
|
||||
(0 > colslice[0]) == True or
|
||||
(parent.shape[1] < colslice[1]) == True):
|
||||
raise IndexError()
|
||||
if isinstance(parent, MatrixSlice):
|
||||
return mat_slice_of_slice(parent, rowslice, colslice)
|
||||
return Basic.__new__(cls, parent, Tuple(*rowslice), Tuple(*colslice))
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
rows = self.rowslice[1] - self.rowslice[0]
|
||||
rows = rows if self.rowslice[2] == 1 else floor(rows/self.rowslice[2])
|
||||
cols = self.colslice[1] - self.colslice[0]
|
||||
cols = cols if self.colslice[2] == 1 else floor(cols/self.colslice[2])
|
||||
return rows, cols
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self.parent._entry(i*self.rowslice[2] + self.rowslice[0],
|
||||
j*self.colslice[2] + self.colslice[0],
|
||||
**kwargs)
|
||||
|
||||
@property
|
||||
def on_diag(self):
|
||||
return self.rowslice == self.colslice
|
||||
|
||||
|
||||
def slice_of_slice(s, t):
|
||||
start1, stop1, step1 = s
|
||||
start2, stop2, step2 = t
|
||||
|
||||
start = start1 + start2*step1
|
||||
step = step1 * step2
|
||||
stop = start1 + step1*stop2
|
||||
|
||||
if stop > stop1:
|
||||
raise IndexError()
|
||||
|
||||
return start, stop, step
|
||||
|
||||
|
||||
def mat_slice_of_slice(parent, rowslice, colslice):
|
||||
""" Collapse nested matrix slices
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> X = MatrixSymbol('X', 10, 10)
|
||||
>>> X[:, 1:5][5:8, :]
|
||||
X[5:8, 1:5]
|
||||
>>> X[1:9:2, 2:6][1:3, 2]
|
||||
X[3:7:2, 4:5]
|
||||
"""
|
||||
row = slice_of_slice(parent.rowslice, rowslice)
|
||||
col = slice_of_slice(parent.colslice, colslice)
|
||||
return MatrixSlice(parent.parent, row, col)
|
||||
@@ -0,0 +1,299 @@
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import _sympify
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from .matexpr import MatrixExpr
|
||||
|
||||
|
||||
class ZeroMatrix(MatrixExpr):
|
||||
"""The Matrix Zero 0 - additive identity
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, ZeroMatrix
|
||||
>>> A = MatrixSymbol('A', 3, 5)
|
||||
>>> Z = ZeroMatrix(3, 5)
|
||||
>>> A + Z
|
||||
A
|
||||
>>> Z*A.T
|
||||
0
|
||||
"""
|
||||
is_ZeroMatrix = True
|
||||
|
||||
def __new__(cls, m, n):
|
||||
m, n = _sympify(m), _sympify(n)
|
||||
cls._check_dim(m)
|
||||
cls._check_dim(n)
|
||||
|
||||
return super().__new__(cls, m, n)
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.args[0], self.args[1])
|
||||
|
||||
def _eval_power(self, exp):
|
||||
# exp = -1, 0, 1 are already handled at this stage
|
||||
if (exp < 0) == True:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible")
|
||||
return self
|
||||
|
||||
def _eval_transpose(self):
|
||||
return ZeroMatrix(self.cols, self.rows)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return ZeroMatrix(self.cols, self.rows)
|
||||
|
||||
def _eval_trace(self):
|
||||
return S.Zero
|
||||
|
||||
def _eval_determinant(self):
|
||||
return S.Zero
|
||||
|
||||
def _eval_inverse(self):
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
return (self, self)
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return S.Zero
|
||||
|
||||
|
||||
class GenericZeroMatrix(ZeroMatrix):
|
||||
"""
|
||||
A zero matrix without a specified shape
|
||||
|
||||
This exists primarily so MatAdd() with no arguments can return something
|
||||
meaningful.
|
||||
"""
|
||||
def __new__(cls):
|
||||
# super(ZeroMatrix, cls) instead of super(GenericZeroMatrix, cls)
|
||||
# because ZeroMatrix.__new__ doesn't have the same signature
|
||||
return super(ZeroMatrix, cls).__new__(cls)
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
raise TypeError("GenericZeroMatrix does not have a specified shape")
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
raise TypeError("GenericZeroMatrix does not have a specified shape")
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
raise TypeError("GenericZeroMatrix does not have a specified shape")
|
||||
|
||||
# Avoid Matrix.__eq__ which might call .shape
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, GenericZeroMatrix)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
|
||||
class Identity(MatrixExpr):
|
||||
"""The Matrix Identity I - multiplicative identity
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Identity, MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 3, 5)
|
||||
>>> I = Identity(3)
|
||||
>>> I*A
|
||||
A
|
||||
"""
|
||||
|
||||
is_Identity = True
|
||||
|
||||
def __new__(cls, n):
|
||||
n = _sympify(n)
|
||||
cls._check_dim(n)
|
||||
|
||||
return super().__new__(cls, n)
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return (self.args[0], self.args[0])
|
||||
|
||||
@property
|
||||
def is_square(self):
|
||||
return True
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self
|
||||
|
||||
def _eval_trace(self):
|
||||
return self.rows
|
||||
|
||||
def _eval_inverse(self):
|
||||
return self
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
return (self, ZeroMatrix(*self.shape))
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
eq = Eq(i, j)
|
||||
if eq is S.true:
|
||||
return S.One
|
||||
elif eq is S.false:
|
||||
return S.Zero
|
||||
return KroneckerDelta(i, j, (0, self.cols-1))
|
||||
|
||||
def _eval_determinant(self):
|
||||
return S.One
|
||||
|
||||
def _eval_power(self, exp):
|
||||
return self
|
||||
|
||||
|
||||
class GenericIdentity(Identity):
|
||||
"""
|
||||
An identity matrix without a specified shape
|
||||
|
||||
This exists primarily so MatMul() with no arguments can return something
|
||||
meaningful.
|
||||
"""
|
||||
def __new__(cls):
|
||||
# super(Identity, cls) instead of super(GenericIdentity, cls) because
|
||||
# Identity.__new__ doesn't have the same signature
|
||||
return super(Identity, cls).__new__(cls)
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
raise TypeError("GenericIdentity does not have a specified shape")
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
raise TypeError("GenericIdentity does not have a specified shape")
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
raise TypeError("GenericIdentity does not have a specified shape")
|
||||
|
||||
@property
|
||||
def is_square(self):
|
||||
return True
|
||||
|
||||
# Avoid Matrix.__eq__ which might call .shape
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, GenericIdentity)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return super().__hash__()
|
||||
|
||||
|
||||
class OneMatrix(MatrixExpr):
|
||||
"""
|
||||
Matrix whose all entries are ones.
|
||||
"""
|
||||
def __new__(cls, m, n, evaluate=False):
|
||||
m, n = _sympify(m), _sympify(n)
|
||||
cls._check_dim(m)
|
||||
cls._check_dim(n)
|
||||
|
||||
if evaluate:
|
||||
condition = Eq(m, 1) & Eq(n, 1)
|
||||
if condition == True:
|
||||
return Identity(1)
|
||||
|
||||
obj = super().__new__(cls, m, n)
|
||||
return obj
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._args
|
||||
|
||||
@property
|
||||
def is_Identity(self):
|
||||
return self._is_1x1() == True
|
||||
|
||||
def as_explicit(self):
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
return ImmutableDenseMatrix.ones(*self.shape)
|
||||
|
||||
def doit(self, **hints):
|
||||
args = self.args
|
||||
if hints.get('deep', True):
|
||||
args = [a.doit(**hints) for a in args]
|
||||
return self.func(*args, evaluate=True)
|
||||
|
||||
def _eval_power(self, exp):
|
||||
# exp = -1, 0, 1 are already handled at this stage
|
||||
if self._is_1x1() == True:
|
||||
return Identity(1)
|
||||
if (exp < 0) == True:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible")
|
||||
if ask(Q.integer(exp)):
|
||||
return self.shape[0] ** (exp - 1) * OneMatrix(*self.shape)
|
||||
return super()._eval_power(exp)
|
||||
|
||||
def _eval_transpose(self):
|
||||
return OneMatrix(self.cols, self.rows)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return OneMatrix(self.cols, self.rows)
|
||||
|
||||
def _eval_trace(self):
|
||||
return S.One*self.rows
|
||||
|
||||
def _is_1x1(self):
|
||||
"""Returns true if the matrix is known to be 1x1"""
|
||||
shape = self.shape
|
||||
return Eq(shape[0], 1) & Eq(shape[1], 1)
|
||||
|
||||
def _eval_determinant(self):
|
||||
condition = self._is_1x1()
|
||||
if condition == True:
|
||||
return S.One
|
||||
elif condition == False:
|
||||
return S.Zero
|
||||
else:
|
||||
from sympy.matrices.expressions.determinant import Determinant
|
||||
return Determinant(self)
|
||||
|
||||
def _eval_inverse(self):
|
||||
condition = self._is_1x1()
|
||||
if condition == True:
|
||||
return Identity(1)
|
||||
elif condition == False:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
else:
|
||||
from .inverse import Inverse
|
||||
return Inverse(self)
|
||||
|
||||
def _eval_as_real_imag(self):
|
||||
return (self, ZeroMatrix(*self.shape))
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return S.One
|
||||
@@ -0,0 +1,34 @@
|
||||
from sympy.core import symbols, S
|
||||
from sympy.functions import adjoint, conjugate, transpose
|
||||
from sympy.matrices.expressions import MatrixSymbol, Adjoint, trace, Transpose
|
||||
from sympy.matrices import eye, Matrix
|
||||
|
||||
n, m, l, k, p = symbols('n m l k p', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
|
||||
def test_adjoint():
|
||||
Sq = MatrixSymbol('Sq', n, n)
|
||||
|
||||
assert Adjoint(A).shape == (m, n)
|
||||
assert Adjoint(A*B).shape == (l, n)
|
||||
assert adjoint(Adjoint(A)) == A
|
||||
assert isinstance(Adjoint(Adjoint(A)), Adjoint)
|
||||
|
||||
assert conjugate(Adjoint(A)) == Transpose(A)
|
||||
assert transpose(Adjoint(A)) == Adjoint(Transpose(A))
|
||||
|
||||
assert Adjoint(eye(3)).doit() == eye(3)
|
||||
|
||||
assert Adjoint(S(5)).doit() == S(5)
|
||||
|
||||
assert Adjoint(Matrix([[1, 2], [3, 4]])).doit() == Matrix([[1, 3], [2, 4]])
|
||||
|
||||
assert adjoint(trace(Sq)) == conjugate(trace(Sq))
|
||||
assert trace(adjoint(Sq)) == conjugate(trace(Sq))
|
||||
|
||||
assert Adjoint(Sq)[0, 1] == conjugate(Sq[1, 0])
|
||||
|
||||
assert Adjoint(A*B).doit() == Adjoint(B) * Adjoint(A)
|
||||
@@ -0,0 +1,118 @@
|
||||
from sympy.core.symbol import symbols, Dummy
|
||||
from sympy.matrices.expressions.applyfunc import ElementwiseApplyFunction
|
||||
from sympy.core.function import Lambda
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.trigonometric import sin
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.simplify.simplify import simplify
|
||||
|
||||
|
||||
X = MatrixSymbol("X", 3, 3)
|
||||
Y = MatrixSymbol("Y", 3, 3)
|
||||
|
||||
k = symbols("k")
|
||||
Xk = MatrixSymbol("X", k, k)
|
||||
|
||||
Xd = X.as_explicit()
|
||||
|
||||
x, y, z, t = symbols("x y z t")
|
||||
|
||||
|
||||
def test_applyfunc_matrix():
|
||||
x = Dummy('x')
|
||||
double = Lambda(x, x**2)
|
||||
|
||||
expr = ElementwiseApplyFunction(double, Xd)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
assert expr.doit() == Xd.applyfunc(lambda x: x**2)
|
||||
assert expr.shape == (3, 3)
|
||||
assert expr.func(*expr.args) == expr
|
||||
assert simplify(expr) == expr
|
||||
assert expr[0, 0] == double(Xd[0, 0])
|
||||
|
||||
expr = ElementwiseApplyFunction(double, X)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
assert isinstance(expr.doit(), ElementwiseApplyFunction)
|
||||
assert expr == X.applyfunc(double)
|
||||
assert expr.func(*expr.args) == expr
|
||||
|
||||
expr = ElementwiseApplyFunction(exp, X*Y)
|
||||
assert expr.expr == X*Y
|
||||
assert expr.function.dummy_eq(Lambda(x, exp(x)))
|
||||
assert expr.dummy_eq((X*Y).applyfunc(exp))
|
||||
assert expr.func(*expr.args) == expr
|
||||
|
||||
assert isinstance(X*expr, MatMul)
|
||||
assert (X*expr).shape == (3, 3)
|
||||
Z = MatrixSymbol("Z", 2, 3)
|
||||
assert (Z*expr).shape == (2, 3)
|
||||
|
||||
expr = ElementwiseApplyFunction(exp, Z.T)*ElementwiseApplyFunction(exp, Z)
|
||||
assert expr.shape == (3, 3)
|
||||
expr = ElementwiseApplyFunction(exp, Z)*ElementwiseApplyFunction(exp, Z.T)
|
||||
assert expr.shape == (2, 2)
|
||||
|
||||
M = Matrix([[x, y], [z, t]])
|
||||
expr = ElementwiseApplyFunction(sin, M)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
assert expr.function.dummy_eq(Lambda(x, sin(x)))
|
||||
assert expr.expr == M
|
||||
assert expr.doit() == M.applyfunc(sin)
|
||||
assert expr.doit() == Matrix([[sin(x), sin(y)], [sin(z), sin(t)]])
|
||||
assert expr.func(*expr.args) == expr
|
||||
|
||||
expr = ElementwiseApplyFunction(double, Xk)
|
||||
assert expr.doit() == expr
|
||||
assert expr.subs(k, 2).shape == (2, 2)
|
||||
assert (expr*expr).shape == (k, k)
|
||||
M = MatrixSymbol("M", k, t)
|
||||
expr2 = M.T*expr*M
|
||||
assert isinstance(expr2, MatMul)
|
||||
assert expr2.args[1] == expr
|
||||
assert expr2.shape == (t, t)
|
||||
expr3 = expr*M
|
||||
assert expr3.shape == (k, t)
|
||||
|
||||
expr1 = ElementwiseApplyFunction(lambda x: x+1, Xk)
|
||||
expr2 = ElementwiseApplyFunction(lambda x: x, Xk)
|
||||
assert expr1 != expr2
|
||||
|
||||
|
||||
def test_applyfunc_entry():
|
||||
|
||||
af = X.applyfunc(sin)
|
||||
assert af[0, 0] == sin(X[0, 0])
|
||||
|
||||
af = Xd.applyfunc(sin)
|
||||
assert af[0, 0] == sin(X[0, 0])
|
||||
|
||||
|
||||
def test_applyfunc_as_explicit():
|
||||
|
||||
af = X.applyfunc(sin)
|
||||
assert af.as_explicit() == Matrix([
|
||||
[sin(X[0, 0]), sin(X[0, 1]), sin(X[0, 2])],
|
||||
[sin(X[1, 0]), sin(X[1, 1]), sin(X[1, 2])],
|
||||
[sin(X[2, 0]), sin(X[2, 1]), sin(X[2, 2])],
|
||||
])
|
||||
|
||||
|
||||
def test_applyfunc_transpose():
|
||||
|
||||
af = Xk.applyfunc(sin)
|
||||
assert af.T.dummy_eq(Xk.T.applyfunc(sin))
|
||||
|
||||
|
||||
def test_applyfunc_shape_11_matrices():
|
||||
M = MatrixSymbol("M", 1, 1)
|
||||
|
||||
double = Lambda(x, x*2)
|
||||
|
||||
expr = M.applyfunc(sin)
|
||||
assert isinstance(expr, ElementwiseApplyFunction)
|
||||
|
||||
expr = M.applyfunc(double)
|
||||
assert isinstance(expr, MatMul)
|
||||
assert expr == 2*M
|
||||
@@ -0,0 +1,469 @@
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.testing.pytest import raises, slow
|
||||
from sympy.matrices.expressions.blockmatrix import (
|
||||
block_collapse, bc_matmul, bc_block_plus_ident, BlockDiagMatrix,
|
||||
BlockMatrix, bc_dist, bc_matadd, bc_transpose, bc_inverse,
|
||||
blockcut, reblock_2x2, deblock)
|
||||
from sympy.matrices.expressions import (
|
||||
MatrixSymbol, Identity, trace, det, ZeroMatrix, OneMatrix)
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.matrices import (
|
||||
Matrix, ImmutableMatrix, ImmutableSparseMatrix, zeros)
|
||||
from sympy.core import Tuple, Expr, S, Function
|
||||
from sympy.core.symbol import Symbol, symbols
|
||||
from sympy.functions import transpose, im, re
|
||||
|
||||
i, j, k, l, m, n, p = symbols('i:n, p', integer=True)
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
G = MatrixSymbol('G', n, n)
|
||||
H = MatrixSymbol('H', n, n)
|
||||
b1 = BlockMatrix([[G, H]])
|
||||
b2 = BlockMatrix([[G], [H]])
|
||||
|
||||
def test_bc_matmul():
|
||||
assert bc_matmul(H*b1*b2*G) == BlockMatrix([[(H*G*G + H*H*H)*G]])
|
||||
|
||||
def test_bc_matadd():
|
||||
assert bc_matadd(BlockMatrix([[G, H]]) + BlockMatrix([[H, H]])) == \
|
||||
BlockMatrix([[G+H, H+H]])
|
||||
|
||||
def test_bc_transpose():
|
||||
assert bc_transpose(Transpose(BlockMatrix([[A, B], [C, D]]))) == \
|
||||
BlockMatrix([[A.T, C.T], [B.T, D.T]])
|
||||
|
||||
def test_bc_dist_diag():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
C = MatrixSymbol('C', l, l)
|
||||
X = BlockDiagMatrix(A, B, C)
|
||||
|
||||
assert bc_dist(X+X).equals(BlockDiagMatrix(2*A, 2*B, 2*C))
|
||||
|
||||
def test_block_plus_ident():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
Z = MatrixSymbol('Z', n + m, n + m)
|
||||
assert bc_block_plus_ident(X + Identity(m + n) + Z) == \
|
||||
BlockDiagMatrix(Identity(n), Identity(m)) + X + Z
|
||||
|
||||
def test_BlockMatrix():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, k)
|
||||
C = MatrixSymbol('C', l, m)
|
||||
D = MatrixSymbol('D', l, k)
|
||||
M = MatrixSymbol('M', m + k, p)
|
||||
N = MatrixSymbol('N', l + n, k + m)
|
||||
X = BlockMatrix(Matrix([[A, B], [C, D]]))
|
||||
|
||||
assert X.__class__(*X.args) == X
|
||||
|
||||
# block_collapse does nothing on normal inputs
|
||||
E = MatrixSymbol('E', n, m)
|
||||
assert block_collapse(A + 2*E) == A + 2*E
|
||||
F = MatrixSymbol('F', m, m)
|
||||
assert block_collapse(E.T*A*F) == E.T*A*F
|
||||
|
||||
assert X.shape == (l + n, k + m)
|
||||
assert X.blockshape == (2, 2)
|
||||
assert transpose(X) == BlockMatrix(Matrix([[A.T, C.T], [B.T, D.T]]))
|
||||
assert transpose(X).shape == X.shape[::-1]
|
||||
|
||||
# Test that BlockMatrices and MatrixSymbols can still mix
|
||||
assert (X*M).is_MatMul
|
||||
assert X._blockmul(M).is_MatMul
|
||||
assert (X*M).shape == (n + l, p)
|
||||
assert (X + N).is_MatAdd
|
||||
assert X._blockadd(N).is_MatAdd
|
||||
assert (X + N).shape == X.shape
|
||||
|
||||
E = MatrixSymbol('E', m, 1)
|
||||
F = MatrixSymbol('F', k, 1)
|
||||
|
||||
Y = BlockMatrix(Matrix([[E], [F]]))
|
||||
|
||||
assert (X*Y).shape == (l + n, 1)
|
||||
assert block_collapse(X*Y).blocks[0, 0] == A*E + B*F
|
||||
assert block_collapse(X*Y).blocks[1, 0] == C*E + D*F
|
||||
|
||||
# block_collapse passes down into container objects, transposes, and inverse
|
||||
assert block_collapse(transpose(X*Y)) == transpose(block_collapse(X*Y))
|
||||
assert block_collapse(Tuple(X*Y, 2*X)) == (
|
||||
block_collapse(X*Y), block_collapse(2*X))
|
||||
|
||||
# Make sure that MatrixSymbols will enter 1x1 BlockMatrix if it simplifies
|
||||
Ab = BlockMatrix([[A]])
|
||||
Z = MatrixSymbol('Z', *A.shape)
|
||||
assert block_collapse(Ab + Z) == A + Z
|
||||
|
||||
def test_block_collapse_explicit_matrices():
|
||||
A = Matrix([[1, 2], [3, 4]])
|
||||
assert block_collapse(BlockMatrix([[A]])) == A
|
||||
|
||||
A = ImmutableSparseMatrix([[1, 2], [3, 4]])
|
||||
assert block_collapse(BlockMatrix([[A]])) == A
|
||||
|
||||
def test_issue_17624():
|
||||
a = MatrixSymbol("a", 2, 2)
|
||||
z = ZeroMatrix(2, 2)
|
||||
b = BlockMatrix([[a, z], [z, z]])
|
||||
assert block_collapse(b * b) == BlockMatrix([[a**2, z], [z, z]])
|
||||
assert block_collapse(b * b * b) == BlockMatrix([[a**3, z], [z, z]])
|
||||
|
||||
def test_issue_18618():
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
assert A == Matrix(BlockDiagMatrix(A))
|
||||
|
||||
def test_BlockMatrix_trace():
|
||||
A, B, C, D = [MatrixSymbol(s, 3, 3) for s in 'ABCD']
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert trace(X) == trace(A) + trace(D)
|
||||
assert trace(BlockMatrix([ZeroMatrix(n, n)])) == 0
|
||||
|
||||
def test_BlockMatrix_Determinant():
|
||||
A, B, C, D = [MatrixSymbol(s, 3, 3) for s in 'ABCD']
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.assume import assuming
|
||||
with assuming(Q.invertible(A)):
|
||||
assert det(X) == det(A) * det(X.schur('A'))
|
||||
|
||||
assert isinstance(det(X), Expr)
|
||||
assert det(BlockMatrix([A])) == det(A)
|
||||
assert det(BlockMatrix([ZeroMatrix(n, n)])) == 0
|
||||
|
||||
def test_squareBlockMatrix():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
Y = BlockMatrix([[A]])
|
||||
|
||||
assert X.is_square
|
||||
|
||||
Q = X + Identity(m + n)
|
||||
assert (block_collapse(Q) ==
|
||||
BlockMatrix([[A + Identity(n), B], [C, D + Identity(m)]]))
|
||||
|
||||
assert (X + MatrixSymbol('Q', n + m, n + m)).is_MatAdd
|
||||
assert (X * MatrixSymbol('Q', n + m, n + m)).is_MatMul
|
||||
|
||||
assert block_collapse(Y.I) == A.I
|
||||
|
||||
assert isinstance(X.inverse(), Inverse)
|
||||
|
||||
assert not X.is_Identity
|
||||
|
||||
Z = BlockMatrix([[Identity(n), B], [C, D]])
|
||||
assert not Z.is_Identity
|
||||
|
||||
|
||||
def test_BlockMatrix_2x2_inverse_symbolic():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, k - m)
|
||||
C = MatrixSymbol('C', k - n, m)
|
||||
D = MatrixSymbol('D', k - n, k - m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert X.is_square and X.shape == (k, k)
|
||||
assert isinstance(block_collapse(X.I), Inverse) # Can't invert when none of the blocks is square
|
||||
|
||||
# test code path where only A is invertible
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = ZeroMatrix(m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[A.I + A.I * B * X.schur('A').I * C * A.I, -A.I * B * X.schur('A').I],
|
||||
[-X.schur('A').I * C * A.I, X.schur('A').I],
|
||||
])
|
||||
|
||||
# test code path where only B is invertible
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = ZeroMatrix(m, m)
|
||||
D = MatrixSymbol('D', m, n)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[-X.schur('B').I * D * B.I, X.schur('B').I],
|
||||
[B.I + B.I * A * X.schur('B').I * D * B.I, -B.I * A * X.schur('B').I],
|
||||
])
|
||||
|
||||
# test code path where only C is invertible
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = ZeroMatrix(n, n)
|
||||
C = MatrixSymbol('C', m, m)
|
||||
D = MatrixSymbol('D', m, n)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[-C.I * D * X.schur('C').I, C.I + C.I * D * X.schur('C').I * A * C.I],
|
||||
[X.schur('C').I, -X.schur('C').I * A * C.I],
|
||||
])
|
||||
|
||||
# test code path where only D is invertible
|
||||
A = ZeroMatrix(n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
assert block_collapse(X.inverse()) == BlockMatrix([
|
||||
[X.schur('D').I, -X.schur('D').I * B * D.I],
|
||||
[-D.I * C * X.schur('D').I, D.I + D.I * C * X.schur('D').I * B * D.I],
|
||||
])
|
||||
|
||||
|
||||
def test_BlockMatrix_2x2_inverse_numeric():
|
||||
"""Test 2x2 block matrix inversion numerically for all 4 formulas"""
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
# rank deficient matrices that have full rank when two of them combined
|
||||
D1 = Matrix([[1, 2], [2, 4]])
|
||||
D2 = Matrix([[1, 3], [3, 9]])
|
||||
D3 = Matrix([[1, 4], [4, 16]])
|
||||
assert D1.rank() == D2.rank() == D3.rank() == 1
|
||||
assert (D1 + D2).rank() == (D2 + D3).rank() == (D3 + D1).rank() == 2
|
||||
|
||||
# Only A is invertible
|
||||
K = BlockMatrix([[M, D1], [D2, D3]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
# Only B is invertible
|
||||
K = BlockMatrix([[D1, M], [D2, D3]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
# Only C is invertible
|
||||
K = BlockMatrix([[D1, D2], [M, D3]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
# Only D is invertible
|
||||
K = BlockMatrix([[D1, D2], [D3, M]])
|
||||
assert block_collapse(K.inv()).as_explicit() == K.as_explicit().inv()
|
||||
|
||||
|
||||
@slow
|
||||
def test_BlockMatrix_3x3_symbolic():
|
||||
# Only test one of these, instead of all permutations, because it's slow
|
||||
rowblocksizes = (n, m, k)
|
||||
colblocksizes = (m, k, n)
|
||||
K = BlockMatrix([
|
||||
[MatrixSymbol('M%s%s' % (rows, cols), rows, cols) for cols in colblocksizes]
|
||||
for rows in rowblocksizes
|
||||
])
|
||||
collapse = block_collapse(K.I)
|
||||
assert isinstance(collapse, BlockMatrix)
|
||||
|
||||
|
||||
def test_BlockDiagMatrix():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
C = MatrixSymbol('C', l, l)
|
||||
M = MatrixSymbol('M', n + m + l, n + m + l)
|
||||
|
||||
X = BlockDiagMatrix(A, B, C)
|
||||
Y = BlockDiagMatrix(A, 2*B, 3*C)
|
||||
|
||||
assert X.blocks[1, 1] == B
|
||||
assert X.shape == (n + m + l, n + m + l)
|
||||
assert all(X.blocks[i, j].is_ZeroMatrix if i != j else X.blocks[i, j] in [A, B, C]
|
||||
for i in range(3) for j in range(3))
|
||||
assert X.__class__(*X.args) == X
|
||||
assert X.get_diag_blocks() == (A, B, C)
|
||||
|
||||
assert isinstance(block_collapse(X.I * X), Identity)
|
||||
|
||||
assert bc_matmul(X*X) == BlockDiagMatrix(A*A, B*B, C*C)
|
||||
assert block_collapse(X*X) == BlockDiagMatrix(A*A, B*B, C*C)
|
||||
#XXX: should be == ??
|
||||
assert block_collapse(X + X).equals(BlockDiagMatrix(2*A, 2*B, 2*C))
|
||||
assert block_collapse(X*Y) == BlockDiagMatrix(A*A, 2*B*B, 3*C*C)
|
||||
assert block_collapse(X + Y) == BlockDiagMatrix(2*A, 3*B, 4*C)
|
||||
|
||||
# Ensure that BlockDiagMatrices can still interact with normal MatrixExprs
|
||||
assert (X*(2*M)).is_MatMul
|
||||
assert (X + (2*M)).is_MatAdd
|
||||
|
||||
assert (X._blockmul(M)).is_MatMul
|
||||
assert (X._blockadd(M)).is_MatAdd
|
||||
|
||||
def test_BlockDiagMatrix_nonsquare():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', k, l)
|
||||
X = BlockDiagMatrix(A, B)
|
||||
assert X.shape == (n + k, m + l)
|
||||
assert X.shape == (n + k, m + l)
|
||||
assert X.rowblocksizes == [n, k]
|
||||
assert X.colblocksizes == [m, l]
|
||||
C = MatrixSymbol('C', n, m)
|
||||
D = MatrixSymbol('D', k, l)
|
||||
Y = BlockDiagMatrix(C, D)
|
||||
assert block_collapse(X + Y) == BlockDiagMatrix(A + C, B + D)
|
||||
assert block_collapse(X * Y.T) == BlockDiagMatrix(A * C.T, B * D.T)
|
||||
raises(NonInvertibleMatrixError, lambda: BlockDiagMatrix(A, C.T).inverse())
|
||||
|
||||
def test_BlockDiagMatrix_determinant():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
assert det(BlockDiagMatrix()) == 1
|
||||
assert det(BlockDiagMatrix(A)) == det(A)
|
||||
assert det(BlockDiagMatrix(A, B)) == det(A) * det(B)
|
||||
|
||||
# non-square blocks
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', n, m)
|
||||
assert det(BlockDiagMatrix(C, D)) == 0
|
||||
|
||||
def test_BlockDiagMatrix_trace():
|
||||
assert trace(BlockDiagMatrix()) == 0
|
||||
assert trace(BlockDiagMatrix(ZeroMatrix(n, n))) == 0
|
||||
A = MatrixSymbol('A', n, n)
|
||||
assert trace(BlockDiagMatrix(A)) == trace(A)
|
||||
B = MatrixSymbol('B', m, m)
|
||||
assert trace(BlockDiagMatrix(A, B)) == trace(A) + trace(B)
|
||||
|
||||
# non-square blocks
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', n, m)
|
||||
assert isinstance(trace(BlockDiagMatrix(C, D)), Trace)
|
||||
|
||||
def test_BlockDiagMatrix_transpose():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', k, l)
|
||||
assert transpose(BlockDiagMatrix()) == BlockDiagMatrix()
|
||||
assert transpose(BlockDiagMatrix(A)) == BlockDiagMatrix(A.T)
|
||||
assert transpose(BlockDiagMatrix(A, B)) == BlockDiagMatrix(A.T, B.T)
|
||||
|
||||
def test_issue_2460():
|
||||
bdm1 = BlockDiagMatrix(Matrix([i]), Matrix([j]))
|
||||
bdm2 = BlockDiagMatrix(Matrix([k]), Matrix([l]))
|
||||
assert block_collapse(bdm1 + bdm2) == BlockDiagMatrix(Matrix([i + k]), Matrix([j + l]))
|
||||
|
||||
def test_blockcut():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = blockcut(A, (n/2, n/2), (m/2, m/2))
|
||||
assert B == BlockMatrix([[A[:n/2, :m/2], A[:n/2, m/2:]],
|
||||
[A[n/2:, :m/2], A[n/2:, m/2:]]])
|
||||
|
||||
M = ImmutableMatrix(4, 4, range(16))
|
||||
B = blockcut(M, (2, 2), (2, 2))
|
||||
assert M == ImmutableMatrix(B)
|
||||
|
||||
B = blockcut(M, (1, 3), (2, 2))
|
||||
assert ImmutableMatrix(B.blocks[0, 1]) == ImmutableMatrix([[2, 3]])
|
||||
|
||||
def test_reblock_2x2():
|
||||
B = BlockMatrix([[MatrixSymbol('A_%d%d'%(i,j), 2, 2)
|
||||
for j in range(3)]
|
||||
for i in range(3)])
|
||||
assert B.blocks.shape == (3, 3)
|
||||
|
||||
BB = reblock_2x2(B)
|
||||
assert BB.blocks.shape == (2, 2)
|
||||
|
||||
assert B.shape == BB.shape
|
||||
assert B.as_explicit() == BB.as_explicit()
|
||||
|
||||
def test_deblock():
|
||||
B = BlockMatrix([[MatrixSymbol('A_%d%d'%(i,j), n, n)
|
||||
for j in range(4)]
|
||||
for i in range(4)])
|
||||
|
||||
assert deblock(reblock_2x2(B)) == B
|
||||
|
||||
def test_block_collapse_type():
|
||||
bm1 = BlockDiagMatrix(ImmutableMatrix([1]), ImmutableMatrix([2]))
|
||||
bm2 = BlockDiagMatrix(ImmutableMatrix([3]), ImmutableMatrix([4]))
|
||||
|
||||
assert bm1.T.__class__ == BlockDiagMatrix
|
||||
assert block_collapse(bm1 - bm2).__class__ == BlockDiagMatrix
|
||||
assert block_collapse(Inverse(bm1)).__class__ == BlockDiagMatrix
|
||||
assert block_collapse(Transpose(bm1)).__class__ == BlockDiagMatrix
|
||||
assert bc_transpose(Transpose(bm1)).__class__ == BlockDiagMatrix
|
||||
assert bc_inverse(Inverse(bm1)).__class__ == BlockDiagMatrix
|
||||
|
||||
def test_invalid_block_matrix():
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[Identity(2), Identity(5)],
|
||||
]))
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[Identity(n), Identity(m)],
|
||||
]))
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[ZeroMatrix(n, n), ZeroMatrix(n, n)],
|
||||
[ZeroMatrix(n, n - 1), ZeroMatrix(n, n + 1)],
|
||||
]))
|
||||
raises(ValueError, lambda: BlockMatrix([
|
||||
[ZeroMatrix(n - 1, n), ZeroMatrix(n, n)],
|
||||
[ZeroMatrix(n + 1, n), ZeroMatrix(n, n)],
|
||||
]))
|
||||
|
||||
def test_block_lu_decomposition():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, n)
|
||||
D = MatrixSymbol('D', m, m)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
|
||||
#LDU decomposition
|
||||
L, D, U = X.LDUdecomposition()
|
||||
assert block_collapse(L*D*U) == X
|
||||
|
||||
#UDL decomposition
|
||||
U, D, L = X.UDLdecomposition()
|
||||
assert block_collapse(U*D*L) == X
|
||||
|
||||
#LU decomposition
|
||||
L, U = X.LUdecomposition()
|
||||
assert block_collapse(L*U) == X
|
||||
|
||||
def test_issue_21866():
|
||||
n = 10
|
||||
I = Identity(n)
|
||||
O = ZeroMatrix(n, n)
|
||||
A = BlockMatrix([[ I, O, O, O ],
|
||||
[ O, I, O, O ],
|
||||
[ O, O, I, O ],
|
||||
[ I, O, O, I ]])
|
||||
Ainv = block_collapse(A.inv())
|
||||
AinvT = BlockMatrix([[ I, O, O, O ],
|
||||
[ O, I, O, O ],
|
||||
[ O, O, I, O ],
|
||||
[ -I, O, O, I ]])
|
||||
assert Ainv == AinvT
|
||||
|
||||
|
||||
def test_adjoint_and_special_matrices():
|
||||
A = Identity(3)
|
||||
B = OneMatrix(3, 2)
|
||||
C = ZeroMatrix(2, 3)
|
||||
D = Identity(2)
|
||||
X = BlockMatrix([[A, B], [C, D]])
|
||||
X2 = BlockMatrix([[A, S.ImaginaryUnit*B], [C, D]])
|
||||
assert X.adjoint() == BlockMatrix([[A, ZeroMatrix(3, 2)], [OneMatrix(2, 3), D]])
|
||||
assert re(X) == X
|
||||
assert X2.adjoint() == BlockMatrix([[A, ZeroMatrix(3, 2)], [-S.ImaginaryUnit*OneMatrix(2, 3), D]])
|
||||
assert im(X2) == BlockMatrix([[ZeroMatrix(3, 3), OneMatrix(3, 2)], [ZeroMatrix(2, 3), ZeroMatrix(2, 2)]])
|
||||
|
||||
|
||||
def test_block_matrix_derivative():
|
||||
x = symbols('x')
|
||||
A = Matrix(3, 3, [Function(f'a{i}')(x) for i in range(9)])
|
||||
bc = BlockMatrix([[A[:2, :2], A[:2, 2]], [A[2, :2], A[2:, 2]]])
|
||||
assert Matrix(bc.diff(x)) - A.diff(x) == zeros(3, 3)
|
||||
|
||||
|
||||
def test_transpose_inverse_commute():
|
||||
n = Symbol('n')
|
||||
I = Identity(n)
|
||||
Z = ZeroMatrix(n, n)
|
||||
A = BlockMatrix([[I, Z], [Z, I]])
|
||||
|
||||
assert block_collapse(A.transpose().inverse()) == A
|
||||
assert block_collapse(A.inverse().transpose()) == A
|
||||
|
||||
assert block_collapse(MatPow(A.transpose(), -2)) == MatPow(A, -2)
|
||||
assert block_collapse(MatPow(A, -2).transpose()) == MatPow(A, -2)
|
||||
@@ -0,0 +1,48 @@
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.symbol import Symbol, symbols
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
from sympy.matrices.expressions.companion import CompanionMatrix
|
||||
from sympy.polys.polytools import Poly
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_creation():
|
||||
x = Symbol('x')
|
||||
y = Symbol('y')
|
||||
raises(ValueError, lambda: CompanionMatrix(1))
|
||||
raises(ValueError, lambda: CompanionMatrix(Poly([1], x)))
|
||||
raises(ValueError, lambda: CompanionMatrix(Poly([2, 1], x)))
|
||||
raises(ValueError, lambda: CompanionMatrix(Poly(x*y, [x, y])))
|
||||
assert unchanged(CompanionMatrix, Poly([1, 2, 3], x))
|
||||
|
||||
|
||||
def test_shape():
|
||||
c0, c1, c2 = symbols('c0:3')
|
||||
x = Symbol('x')
|
||||
assert CompanionMatrix(Poly([1, c0], x)).shape == (1, 1)
|
||||
assert CompanionMatrix(Poly([1, c1, c0], x)).shape == (2, 2)
|
||||
assert CompanionMatrix(Poly([1, c2, c1, c0], x)).shape == (3, 3)
|
||||
|
||||
|
||||
def test_entry():
|
||||
c0, c1, c2 = symbols('c0:3')
|
||||
x = Symbol('x')
|
||||
A = CompanionMatrix(Poly([1, c2, c1, c0], x))
|
||||
assert A[0, 0] == 0
|
||||
assert A[1, 0] == 1
|
||||
assert A[1, 1] == 0
|
||||
assert A[2, 1] == 1
|
||||
assert A[0, 2] == -c0
|
||||
assert A[1, 2] == -c1
|
||||
assert A[2, 2] == -c2
|
||||
|
||||
|
||||
def test_as_explicit():
|
||||
c0, c1, c2 = symbols('c0:3')
|
||||
x = Symbol('x')
|
||||
assert CompanionMatrix(Poly([1, c0], x)).as_explicit() == \
|
||||
ImmutableDenseMatrix([-c0])
|
||||
assert CompanionMatrix(Poly([1, c1, c0], x)).as_explicit() == \
|
||||
ImmutableDenseMatrix([[0, -c0], [1, -c1]])
|
||||
assert CompanionMatrix(Poly([1, c2, c1, c0], x)).as_explicit() == \
|
||||
ImmutableDenseMatrix([[0, 0, -c0], [1, 0, -c1], [0, 1, -c2]])
|
||||
@@ -0,0 +1,477 @@
|
||||
"""
|
||||
Some examples have been taken from:
|
||||
|
||||
http://www.math.uwaterloo.ca/~hwolkowi//matrixcookbook.pdf
|
||||
"""
|
||||
from sympy import KroneckerProduct
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.exponential import (exp, log)
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin, tan)
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.expressions.determinant import Determinant
|
||||
from sympy.matrices.expressions.diagonal import DiagMatrix
|
||||
from sympy.matrices.expressions.hadamard import (HadamardPower, HadamardProduct, hadamard_product)
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import OneMatrix
|
||||
from sympy.matrices.expressions.trace import Trace
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.special import (Identity, ZeroMatrix)
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
from sympy.matrices.expressions import hadamard_power
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayAdd, ArrayTensorProduct, PermuteDims
|
||||
|
||||
i, j, k = symbols("i j k")
|
||||
m, n = symbols("m n")
|
||||
|
||||
X = MatrixSymbol("X", k, k)
|
||||
x = MatrixSymbol("x", k, 1)
|
||||
y = MatrixSymbol("y", k, 1)
|
||||
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
D = MatrixSymbol("D", k, k)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
b = MatrixSymbol("b", k, 1)
|
||||
c = MatrixSymbol("c", k, 1)
|
||||
d = MatrixSymbol("d", k, 1)
|
||||
|
||||
|
||||
KDelta = lambda i, j: KroneckerDelta(i, j, (0, k-1))
|
||||
|
||||
|
||||
def _check_derivative_with_explicit_matrix(expr, x, diffexpr, dim=2):
|
||||
# TODO: this is commented because it slows down the tests.
|
||||
return
|
||||
|
||||
expr = expr.xreplace({k: dim})
|
||||
x = x.xreplace({k: dim})
|
||||
diffexpr = diffexpr.xreplace({k: dim})
|
||||
|
||||
expr = expr.as_explicit()
|
||||
x = x.as_explicit()
|
||||
diffexpr = diffexpr.as_explicit()
|
||||
|
||||
assert expr.diff(x).reshape(*diffexpr.shape).tomatrix() == diffexpr
|
||||
|
||||
|
||||
def test_matrix_derivative_by_scalar():
|
||||
assert A.diff(i) == ZeroMatrix(k, k)
|
||||
assert (A*(X + B)*c).diff(i) == ZeroMatrix(k, 1)
|
||||
assert x.diff(i) == ZeroMatrix(k, 1)
|
||||
assert (x.T*y).diff(i) == ZeroMatrix(1, 1)
|
||||
assert (x*x.T).diff(i) == ZeroMatrix(k, k)
|
||||
assert (x + y).diff(i) == ZeroMatrix(k, 1)
|
||||
assert hadamard_power(x, 2).diff(i) == ZeroMatrix(k, 1)
|
||||
assert hadamard_power(x, i).diff(i).dummy_eq(
|
||||
HadamardProduct(x.applyfunc(log), HadamardPower(x, i)))
|
||||
assert hadamard_product(x, y).diff(i) == ZeroMatrix(k, 1)
|
||||
assert hadamard_product(i*OneMatrix(k, 1), x, y).diff(i) == hadamard_product(x, y)
|
||||
assert (i*x).diff(i) == x
|
||||
assert (sin(i)*A*B*x).diff(i) == cos(i)*A*B*x
|
||||
assert x.applyfunc(sin).diff(i) == ZeroMatrix(k, 1)
|
||||
assert Trace(i**2*X).diff(i) == 2*i*Trace(X)
|
||||
|
||||
mu = symbols("mu")
|
||||
expr = (2*mu*x)
|
||||
assert expr.diff(x) == 2*mu*Identity(k)
|
||||
|
||||
|
||||
def test_one_matrix():
|
||||
assert MatMul(x.T, OneMatrix(k, 1)).diff(x) == OneMatrix(k, 1)
|
||||
|
||||
|
||||
def test_matrix_derivative_non_matrix_result():
|
||||
# This is a 4-dimensional array:
|
||||
I = Identity(k)
|
||||
AdA = PermuteDims(ArrayTensorProduct(I, I), Permutation(3)(1, 2))
|
||||
assert A.diff(A) == AdA
|
||||
assert A.T.diff(A) == PermuteDims(ArrayTensorProduct(I, I), Permutation(3)(1, 2, 3))
|
||||
assert (2*A).diff(A) == PermuteDims(ArrayTensorProduct(2*I, I), Permutation(3)(1, 2))
|
||||
assert MatAdd(A, A).diff(A) == ArrayAdd(AdA, AdA)
|
||||
assert (A + B).diff(A) == AdA
|
||||
|
||||
|
||||
def test_matrix_derivative_trivial_cases():
|
||||
# Cookbook example 33:
|
||||
# TODO: find a way to represent a four-dimensional zero-array:
|
||||
assert X.diff(A) == ArrayDerivative(X, A)
|
||||
|
||||
|
||||
def test_matrix_derivative_with_inverse():
|
||||
|
||||
# Cookbook example 61:
|
||||
expr = a.T*Inverse(X)*b
|
||||
assert expr.diff(X) == -Inverse(X).T*a*b.T*Inverse(X).T
|
||||
|
||||
# Cookbook example 62:
|
||||
expr = Determinant(Inverse(X))
|
||||
# Not implemented yet:
|
||||
# assert expr.diff(X) == -Determinant(X.inv())*(X.inv()).T
|
||||
|
||||
# Cookbook example 63:
|
||||
expr = Trace(A*Inverse(X)*B)
|
||||
assert expr.diff(X) == -(X**(-1)*B*A*X**(-1)).T
|
||||
|
||||
# Cookbook example 64:
|
||||
expr = Trace(Inverse(X + A))
|
||||
assert expr.diff(X) == -(Inverse(X + A)).T**2
|
||||
|
||||
|
||||
def test_matrix_derivative_vectors_and_scalars():
|
||||
|
||||
assert x.diff(x) == Identity(k)
|
||||
assert x[i, 0].diff(x[m, 0]).doit() == KDelta(m, i)
|
||||
|
||||
assert x.T.diff(x) == Identity(k)
|
||||
|
||||
# Cookbook example 69:
|
||||
expr = x.T*a
|
||||
assert expr.diff(x) == a
|
||||
assert expr[0, 0].diff(x[m, 0]).doit() == a[m, 0]
|
||||
expr = a.T*x
|
||||
assert expr.diff(x) == a
|
||||
|
||||
# Cookbook example 70:
|
||||
expr = a.T*X*b
|
||||
assert expr.diff(X) == a*b.T
|
||||
|
||||
# Cookbook example 71:
|
||||
expr = a.T*X.T*b
|
||||
assert expr.diff(X) == b*a.T
|
||||
|
||||
# Cookbook example 72:
|
||||
expr = a.T*X*a
|
||||
assert expr.diff(X) == a*a.T
|
||||
expr = a.T*X.T*a
|
||||
assert expr.diff(X) == a*a.T
|
||||
|
||||
# Cookbook example 77:
|
||||
expr = b.T*X.T*X*c
|
||||
assert expr.diff(X) == X*b*c.T + X*c*b.T
|
||||
|
||||
# Cookbook example 78:
|
||||
expr = (B*x + b).T*C*(D*x + d)
|
||||
assert expr.diff(x) == B.T*C*(D*x + d) + D.T*C.T*(B*x + b)
|
||||
|
||||
# Cookbook example 81:
|
||||
expr = x.T*B*x
|
||||
assert expr.diff(x) == B*x + B.T*x
|
||||
|
||||
# Cookbook example 82:
|
||||
expr = b.T*X.T*D*X*c
|
||||
assert expr.diff(X) == D.T*X*b*c.T + D*X*c*b.T
|
||||
|
||||
# Cookbook example 83:
|
||||
expr = (X*b + c).T*D*(X*b + c)
|
||||
assert expr.diff(X) == D*(X*b + c)*b.T + D.T*(X*b + c)*b.T
|
||||
assert str(expr[0, 0].diff(X[m, n]).doit()) == \
|
||||
'b[n, 0]*Sum((c[_i_1, 0] + Sum(X[_i_1, _i_3]*b[_i_3, 0], (_i_3, 0, k - 1)))*D[_i_1, m], (_i_1, 0, k - 1)) + Sum((c[_i_2, 0] + Sum(X[_i_2, _i_4]*b[_i_4, 0], (_i_4, 0, k - 1)))*D[m, _i_2]*b[n, 0], (_i_2, 0, k - 1))'
|
||||
|
||||
# See https://github.com/sympy/sympy/issues/16504#issuecomment-1018339957
|
||||
expr = x*x.T*x
|
||||
I = Identity(k)
|
||||
assert expr.diff(x) == KroneckerProduct(I, x.T*x) + 2*x*x.T
|
||||
|
||||
|
||||
def test_matrix_derivatives_of_traces():
|
||||
|
||||
expr = Trace(A)*A
|
||||
I = Identity(k)
|
||||
assert expr.diff(A) == ArrayAdd(ArrayTensorProduct(I, A), PermuteDims(ArrayTensorProduct(Trace(A)*I, I), Permutation(3)(1, 2)))
|
||||
assert expr[i, j].diff(A[m, n]).doit() == (
|
||||
KDelta(i, m)*KDelta(j, n)*Trace(A) +
|
||||
KDelta(m, n)*A[i, j]
|
||||
)
|
||||
|
||||
## First order:
|
||||
|
||||
# Cookbook example 99:
|
||||
expr = Trace(X)
|
||||
assert expr.diff(X) == Identity(k)
|
||||
assert expr.rewrite(Sum).diff(X[m, n]).doit() == KDelta(m, n)
|
||||
|
||||
# Cookbook example 100:
|
||||
expr = Trace(X*A)
|
||||
assert expr.diff(X) == A.T
|
||||
assert expr.rewrite(Sum).diff(X[m, n]).doit() == A[n, m]
|
||||
|
||||
# Cookbook example 101:
|
||||
expr = Trace(A*X*B)
|
||||
assert expr.diff(X) == A.T*B.T
|
||||
assert expr.rewrite(Sum).diff(X[m, n]).doit().dummy_eq((A.T*B.T)[m, n])
|
||||
|
||||
# Cookbook example 102:
|
||||
expr = Trace(A*X.T*B)
|
||||
assert expr.diff(X) == B*A
|
||||
|
||||
# Cookbook example 103:
|
||||
expr = Trace(X.T*A)
|
||||
assert expr.diff(X) == A
|
||||
|
||||
# Cookbook example 104:
|
||||
expr = Trace(A*X.T)
|
||||
assert expr.diff(X) == A
|
||||
|
||||
# Cookbook example 105:
|
||||
# TODO: TensorProduct is not supported
|
||||
#expr = Trace(TensorProduct(A, X))
|
||||
#assert expr.diff(X) == Trace(A)*Identity(k)
|
||||
|
||||
## Second order:
|
||||
|
||||
# Cookbook example 106:
|
||||
expr = Trace(X**2)
|
||||
assert expr.diff(X) == 2*X.T
|
||||
|
||||
# Cookbook example 107:
|
||||
expr = Trace(X**2*B)
|
||||
assert expr.diff(X) == (X*B + B*X).T
|
||||
expr = Trace(MatMul(X, X, B))
|
||||
assert expr.diff(X) == (X*B + B*X).T
|
||||
|
||||
# Cookbook example 108:
|
||||
expr = Trace(X.T*B*X)
|
||||
assert expr.diff(X) == B*X + B.T*X
|
||||
|
||||
# Cookbook example 109:
|
||||
expr = Trace(B*X*X.T)
|
||||
assert expr.diff(X) == B*X + B.T*X
|
||||
|
||||
# Cookbook example 110:
|
||||
expr = Trace(X*X.T*B)
|
||||
assert expr.diff(X) == B*X + B.T*X
|
||||
|
||||
# Cookbook example 111:
|
||||
expr = Trace(X*B*X.T)
|
||||
assert expr.diff(X) == X*B.T + X*B
|
||||
|
||||
# Cookbook example 112:
|
||||
expr = Trace(B*X.T*X)
|
||||
assert expr.diff(X) == X*B.T + X*B
|
||||
|
||||
# Cookbook example 113:
|
||||
expr = Trace(X.T*X*B)
|
||||
assert expr.diff(X) == X*B.T + X*B
|
||||
|
||||
# Cookbook example 114:
|
||||
expr = Trace(A*X*B*X)
|
||||
assert expr.diff(X) == A.T*X.T*B.T + B.T*X.T*A.T
|
||||
|
||||
# Cookbook example 115:
|
||||
expr = Trace(X.T*X)
|
||||
assert expr.diff(X) == 2*X
|
||||
expr = Trace(X*X.T)
|
||||
assert expr.diff(X) == 2*X
|
||||
|
||||
# Cookbook example 116:
|
||||
expr = Trace(B.T*X.T*C*X*B)
|
||||
assert expr.diff(X) == C.T*X*B*B.T + C*X*B*B.T
|
||||
|
||||
# Cookbook example 117:
|
||||
expr = Trace(X.T*B*X*C)
|
||||
assert expr.diff(X) == B*X*C + B.T*X*C.T
|
||||
|
||||
# Cookbook example 118:
|
||||
expr = Trace(A*X*B*X.T*C)
|
||||
assert expr.diff(X) == A.T*C.T*X*B.T + C*A*X*B
|
||||
|
||||
# Cookbook example 119:
|
||||
expr = Trace((A*X*B + C)*(A*X*B + C).T)
|
||||
assert expr.diff(X) == 2*A.T*(A*X*B + C)*B.T
|
||||
|
||||
# Cookbook example 120:
|
||||
# TODO: no support for TensorProduct.
|
||||
# expr = Trace(TensorProduct(X, X))
|
||||
# expr = Trace(X)*Trace(X)
|
||||
# expr.diff(X) == 2*Trace(X)*Identity(k)
|
||||
|
||||
# Higher Order
|
||||
|
||||
# Cookbook example 121:
|
||||
expr = Trace(X**k)
|
||||
#assert expr.diff(X) == k*(X**(k-1)).T
|
||||
|
||||
# Cookbook example 122:
|
||||
expr = Trace(A*X**k)
|
||||
#assert expr.diff(X) == # Needs indices
|
||||
|
||||
# Cookbook example 123:
|
||||
expr = Trace(B.T*X.T*C*X*X.T*C*X*B)
|
||||
assert expr.diff(X) == C*X*X.T*C*X*B*B.T + C.T*X*B*B.T*X.T*C.T*X + C*X*B*B.T*X.T*C*X + C.T*X*X.T*C.T*X*B*B.T
|
||||
|
||||
# Other
|
||||
|
||||
# Cookbook example 124:
|
||||
expr = Trace(A*X**(-1)*B)
|
||||
assert expr.diff(X) == -Inverse(X).T*A.T*B.T*Inverse(X).T
|
||||
|
||||
# Cookbook example 125:
|
||||
expr = Trace(Inverse(X.T*C*X)*A)
|
||||
# Warning: result in the cookbook is equivalent if B and C are symmetric:
|
||||
assert expr.diff(X) == - X.inv().T*A.T*X.inv()*C.inv().T*X.inv().T - X.inv().T*A*X.inv()*C.inv()*X.inv().T
|
||||
|
||||
# Cookbook example 126:
|
||||
expr = Trace((X.T*C*X).inv()*(X.T*B*X))
|
||||
assert expr.diff(X) == -2*C*X*(X.T*C*X).inv()*X.T*B*X*(X.T*C*X).inv() + 2*B*X*(X.T*C*X).inv()
|
||||
|
||||
# Cookbook example 127:
|
||||
expr = Trace((A + X.T*C*X).inv()*(X.T*B*X))
|
||||
# Warning: result in the cookbook is equivalent if B and C are symmetric:
|
||||
assert expr.diff(X) == B*X*Inverse(A + X.T*C*X) - C*X*Inverse(A + X.T*C*X)*X.T*B*X*Inverse(A + X.T*C*X) - C.T*X*Inverse(A.T + (C*X).T*X)*X.T*B.T*X*Inverse(A.T + (C*X).T*X) + B.T*X*Inverse(A.T + (C*X).T*X)
|
||||
|
||||
|
||||
def test_derivatives_of_complicated_matrix_expr():
|
||||
expr = a.T*(A*X*(X.T*B + X*A) + B.T*X.T*(a*b.T*(X*D*X.T + X*(X.T*B + A*X)*D*B - X.T*C.T*A)*B + B*(X*D.T + B*A*X*A.T - 3*X*D))*B + 42*X*B*X.T*A.T*(X + X.T))*b
|
||||
result = (B*(B*A*X*A.T - 3*X*D + X*D.T) + a*b.T*(X*(A*X + X.T*B)*D*B + X*D*X.T - X.T*C.T*A)*B)*B*b*a.T*B.T + B**2*b*a.T*B.T*X.T*a*b.T*X*D + 42*A*X*B.T*X.T*a*b.T + B*D*B**3*b*a.T*B.T*X.T*a*b.T*X + B*b*a.T*A*X + a*b.T*(42*X + 42*X.T)*A*X*B.T + b*a.T*X*B*a*b.T*B.T**2*X*D.T + b*a.T*X*B*a*b.T*B.T**3*D.T*(B.T*X + X.T*A.T) + 42*b*a.T*X*B*X.T*A.T + A.T*(42*X + 42*X.T)*b*a.T*X*B + A.T*B.T**2*X*B*a*b.T*B.T*A + A.T*a*b.T*(A.T*X.T + B.T*X) + A.T*X.T*b*a.T*X*B*a*b.T*B.T**3*D.T + B.T*X*B*a*b.T*B.T*D - 3*B.T*X*B*a*b.T*B.T*D.T - C.T*A*B**2*b*a.T*B.T*X.T*a*b.T + X.T*A.T*a*b.T*A.T
|
||||
assert expr.diff(X) == result
|
||||
|
||||
|
||||
def test_mixed_deriv_mixed_expressions():
|
||||
|
||||
expr = 3*Trace(A)
|
||||
assert expr.diff(A) == 3*Identity(k)
|
||||
|
||||
expr = k
|
||||
deriv = expr.diff(A)
|
||||
assert isinstance(deriv, ZeroMatrix)
|
||||
assert deriv == ZeroMatrix(k, k)
|
||||
|
||||
expr = Trace(A)**2
|
||||
assert expr.diff(A) == (2*Trace(A))*Identity(k)
|
||||
|
||||
expr = Trace(A)*A
|
||||
I = Identity(k)
|
||||
assert expr.diff(A) == ArrayAdd(ArrayTensorProduct(I, A), PermuteDims(ArrayTensorProduct(Trace(A)*I, I), Permutation(3)(1, 2)))
|
||||
|
||||
expr = Trace(Trace(A)*A)
|
||||
assert expr.diff(A) == (2*Trace(A))*Identity(k)
|
||||
|
||||
expr = Trace(Trace(Trace(A)*A)*A)
|
||||
assert expr.diff(A) == (3*Trace(A)**2)*Identity(k)
|
||||
|
||||
|
||||
def test_derivatives_matrix_norms():
|
||||
|
||||
expr = x.T*y
|
||||
assert expr.diff(x) == y
|
||||
assert expr[0, 0].diff(x[m, 0]).doit() == y[m, 0]
|
||||
|
||||
expr = (x.T*y)**S.Half
|
||||
assert expr.diff(x) == y/(2*sqrt(x.T*y))
|
||||
|
||||
expr = (x.T*x)**S.Half
|
||||
assert expr.diff(x) == x*(x.T*x)**Rational(-1, 2)
|
||||
|
||||
expr = (c.T*a*x.T*b)**S.Half
|
||||
assert expr.diff(x) == b*a.T*c/sqrt(c.T*a*x.T*b)/2
|
||||
|
||||
expr = (c.T*a*x.T*b)**Rational(1, 3)
|
||||
assert expr.diff(x) == b*a.T*c*(c.T*a*x.T*b)**Rational(-2, 3)/3
|
||||
|
||||
expr = (a.T*X*b)**S.Half
|
||||
assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*b.T
|
||||
|
||||
expr = d.T*x*(a.T*X*b)**S.Half*y.T*c
|
||||
assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*x.T*d*y.T*c*b.T
|
||||
|
||||
|
||||
def test_derivatives_elementwise_applyfunc():
|
||||
|
||||
expr = x.applyfunc(tan)
|
||||
assert expr.diff(x).dummy_eq(
|
||||
DiagMatrix(x.applyfunc(lambda x: tan(x)**2 + 1)))
|
||||
assert expr[i, 0].diff(x[m, 0]).doit() == (tan(x[i, 0])**2 + 1)*KDelta(i, m)
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = (i**2*x).applyfunc(sin)
|
||||
assert expr.diff(i).dummy_eq(
|
||||
HadamardProduct((2*i)*x, (i**2*x).applyfunc(cos)))
|
||||
assert expr[i, 0].diff(i).doit() == 2*i*x[i, 0]*cos(i**2*x[i, 0])
|
||||
_check_derivative_with_explicit_matrix(expr, i, expr.diff(i))
|
||||
|
||||
expr = (log(i)*A*B).applyfunc(sin)
|
||||
assert expr.diff(i).dummy_eq(
|
||||
HadamardProduct(A*B/i, (log(i)*A*B).applyfunc(cos)))
|
||||
_check_derivative_with_explicit_matrix(expr, i, expr.diff(i))
|
||||
|
||||
expr = A*x.applyfunc(exp)
|
||||
# TODO: restore this result (currently returning the transpose):
|
||||
# assert expr.diff(x).dummy_eq(DiagMatrix(x.applyfunc(exp))*A.T)
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = x.T*A*x + k*y.applyfunc(sin).T*x
|
||||
assert expr.diff(x).dummy_eq(A.T*x + A*x + k*y.applyfunc(sin))
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = x.applyfunc(sin).T*y
|
||||
# TODO: restore (currently returning the transpose):
|
||||
# assert expr.diff(x).dummy_eq(DiagMatrix(x.applyfunc(cos))*y)
|
||||
_check_derivative_with_explicit_matrix(expr, x, expr.diff(x))
|
||||
|
||||
expr = (a.T * X * b).applyfunc(sin)
|
||||
assert expr.diff(X).dummy_eq(a*(a.T*X*b).applyfunc(cos)*b.T)
|
||||
_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T * X.applyfunc(sin) * b
|
||||
assert expr.diff(X).dummy_eq(
|
||||
DiagMatrix(a)*X.applyfunc(cos)*DiagMatrix(b))
|
||||
_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T * (A*X*B).applyfunc(sin) * b
|
||||
assert expr.diff(X).dummy_eq(
|
||||
A.T*DiagMatrix(a)*(A*X*B).applyfunc(cos)*DiagMatrix(b)*B.T)
|
||||
_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T * (A*X*b).applyfunc(sin) * b.T
|
||||
# TODO: not implemented
|
||||
#assert expr.diff(X) == ...
|
||||
#_check_derivative_with_explicit_matrix(expr, X, expr.diff(X))
|
||||
|
||||
expr = a.T*A*X.applyfunc(sin)*B*b
|
||||
assert expr.diff(X).dummy_eq(
|
||||
HadamardProduct(A.T * a * b.T * B.T, X.applyfunc(cos)))
|
||||
|
||||
expr = a.T * (A*X.applyfunc(sin)*B).applyfunc(log) * b
|
||||
# TODO: wrong
|
||||
# assert expr.diff(X) == A.T*DiagMatrix(a)*(A*X.applyfunc(sin)*B).applyfunc(Lambda(k, 1/k))*DiagMatrix(b)*B.T
|
||||
|
||||
expr = a.T * (X.applyfunc(sin)).applyfunc(log) * b
|
||||
# TODO: wrong
|
||||
# assert expr.diff(X) == DiagMatrix(a)*X.applyfunc(sin).applyfunc(Lambda(k, 1/k))*DiagMatrix(b)
|
||||
|
||||
|
||||
def test_derivatives_of_hadamard_expressions():
|
||||
|
||||
# Hadamard Product
|
||||
|
||||
expr = hadamard_product(a, x, b)
|
||||
assert expr.diff(x) == DiagMatrix(hadamard_product(b, a))
|
||||
|
||||
expr = a.T*hadamard_product(A, X, B)*b
|
||||
assert expr.diff(X) == HadamardProduct(a*b.T, A, B)
|
||||
|
||||
# Hadamard Power
|
||||
|
||||
expr = hadamard_power(x, 2)
|
||||
assert expr.diff(x).doit() == 2*DiagMatrix(x)
|
||||
|
||||
expr = hadamard_power(x.T, 2)
|
||||
assert expr.diff(x).doit() == 2*DiagMatrix(x)
|
||||
|
||||
expr = hadamard_power(x, S.Half)
|
||||
assert expr.diff(x) == S.Half*DiagMatrix(hadamard_power(x, Rational(-1, 2)))
|
||||
|
||||
expr = hadamard_power(a.T*X*b, 2)
|
||||
assert expr.diff(X) == 2*a*a.T*X*b*b.T
|
||||
|
||||
expr = hadamard_power(a.T*X*b, S.Half)
|
||||
assert expr.diff(X) == a/(2*sqrt(a.T*X*b))*b.T
|
||||
@@ -0,0 +1,65 @@
|
||||
from sympy.core import S, symbols
|
||||
from sympy.matrices import eye, ones, Matrix, ShapeError
|
||||
from sympy.matrices.expressions import (
|
||||
Identity, MatrixExpr, MatrixSymbol, Determinant,
|
||||
det, per, ZeroMatrix, Transpose,
|
||||
Permanent, MatMul
|
||||
)
|
||||
from sympy.matrices.expressions.special import OneMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = MatrixSymbol('C', 3, 4)
|
||||
|
||||
|
||||
def test_det():
|
||||
assert isinstance(Determinant(A), Determinant)
|
||||
assert not isinstance(Determinant(A), MatrixExpr)
|
||||
raises(ShapeError, lambda: Determinant(C))
|
||||
assert det(eye(3)) == 1
|
||||
assert det(Matrix(3, 3, [1, 3, 2, 4, 1, 3, 2, 5, 2])) == 17
|
||||
_ = A / det(A) # Make sure this is possible
|
||||
|
||||
raises(TypeError, lambda: Determinant(S.One))
|
||||
|
||||
assert Determinant(A).arg is A
|
||||
|
||||
|
||||
def test_eval_determinant():
|
||||
assert det(Identity(n)) == 1
|
||||
assert det(ZeroMatrix(n, n)) == 0
|
||||
assert det(OneMatrix(n, n)) == Determinant(OneMatrix(n, n))
|
||||
assert det(OneMatrix(1, 1)) == 1
|
||||
assert det(OneMatrix(2, 2)) == 0
|
||||
assert det(Transpose(A)) == det(A)
|
||||
assert Determinant(MatMul(eye(2), eye(2))).doit(deep=True) == 1
|
||||
|
||||
|
||||
def test_refine():
|
||||
assert refine(det(A), Q.orthogonal(A)) == 1
|
||||
assert refine(det(A), Q.singular(A)) == 0
|
||||
assert refine(det(A), Q.unit_triangular(A)) == 1
|
||||
assert refine(det(A), Q.normal(A)) == det(A)
|
||||
|
||||
|
||||
def test_commutative():
|
||||
det_a = Determinant(A)
|
||||
det_b = Determinant(B)
|
||||
assert det_a.is_commutative
|
||||
assert det_b.is_commutative
|
||||
assert det_a * det_b == det_b * det_a
|
||||
|
||||
|
||||
def test_permanent():
|
||||
assert isinstance(Permanent(A), Permanent)
|
||||
assert not isinstance(Permanent(A), MatrixExpr)
|
||||
assert isinstance(Permanent(C), Permanent)
|
||||
assert Permanent(ones(3, 3)).doit() == 6
|
||||
_ = C / per(C)
|
||||
assert per(Matrix(3, 3, [1, 3, 2, 4, 1, 3, 2, 5, 2])) == 103
|
||||
raises(TypeError, lambda: Permanent(S.One))
|
||||
assert Permanent(A).arg is A
|
||||
@@ -0,0 +1,156 @@
|
||||
from sympy.matrices.expressions import MatrixSymbol
|
||||
from sympy.matrices.expressions.diagonal import DiagonalMatrix, DiagonalOf, DiagMatrix, diagonalize_vector
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.special import Identity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
n = Symbol('n')
|
||||
m = Symbol('m')
|
||||
|
||||
|
||||
def test_DiagonalMatrix():
|
||||
x = MatrixSymbol('x', n, m)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length is None
|
||||
assert D.shape == (n, m)
|
||||
|
||||
x = MatrixSymbol('x', n, n)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length == n
|
||||
assert D.shape == (n, n)
|
||||
assert D[1, 2] == 0
|
||||
assert D[1, 1] == x[1, 1]
|
||||
i = Symbol('i')
|
||||
j = Symbol('j')
|
||||
x = MatrixSymbol('x', 3, 3)
|
||||
ij = DiagonalMatrix(x)[i, j]
|
||||
assert ij != 0
|
||||
assert ij.subs({i:0, j:0}) == x[0, 0]
|
||||
assert ij.subs({i:0, j:1}) == 0
|
||||
assert ij.subs({i:1, j:1}) == x[1, 1]
|
||||
assert ask(Q.diagonal(D)) # affirm that D is diagonal
|
||||
|
||||
x = MatrixSymbol('x', n, 3)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length == 3
|
||||
assert D.shape == (n, 3)
|
||||
assert D[2, m] == KroneckerDelta(2, m)*x[2, m]
|
||||
assert D[3, m] == 0
|
||||
raises(IndexError, lambda: D[m, 3])
|
||||
|
||||
x = MatrixSymbol('x', 3, n)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length == 3
|
||||
assert D.shape == (3, n)
|
||||
assert D[m, 2] == KroneckerDelta(m, 2)*x[m, 2]
|
||||
assert D[m, 3] == 0
|
||||
raises(IndexError, lambda: D[3, m])
|
||||
|
||||
x = MatrixSymbol('x', n, m)
|
||||
D = DiagonalMatrix(x)
|
||||
assert D.diagonal_length is None
|
||||
assert D.shape == (n, m)
|
||||
assert D[m, 4] != 0
|
||||
|
||||
x = MatrixSymbol('x', 3, 4)
|
||||
assert [DiagonalMatrix(x)[i] for i in range(12)] == [
|
||||
x[0, 0], 0, 0, 0, 0, x[1, 1], 0, 0, 0, 0, x[2, 2], 0]
|
||||
|
||||
# shape is retained, issue 12427
|
||||
assert (
|
||||
DiagonalMatrix(MatrixSymbol('x', 3, 4))*
|
||||
DiagonalMatrix(MatrixSymbol('x', 4, 2))).shape == (3, 2)
|
||||
|
||||
|
||||
def test_DiagonalOf():
|
||||
x = MatrixSymbol('x', n, n)
|
||||
d = DiagonalOf(x)
|
||||
assert d.shape == (n, 1)
|
||||
assert d.diagonal_length == n
|
||||
assert d[2, 0] == d[2] == x[2, 2]
|
||||
|
||||
x = MatrixSymbol('x', n, m)
|
||||
d = DiagonalOf(x)
|
||||
assert d.shape == (None, 1)
|
||||
assert d.diagonal_length is None
|
||||
assert d[2, 0] == d[2] == x[2, 2]
|
||||
|
||||
d = DiagonalOf(MatrixSymbol('x', 4, 3))
|
||||
assert d.shape == (3, 1)
|
||||
d = DiagonalOf(MatrixSymbol('x', n, 3))
|
||||
assert d.shape == (3, 1)
|
||||
d = DiagonalOf(MatrixSymbol('x', 3, n))
|
||||
assert d.shape == (3, 1)
|
||||
x = MatrixSymbol('x', n, m)
|
||||
assert [DiagonalOf(x)[i] for i in range(4)] ==[
|
||||
x[0, 0], x[1, 1], x[2, 2], x[3, 3]]
|
||||
|
||||
|
||||
def test_DiagMatrix():
|
||||
x = MatrixSymbol('x', n, 1)
|
||||
d = DiagMatrix(x)
|
||||
assert d.shape == (n, n)
|
||||
assert d[0, 1] == 0
|
||||
assert d[0, 0] == x[0, 0]
|
||||
|
||||
a = MatrixSymbol('a', 1, 1)
|
||||
d = diagonalize_vector(a)
|
||||
assert isinstance(d, MatrixSymbol)
|
||||
assert a == d
|
||||
assert diagonalize_vector(Identity(3)) == Identity(3)
|
||||
assert DiagMatrix(Identity(3)).doit() == Identity(3)
|
||||
assert isinstance(DiagMatrix(Identity(3)), DiagMatrix)
|
||||
|
||||
# A diagonal matrix is equal to its transpose:
|
||||
assert DiagMatrix(x).T == DiagMatrix(x)
|
||||
assert diagonalize_vector(x.T) == DiagMatrix(x)
|
||||
|
||||
dx = DiagMatrix(x)
|
||||
assert dx[0, 0] == x[0, 0]
|
||||
assert dx[1, 1] == x[1, 0]
|
||||
assert dx[0, 1] == 0
|
||||
assert dx[0, m] == x[0, 0]*KroneckerDelta(0, m)
|
||||
|
||||
z = MatrixSymbol('z', 1, n)
|
||||
dz = DiagMatrix(z)
|
||||
assert dz[0, 0] == z[0, 0]
|
||||
assert dz[1, 1] == z[0, 1]
|
||||
assert dz[0, 1] == 0
|
||||
assert dz[0, m] == z[0, m]*KroneckerDelta(0, m)
|
||||
|
||||
v = MatrixSymbol('v', 3, 1)
|
||||
dv = DiagMatrix(v)
|
||||
assert dv.as_explicit() == Matrix([
|
||||
[v[0, 0], 0, 0],
|
||||
[0, v[1, 0], 0],
|
||||
[0, 0, v[2, 0]],
|
||||
])
|
||||
|
||||
v = MatrixSymbol('v', 1, 3)
|
||||
dv = DiagMatrix(v)
|
||||
assert dv.as_explicit() == Matrix([
|
||||
[v[0, 0], 0, 0],
|
||||
[0, v[0, 1], 0],
|
||||
[0, 0, v[0, 2]],
|
||||
])
|
||||
|
||||
dv = DiagMatrix(3*v)
|
||||
assert dv.args == (3*v,)
|
||||
assert dv.doit() == 3*DiagMatrix(v)
|
||||
assert isinstance(dv.doit(), MatMul)
|
||||
|
||||
a = MatrixSymbol("a", 3, 1).as_explicit()
|
||||
expr = DiagMatrix(a)
|
||||
result = Matrix([
|
||||
[a[0, 0], 0, 0],
|
||||
[0, a[1, 0], 0],
|
||||
[0, 0, a[2, 0]],
|
||||
])
|
||||
assert expr.doit() == result
|
||||
expr = DiagMatrix(a.T)
|
||||
assert expr.doit() == result
|
||||
@@ -0,0 +1,35 @@
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.dotproduct import DotProduct
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
A = Matrix(3, 1, [1, 2, 3])
|
||||
B = Matrix(3, 1, [1, 3, 5])
|
||||
C = Matrix(4, 1, [1, 2, 4, 5])
|
||||
D = Matrix(2, 2, [1, 2, 3, 4])
|
||||
|
||||
def test_docproduct():
|
||||
assert DotProduct(A, B).doit() == 22
|
||||
assert DotProduct(A.T, B).doit() == 22
|
||||
assert DotProduct(A, B.T).doit() == 22
|
||||
assert DotProduct(A.T, B.T).doit() == 22
|
||||
|
||||
raises(TypeError, lambda: DotProduct(1, A))
|
||||
raises(TypeError, lambda: DotProduct(A, 1))
|
||||
raises(TypeError, lambda: DotProduct(A, D))
|
||||
raises(TypeError, lambda: DotProduct(D, A))
|
||||
|
||||
raises(TypeError, lambda: DotProduct(B, C).doit())
|
||||
|
||||
def test_dotproduct_symbolic():
|
||||
A = MatrixSymbol('A', 3, 1)
|
||||
B = MatrixSymbol('B', 3, 1)
|
||||
|
||||
dot = DotProduct(A, B)
|
||||
assert dot.is_scalar == True
|
||||
assert unchanged(Mul, 2, dot)
|
||||
# XXX Fix forced evaluation for arithmetics with matrix expressions
|
||||
assert dot * A == (A[0, 0]*B[0, 0] + A[1, 0]*B[1, 0] + A[2, 0]*B[2, 0])*A
|
||||
@@ -0,0 +1,29 @@
|
||||
from sympy.matrices.expressions.factorizations import lu, LofCholesky, qr, svd
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
|
||||
n = Symbol('n')
|
||||
X = MatrixSymbol('X', n, n)
|
||||
|
||||
def test_LU():
|
||||
L, U = lu(X)
|
||||
assert L.shape == U.shape == X.shape
|
||||
assert ask(Q.lower_triangular(L))
|
||||
assert ask(Q.upper_triangular(U))
|
||||
|
||||
def test_Cholesky():
|
||||
LofCholesky(X)
|
||||
|
||||
def test_QR():
|
||||
Q_, R = qr(X)
|
||||
assert Q_.shape == R.shape == X.shape
|
||||
assert ask(Q.orthogonal(Q_))
|
||||
assert ask(Q.upper_triangular(R))
|
||||
|
||||
def test_svd():
|
||||
U, S, V = svd(X)
|
||||
assert U.shape == S.shape == V.shape == X.shape
|
||||
assert ask(Q.orthogonal(U))
|
||||
assert ask(Q.orthogonal(V))
|
||||
assert ask(Q.diagonal(S))
|
||||
@@ -0,0 +1,44 @@
|
||||
from sympy.assumptions.ask import (Q, ask)
|
||||
from sympy.core.numbers import (I, Rational)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.exponential import exp
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices.expressions.fourier import DFT, IDFT
|
||||
from sympy.matrices import det, Matrix, Identity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_dft_creation():
|
||||
assert DFT(2)
|
||||
assert DFT(0)
|
||||
raises(ValueError, lambda: DFT(-1))
|
||||
raises(ValueError, lambda: DFT(2.0))
|
||||
raises(ValueError, lambda: DFT(2 + 1j))
|
||||
|
||||
n = symbols('n')
|
||||
assert DFT(n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: DFT(n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: DFT(n))
|
||||
|
||||
|
||||
def test_dft():
|
||||
n, i, j = symbols('n i j')
|
||||
assert DFT(4).shape == (4, 4)
|
||||
assert ask(Q.unitary(DFT(4)))
|
||||
assert Abs(simplify(det(Matrix(DFT(4))))) == 1
|
||||
assert DFT(n)*IDFT(n) == Identity(n)
|
||||
assert DFT(n)[i, j] == exp(-2*S.Pi*I/n)**(i*j) / sqrt(n)
|
||||
|
||||
|
||||
def test_dft2():
|
||||
assert DFT(1).as_explicit() == Matrix([[1]])
|
||||
assert DFT(2).as_explicit() == 1/sqrt(2)*Matrix([[1,1],[1,-1]])
|
||||
assert DFT(4).as_explicit() == Matrix([[S.Half, S.Half, S.Half, S.Half],
|
||||
[S.Half, -I/2, Rational(-1,2), I/2],
|
||||
[S.Half, Rational(-1,2), S.Half, Rational(-1,2)],
|
||||
[S.Half, I/2, Rational(-1,2), -I/2]])
|
||||
@@ -0,0 +1,54 @@
|
||||
from sympy.core import symbols, Lambda
|
||||
from sympy.core.sympify import SympifyError
|
||||
from sympy.functions import KroneckerDelta
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions import FunctionMatrix, MatrixExpr, Identity
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_funcmatrix_creation():
|
||||
i, j, k = symbols('i j k')
|
||||
assert FunctionMatrix(2, 2, Lambda((i, j), 0))
|
||||
assert FunctionMatrix(0, 0, Lambda((i, j), 0))
|
||||
|
||||
raises(ValueError, lambda: FunctionMatrix(-1, 0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2.0, 0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2j, 0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(0, -1, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(0, 2.0, Lambda((i, j), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(0, 2j, Lambda((i, j), 0)))
|
||||
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda(i, 0)))
|
||||
raises(SympifyError, lambda: FunctionMatrix(2, 2, lambda i, j: 0))
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda((i,), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, Lambda((i, j, k), 0)))
|
||||
raises(ValueError, lambda: FunctionMatrix(2, 2, i+j))
|
||||
assert FunctionMatrix(2, 2, "lambda i, j: 0") == \
|
||||
FunctionMatrix(2, 2, Lambda((i, j), 0))
|
||||
|
||||
m = FunctionMatrix(2, 2, KroneckerDelta)
|
||||
assert m.as_explicit() == Identity(2).as_explicit()
|
||||
assert m.args[2].dummy_eq(Lambda((i, j), KroneckerDelta(i, j)))
|
||||
|
||||
n = symbols('n')
|
||||
assert FunctionMatrix(n, n, Lambda((i, j), 0))
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: FunctionMatrix(n, n, Lambda((i, j), 0)))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: FunctionMatrix(n, n, Lambda((i, j), 0)))
|
||||
|
||||
|
||||
def test_funcmatrix():
|
||||
i, j = symbols('i,j')
|
||||
X = FunctionMatrix(3, 3, Lambda((i, j), i - j))
|
||||
assert X[1, 1] == 0
|
||||
assert X[1, 2] == -1
|
||||
assert X.shape == (3, 3)
|
||||
assert X.rows == X.cols == 3
|
||||
assert Matrix(X) == Matrix(3, 3, lambda i, j: i - j)
|
||||
assert isinstance(X*X + X, MatrixExpr)
|
||||
|
||||
|
||||
def test_replace_issue():
|
||||
X = FunctionMatrix(3, 3, KroneckerDelta)
|
||||
assert X.replace(lambda x: True, lambda x: x) == X
|
||||
@@ -0,0 +1,141 @@
|
||||
from sympy.matrices.dense import Matrix, eye
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.special import Identity, OneMatrix, ZeroMatrix
|
||||
from sympy.core import symbols
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
from sympy.matrices import MatrixSymbol
|
||||
from sympy.matrices.expressions import (HadamardProduct, hadamard_product, HadamardPower, hadamard_power)
|
||||
|
||||
n, m, k = symbols('n,m,k')
|
||||
Z = MatrixSymbol('Z', n, n)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, k)
|
||||
|
||||
|
||||
def test_HadamardProduct():
|
||||
assert HadamardProduct(A, B, A).shape == A.shape
|
||||
|
||||
raises(TypeError, lambda: HadamardProduct(A, n))
|
||||
raises(TypeError, lambda: HadamardProduct(A, 1))
|
||||
|
||||
assert HadamardProduct(A, 2*B, -A)[1, 1] == \
|
||||
-2 * A[1, 1] * B[1, 1] * A[1, 1]
|
||||
|
||||
mix = HadamardProduct(Z*A, B)*C
|
||||
assert mix.shape == (n, k)
|
||||
|
||||
assert set(HadamardProduct(A, B, A).T.args) == {A.T, A.T, B.T}
|
||||
|
||||
|
||||
def test_HadamardProduct_isnt_commutative():
|
||||
assert HadamardProduct(A, B) != HadamardProduct(B, A)
|
||||
|
||||
|
||||
def test_mixed_indexing():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
Z = MatrixSymbol('Z', 2, 2)
|
||||
|
||||
assert (X*HadamardProduct(Y, Z))[0, 0] == \
|
||||
X[0, 0]*Y[0, 0]*Z[0, 0] + X[0, 1]*Y[1, 0]*Z[1, 0]
|
||||
|
||||
|
||||
def test_canonicalize():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
with warns_deprecated_sympy():
|
||||
expr = HadamardProduct(X, check=False)
|
||||
assert isinstance(expr, HadamardProduct)
|
||||
expr2 = expr.doit() # unpack is called
|
||||
assert isinstance(expr2, MatrixSymbol)
|
||||
Z = ZeroMatrix(2, 2)
|
||||
U = OneMatrix(2, 2)
|
||||
assert HadamardProduct(Z, X).doit() == Z
|
||||
assert HadamardProduct(U, X, X, U).doit() == HadamardPower(X, 2)
|
||||
assert HadamardProduct(X, U, Y).doit() == HadamardProduct(X, Y)
|
||||
assert HadamardProduct(X, Z, U, Y).doit() == Z
|
||||
|
||||
|
||||
def test_hadamard():
|
||||
m, n, p = symbols('m, n, p', integer=True)
|
||||
A = MatrixSymbol('A', m, n)
|
||||
B = MatrixSymbol('B', m, n)
|
||||
X = MatrixSymbol('X', m, m)
|
||||
I = Identity(m)
|
||||
|
||||
raises(TypeError, lambda: hadamard_product())
|
||||
assert hadamard_product(A) == A
|
||||
assert isinstance(hadamard_product(A, B), HadamardProduct)
|
||||
assert hadamard_product(A, B).doit() == hadamard_product(A, B)
|
||||
assert hadamard_product(X, I) == HadamardProduct(I, X)
|
||||
assert isinstance(hadamard_product(X, I), HadamardProduct)
|
||||
|
||||
a = MatrixSymbol("a", k, 1)
|
||||
expr = MatAdd(ZeroMatrix(k, 1), OneMatrix(k, 1))
|
||||
expr = HadamardProduct(expr, a)
|
||||
assert expr.doit() == a
|
||||
|
||||
raises(ValueError, lambda: HadamardProduct())
|
||||
|
||||
|
||||
def test_hadamard_product_with_explicit_mat():
|
||||
A = MatrixSymbol("A", 3, 3).as_explicit()
|
||||
B = MatrixSymbol("B", 3, 3).as_explicit()
|
||||
X = MatrixSymbol("X", 3, 3)
|
||||
expr = hadamard_product(A, B)
|
||||
ret = Matrix([i*j for i, j in zip(A, B)]).reshape(3, 3)
|
||||
assert expr == ret
|
||||
expr = hadamard_product(A, X, B)
|
||||
assert expr == HadamardProduct(ret, X)
|
||||
expr = hadamard_product(eye(3), A)
|
||||
assert expr == Matrix([[A[0, 0], 0, 0], [0, A[1, 1], 0], [0, 0, A[2, 2]]])
|
||||
expr = hadamard_product(eye(3), eye(3))
|
||||
assert expr == eye(3)
|
||||
|
||||
|
||||
def test_hadamard_power():
|
||||
m, n, p = symbols('m, n, p', integer=True)
|
||||
A = MatrixSymbol('A', m, n)
|
||||
|
||||
assert hadamard_power(A, 1) == A
|
||||
assert isinstance(hadamard_power(A, 2), HadamardPower)
|
||||
assert hadamard_power(A, n).T == hadamard_power(A.T, n)
|
||||
assert hadamard_power(A, n)[0, 0] == A[0, 0]**n
|
||||
assert hadamard_power(m, n) == m**n
|
||||
raises(ValueError, lambda: hadamard_power(A, A))
|
||||
|
||||
|
||||
def test_hadamard_power_explicit():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
a, b = symbols('a b')
|
||||
|
||||
assert HadamardPower(a, b) == a**b
|
||||
|
||||
assert HadamardPower(a, B).as_explicit() == \
|
||||
Matrix([
|
||||
[a**B[0, 0], a**B[0, 1]],
|
||||
[a**B[1, 0], a**B[1, 1]]])
|
||||
|
||||
assert HadamardPower(A, b).as_explicit() == \
|
||||
Matrix([
|
||||
[A[0, 0]**b, A[0, 1]**b],
|
||||
[A[1, 0]**b, A[1, 1]**b]])
|
||||
|
||||
assert HadamardPower(A, B).as_explicit() == \
|
||||
Matrix([
|
||||
[A[0, 0]**B[0, 0], A[0, 1]**B[0, 1]],
|
||||
[A[1, 0]**B[1, 0], A[1, 1]**B[1, 1]]])
|
||||
|
||||
|
||||
def test_shape_error():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
raises(ShapeError, lambda: HadamardProduct(A, B))
|
||||
raises(ShapeError, lambda: HadamardPower(A, B))
|
||||
A = MatrixSymbol('A', 3, 2)
|
||||
raises(ShapeError, lambda: HadamardProduct(A, B))
|
||||
raises(ShapeError, lambda: HadamardPower(A, B))
|
||||
@@ -0,0 +1,299 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.symbol import symbols, Symbol, Dummy
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.dense import eye
|
||||
from sympy.matrices.expressions.blockmatrix import BlockMatrix
|
||||
from sympy.matrices.expressions.hadamard import HadamardPower
|
||||
from sympy.matrices.expressions.matexpr import (MatrixSymbol,
|
||||
MatrixExpr, MatrixElement)
|
||||
from sympy.matrices.expressions.matpow import MatPow
|
||||
from sympy.matrices.expressions.special import (ZeroMatrix, Identity,
|
||||
OneMatrix)
|
||||
from sympy.matrices.expressions.trace import Trace, trace
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct
|
||||
from sympy.testing.pytest import XFAIL, raises
|
||||
|
||||
k, l, m, n = symbols('k l m n', integer=True)
|
||||
i, j = symbols('i j', integer=True)
|
||||
|
||||
W = MatrixSymbol('W', k, l)
|
||||
X = MatrixSymbol('X', l, m)
|
||||
Y = MatrixSymbol('Y', l, m)
|
||||
Z = MatrixSymbol('Z', m, n)
|
||||
|
||||
X1 = MatrixSymbol('X1', m, m)
|
||||
X2 = MatrixSymbol('X2', m, m)
|
||||
X3 = MatrixSymbol('X3', m, m)
|
||||
X4 = MatrixSymbol('X4', m, m)
|
||||
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
x = MatrixSymbol('x', 1, 2)
|
||||
y = MatrixSymbol('x', 2, 1)
|
||||
|
||||
|
||||
def test_symbolic_indexing():
|
||||
x12 = X[1, 2]
|
||||
assert all(s in str(x12) for s in ['1', '2', X.name])
|
||||
# We don't care about the exact form of this. We do want to make sure
|
||||
# that all of these features are present
|
||||
|
||||
|
||||
def test_add_index():
|
||||
assert (X + Y)[i, j] == X[i, j] + Y[i, j]
|
||||
|
||||
|
||||
def test_mul_index():
|
||||
assert (A*y)[0, 0] == A[0, 0]*y[0, 0] + A[0, 1]*y[1, 0]
|
||||
assert (A*B).as_mutable() == (A.as_mutable() * B.as_mutable())
|
||||
X = MatrixSymbol('X', n, m)
|
||||
Y = MatrixSymbol('Y', m, k)
|
||||
|
||||
result = (X*Y)[4,2]
|
||||
expected = Sum(X[4, i]*Y[i, 2], (i, 0, m - 1))
|
||||
assert result.args[0].dummy_eq(expected.args[0], i)
|
||||
assert result.args[1][1:] == expected.args[1][1:]
|
||||
|
||||
|
||||
def test_pow_index():
|
||||
Q = MatPow(A, 2)
|
||||
assert Q[0, 0] == A[0, 0]**2 + A[0, 1]*A[1, 0]
|
||||
n = symbols("n")
|
||||
Q2 = A**n
|
||||
assert Q2[0, 0] == 2*(
|
||||
-sqrt((A[0, 0] + A[1, 1])**2 - 4*A[0, 0]*A[1, 1] +
|
||||
4*A[0, 1]*A[1, 0])/2 + A[0, 0]/2 + A[1, 1]/2
|
||||
)**n * \
|
||||
A[0, 1]*A[1, 0]/(
|
||||
(sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] +
|
||||
A[1, 1]**2) + A[0, 0] - A[1, 1])*
|
||||
sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)
|
||||
) - 2*(
|
||||
sqrt((A[0, 0] + A[1, 1])**2 - 4*A[0, 0]*A[1, 1] +
|
||||
4*A[0, 1]*A[1, 0])/2 + A[0, 0]/2 + A[1, 1]/2
|
||||
)**n * A[0, 1]*A[1, 0]/(
|
||||
(-sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] +
|
||||
A[1, 1]**2) + A[0, 0] - A[1, 1])*
|
||||
sqrt(A[0, 0]**2 - 2*A[0, 0]*A[1, 1] + 4*A[0, 1]*A[1, 0] + A[1, 1]**2)
|
||||
)
|
||||
|
||||
|
||||
def test_transpose_index():
|
||||
assert X.T[i, j] == X[j, i]
|
||||
|
||||
|
||||
def test_Identity_index():
|
||||
I = Identity(3)
|
||||
assert I[0, 0] == I[1, 1] == I[2, 2] == 1
|
||||
assert I[1, 0] == I[0, 1] == I[2, 1] == 0
|
||||
assert I[i, 0].delta_range == (0, 2)
|
||||
raises(IndexError, lambda: I[3, 3])
|
||||
|
||||
|
||||
def test_block_index():
|
||||
I = Identity(3)
|
||||
Z = ZeroMatrix(3, 3)
|
||||
B = BlockMatrix([[I, I], [I, I]])
|
||||
e3 = ImmutableMatrix(eye(3))
|
||||
BB = BlockMatrix([[e3, e3], [e3, e3]])
|
||||
assert B[0, 0] == B[3, 0] == B[0, 3] == B[3, 3] == 1
|
||||
assert B[4, 3] == B[5, 1] == 0
|
||||
|
||||
BB = BlockMatrix([[e3, e3], [e3, e3]])
|
||||
assert B.as_explicit() == BB.as_explicit()
|
||||
|
||||
BI = BlockMatrix([[I, Z], [Z, I]])
|
||||
|
||||
assert BI.as_explicit().equals(eye(6))
|
||||
|
||||
|
||||
def test_block_index_symbolic():
|
||||
# Note that these matrices may be zero-sized and indices may be negative, which causes
|
||||
# all naive simplifications given in the comments to be invalid
|
||||
A1 = MatrixSymbol('A1', n, k)
|
||||
A2 = MatrixSymbol('A2', n, l)
|
||||
A3 = MatrixSymbol('A3', m, k)
|
||||
A4 = MatrixSymbol('A4', m, l)
|
||||
A = BlockMatrix([[A1, A2], [A3, A4]])
|
||||
assert A[0, 0] == MatrixElement(A, 0, 0) # Cannot be A1[0, 0]
|
||||
assert A[n - 1, k - 1] == A1[n - 1, k - 1]
|
||||
assert A[n, k] == A4[0, 0]
|
||||
assert A[n + m - 1, 0] == MatrixElement(A, n + m - 1, 0) # Cannot be A3[m - 1, 0]
|
||||
assert A[0, k + l - 1] == MatrixElement(A, 0, k + l - 1) # Cannot be A2[0, l - 1]
|
||||
assert A[n + m - 1, k + l - 1] == MatrixElement(A, n + m - 1, k + l - 1) # Cannot be A4[m - 1, l - 1]
|
||||
assert A[i, j] == MatrixElement(A, i, j)
|
||||
assert A[n + i, k + j] == MatrixElement(A, n + i, k + j) # Cannot be A4[i, j]
|
||||
assert A[n - i - 1, k - j - 1] == MatrixElement(A, n - i - 1, k - j - 1) # Cannot be A1[n - i - 1, k - j - 1]
|
||||
|
||||
|
||||
def test_block_index_symbolic_nonzero():
|
||||
# All invalid simplifications from test_block_index_symbolic() that become valid if all
|
||||
# matrices have nonzero size and all indices are nonnegative
|
||||
k, l, m, n = symbols('k l m n', integer=True, positive=True)
|
||||
i, j = symbols('i j', integer=True, nonnegative=True)
|
||||
A1 = MatrixSymbol('A1', n, k)
|
||||
A2 = MatrixSymbol('A2', n, l)
|
||||
A3 = MatrixSymbol('A3', m, k)
|
||||
A4 = MatrixSymbol('A4', m, l)
|
||||
A = BlockMatrix([[A1, A2], [A3, A4]])
|
||||
assert A[0, 0] == A1[0, 0]
|
||||
assert A[n + m - 1, 0] == A3[m - 1, 0]
|
||||
assert A[0, k + l - 1] == A2[0, l - 1]
|
||||
assert A[n + m - 1, k + l - 1] == A4[m - 1, l - 1]
|
||||
assert A[i, j] == MatrixElement(A, i, j)
|
||||
assert A[n + i, k + j] == A4[i, j]
|
||||
assert A[n - i - 1, k - j - 1] == A1[n - i - 1, k - j - 1]
|
||||
assert A[2 * n, 2 * k] == A4[n, k]
|
||||
|
||||
|
||||
def test_block_index_large():
|
||||
n, m, k = symbols('n m k', integer=True, positive=True)
|
||||
i = symbols('i', integer=True, nonnegative=True)
|
||||
A1 = MatrixSymbol('A1', n, n)
|
||||
A2 = MatrixSymbol('A2', n, m)
|
||||
A3 = MatrixSymbol('A3', n, k)
|
||||
A4 = MatrixSymbol('A4', m, n)
|
||||
A5 = MatrixSymbol('A5', m, m)
|
||||
A6 = MatrixSymbol('A6', m, k)
|
||||
A7 = MatrixSymbol('A7', k, n)
|
||||
A8 = MatrixSymbol('A8', k, m)
|
||||
A9 = MatrixSymbol('A9', k, k)
|
||||
A = BlockMatrix([[A1, A2, A3], [A4, A5, A6], [A7, A8, A9]])
|
||||
assert A[n + i, n + i] == MatrixElement(A, n + i, n + i)
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_block_index_symbolic_fail():
|
||||
# To make this work, symbolic matrix dimensions would need to be somehow assumed nonnegative
|
||||
# even if the symbols aren't specified as such. Then 2 * n < n would correctly evaluate to
|
||||
# False in BlockMatrix._entry()
|
||||
A1 = MatrixSymbol('A1', n, 1)
|
||||
A2 = MatrixSymbol('A2', m, 1)
|
||||
A = BlockMatrix([[A1], [A2]])
|
||||
assert A[2 * n, 0] == A2[n, 0]
|
||||
|
||||
|
||||
def test_slicing():
|
||||
A.as_explicit()[0, :] # does not raise an error
|
||||
|
||||
|
||||
def test_errors():
|
||||
raises(IndexError, lambda: Identity(2)[1, 2, 3, 4, 5])
|
||||
raises(IndexError, lambda: Identity(2)[[1, 2, 3, 4, 5]])
|
||||
|
||||
|
||||
def test_matrix_expression_to_indices():
|
||||
i, j = symbols("i, j")
|
||||
i1, i2, i3 = symbols("i_1:4")
|
||||
|
||||
def replace_dummies(expr):
|
||||
repl = {i: Symbol(i.name) for i in expr.atoms(Dummy)}
|
||||
return expr.xreplace(repl)
|
||||
|
||||
expr = W*X*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = Z.T*X.T*W.T
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[j, i2]*X[i2, i1]*Z[i1, i], (i1, 0, m-1), (i2, 0, l-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j), i) == expr
|
||||
|
||||
expr = W*X*Z + W*Y*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) +\
|
||||
Sum(W[i, i1]*Y[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = 2*W*X*Z + 3*W*Y*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
2*Sum(W[i, i1]*X[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1)) +\
|
||||
3*Sum(W[i, i1]*Y[i1, i2]*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = W*(X + Y)*Z
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum(W[i, i1]*(X[i1, i2] + Y[i1, i2])*Z[i2, j], (i1, 0, l-1), (i2, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr._entry(i, j)) == expr
|
||||
|
||||
expr = A*B**2*A
|
||||
#assert replace_dummies(expr._entry(i, j)) == \
|
||||
# Sum(A[i, i1]*B[i1, i2]*B[i2, i3]*A[i3, j], (i1, 0, 1), (i2, 0, 1), (i3, 0, 1))
|
||||
|
||||
# Check that different dummies are used in sub-multiplications:
|
||||
expr = (X1*X2 + X2*X1)*X3
|
||||
assert replace_dummies(expr._entry(i, j)) == \
|
||||
Sum((Sum(X1[i, i2] * X2[i2, i1], (i2, 0, m - 1)) + Sum(X1[i3, i1] * X2[i, i3], (i3, 0, m - 1))) * X3[
|
||||
i1, j], (i1, 0, m - 1))
|
||||
|
||||
|
||||
def test_matrix_expression_from_index_summation():
|
||||
from sympy.abc import a,b,c,d
|
||||
A = MatrixSymbol("A", k, k)
|
||||
B = MatrixSymbol("B", k, k)
|
||||
C = MatrixSymbol("C", k, k)
|
||||
w1 = MatrixSymbol("w1", k, 1)
|
||||
|
||||
i0, i1, i2, i3, i4 = symbols("i0:5", cls=Dummy)
|
||||
|
||||
expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == W*X*Z
|
||||
expr = Sum(W.T[b,a]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == W*X*Z
|
||||
expr = Sum(A[b, a]*B[b, c]*C[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixSymbol.from_index_summation(expr, a) == A.T*B*C
|
||||
expr = Sum(A[b, a]*B[c, b]*C[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixSymbol.from_index_summation(expr, a) == A.T*B.T*C
|
||||
expr = Sum(C[c, d]*A[b, a]*B[c, b], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixSymbol.from_index_summation(expr, a) == A.T*B.T*C
|
||||
expr = Sum(A[a, b] + B[a, b], (a, 0, k-1), (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == OneMatrix(1, k)*A*OneMatrix(k, 1) + OneMatrix(1, k)*B*OneMatrix(k, 1)
|
||||
expr = Sum(A[a, b]**2, (a, 0, k - 1), (b, 0, k - 1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == Trace(A * A.T)
|
||||
expr = Sum(A[a, b]**3, (a, 0, k - 1), (b, 0, k - 1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == Trace(HadamardPower(A.T, 2) * A)
|
||||
expr = Sum((A[a, b] + B[a, b])*C[b, c], (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == (A+B)*C
|
||||
expr = Sum((A[a, b] + B[b, a])*C[b, c], (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == (A+B.T)*C
|
||||
expr = Sum(A[a, b]*A[b, c]*A[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A**3
|
||||
expr = Sum(A[a, b]*A[b, c]*B[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A**2*B
|
||||
|
||||
# Parse the trace of a matrix:
|
||||
|
||||
expr = Sum(A[a, a], (a, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, None) == trace(A)
|
||||
expr = Sum(A[a, a]*B[b, c]*C[c, d], (a, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, b) == trace(A)*B*C
|
||||
|
||||
# Check wrong sum ranges (should raise an exception):
|
||||
|
||||
## Case 1: 0 to m instead of 0 to m-1
|
||||
expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 0, m))
|
||||
raises(ValueError, lambda: MatrixExpr.from_index_summation(expr, a))
|
||||
## Case 2: 1 to m-1 instead of 0 to m-1
|
||||
expr = Sum(W[a,b]*X[b,c]*Z[c,d], (b, 0, l-1), (c, 1, m-1))
|
||||
raises(ValueError, lambda: MatrixExpr.from_index_summation(expr, a))
|
||||
|
||||
# Parse nested sums:
|
||||
expr = Sum(A[a, b]*Sum(B[b, c]*C[c, d], (c, 0, k-1)), (b, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A*B*C
|
||||
|
||||
# Test Kronecker delta:
|
||||
expr = Sum(A[a, b]*KroneckerDelta(b, c)*B[c, d], (b, 0, k-1), (c, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, a) == A*B
|
||||
|
||||
expr = Sum(KroneckerDelta(i1, m)*KroneckerDelta(i2, n)*A[i, i1]*A[j, i2], (i1, 0, k-1), (i2, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, m) == ArrayTensorProduct(A.T, A)
|
||||
|
||||
# Test numbered indices:
|
||||
expr = Sum(A[i1, i2]*w1[i2, 0], (i2, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, i1) == MatrixElement(A*w1, i1, 0)
|
||||
|
||||
expr = Sum(A[i1, i2]*B[i2, 0], (i2, 0, k-1))
|
||||
assert MatrixExpr.from_index_summation(expr, i1) == MatrixElement(A*B, i1, 0)
|
||||
@@ -0,0 +1,69 @@
|
||||
from sympy.core import symbols, S
|
||||
from sympy.matrices.expressions import MatrixSymbol, Inverse, MatPow, ZeroMatrix, OneMatrix
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError, NonSquareMatrixError
|
||||
from sympy.matrices import eye, Identity
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
|
||||
n, m, l = symbols('n m l', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
|
||||
|
||||
def test_inverse():
|
||||
assert Inverse(C).args == (C, S.NegativeOne)
|
||||
assert Inverse(C).shape == (n, n)
|
||||
assert Inverse(A*E).shape == (n, n)
|
||||
assert Inverse(E*A).shape == (m, m)
|
||||
assert Inverse(C).inverse() == C
|
||||
assert Inverse(Inverse(C)).doit() == C
|
||||
assert isinstance(Inverse(Inverse(C)), Inverse)
|
||||
|
||||
assert Inverse(*Inverse(E*A).args) == Inverse(E*A)
|
||||
|
||||
assert C.inverse().inverse() == C
|
||||
|
||||
assert C.inverse()*C == Identity(C.rows)
|
||||
|
||||
assert Identity(n).inverse() == Identity(n)
|
||||
assert (3*Identity(n)).inverse() == Identity(n)/3
|
||||
|
||||
# Simplifies Muls if possible (i.e. submatrices are square)
|
||||
assert (C*D).inverse() == D.I*C.I
|
||||
# But still works when not possible
|
||||
assert isinstance((A*E).inverse(), Inverse)
|
||||
assert Inverse(C*D).doit(inv_expand=False) == Inverse(C*D)
|
||||
|
||||
assert Inverse(eye(3)).doit() == eye(3)
|
||||
assert Inverse(eye(3)).doit(deep=False) == eye(3)
|
||||
|
||||
assert OneMatrix(1, 1).I == Identity(1)
|
||||
assert isinstance(OneMatrix(n, n).I, Inverse)
|
||||
|
||||
def test_inverse_non_invertible():
|
||||
raises(NonInvertibleMatrixError, lambda: ZeroMatrix(n, n).I)
|
||||
raises(NonInvertibleMatrixError, lambda: OneMatrix(2, 2).I)
|
||||
|
||||
def test_refine():
|
||||
assert refine(C.I, Q.orthogonal(C)) == C.T
|
||||
|
||||
|
||||
def test_inverse_matpow_canonicalization():
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
assert Inverse(MatPow(A, 3)).doit() == MatPow(Inverse(A), 3).doit()
|
||||
|
||||
|
||||
def test_nonsquare_error():
|
||||
A = MatrixSymbol('A', 3, 4)
|
||||
raises(NonSquareMatrixError, lambda: Inverse(A))
|
||||
|
||||
|
||||
def test_adjoint_trnaspose_conjugate():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
assert A.transpose().inverse() == A.inverse().transpose()
|
||||
assert A.conjugate().inverse() == A.inverse().conjugate()
|
||||
assert A.adjoint().inverse() == A.inverse().adjoint()
|
||||
@@ -0,0 +1,150 @@
|
||||
from sympy.core.mod import Mod
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.matrices.dense import (Matrix, eye)
|
||||
from sympy.matrices import MatrixSymbol, Identity
|
||||
from sympy.matrices.expressions import det, trace
|
||||
|
||||
from sympy.matrices.expressions.kronecker import (KroneckerProduct,
|
||||
kronecker_product,
|
||||
combine_kronecker)
|
||||
|
||||
|
||||
mat1 = Matrix([[1, 2 * I], [1 + I, 3]])
|
||||
mat2 = Matrix([[2 * I, 3], [4 * I, 2]])
|
||||
|
||||
i, j, k, n, m, o, p, x = symbols('i,j,k,n,m,o,p,x')
|
||||
Z = MatrixSymbol('Z', n, n)
|
||||
W = MatrixSymbol('W', m, m)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
C = MatrixSymbol('C', m, k)
|
||||
|
||||
|
||||
def test_KroneckerProduct():
|
||||
assert isinstance(KroneckerProduct(A, B), KroneckerProduct)
|
||||
assert KroneckerProduct(A, B).subs(A, C) == KroneckerProduct(C, B)
|
||||
assert KroneckerProduct(A, C).shape == (n*m, m*k)
|
||||
assert (KroneckerProduct(A, C) + KroneckerProduct(-A, C)).is_ZeroMatrix
|
||||
assert (KroneckerProduct(W, Z) * KroneckerProduct(W.I, Z.I)).is_Identity
|
||||
|
||||
|
||||
def test_KroneckerProduct_identity():
|
||||
assert KroneckerProduct(Identity(m), Identity(n)) == Identity(m*n)
|
||||
assert KroneckerProduct(eye(2), eye(3)) == eye(6)
|
||||
|
||||
|
||||
def test_KroneckerProduct_explicit():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
kp = KroneckerProduct(X, Y)
|
||||
assert kp.shape == (4, 4)
|
||||
assert kp.as_explicit() == Matrix(
|
||||
[
|
||||
[X[0, 0]*Y[0, 0], X[0, 0]*Y[0, 1], X[0, 1]*Y[0, 0], X[0, 1]*Y[0, 1]],
|
||||
[X[0, 0]*Y[1, 0], X[0, 0]*Y[1, 1], X[0, 1]*Y[1, 0], X[0, 1]*Y[1, 1]],
|
||||
[X[1, 0]*Y[0, 0], X[1, 0]*Y[0, 1], X[1, 1]*Y[0, 0], X[1, 1]*Y[0, 1]],
|
||||
[X[1, 0]*Y[1, 0], X[1, 0]*Y[1, 1], X[1, 1]*Y[1, 0], X[1, 1]*Y[1, 1]]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_tensor_product_adjoint():
|
||||
assert KroneckerProduct(I*A, B).adjoint() == \
|
||||
-I*KroneckerProduct(A.adjoint(), B.adjoint())
|
||||
assert KroneckerProduct(mat1, mat2).adjoint() == \
|
||||
kronecker_product(mat1.adjoint(), mat2.adjoint())
|
||||
|
||||
|
||||
def test_tensor_product_conjugate():
|
||||
assert KroneckerProduct(I*A, B).conjugate() == \
|
||||
-I*KroneckerProduct(A.conjugate(), B.conjugate())
|
||||
assert KroneckerProduct(mat1, mat2).conjugate() == \
|
||||
kronecker_product(mat1.conjugate(), mat2.conjugate())
|
||||
|
||||
|
||||
def test_tensor_product_transpose():
|
||||
assert KroneckerProduct(I*A, B).transpose() == \
|
||||
I*KroneckerProduct(A.transpose(), B.transpose())
|
||||
assert KroneckerProduct(mat1, mat2).transpose() == \
|
||||
kronecker_product(mat1.transpose(), mat2.transpose())
|
||||
|
||||
|
||||
def test_KroneckerProduct_is_associative():
|
||||
assert kronecker_product(A, kronecker_product(
|
||||
B, C)) == kronecker_product(kronecker_product(A, B), C)
|
||||
assert kronecker_product(A, kronecker_product(
|
||||
B, C)) == KroneckerProduct(A, B, C)
|
||||
|
||||
|
||||
def test_KroneckerProduct_is_bilinear():
|
||||
assert kronecker_product(x*A, B) == x*kronecker_product(A, B)
|
||||
assert kronecker_product(A, x*B) == x*kronecker_product(A, B)
|
||||
|
||||
|
||||
def test_KroneckerProduct_determinant():
|
||||
kp = kronecker_product(W, Z)
|
||||
assert det(kp) == det(W)**n * det(Z)**m
|
||||
|
||||
|
||||
def test_KroneckerProduct_trace():
|
||||
kp = kronecker_product(W, Z)
|
||||
assert trace(kp) == trace(W)*trace(Z)
|
||||
|
||||
|
||||
def test_KroneckerProduct_isnt_commutative():
|
||||
assert KroneckerProduct(A, B) != KroneckerProduct(B, A)
|
||||
assert KroneckerProduct(A, B).is_commutative is False
|
||||
|
||||
|
||||
def test_KroneckerProduct_extracts_commutative_part():
|
||||
assert kronecker_product(x * A, 2 * B) == x * \
|
||||
2 * KroneckerProduct(A, B)
|
||||
|
||||
|
||||
def test_KroneckerProduct_inverse():
|
||||
kp = kronecker_product(W, Z)
|
||||
assert kp.inverse() == kronecker_product(W.inverse(), Z.inverse())
|
||||
|
||||
|
||||
def test_KroneckerProduct_combine_add():
|
||||
kp1 = kronecker_product(A, B)
|
||||
kp2 = kronecker_product(C, W)
|
||||
assert combine_kronecker(kp1*kp2) == kronecker_product(A*C, B*W)
|
||||
|
||||
|
||||
def test_KroneckerProduct_combine_mul():
|
||||
X = MatrixSymbol('X', m, n)
|
||||
Y = MatrixSymbol('Y', m, n)
|
||||
kp1 = kronecker_product(A, X)
|
||||
kp2 = kronecker_product(B, Y)
|
||||
assert combine_kronecker(kp1+kp2) == kronecker_product(A+B, X+Y)
|
||||
|
||||
|
||||
def test_KroneckerProduct_combine_pow():
|
||||
X = MatrixSymbol('X', n, n)
|
||||
Y = MatrixSymbol('Y', n, n)
|
||||
assert combine_kronecker(KroneckerProduct(
|
||||
X, Y)**x) == KroneckerProduct(X**x, Y**x)
|
||||
assert combine_kronecker(x * KroneckerProduct(X, Y)
|
||||
** 2) == x * KroneckerProduct(X**2, Y**2)
|
||||
assert combine_kronecker(
|
||||
x * (KroneckerProduct(X, Y)**2) * KroneckerProduct(A, B)) == x * KroneckerProduct(X**2 * A, Y**2 * B)
|
||||
# cannot simplify because of non-square arguments to kronecker product:
|
||||
assert combine_kronecker(KroneckerProduct(A, B.T) ** m) == KroneckerProduct(A, B.T) ** m
|
||||
|
||||
|
||||
def test_KroneckerProduct_expand():
|
||||
X = MatrixSymbol('X', n, n)
|
||||
Y = MatrixSymbol('Y', n, n)
|
||||
|
||||
assert KroneckerProduct(X + Y, Y + Z).expand(kroneckerproduct=True) == \
|
||||
KroneckerProduct(X, Y) + KroneckerProduct(X, Z) + \
|
||||
KroneckerProduct(Y, Y) + KroneckerProduct(Y, Z)
|
||||
|
||||
def test_KroneckerProduct_entry():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', o, p)
|
||||
|
||||
assert KroneckerProduct(A, B)._entry(i, j) == A[Mod(floor(i/o), n), Mod(floor(j/p), m)]*B[Mod(i, o), Mod(j, p)]
|
||||
@@ -0,0 +1,58 @@
|
||||
from sympy.matrices.expressions import MatrixSymbol, MatAdd, MatPow, MatMul
|
||||
from sympy.matrices.expressions.special import GenericZeroMatrix, ZeroMatrix
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices import eye, ImmutableMatrix
|
||||
from sympy.core import Add, Basic, S
|
||||
from sympy.core.add import add
|
||||
from sympy.testing.pytest import XFAIL, raises
|
||||
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
Y = MatrixSymbol('Y', 2, 2)
|
||||
|
||||
def test_evaluate():
|
||||
assert MatAdd(X, X, evaluate=True) == add(X, X, evaluate=True) == MatAdd(X, X).doit()
|
||||
|
||||
def test_sort_key():
|
||||
assert MatAdd(Y, X).doit().args == add(Y, X).doit().args == (X, Y)
|
||||
|
||||
|
||||
def test_matadd_sympify():
|
||||
assert isinstance(MatAdd(eye(1), eye(1)).args[0], Basic)
|
||||
assert isinstance(add(eye(1), eye(1)).args[0], Basic)
|
||||
|
||||
|
||||
def test_matadd_of_matrices():
|
||||
assert MatAdd(eye(2), 4*eye(2), eye(2)).doit() == ImmutableMatrix(6*eye(2))
|
||||
assert add(eye(2), 4*eye(2), eye(2)).doit() == ImmutableMatrix(6*eye(2))
|
||||
|
||||
|
||||
def test_doit_args():
|
||||
A = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
B = ImmutableMatrix([[2, 3], [4, 5]])
|
||||
assert MatAdd(A, MatPow(B, 2)).doit() == A + B**2
|
||||
assert MatAdd(A, MatMul(A, B)).doit() == A + A*B
|
||||
assert (MatAdd(A, X, MatMul(A, B), Y, MatAdd(2*A, B)).doit() ==
|
||||
add(A, X, MatMul(A, B), Y, add(2*A, B)).doit() ==
|
||||
MatAdd(3*A + A*B + B, X, Y))
|
||||
|
||||
|
||||
def test_generic_identity():
|
||||
assert MatAdd.identity == GenericZeroMatrix()
|
||||
assert MatAdd.identity != S.Zero
|
||||
|
||||
|
||||
def test_zero_matrix_add():
|
||||
assert Add(ZeroMatrix(2, 2), ZeroMatrix(2, 2)) == ZeroMatrix(2, 2)
|
||||
|
||||
@XFAIL
|
||||
def test_matrix_Add_with_scalar():
|
||||
raises(TypeError, lambda: Add(0, ZeroMatrix(2, 2)))
|
||||
|
||||
|
||||
def test_shape_error():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
raises(ShapeError, lambda: MatAdd(A, B))
|
||||
|
||||
A = MatrixSymbol('A', 3, 2)
|
||||
raises(ShapeError, lambda: MatAdd(A, B))
|
||||
@@ -0,0 +1,592 @@
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.core.exprtools import gcd_terms
|
||||
from sympy.core.function import (diff, expand)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.core.symbol import (Dummy, Symbol, Str)
|
||||
from sympy.functions.special.tensor_functions import KroneckerDelta
|
||||
from sympy.matrices.dense import zeros
|
||||
from sympy.polys.polytools import factor
|
||||
|
||||
from sympy.core import (S, symbols, Add, Mul, SympifyError, Rational,
|
||||
Function)
|
||||
from sympy.functions import sin, cos, tan, sqrt, cbrt, exp
|
||||
from sympy.simplify import simplify
|
||||
from sympy.matrices import (ImmutableMatrix, Inverse, MatAdd, MatMul,
|
||||
MatPow, Matrix, MatrixExpr, MatrixSymbol,
|
||||
SparseMatrix, Transpose, Adjoint, MatrixSet)
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.expressions.determinant import Determinant, det
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, Identity
|
||||
from sympy.testing.pytest import raises, XFAIL, skip
|
||||
from importlib.metadata import version
|
||||
|
||||
n, m, l, k, p = symbols('n m l k p', integer=True)
|
||||
x = symbols('x')
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
w = MatrixSymbol('w', n, 1)
|
||||
|
||||
|
||||
def test_matrix_symbol_creation():
|
||||
assert MatrixSymbol('A', 2, 2)
|
||||
assert MatrixSymbol('A', 0, 0)
|
||||
raises(ValueError, lambda: MatrixSymbol('A', -1, 2))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2.0, 2))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2j, 2))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2, -1))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2, 2.0))
|
||||
raises(ValueError, lambda: MatrixSymbol('A', 2, 2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert MatrixSymbol('A', n, n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: MatrixSymbol('A', n, n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: MatrixSymbol('A', n, n))
|
||||
|
||||
|
||||
def test_matexpr_properties():
|
||||
assert A.shape == (n, m)
|
||||
assert (A * B).shape == (n, l)
|
||||
assert A[0, 1].indices == (0, 1)
|
||||
assert A[0, 0].symbol == A
|
||||
assert A[0, 0].symbol.name == 'A'
|
||||
|
||||
|
||||
def test_matexpr():
|
||||
assert (x*A).shape == A.shape
|
||||
assert (x*A).__class__ == MatMul
|
||||
assert 2*A - A - A == ZeroMatrix(*A.shape)
|
||||
assert (A*B).shape == (n, l)
|
||||
|
||||
|
||||
def test_matexpr_subs():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', m, l)
|
||||
|
||||
assert A.subs(n, m).shape == (m, m)
|
||||
assert (A*B).subs(B, C) == A*C
|
||||
assert (A*B).subs(l, n).is_square
|
||||
|
||||
W = MatrixSymbol("W", 3, 3)
|
||||
X = MatrixSymbol("X", 2, 2)
|
||||
Y = MatrixSymbol("Y", 1, 2)
|
||||
Z = MatrixSymbol("Z", n, 2)
|
||||
# no restrictions on Symbol replacement
|
||||
assert X.subs(X, Y) == Y
|
||||
# it might be better to just change the name
|
||||
y = Str('y')
|
||||
assert X.subs(Str("X"), y).args == (y, 2, 2)
|
||||
# it's ok to introduce a wider matrix
|
||||
assert X[1, 1].subs(X, W) == W[1, 1]
|
||||
# but for a given MatrixExpression, only change
|
||||
# name if indexing on the new shape is valid.
|
||||
# Here, X is 2,2; Y is 1,2 and Y[1, 1] is out
|
||||
# of range so an error is raised
|
||||
raises(IndexError, lambda: X[1, 1].subs(X, Y))
|
||||
# here, [0, 1] is in range so the subs succeeds
|
||||
assert X[0, 1].subs(X, Y) == Y[0, 1]
|
||||
# and here the size of n will accept any index
|
||||
# in the first position
|
||||
assert W[2, 1].subs(W, Z) == Z[2, 1]
|
||||
# but not in the second position
|
||||
raises(IndexError, lambda: W[2, 2].subs(W, Z))
|
||||
# any matrix should raise if invalid
|
||||
raises(IndexError, lambda: W[2, 2].subs(W, zeros(2)))
|
||||
|
||||
A = SparseMatrix([[1, 2], [3, 4]])
|
||||
B = Matrix([[1, 2], [3, 4]])
|
||||
C, D = MatrixSymbol('C', 2, 2), MatrixSymbol('D', 2, 2)
|
||||
|
||||
assert (C*D).subs({C: A, D: B}) == MatMul(A, B)
|
||||
|
||||
|
||||
def test_addition():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', n, m)
|
||||
|
||||
assert isinstance(A + B, MatAdd)
|
||||
assert (A + B).shape == A.shape
|
||||
assert isinstance(A - A + 2*B, MatMul)
|
||||
|
||||
raises(TypeError, lambda: A + 1)
|
||||
raises(TypeError, lambda: 5 + A)
|
||||
raises(TypeError, lambda: 5 - A)
|
||||
|
||||
assert A + ZeroMatrix(n, m) - A == ZeroMatrix(n, m)
|
||||
raises(TypeError, lambda: ZeroMatrix(n, m) + S.Zero)
|
||||
|
||||
|
||||
def test_multiplication():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
assert (2*A*B).shape == (n, l)
|
||||
assert (A*0*B) == ZeroMatrix(n, l)
|
||||
assert (2*A).shape == A.shape
|
||||
|
||||
assert A * ZeroMatrix(m, m) * B == ZeroMatrix(n, l)
|
||||
|
||||
assert C * Identity(n) * C.I == Identity(n)
|
||||
|
||||
assert B/2 == S.Half*B
|
||||
raises(NotImplementedError, lambda: 2/B)
|
||||
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
assert Identity(n) * (A + B) == A + B
|
||||
|
||||
assert A**2*A == A**3
|
||||
assert A**2*(A.I)**3 == A.I
|
||||
assert A**3*(A.I)**2 == A
|
||||
|
||||
|
||||
def test_MatPow():
|
||||
A = MatrixSymbol('A', n, n)
|
||||
|
||||
AA = MatPow(A, 2)
|
||||
assert AA.exp == 2
|
||||
assert AA.base == A
|
||||
assert (A**n).exp == n
|
||||
|
||||
assert A**0 == Identity(n)
|
||||
assert A**1 == A
|
||||
assert A**2 == AA
|
||||
assert A**-1 == Inverse(A)
|
||||
assert (A**-1)**-1 == A
|
||||
assert (A**2)**3 == A**6
|
||||
assert A**S.Half == sqrt(A)
|
||||
assert A**Rational(1, 3) == cbrt(A)
|
||||
raises(NonSquareMatrixError, lambda: MatrixSymbol('B', 3, 2)**2)
|
||||
|
||||
|
||||
def test_MatrixSymbol():
|
||||
n, m, t = symbols('n,m,t')
|
||||
X = MatrixSymbol('X', n, m)
|
||||
assert X.shape == (n, m)
|
||||
raises(TypeError, lambda: MatrixSymbol('X', n, m)(t)) # issue 5855
|
||||
assert X.doit() == X
|
||||
|
||||
|
||||
def test_dense_conversion():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
assert ImmutableMatrix(X) == ImmutableMatrix(2, 2, lambda i, j: X[i, j])
|
||||
assert Matrix(X) == Matrix(2, 2, lambda i, j: X[i, j])
|
||||
|
||||
|
||||
def test_free_symbols():
|
||||
assert (C*D).free_symbols == {C, D}
|
||||
|
||||
|
||||
def test_zero_matmul():
|
||||
assert isinstance(S.Zero * MatrixSymbol('X', 2, 2), MatrixExpr)
|
||||
|
||||
|
||||
def test_matadd_simplify():
|
||||
A = MatrixSymbol('A', 1, 1)
|
||||
assert simplify(MatAdd(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \
|
||||
MatAdd(A, Matrix([[1]]))
|
||||
|
||||
|
||||
def test_matmul_simplify():
|
||||
A = MatrixSymbol('A', 1, 1)
|
||||
assert simplify(MatMul(A, ImmutableMatrix([[sin(x)**2 + cos(x)**2]]))) == \
|
||||
MatMul(A, Matrix([[1]]))
|
||||
|
||||
|
||||
def test_invariants():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
X = MatrixSymbol('X', n, n)
|
||||
objs = [Identity(n), ZeroMatrix(m, n), A, MatMul(A, B), MatAdd(A, A),
|
||||
Transpose(A), Adjoint(A), Inverse(X), MatPow(X, 2), MatPow(X, -1),
|
||||
MatPow(X, 0)]
|
||||
for obj in objs:
|
||||
assert obj == obj.__class__(*obj.args)
|
||||
|
||||
|
||||
def test_matexpr_indexing():
|
||||
A = MatrixSymbol('A', n, m)
|
||||
A[1, 2]
|
||||
A[l, k]
|
||||
A[l + 1, k + 1]
|
||||
A = MatrixSymbol('A', 2, 1)
|
||||
for i in range(-2, 2):
|
||||
for j in range(-1, 1):
|
||||
A[i, j]
|
||||
|
||||
|
||||
def test_single_indexing():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
assert A[1] == A[0, 1]
|
||||
assert A[int(1)] == A[0, 1]
|
||||
assert A[3] == A[1, 0]
|
||||
assert list(A[:2, :2]) == [A[0, 0], A[0, 1], A[1, 0], A[1, 1]]
|
||||
raises(IndexError, lambda: A[6])
|
||||
raises(IndexError, lambda: A[n])
|
||||
B = MatrixSymbol('B', n, m)
|
||||
raises(IndexError, lambda: B[1])
|
||||
B = MatrixSymbol('B', n, 3)
|
||||
assert B[3] == B[1, 0]
|
||||
|
||||
|
||||
def test_MatrixElement_commutative():
|
||||
assert A[0, 1]*A[1, 0] == A[1, 0]*A[0, 1]
|
||||
|
||||
|
||||
def test_MatrixSymbol_determinant():
|
||||
A = MatrixSymbol('A', 4, 4)
|
||||
assert A.as_explicit().det() == A[0, 0]*A[1, 1]*A[2, 2]*A[3, 3] - \
|
||||
A[0, 0]*A[1, 1]*A[2, 3]*A[3, 2] - A[0, 0]*A[1, 2]*A[2, 1]*A[3, 3] + \
|
||||
A[0, 0]*A[1, 2]*A[2, 3]*A[3, 1] + A[0, 0]*A[1, 3]*A[2, 1]*A[3, 2] - \
|
||||
A[0, 0]*A[1, 3]*A[2, 2]*A[3, 1] - A[0, 1]*A[1, 0]*A[2, 2]*A[3, 3] + \
|
||||
A[0, 1]*A[1, 0]*A[2, 3]*A[3, 2] + A[0, 1]*A[1, 2]*A[2, 0]*A[3, 3] - \
|
||||
A[0, 1]*A[1, 2]*A[2, 3]*A[3, 0] - A[0, 1]*A[1, 3]*A[2, 0]*A[3, 2] + \
|
||||
A[0, 1]*A[1, 3]*A[2, 2]*A[3, 0] + A[0, 2]*A[1, 0]*A[2, 1]*A[3, 3] - \
|
||||
A[0, 2]*A[1, 0]*A[2, 3]*A[3, 1] - A[0, 2]*A[1, 1]*A[2, 0]*A[3, 3] + \
|
||||
A[0, 2]*A[1, 1]*A[2, 3]*A[3, 0] + A[0, 2]*A[1, 3]*A[2, 0]*A[3, 1] - \
|
||||
A[0, 2]*A[1, 3]*A[2, 1]*A[3, 0] - A[0, 3]*A[1, 0]*A[2, 1]*A[3, 2] + \
|
||||
A[0, 3]*A[1, 0]*A[2, 2]*A[3, 1] + A[0, 3]*A[1, 1]*A[2, 0]*A[3, 2] - \
|
||||
A[0, 3]*A[1, 1]*A[2, 2]*A[3, 0] - A[0, 3]*A[1, 2]*A[2, 0]*A[3, 1] + \
|
||||
A[0, 3]*A[1, 2]*A[2, 1]*A[3, 0]
|
||||
|
||||
B = MatrixSymbol('B', 4, 4)
|
||||
assert Determinant(A + B).doit() == det(A + B) == (A + B).det()
|
||||
|
||||
|
||||
def test_MatrixElement_diff():
|
||||
assert (A[3, 0]*A[0, 0]).diff(A[0, 0]) == A[3, 0]
|
||||
|
||||
|
||||
def test_MatrixElement_doit():
|
||||
u = MatrixSymbol('u', 2, 1)
|
||||
v = ImmutableMatrix([3, 5])
|
||||
assert u[0, 0].subs(u, v).doit() == v[0, 0]
|
||||
|
||||
|
||||
def test_identity_powers():
|
||||
M = Identity(n)
|
||||
assert MatPow(M, 3).doit() == M**3
|
||||
assert M**n == M
|
||||
assert MatPow(M, 0).doit() == M**2
|
||||
assert M**-2 == M
|
||||
assert MatPow(M, -2).doit() == M**0
|
||||
N = Identity(3)
|
||||
assert MatPow(N, 2).doit() == N**n
|
||||
assert MatPow(N, 3).doit() == N
|
||||
assert MatPow(N, -2).doit() == N**4
|
||||
assert MatPow(N, 2).doit() == N**0
|
||||
|
||||
|
||||
def test_Zero_power():
|
||||
z1 = ZeroMatrix(n, n)
|
||||
assert z1**4 == z1
|
||||
raises(ValueError, lambda:z1**-2)
|
||||
assert z1**0 == Identity(n)
|
||||
assert MatPow(z1, 2).doit() == z1**2
|
||||
raises(ValueError, lambda:MatPow(z1, -2).doit())
|
||||
z2 = ZeroMatrix(3, 3)
|
||||
assert MatPow(z2, 4).doit() == z2**4
|
||||
raises(ValueError, lambda:z2**-3)
|
||||
assert z2**3 == MatPow(z2, 3).doit()
|
||||
assert z2**0 == Identity(3)
|
||||
raises(ValueError, lambda:MatPow(z2, -1).doit())
|
||||
|
||||
|
||||
def test_matrixelement_diff():
|
||||
dexpr = diff((D*w)[k,0], w[p,0])
|
||||
|
||||
assert w[k, p].diff(w[k, p]) == 1
|
||||
assert w[k, p].diff(w[0, 0]) == KroneckerDelta(0, k, (0, n-1))*KroneckerDelta(0, p, (0, 0))
|
||||
_i_1 = Dummy("_i_1")
|
||||
assert dexpr.dummy_eq(Sum(KroneckerDelta(_i_1, p, (0, n-1))*D[k, _i_1], (_i_1, 0, n - 1)))
|
||||
assert dexpr.doit() == D[k, p]
|
||||
|
||||
|
||||
def test_MatrixElement_with_values():
|
||||
x, y, z, w = symbols("x y z w")
|
||||
M = Matrix([[x, y], [z, w]])
|
||||
i, j = symbols("i, j")
|
||||
Mij = M[i, j]
|
||||
assert isinstance(Mij, MatrixElement)
|
||||
Ms = SparseMatrix([[2, 3], [4, 5]])
|
||||
msij = Ms[i, j]
|
||||
assert isinstance(msij, MatrixElement)
|
||||
for oi, oj in [(0, 0), (0, 1), (1, 0), (1, 1)]:
|
||||
assert Mij.subs({i: oi, j: oj}) == M[oi, oj]
|
||||
assert msij.subs({i: oi, j: oj}) == Ms[oi, oj]
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
assert A[0, 0].subs(A, M) == x
|
||||
assert A[i, j].subs(A, M) == M[i, j]
|
||||
assert M[i, j].subs(M, A) == A[i, j]
|
||||
|
||||
assert isinstance(M[3*i - 2, j], MatrixElement)
|
||||
assert M[3*i - 2, j].subs({i: 1, j: 0}) == M[1, 0]
|
||||
assert isinstance(M[i, 0], MatrixElement)
|
||||
assert M[i, 0].subs(i, 0) == M[0, 0]
|
||||
assert M[0, i].subs(i, 1) == M[0, 1]
|
||||
|
||||
assert M[i, j].diff(x) == Matrix([[1, 0], [0, 0]])[i, j]
|
||||
|
||||
raises(ValueError, lambda: M[i, 2])
|
||||
raises(ValueError, lambda: M[i, -1])
|
||||
raises(ValueError, lambda: M[2, i])
|
||||
raises(ValueError, lambda: M[-1, i])
|
||||
|
||||
|
||||
def test_inv():
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
assert B.inv() == B**-1
|
||||
|
||||
# https://github.com/sympy/sympy/issues/19162
|
||||
X = MatrixSymbol('X', 1, 1).as_explicit()
|
||||
assert X.inv() == Matrix([[1/X[0, 0]]])
|
||||
|
||||
X = MatrixSymbol('X', 2, 2).as_explicit()
|
||||
detX = X[0, 0]*X[1, 1] - X[0, 1]*X[1, 0]
|
||||
invX = Matrix([[ X[1, 1], -X[0, 1]],
|
||||
[-X[1, 0], X[0, 0]]]) / detX
|
||||
assert X.inv() == invX
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_factor_expand():
|
||||
A = MatrixSymbol("A", n, n)
|
||||
B = MatrixSymbol("B", n, n)
|
||||
expr1 = (A + B)*(C + D)
|
||||
expr2 = A*C + B*C + A*D + B*D
|
||||
assert expr1 != expr2
|
||||
assert expand(expr1) == expr2
|
||||
assert factor(expr2) == expr1
|
||||
|
||||
expr = B**(-1)*(A**(-1)*B**(-1) - A**(-1)*C*B**(-1))**(-1)*A**(-1)
|
||||
I = Identity(n)
|
||||
# Ideally we get the first, but we at least don't want a wrong answer
|
||||
assert factor(expr) in [I - C, B**-1*(A**-1*(I - C)*B**-1)**-1*A**-1]
|
||||
|
||||
def test_numpy_conversion():
|
||||
try:
|
||||
from numpy import array, array_equal
|
||||
except ImportError:
|
||||
skip('NumPy must be available to test creating matrices from ndarrays')
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
np_array = array([[MatrixElement(A, 0, 0), MatrixElement(A, 0, 1)],
|
||||
[MatrixElement(A, 1, 0), MatrixElement(A, 1, 1)]])
|
||||
assert array_equal(array(A), np_array)
|
||||
assert array_equal(array(A, copy=True), np_array)
|
||||
if(int(version('numpy').split('.')[0]) >= 2): #run this test only if numpy is new enough that copy variable is passed properly.
|
||||
raises(TypeError, lambda: array(A, copy=False))
|
||||
|
||||
def test_issue_2749():
|
||||
A = MatrixSymbol("A", 5, 2)
|
||||
assert (A.T * A).I.as_explicit() == Matrix([[(A.T * A).I[0, 0], (A.T * A).I[0, 1]], \
|
||||
[(A.T * A).I[1, 0], (A.T * A).I[1, 1]]])
|
||||
|
||||
|
||||
def test_issue_2750():
|
||||
x = MatrixSymbol('x', 1, 1)
|
||||
assert (x.T*x).as_explicit()**-1 == Matrix([[x[0, 0]**(-2)]])
|
||||
|
||||
|
||||
def test_issue_7842():
|
||||
A = MatrixSymbol('A', 3, 1)
|
||||
B = MatrixSymbol('B', 2, 1)
|
||||
assert Eq(A, B) == False
|
||||
assert Eq(A[1,0], B[1, 0]).func is Eq
|
||||
A = ZeroMatrix(2, 3)
|
||||
B = ZeroMatrix(2, 3)
|
||||
assert Eq(A, B) == True
|
||||
|
||||
|
||||
def test_issue_21195():
|
||||
t = symbols('t')
|
||||
x = Function('x')(t)
|
||||
dx = x.diff(t)
|
||||
exp1 = cos(x) + cos(x)*dx
|
||||
exp2 = sin(x) + tan(x)*(dx.diff(t))
|
||||
exp3 = sin(x)*sin(t)*(dx.diff(t)).diff(t)
|
||||
A = Matrix([[exp1], [exp2], [exp3]])
|
||||
B = Matrix([[exp1.diff(x)], [exp2.diff(x)], [exp3.diff(x)]])
|
||||
assert A.diff(x) == B
|
||||
|
||||
|
||||
def test_issue_24859():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = MatrixSymbol('B', 3, 2)
|
||||
J = A*B
|
||||
Jinv = Matrix(J).adjugate()
|
||||
u = MatrixSymbol('u', 2, 3)
|
||||
Jk = Jinv.subs(A, A + x*u)
|
||||
|
||||
expected = B[0, 1]*u[1, 0] + B[1, 1]*u[1, 1] + B[2, 1]*u[1, 2]
|
||||
assert Jk[0, 0].diff(x) == expected
|
||||
assert diff(Jk[0, 0], x).doit() == expected
|
||||
|
||||
|
||||
def test_MatMul_postprocessor():
|
||||
z = zeros(2)
|
||||
z1 = ZeroMatrix(2, 2)
|
||||
assert Mul(0, z) == Mul(z, 0) in [z, z1]
|
||||
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
Mx = Matrix([[x, 2*x], [3*x, 4*x]])
|
||||
assert Mul(x, M) == Mul(M, x) == Mx
|
||||
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
assert Mul(A, M) == MatMul(A, M)
|
||||
assert Mul(M, A) == MatMul(M, A)
|
||||
# Scalars should be absorbed into constant matrices
|
||||
a = Mul(x, M, A)
|
||||
b = Mul(M, x, A)
|
||||
c = Mul(M, A, x)
|
||||
assert a == b == c == MatMul(Mx, A)
|
||||
a = Mul(x, A, M)
|
||||
b = Mul(A, x, M)
|
||||
c = Mul(A, M, x)
|
||||
assert a == b == c == MatMul(A, Mx)
|
||||
assert Mul(M, M) == M**2
|
||||
assert Mul(A, M, M) == MatMul(A, M**2)
|
||||
assert Mul(M, M, A) == MatMul(M**2, A)
|
||||
assert Mul(M, A, M) == MatMul(M, A, M)
|
||||
|
||||
assert Mul(A, x, M, M, x) == MatMul(A, Mx**2)
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_MatAdd_postprocessor_xfail():
|
||||
# This is difficult to get working because of the way that Add processes
|
||||
# its args.
|
||||
z = zeros(2)
|
||||
assert Add(z, S.NaN) == Add(S.NaN, z)
|
||||
|
||||
|
||||
def test_MatAdd_postprocessor():
|
||||
# Some of these are nonsensical, but we do not raise errors for Add
|
||||
# because that breaks algorithms that want to replace matrices with dummy
|
||||
# symbols.
|
||||
|
||||
z = zeros(2)
|
||||
|
||||
assert Add(0, z) == Add(z, 0) == z
|
||||
|
||||
a = Add(S.Infinity, z)
|
||||
assert a == Add(z, S.Infinity)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (S.Infinity, z)
|
||||
|
||||
a = Add(S.ComplexInfinity, z)
|
||||
assert a == Add(z, S.ComplexInfinity)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (S.ComplexInfinity, z)
|
||||
|
||||
a = Add(z, S.NaN)
|
||||
# assert a == Add(S.NaN, z) # See the XFAIL above
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (S.NaN, z)
|
||||
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
a = Add(x, M)
|
||||
assert a == Add(M, x)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (x, M)
|
||||
|
||||
A = MatrixSymbol("A", 2, 2)
|
||||
assert Add(A, M) == Add(M, A) == A + M
|
||||
|
||||
# Scalars should be absorbed into constant matrices (producing an error)
|
||||
a = Add(x, M, A)
|
||||
assert a == Add(M, x, A) == Add(M, A, x) == Add(x, A, M) == Add(A, x, M) == Add(A, M, x)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (x, A + M)
|
||||
|
||||
assert Add(M, M) == 2*M
|
||||
assert Add(M, A, M) == Add(M, M, A) == Add(A, M, M) == A + 2*M
|
||||
|
||||
a = Add(A, x, M, M, x)
|
||||
assert isinstance(a, Add)
|
||||
assert a.args == (2*x, A + 2*M)
|
||||
|
||||
|
||||
def test_simplify_matrix_expressions():
|
||||
# Various simplification functions
|
||||
assert type(gcd_terms(C*D + D*C)) == MatAdd
|
||||
a = gcd_terms(2*C*D + 4*D*C)
|
||||
assert type(a) == MatAdd
|
||||
assert a.args == (2*C*D, 4*D*C)
|
||||
|
||||
|
||||
def test_exp():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
expr1 = exp(A)*exp(B)
|
||||
expr2 = exp(B)*exp(A)
|
||||
assert expr1 != expr2
|
||||
assert expr1 - expr2 != 0
|
||||
assert not isinstance(expr1, exp)
|
||||
assert not isinstance(expr2, exp)
|
||||
|
||||
|
||||
def test_invalid_args():
|
||||
raises(SympifyError, lambda: MatrixSymbol(1, 2, 'A'))
|
||||
|
||||
|
||||
def test_matrixsymbol_from_symbol():
|
||||
# The label should be preserved during doit and subs
|
||||
A_label = Symbol('A', complex=True)
|
||||
A = MatrixSymbol(A_label, 2, 2)
|
||||
|
||||
A_1 = A.doit()
|
||||
A_2 = A.subs(2, 3)
|
||||
assert A_1.args == A.args
|
||||
assert A_2.args[0] == A.args[0]
|
||||
|
||||
|
||||
def test_as_explicit():
|
||||
Z = MatrixSymbol('Z', 2, 3)
|
||||
assert Z.as_explicit() == ImmutableMatrix([
|
||||
[Z[0, 0], Z[0, 1], Z[0, 2]],
|
||||
[Z[1, 0], Z[1, 1], Z[1, 2]],
|
||||
])
|
||||
raises(ValueError, lambda: A.as_explicit())
|
||||
|
||||
|
||||
def test_MatrixSet():
|
||||
M = MatrixSet(2, 2, set=S.Reals)
|
||||
assert M.shape == (2, 2)
|
||||
assert M.set == S.Reals
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert X in M
|
||||
X = ZeroMatrix(2, 2)
|
||||
assert X in M
|
||||
raises(TypeError, lambda: A in M)
|
||||
raises(TypeError, lambda: 1 in M)
|
||||
M = MatrixSet(n, m, set=S.Reals)
|
||||
assert A in M
|
||||
raises(TypeError, lambda: C in M)
|
||||
raises(TypeError, lambda: X in M)
|
||||
M = MatrixSet(2, 2, set={1, 2, 3})
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
Y = Matrix([[1, 2]])
|
||||
assert (X in M) == S.false
|
||||
assert (Y in M) == S.false
|
||||
raises(ValueError, lambda: MatrixSet(2, -2, S.Reals))
|
||||
raises(ValueError, lambda: MatrixSet(2.4, -1, S.Reals))
|
||||
raises(TypeError, lambda: MatrixSet(2, 2, (1, 2, 3)))
|
||||
|
||||
|
||||
def test_matrixsymbol_solving():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 2, 2)
|
||||
Z = ZeroMatrix(2, 2)
|
||||
assert -(-A + B) - A + B == Z
|
||||
assert (-(-A + B) - A + B).simplify() == Z
|
||||
assert (-(-A + B) - A + B).expand() == Z
|
||||
assert (-(-A + B) - A + B - Z).simplify() == Z
|
||||
assert (-(-A + B) - A + B - Z).expand() == Z
|
||||
assert (A*(A + B) + B*(A.T + B.T)).expand() == A**2 + A*B + B*A.T + B*B.T
|
||||
@@ -0,0 +1,193 @@
|
||||
from sympy.core import I, symbols, Basic, Mul, S
|
||||
from sympy.core.mul import mul
|
||||
from sympy.functions import adjoint, transpose
|
||||
from sympy.matrices.exceptions import ShapeError
|
||||
from sympy.matrices import (Identity, Inverse, Matrix, MatrixSymbol, ZeroMatrix,
|
||||
eye, ImmutableMatrix)
|
||||
from sympy.matrices.expressions import Adjoint, Transpose, det, MatPow
|
||||
from sympy.matrices.expressions.special import GenericIdentity
|
||||
from sympy.matrices.expressions.matmul import (factor_in_front, remove_ids,
|
||||
MatMul, combine_powers, any_zeros, unpack, only_squares)
|
||||
from sympy.strategies import null_safe
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
from sympy.core.symbol import Symbol
|
||||
|
||||
from sympy.testing.pytest import XFAIL, raises
|
||||
|
||||
n, m, l, k = symbols('n m l k', integer=True)
|
||||
x = symbols('x')
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
|
||||
def test_evaluate():
|
||||
assert MatMul(C, C, evaluate=True) == MatMul(C, C).doit()
|
||||
|
||||
def test_adjoint():
|
||||
assert adjoint(A*B) == Adjoint(B)*Adjoint(A)
|
||||
assert adjoint(2*A*B) == 2*Adjoint(B)*Adjoint(A)
|
||||
assert adjoint(2*I*C) == -2*I*Adjoint(C)
|
||||
|
||||
M = Matrix(2, 2, [1, 2 + I, 3, 4])
|
||||
MA = Matrix(2, 2, [1, 3, 2 - I, 4])
|
||||
assert adjoint(M) == MA
|
||||
assert adjoint(2*M) == 2*MA
|
||||
assert adjoint(MatMul(2, M)) == MatMul(2, MA).doit()
|
||||
|
||||
|
||||
def test_transpose():
|
||||
assert transpose(A*B) == Transpose(B)*Transpose(A)
|
||||
assert transpose(2*A*B) == 2*Transpose(B)*Transpose(A)
|
||||
assert transpose(2*I*C) == 2*I*Transpose(C)
|
||||
|
||||
M = Matrix(2, 2, [1, 2 + I, 3, 4])
|
||||
MT = Matrix(2, 2, [1, 3, 2 + I, 4])
|
||||
assert transpose(M) == MT
|
||||
assert transpose(2*M) == 2*MT
|
||||
assert transpose(x*M) == x*MT
|
||||
assert transpose(MatMul(2, M)) == MatMul(2, MT).doit()
|
||||
|
||||
|
||||
def test_factor_in_front():
|
||||
assert factor_in_front(MatMul(A, 2, B, evaluate=False)) ==\
|
||||
MatMul(2, A, B, evaluate=False)
|
||||
|
||||
|
||||
def test_remove_ids():
|
||||
assert remove_ids(MatMul(A, Identity(m), B, evaluate=False)) == \
|
||||
MatMul(A, B, evaluate=False)
|
||||
assert null_safe(remove_ids)(MatMul(Identity(n), evaluate=False)) == \
|
||||
MatMul(Identity(n), evaluate=False)
|
||||
|
||||
|
||||
def test_combine_powers():
|
||||
assert combine_powers(MatMul(D, Inverse(D), D, evaluate=False)) == \
|
||||
MatMul(Identity(n), D, evaluate=False)
|
||||
assert combine_powers(MatMul(B.T, Inverse(E*A), E, A, B, evaluate=False)) == \
|
||||
MatMul(B.T, Identity(m), B, evaluate=False)
|
||||
assert combine_powers(MatMul(A, E, Inverse(A*E), D, evaluate=False)) == \
|
||||
MatMul(Identity(n), D, evaluate=False)
|
||||
|
||||
|
||||
def test_any_zeros():
|
||||
assert any_zeros(MatMul(A, ZeroMatrix(m, k), evaluate=False)) == \
|
||||
ZeroMatrix(n, k)
|
||||
|
||||
|
||||
def test_unpack():
|
||||
assert unpack(MatMul(A, evaluate=False)) == A
|
||||
x = MatMul(A, B)
|
||||
assert unpack(x) == x
|
||||
|
||||
|
||||
def test_only_squares():
|
||||
assert only_squares(C) == [C]
|
||||
assert only_squares(C, D) == [C, D]
|
||||
assert only_squares(C, A, A.T, D) == [C, A*A.T, D]
|
||||
|
||||
|
||||
def test_determinant():
|
||||
assert det(2*C) == 2**n*det(C)
|
||||
assert det(2*C*D) == 2**n*det(C)*det(D)
|
||||
assert det(3*C*A*A.T*D) == 3**n*det(C)*det(A*A.T)*det(D)
|
||||
|
||||
|
||||
def test_doit():
|
||||
assert MatMul(C, 2, D).args == (C, 2, D)
|
||||
assert MatMul(C, 2, D).doit().args == (2, C, D)
|
||||
assert MatMul(C, Transpose(D*C)).args == (C, Transpose(D*C))
|
||||
assert MatMul(C, Transpose(D*C)).doit(deep=True).args == (C, C.T, D.T)
|
||||
|
||||
|
||||
def test_doit_drills_down():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
Y = ImmutableMatrix([[2, 3], [4, 5]])
|
||||
assert MatMul(X, MatPow(Y, 2)).doit() == X*Y**2
|
||||
assert MatMul(C, Transpose(D*C)).doit().args == (C, C.T, D.T)
|
||||
|
||||
|
||||
def test_doit_deep_false_still_canonical():
|
||||
assert (MatMul(C, Transpose(D*C), 2).doit(deep=False).args ==
|
||||
(2, C, Transpose(D*C)))
|
||||
|
||||
|
||||
def test_matmul_scalar_Matrix_doit():
|
||||
# Issue 9053
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert MatMul(2, X).doit() == 2*X
|
||||
|
||||
|
||||
def test_matmul_sympify():
|
||||
assert isinstance(MatMul(eye(1), eye(1)).args[0], Basic)
|
||||
|
||||
|
||||
def test_collapse_MatrixBase():
|
||||
A = Matrix([[1, 1], [1, 1]])
|
||||
B = Matrix([[1, 2], [3, 4]])
|
||||
assert MatMul(A, B).doit() == ImmutableMatrix([[4, 6], [4, 6]])
|
||||
|
||||
|
||||
def test_refine():
|
||||
assert refine(C*C.T*D, Q.orthogonal(C)).doit() == D
|
||||
|
||||
kC = k*C
|
||||
assert refine(kC*C.T, Q.orthogonal(C)).doit() == k*Identity(n)
|
||||
assert refine(kC* kC.T, Q.orthogonal(C)).doit() == (k**2)*Identity(n)
|
||||
|
||||
def test_matmul_no_matrices():
|
||||
assert MatMul(1) == 1
|
||||
assert MatMul(n, m) == n*m
|
||||
assert not isinstance(MatMul(n, m), MatMul)
|
||||
|
||||
def test_matmul_args_cnc():
|
||||
assert MatMul(n, A, A.T).args_cnc() == [[n], [A, A.T]]
|
||||
assert MatMul(A, A.T).args_cnc() == [[], [A, A.T]]
|
||||
|
||||
@XFAIL
|
||||
def test_matmul_args_cnc_symbols():
|
||||
# Not currently supported
|
||||
a, b = symbols('a b', commutative=False)
|
||||
assert MatMul(n, a, b, A, A.T).args_cnc() == [[n], [a, b, A, A.T]]
|
||||
assert MatMul(n, a, A, b, A.T).args_cnc() == [[n], [a, A, b, A.T]]
|
||||
|
||||
def test_issue_12950():
|
||||
M = Matrix([[Symbol("x")]]) * MatrixSymbol("A", 1, 1)
|
||||
assert MatrixSymbol("A", 1, 1).as_explicit()[0]*Symbol('x') == M.as_explicit()[0]
|
||||
|
||||
def test_construction_with_Mul():
|
||||
assert Mul(C, D) == MatMul(C, D)
|
||||
assert Mul(D, C) == MatMul(D, C)
|
||||
|
||||
def test_construction_with_mul():
|
||||
assert mul(C, D) == MatMul(C, D)
|
||||
assert mul(D, C) == MatMul(D, C)
|
||||
assert mul(C, D) != MatMul(D, C)
|
||||
|
||||
def test_generic_identity():
|
||||
assert MatMul.identity == GenericIdentity()
|
||||
assert MatMul.identity != S.One
|
||||
|
||||
|
||||
def test_issue_23519():
|
||||
N = Symbol("N", integer=True)
|
||||
M1 = MatrixSymbol("M1", N, N)
|
||||
M2 = MatrixSymbol("M2", N, N)
|
||||
I = Identity(N)
|
||||
z = (M2 + 2 * (M2 + I) * M1 + I)
|
||||
assert z.coeff(M1) == 2*I + 2*M2
|
||||
|
||||
|
||||
def test_shape_error():
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
B = MatrixSymbol('B', 3, 3)
|
||||
raises(ShapeError, lambda: MatMul(A, B))
|
||||
|
||||
|
||||
def test_matmul_transpose():
|
||||
# https://github.com/sympy/sympy/issues/9503
|
||||
M = Matrix(2, 2, [1, 2 + I, 3, 4])
|
||||
a = Symbol('a')
|
||||
assert (MatMul(a, M).T).expand() == (a*Matrix([[1, 3],[2 + I, 4]])).expand()
|
||||
@@ -0,0 +1,217 @@
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.simplify.powsimp import powsimp
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core import symbols, S
|
||||
from sympy.matrices import Identity, MatrixSymbol, ImmutableMatrix, ZeroMatrix, OneMatrix, Matrix
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices.expressions import MatPow, MatAdd, MatMul
|
||||
from sympy.matrices.expressions.inverse import Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixElement
|
||||
|
||||
n, m, l, k = symbols('n m l k', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
D = MatrixSymbol('D', n, n)
|
||||
E = MatrixSymbol('E', m, n)
|
||||
|
||||
|
||||
def test_entry_matrix():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert MatPow(X, 0)[0, 0] == 1
|
||||
assert MatPow(X, 0)[0, 1] == 0
|
||||
assert MatPow(X, 1)[0, 0] == 1
|
||||
assert MatPow(X, 1)[0, 1] == 2
|
||||
assert MatPow(X, 2)[0, 0] == 7
|
||||
|
||||
|
||||
def test_entry_symbol():
|
||||
from sympy.concrete import Sum
|
||||
assert MatPow(C, 0)[0, 0] == 1
|
||||
assert MatPow(C, 0)[0, 1] == 0
|
||||
assert MatPow(C, 1)[0, 0] == C[0, 0]
|
||||
assert isinstance(MatPow(C, 2)[0, 0], Sum)
|
||||
assert isinstance(MatPow(C, n)[0, 0], MatrixElement)
|
||||
|
||||
|
||||
def test_as_explicit_symbol():
|
||||
X = MatrixSymbol('X', 2, 2)
|
||||
assert MatPow(X, 0).as_explicit() == ImmutableMatrix(Identity(2))
|
||||
assert MatPow(X, 1).as_explicit() == X.as_explicit()
|
||||
assert MatPow(X, 2).as_explicit() == (X.as_explicit())**2
|
||||
assert MatPow(X, n).as_explicit() == ImmutableMatrix([
|
||||
[(X ** n)[0, 0], (X ** n)[0, 1]],
|
||||
[(X ** n)[1, 0], (X ** n)[1, 1]],
|
||||
])
|
||||
|
||||
a = MatrixSymbol("a", 3, 1)
|
||||
b = MatrixSymbol("b", 3, 1)
|
||||
c = MatrixSymbol("c", 3, 1)
|
||||
|
||||
expr = (a.T*b)**S.Half
|
||||
assert expr.as_explicit() == Matrix([[sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])]])
|
||||
|
||||
expr = c*(a.T*b)**S.Half
|
||||
m = sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])
|
||||
assert expr.as_explicit() == Matrix([[c[0, 0]*m], [c[1, 0]*m], [c[2, 0]*m]])
|
||||
|
||||
expr = (a*b.T)**S.Half
|
||||
denom = sqrt(a[0, 0]*b[0, 0] + a[1, 0]*b[1, 0] + a[2, 0]*b[2, 0])
|
||||
expected = (a*b.T).as_explicit()/denom
|
||||
assert expr.as_explicit() == expected
|
||||
|
||||
expr = X**-1
|
||||
det = X[0, 0]*X[1, 1] - X[1, 0]*X[0, 1]
|
||||
expected = Matrix([[X[1, 1], -X[0, 1]], [-X[1, 0], X[0, 0]]])/det
|
||||
assert expr.as_explicit() == expected
|
||||
|
||||
expr = X**m
|
||||
assert expr.as_explicit() == X.as_explicit()**m
|
||||
|
||||
|
||||
def test_as_explicit_matrix():
|
||||
A = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert MatPow(A, 0).as_explicit() == ImmutableMatrix(Identity(2))
|
||||
assert MatPow(A, 1).as_explicit() == A
|
||||
assert MatPow(A, 2).as_explicit() == A**2
|
||||
assert MatPow(A, -1).as_explicit() == A.inv()
|
||||
assert MatPow(A, -2).as_explicit() == (A.inv())**2
|
||||
# less expensive than testing on a 2x2
|
||||
A = ImmutableMatrix([4])
|
||||
assert MatPow(A, S.Half).as_explicit() == A**S.Half
|
||||
|
||||
|
||||
def test_doit_symbol():
|
||||
assert MatPow(C, 0).doit() == Identity(n)
|
||||
assert MatPow(C, 1).doit() == C
|
||||
assert MatPow(C, -1).doit() == C.I
|
||||
for r in [2, S.Half, S.Pi, n]:
|
||||
assert MatPow(C, r).doit() == MatPow(C, r)
|
||||
|
||||
|
||||
def test_doit_matrix():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert MatPow(X, 0).doit() == ImmutableMatrix(Identity(2))
|
||||
assert MatPow(X, 1).doit() == X
|
||||
assert MatPow(X, 2).doit() == X**2
|
||||
assert MatPow(X, -1).doit() == X.inv()
|
||||
assert MatPow(X, -2).doit() == (X.inv())**2
|
||||
# less expensive than testing on a 2x2
|
||||
assert MatPow(ImmutableMatrix([4]), S.Half).doit() == ImmutableMatrix([2])
|
||||
X = ImmutableMatrix([[0, 2], [0, 4]]) # det() == 0
|
||||
raises(ValueError, lambda: MatPow(X,-1).doit())
|
||||
raises(ValueError, lambda: MatPow(X,-2).doit())
|
||||
|
||||
|
||||
def test_nonsquare():
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
B = ImmutableMatrix([[1, 2, 3], [4, 5, 6]])
|
||||
for r in [-1, 0, 1, 2, S.Half, S.Pi, n]:
|
||||
raises(NonSquareMatrixError, lambda: MatPow(A, r))
|
||||
raises(NonSquareMatrixError, lambda: MatPow(B, r))
|
||||
|
||||
|
||||
def test_doit_equals_pow(): #17179
|
||||
X = ImmutableMatrix ([[1,0],[0,1]])
|
||||
assert MatPow(X, n).doit() == X**n == X
|
||||
|
||||
|
||||
def test_doit_nested_MatrixExpr():
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
Y = ImmutableMatrix([[2, 3], [4, 5]])
|
||||
assert MatPow(MatMul(X, Y), 2).doit() == (X*Y)**2
|
||||
assert MatPow(MatAdd(X, Y), 2).doit() == (X + Y)**2
|
||||
|
||||
|
||||
def test_identity_power():
|
||||
k = Identity(n)
|
||||
assert MatPow(k, 4).doit() == k
|
||||
assert MatPow(k, n).doit() == k
|
||||
assert MatPow(k, -3).doit() == k
|
||||
assert MatPow(k, 0).doit() == k
|
||||
l = Identity(3)
|
||||
assert MatPow(l, n).doit() == l
|
||||
assert MatPow(l, -1).doit() == l
|
||||
assert MatPow(l, 0).doit() == l
|
||||
|
||||
|
||||
def test_zero_power():
|
||||
z1 = ZeroMatrix(n, n)
|
||||
assert MatPow(z1, 3).doit() == z1
|
||||
raises(ValueError, lambda:MatPow(z1, -1).doit())
|
||||
assert MatPow(z1, 0).doit() == Identity(n)
|
||||
assert MatPow(z1, n).doit() == z1
|
||||
raises(ValueError, lambda:MatPow(z1, -2).doit())
|
||||
z2 = ZeroMatrix(4, 4)
|
||||
assert MatPow(z2, n).doit() == z2
|
||||
raises(ValueError, lambda:MatPow(z2, -3).doit())
|
||||
assert MatPow(z2, 2).doit() == z2
|
||||
assert MatPow(z2, 0).doit() == Identity(4)
|
||||
raises(ValueError, lambda:MatPow(z2, -1).doit())
|
||||
|
||||
|
||||
def test_OneMatrix_power():
|
||||
o = OneMatrix(3, 3)
|
||||
assert o ** 0 == Identity(3)
|
||||
assert o ** 1 == o
|
||||
assert o * o == o ** 2 == 3 * o
|
||||
assert o * o * o == o ** 3 == 9 * o
|
||||
|
||||
o = OneMatrix(n, n)
|
||||
assert o * o == o ** 2 == n * o
|
||||
# powsimp necessary as n ** (n - 2) * n does not produce n ** (n - 1)
|
||||
assert powsimp(o ** (n - 1) * o) == o ** n == n ** (n - 1) * o
|
||||
|
||||
|
||||
def test_transpose_power():
|
||||
from sympy.matrices.expressions.transpose import Transpose as TP
|
||||
|
||||
assert (C*D).T**5 == ((C*D)**5).T == (D.T * C.T)**5
|
||||
assert ((C*D).T**5).T == (C*D)**5
|
||||
|
||||
assert (C.T.I.T)**7 == C**-7
|
||||
assert (C.T**l).T**k == C**(l*k)
|
||||
|
||||
assert ((E.T * A.T)**5).T == (A*E)**5
|
||||
assert ((A*E).T**5).T**7 == (A*E)**35
|
||||
assert TP(TP(C**2 * D**3)**5).doit() == (C**2 * D**3)**5
|
||||
|
||||
assert ((D*C)**-5).T**-5 == ((D*C)**25).T
|
||||
assert (((D*C)**l).T**k).T == (D*C)**(l*k)
|
||||
|
||||
|
||||
def test_Inverse():
|
||||
assert Inverse(MatPow(C, 0)).doit() == Identity(n)
|
||||
assert Inverse(MatPow(C, 1)).doit() == Inverse(C)
|
||||
assert Inverse(MatPow(C, 2)).doit() == MatPow(C, -2)
|
||||
assert Inverse(MatPow(C, -1)).doit() == C
|
||||
|
||||
assert MatPow(Inverse(C), 0).doit() == Identity(n)
|
||||
assert MatPow(Inverse(C), 1).doit() == Inverse(C)
|
||||
assert MatPow(Inverse(C), 2).doit() == MatPow(C, -2)
|
||||
assert MatPow(Inverse(C), -1).doit() == C
|
||||
|
||||
|
||||
def test_combine_powers():
|
||||
assert (C ** 1) ** 1 == C
|
||||
assert (C ** 2) ** 3 == MatPow(C, 6)
|
||||
assert (C ** -2) ** -3 == MatPow(C, 6)
|
||||
assert (C ** -1) ** -1 == C
|
||||
assert (((C ** 2) ** 3) ** 4) ** 5 == MatPow(C, 120)
|
||||
assert (C ** n) ** n == C ** (n ** 2)
|
||||
|
||||
|
||||
def test_unchanged():
|
||||
assert unchanged(MatPow, C, 0)
|
||||
assert unchanged(MatPow, C, 1)
|
||||
assert unchanged(MatPow, Inverse(C), -1)
|
||||
assert unchanged(Inverse, MatPow(C, -1), -1)
|
||||
assert unchanged(MatPow, MatPow(C, -1), -1)
|
||||
assert unchanged(MatPow, MatPow(C, 1), 1)
|
||||
|
||||
|
||||
def test_no_exponentiation():
|
||||
# if this passes, Pow.as_numer_denom should recognize
|
||||
# MatAdd as exponent
|
||||
raises(NotImplementedError, lambda: 3**(-2*C))
|
||||
@@ -0,0 +1,166 @@
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions import \
|
||||
MatMul, BlockDiagMatrix, Determinant, Inverse
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.special import ZeroMatrix, OneMatrix, Identity
|
||||
from sympy.matrices.expressions.permutation import \
|
||||
MatrixPermute, PermutationMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.core.symbol import Symbol
|
||||
|
||||
|
||||
def test_PermutationMatrix_basic():
|
||||
p = Permutation([1, 0])
|
||||
assert unchanged(PermutationMatrix, p)
|
||||
raises(ValueError, lambda: PermutationMatrix((0, 1, 2)))
|
||||
assert PermutationMatrix(p).as_explicit() == Matrix([[0, 1], [1, 0]])
|
||||
assert isinstance(PermutationMatrix(p)*MatrixSymbol('A', 2, 2), MatMul)
|
||||
|
||||
|
||||
def test_PermutationMatrix_matmul():
|
||||
p = Permutation([1, 2, 0])
|
||||
P = PermutationMatrix(p)
|
||||
M = Matrix([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
|
||||
assert (P*M).as_explicit() == P.as_explicit()*M
|
||||
assert (M*P).as_explicit() == M*P.as_explicit()
|
||||
|
||||
P1 = PermutationMatrix(Permutation([1, 2, 0]))
|
||||
P2 = PermutationMatrix(Permutation([2, 1, 0]))
|
||||
P3 = PermutationMatrix(Permutation([1, 0, 2]))
|
||||
assert P1*P2 == P3
|
||||
|
||||
|
||||
def test_PermutationMatrix_matpow():
|
||||
p1 = Permutation([1, 2, 0])
|
||||
P1 = PermutationMatrix(p1)
|
||||
p2 = Permutation([2, 0, 1])
|
||||
P2 = PermutationMatrix(p2)
|
||||
assert P1**2 == P2
|
||||
assert P1**3 == Identity(3)
|
||||
|
||||
|
||||
def test_PermutationMatrix_identity():
|
||||
p = Permutation([0, 1])
|
||||
assert PermutationMatrix(p).is_Identity
|
||||
|
||||
p = Permutation([1, 0])
|
||||
assert not PermutationMatrix(p).is_Identity
|
||||
|
||||
|
||||
def test_PermutationMatrix_determinant():
|
||||
P = PermutationMatrix(Permutation([0, 1, 2]))
|
||||
assert Determinant(P).doit() == 1
|
||||
P = PermutationMatrix(Permutation([0, 2, 1]))
|
||||
assert Determinant(P).doit() == -1
|
||||
P = PermutationMatrix(Permutation([2, 0, 1]))
|
||||
assert Determinant(P).doit() == 1
|
||||
|
||||
|
||||
def test_PermutationMatrix_inverse():
|
||||
P = PermutationMatrix(Permutation(0, 1, 2))
|
||||
assert Inverse(P).doit() == PermutationMatrix(Permutation(0, 2, 1))
|
||||
|
||||
|
||||
def test_PermutationMatrix_rewrite_BlockDiagMatrix():
|
||||
P = PermutationMatrix(Permutation([0, 1, 2, 3, 4, 5]))
|
||||
P0 = PermutationMatrix(Permutation([0]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P0, P0, P0, P0, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation([0, 1, 3, 2, 4, 5]))
|
||||
P10 = PermutationMatrix(Permutation(0, 1))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P0, P10, P0, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation([1, 0, 3, 2, 5, 4]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P10, P10, P10)
|
||||
|
||||
P = PermutationMatrix(Permutation([0, 4, 3, 2, 1, 5]))
|
||||
P3210 = PermutationMatrix(Permutation([3, 2, 1, 0]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P3210, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation([0, 4, 2, 3, 1, 5]))
|
||||
P3120 = PermutationMatrix(Permutation([3, 1, 2, 0]))
|
||||
assert P.rewrite(BlockDiagMatrix) == \
|
||||
BlockDiagMatrix(P0, P3120, P0)
|
||||
|
||||
P = PermutationMatrix(Permutation(0, 3)(1, 4)(2, 5))
|
||||
assert P.rewrite(BlockDiagMatrix) == BlockDiagMatrix(P)
|
||||
|
||||
|
||||
def test_MartrixPermute_basic():
|
||||
p = Permutation(0, 1)
|
||||
P = PermutationMatrix(p)
|
||||
A = MatrixSymbol('A', 2, 2)
|
||||
|
||||
raises(ValueError, lambda: MatrixPermute(Symbol('x'), p))
|
||||
raises(ValueError, lambda: MatrixPermute(A, Symbol('x')))
|
||||
|
||||
assert MatrixPermute(A, P) == MatrixPermute(A, p)
|
||||
raises(ValueError, lambda: MatrixPermute(A, p, 2))
|
||||
|
||||
pp = Permutation(0, 1, size=3)
|
||||
assert MatrixPermute(A, pp) == MatrixPermute(A, p)
|
||||
pp = Permutation(0, 1, 2)
|
||||
raises(ValueError, lambda: MatrixPermute(A, pp))
|
||||
|
||||
|
||||
def test_MatrixPermute_shape():
|
||||
p = Permutation(0, 1)
|
||||
A = MatrixSymbol('A', 2, 3)
|
||||
assert MatrixPermute(A, p).shape == (2, 3)
|
||||
|
||||
|
||||
def test_MatrixPermute_explicit():
|
||||
p = Permutation(0, 1, 2)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
AA = A.as_explicit()
|
||||
assert MatrixPermute(A, p, 0).as_explicit() == \
|
||||
AA.permute(p, orientation='rows')
|
||||
assert MatrixPermute(A, p, 1).as_explicit() == \
|
||||
AA.permute(p, orientation='cols')
|
||||
|
||||
|
||||
def test_MatrixPermute_rewrite_MatMul():
|
||||
p = Permutation(0, 1, 2)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
|
||||
assert MatrixPermute(A, p, 0).rewrite(MatMul).as_explicit() == \
|
||||
MatrixPermute(A, p, 0).as_explicit()
|
||||
assert MatrixPermute(A, p, 1).rewrite(MatMul).as_explicit() == \
|
||||
MatrixPermute(A, p, 1).as_explicit()
|
||||
|
||||
|
||||
def test_MatrixPermute_doit():
|
||||
p = Permutation(0, 1, 2)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
assert MatrixPermute(A, p).doit() == MatrixPermute(A, p)
|
||||
|
||||
p = Permutation(0, size=3)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
assert MatrixPermute(A, p).doit().as_explicit() == \
|
||||
MatrixPermute(A, p).as_explicit()
|
||||
|
||||
p = Permutation(0, 1, 2)
|
||||
A = Identity(3)
|
||||
assert MatrixPermute(A, p, 0).doit().as_explicit() == \
|
||||
MatrixPermute(A, p, 0).as_explicit()
|
||||
assert MatrixPermute(A, p, 1).doit().as_explicit() == \
|
||||
MatrixPermute(A, p, 1).as_explicit()
|
||||
|
||||
A = ZeroMatrix(3, 3)
|
||||
assert MatrixPermute(A, p).doit() == A
|
||||
A = OneMatrix(3, 3)
|
||||
assert MatrixPermute(A, p).doit() == A
|
||||
|
||||
A = MatrixSymbol('A', 4, 4)
|
||||
p1 = Permutation(0, 1, 2, 3)
|
||||
p2 = Permutation(0, 2, 3, 1)
|
||||
expr = MatrixPermute(MatrixPermute(A, p1, 0), p2, 0)
|
||||
assert expr.as_explicit() == expr.doit().as_explicit()
|
||||
expr = MatrixPermute(MatrixPermute(A, p1, 1), p2, 1)
|
||||
assert expr.as_explicit() == expr.doit().as_explicit()
|
||||
@@ -0,0 +1,42 @@
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.sets import MatrixSet
|
||||
from sympy.matrices.expressions.special import ZeroMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.sets.sets import SetKind
|
||||
from sympy.matrices.kind import MatrixKind
|
||||
from sympy.core.kind import NumberKind
|
||||
|
||||
|
||||
def test_MatrixSet():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
M = MatrixSet(2, 2, set=S.Reals)
|
||||
assert M.shape == (2, 2)
|
||||
assert M.set == S.Reals
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert X in M
|
||||
X = ZeroMatrix(2, 2)
|
||||
assert X in M
|
||||
raises(TypeError, lambda: A in M)
|
||||
raises(TypeError, lambda: 1 in M)
|
||||
M = MatrixSet(n, m, set=S.Reals)
|
||||
assert A in M
|
||||
raises(TypeError, lambda: C in M)
|
||||
raises(TypeError, lambda: X in M)
|
||||
M = MatrixSet(2, 2, set={1, 2, 3})
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
Y = Matrix([[1, 2]])
|
||||
assert (X in M) == S.false
|
||||
assert (Y in M) == S.false
|
||||
raises(ValueError, lambda: MatrixSet(2, -2, S.Reals))
|
||||
raises(ValueError, lambda: MatrixSet(2.4, -1, S.Reals))
|
||||
raises(TypeError, lambda: MatrixSet(2, 2, (1, 2, 3)))
|
||||
|
||||
|
||||
def test_SetKind_MatrixSet():
|
||||
assert MatrixSet(2, 2, set=S.Reals).kind is SetKind(MatrixKind(NumberKind))
|
||||
@@ -0,0 +1,65 @@
|
||||
from sympy.matrices.expressions.slice import MatrixSlice
|
||||
from sympy.matrices.expressions import MatrixSymbol
|
||||
from sympy.abc import a, b, c, d, k, l, m, n
|
||||
from sympy.testing.pytest import raises, XFAIL
|
||||
from sympy.functions.elementary.integers import floor
|
||||
from sympy.assumptions import assuming, Q
|
||||
|
||||
|
||||
X = MatrixSymbol('X', n, m)
|
||||
Y = MatrixSymbol('Y', m, k)
|
||||
|
||||
def test_shape():
|
||||
B = MatrixSlice(X, (a, b), (c, d))
|
||||
assert B.shape == (b - a, d - c)
|
||||
|
||||
def test_entry():
|
||||
B = MatrixSlice(X, (a, b), (c, d))
|
||||
assert B[0,0] == X[a, c]
|
||||
assert B[k,l] == X[a+k, c+l]
|
||||
raises(IndexError, lambda : MatrixSlice(X, 1, (2, 5))[1, 0])
|
||||
|
||||
assert X[1::2, :][1, 3] == X[1+2, 3]
|
||||
assert X[:, 1::2][3, 1] == X[3, 1+2]
|
||||
|
||||
def test_on_diag():
|
||||
assert not MatrixSlice(X, (a, b), (c, d)).on_diag
|
||||
assert MatrixSlice(X, (a, b), (a, b)).on_diag
|
||||
|
||||
def test_inputs():
|
||||
assert MatrixSlice(X, 1, (2, 5)) == MatrixSlice(X, (1, 2), (2, 5))
|
||||
assert MatrixSlice(X, 1, (2, 5)).shape == (1, 3)
|
||||
|
||||
def test_slicing():
|
||||
assert X[1:5, 2:4] == MatrixSlice(X, (1, 5), (2, 4))
|
||||
assert X[1, 2:4] == MatrixSlice(X, 1, (2, 4))
|
||||
assert X[1:5, :].shape == (4, X.shape[1])
|
||||
assert X[:, 1:5].shape == (X.shape[0], 4)
|
||||
|
||||
assert X[::2, ::2].shape == (floor(n/2), floor(m/2))
|
||||
assert X[2, :] == MatrixSlice(X, 2, (0, m))
|
||||
assert X[k, :] == MatrixSlice(X, k, (0, m))
|
||||
|
||||
def test_exceptions():
|
||||
X = MatrixSymbol('x', 10, 20)
|
||||
raises(IndexError, lambda: X[0:12, 2])
|
||||
raises(IndexError, lambda: X[0:9, 22])
|
||||
raises(IndexError, lambda: X[-1:5, 2])
|
||||
|
||||
@XFAIL
|
||||
def test_symmetry():
|
||||
X = MatrixSymbol('x', 10, 10)
|
||||
Y = X[:5, 5:]
|
||||
with assuming(Q.symmetric(X)):
|
||||
assert Y.T == X[5:, :5]
|
||||
|
||||
def test_slice_of_slice():
|
||||
X = MatrixSymbol('x', 10, 10)
|
||||
assert X[2, :][:, 3][0, 0] == X[2, 3]
|
||||
assert X[:5, :5][:4, :4] == X[:4, :4]
|
||||
assert X[1:5, 2:6][1:3, 2] == X[2:4, 4]
|
||||
assert X[1:9:2, 2:6][1:3, 2] == X[3:7:2, 4]
|
||||
|
||||
def test_negative_index():
|
||||
X = MatrixSymbol('x', 10, 10)
|
||||
assert X[-1, :] == X[9, :]
|
||||
@@ -0,0 +1,228 @@
|
||||
from sympy.core.add import Add
|
||||
from sympy.core.expr import unchanged
|
||||
from sympy.core.mul import Mul
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.concrete.summations import Sum
|
||||
from sympy.functions.elementary.complexes import im, re
|
||||
from sympy.functions.elementary.piecewise import Piecewise
|
||||
from sympy.matrices.immutable import ImmutableDenseMatrix
|
||||
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
||||
from sympy.matrices.expressions.matadd import MatAdd
|
||||
from sympy.matrices.expressions.special import (
|
||||
ZeroMatrix, GenericZeroMatrix, Identity, GenericIdentity, OneMatrix)
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_zero_matrix_creation():
|
||||
assert unchanged(ZeroMatrix, 2, 2)
|
||||
assert unchanged(ZeroMatrix, 0, 0)
|
||||
raises(ValueError, lambda: ZeroMatrix(-1, 2))
|
||||
raises(ValueError, lambda: ZeroMatrix(2.0, 2))
|
||||
raises(ValueError, lambda: ZeroMatrix(2j, 2))
|
||||
raises(ValueError, lambda: ZeroMatrix(2, -1))
|
||||
raises(ValueError, lambda: ZeroMatrix(2, 2.0))
|
||||
raises(ValueError, lambda: ZeroMatrix(2, 2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert unchanged(ZeroMatrix, n, n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: ZeroMatrix(n, n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: ZeroMatrix(n, n))
|
||||
|
||||
|
||||
def test_generic_zero_matrix():
|
||||
z = GenericZeroMatrix()
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol("A", n, n)
|
||||
|
||||
assert z == z
|
||||
assert z != A
|
||||
assert A != z
|
||||
|
||||
assert z.is_ZeroMatrix
|
||||
|
||||
raises(TypeError, lambda: z.shape)
|
||||
raises(TypeError, lambda: z.rows)
|
||||
raises(TypeError, lambda: z.cols)
|
||||
|
||||
assert MatAdd() == z
|
||||
assert MatAdd(z, A) == MatAdd(A)
|
||||
# Make sure it is hashable
|
||||
hash(z)
|
||||
|
||||
|
||||
def test_identity_matrix_creation():
|
||||
assert Identity(2)
|
||||
assert Identity(0)
|
||||
raises(ValueError, lambda: Identity(-1))
|
||||
raises(ValueError, lambda: Identity(2.0))
|
||||
raises(ValueError, lambda: Identity(2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert Identity(n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: Identity(n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: Identity(n))
|
||||
|
||||
|
||||
def test_generic_identity():
|
||||
I = GenericIdentity()
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol("A", n, n)
|
||||
|
||||
assert I == I
|
||||
assert I != A
|
||||
assert A != I
|
||||
|
||||
assert I.is_Identity
|
||||
assert I**-1 == I
|
||||
|
||||
raises(TypeError, lambda: I.shape)
|
||||
raises(TypeError, lambda: I.rows)
|
||||
raises(TypeError, lambda: I.cols)
|
||||
|
||||
assert MatMul() == I
|
||||
assert MatMul(I, A) == MatMul(A)
|
||||
# Make sure it is hashable
|
||||
hash(I)
|
||||
|
||||
|
||||
def test_one_matrix_creation():
|
||||
assert OneMatrix(2, 2)
|
||||
assert OneMatrix(0, 0)
|
||||
assert Eq(OneMatrix(1, 1), Identity(1))
|
||||
raises(ValueError, lambda: OneMatrix(-1, 2))
|
||||
raises(ValueError, lambda: OneMatrix(2.0, 2))
|
||||
raises(ValueError, lambda: OneMatrix(2j, 2))
|
||||
raises(ValueError, lambda: OneMatrix(2, -1))
|
||||
raises(ValueError, lambda: OneMatrix(2, 2.0))
|
||||
raises(ValueError, lambda: OneMatrix(2, 2j))
|
||||
|
||||
n = symbols('n')
|
||||
assert OneMatrix(n, n)
|
||||
n = symbols('n', integer=False)
|
||||
raises(ValueError, lambda: OneMatrix(n, n))
|
||||
n = symbols('n', negative=True)
|
||||
raises(ValueError, lambda: OneMatrix(n, n))
|
||||
|
||||
|
||||
def test_ZeroMatrix():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
Z = ZeroMatrix(n, m)
|
||||
|
||||
assert A + Z == A
|
||||
assert A*Z.T == ZeroMatrix(n, n)
|
||||
assert Z*A.T == ZeroMatrix(n, n)
|
||||
assert A - A == ZeroMatrix(*A.shape)
|
||||
|
||||
assert Z
|
||||
|
||||
assert Z.transpose() == ZeroMatrix(m, n)
|
||||
assert Z.conjugate() == Z
|
||||
assert Z.adjoint() == ZeroMatrix(m, n)
|
||||
assert re(Z) == Z
|
||||
assert im(Z) == Z
|
||||
|
||||
assert ZeroMatrix(n, n)**0 == Identity(n)
|
||||
assert ZeroMatrix(3, 3).as_explicit() == ImmutableDenseMatrix.zeros(3, 3)
|
||||
|
||||
|
||||
def test_ZeroMatrix_doit():
|
||||
n = symbols('n', integer=True)
|
||||
Znn = ZeroMatrix(Add(n, n, evaluate=False), n)
|
||||
assert isinstance(Znn.rows, Add)
|
||||
assert Znn.doit() == ZeroMatrix(2*n, n)
|
||||
assert isinstance(Znn.doit().rows, Mul)
|
||||
|
||||
|
||||
def test_OneMatrix():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
U = OneMatrix(n, m)
|
||||
|
||||
assert U.shape == (n, m)
|
||||
assert isinstance(A + U, Add)
|
||||
assert U.transpose() == OneMatrix(m, n)
|
||||
assert U.conjugate() == U
|
||||
assert U.adjoint() == OneMatrix(m, n)
|
||||
assert re(U) == U
|
||||
assert im(U) == ZeroMatrix(n, m)
|
||||
|
||||
assert OneMatrix(n, n) ** 0 == Identity(n)
|
||||
|
||||
U = OneMatrix(n, n)
|
||||
assert U[1, 2] == 1
|
||||
|
||||
U = OneMatrix(2, 3)
|
||||
assert U.as_explicit() == ImmutableDenseMatrix.ones(2, 3)
|
||||
|
||||
|
||||
def test_OneMatrix_doit():
|
||||
n = symbols('n', integer=True)
|
||||
Unn = OneMatrix(Add(n, n, evaluate=False), n)
|
||||
assert isinstance(Unn.rows, Add)
|
||||
assert Unn.doit() == OneMatrix(2 * n, n)
|
||||
assert isinstance(Unn.doit().rows, Mul)
|
||||
|
||||
|
||||
def test_OneMatrix_mul():
|
||||
n, m, k = symbols('n m k', integer=True)
|
||||
w = MatrixSymbol('w', n, 1)
|
||||
assert OneMatrix(n, m) * OneMatrix(m, k) == OneMatrix(n, k) * m
|
||||
assert w * OneMatrix(1, 1) == w
|
||||
assert OneMatrix(1, 1) * w.T == w.T
|
||||
|
||||
|
||||
def test_Identity():
|
||||
n, m = symbols('n m', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
i, j = symbols('i j')
|
||||
|
||||
In = Identity(n)
|
||||
Im = Identity(m)
|
||||
|
||||
assert A*Im == A
|
||||
assert In*A == A
|
||||
|
||||
assert In.transpose() == In
|
||||
assert In.inverse() == In
|
||||
assert In.conjugate() == In
|
||||
assert In.adjoint() == In
|
||||
assert re(In) == In
|
||||
assert im(In) == ZeroMatrix(n, n)
|
||||
|
||||
assert In[i, j] != 0
|
||||
assert Sum(In[i, j], (i, 0, n-1), (j, 0, n-1)).subs(n,3).doit() == 3
|
||||
assert Sum(Sum(In[i, j], (i, 0, n-1)), (j, 0, n-1)).subs(n,3).doit() == 3
|
||||
|
||||
# If range exceeds the limit `(0, n-1)`, do not remove `Piecewise`:
|
||||
expr = Sum(In[i, j], (i, 0, n-1))
|
||||
assert expr.doit() == 1
|
||||
expr = Sum(In[i, j], (i, 0, n-2))
|
||||
assert expr.doit().dummy_eq(
|
||||
Piecewise(
|
||||
(1, (j >= 0) & (j <= n-2)),
|
||||
(0, True)
|
||||
)
|
||||
)
|
||||
expr = Sum(In[i, j], (i, 1, n-1))
|
||||
assert expr.doit().dummy_eq(
|
||||
Piecewise(
|
||||
(1, (j >= 1) & (j <= n-1)),
|
||||
(0, True)
|
||||
)
|
||||
)
|
||||
assert Identity(3).as_explicit() == ImmutableDenseMatrix.eye(3)
|
||||
|
||||
|
||||
def test_Identity_doit():
|
||||
n = symbols('n', integer=True)
|
||||
Inn = Identity(Add(n, n, evaluate=False))
|
||||
assert isinstance(Inn.rows, Add)
|
||||
assert Inn.doit() == Identity(2*n)
|
||||
assert isinstance(Inn.doit().rows, Mul)
|
||||
@@ -0,0 +1,116 @@
|
||||
from sympy.core import Lambda, S, symbols
|
||||
from sympy.concrete import Sum
|
||||
from sympy.functions import adjoint, conjugate, transpose
|
||||
from sympy.matrices import eye, Matrix, ShapeError, ImmutableMatrix
|
||||
from sympy.matrices.expressions import (
|
||||
Adjoint, Identity, FunctionMatrix, MatrixExpr, MatrixSymbol, Trace,
|
||||
ZeroMatrix, trace, MatPow, MatAdd, MatMul
|
||||
)
|
||||
from sympy.matrices.expressions.special import OneMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.abc import i
|
||||
|
||||
|
||||
n = symbols('n', integer=True)
|
||||
A = MatrixSymbol('A', n, n)
|
||||
B = MatrixSymbol('B', n, n)
|
||||
C = MatrixSymbol('C', 3, 4)
|
||||
|
||||
|
||||
def test_Trace():
|
||||
assert isinstance(Trace(A), Trace)
|
||||
assert not isinstance(Trace(A), MatrixExpr)
|
||||
raises(ShapeError, lambda: Trace(C))
|
||||
assert trace(eye(3)) == 3
|
||||
assert trace(Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])) == 15
|
||||
|
||||
assert adjoint(Trace(A)) == trace(Adjoint(A))
|
||||
assert conjugate(Trace(A)) == trace(Adjoint(A))
|
||||
assert transpose(Trace(A)) == Trace(A)
|
||||
|
||||
_ = A / Trace(A) # Make sure this is possible
|
||||
|
||||
# Some easy simplifications
|
||||
assert trace(Identity(5)) == 5
|
||||
assert trace(ZeroMatrix(5, 5)) == 0
|
||||
assert trace(OneMatrix(1, 1)) == 1
|
||||
assert trace(OneMatrix(2, 2)) == 2
|
||||
assert trace(OneMatrix(n, n)) == n
|
||||
assert trace(2*A*B) == 2*Trace(A*B)
|
||||
assert trace(A.T) == trace(A)
|
||||
|
||||
i, j = symbols('i j')
|
||||
F = FunctionMatrix(3, 3, Lambda((i, j), i + j))
|
||||
assert trace(F) == (0 + 0) + (1 + 1) + (2 + 2)
|
||||
|
||||
raises(TypeError, lambda: Trace(S.One))
|
||||
|
||||
assert Trace(A).arg is A
|
||||
|
||||
assert str(trace(A)) == str(Trace(A).doit())
|
||||
|
||||
assert Trace(A).is_commutative is True
|
||||
|
||||
def test_Trace_A_plus_B():
|
||||
assert trace(A + B) == Trace(A) + Trace(B)
|
||||
assert Trace(A + B).arg == MatAdd(A, B)
|
||||
assert Trace(A + B).doit() == Trace(A) + Trace(B)
|
||||
|
||||
|
||||
def test_Trace_MatAdd_doit():
|
||||
# See issue #9028
|
||||
X = ImmutableMatrix([[1, 2, 3]]*3)
|
||||
Y = MatrixSymbol('Y', 3, 3)
|
||||
q = MatAdd(X, 2*X, Y, -3*Y)
|
||||
assert Trace(q).arg == q
|
||||
assert Trace(q).doit() == 18 - 2*Trace(Y)
|
||||
|
||||
|
||||
def test_Trace_MatPow_doit():
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert Trace(X).doit() == 5
|
||||
q = MatPow(X, 2)
|
||||
assert Trace(q).arg == q
|
||||
assert Trace(q).doit() == 29
|
||||
|
||||
|
||||
def test_Trace_MutableMatrix_plus():
|
||||
# See issue #9043
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
assert Trace(X) + Trace(X) == 2*Trace(X)
|
||||
|
||||
|
||||
def test_Trace_doit_deep_False():
|
||||
X = Matrix([[1, 2], [3, 4]])
|
||||
q = MatPow(X, 2)
|
||||
assert Trace(q).doit(deep=False).arg == q
|
||||
q = MatAdd(X, 2*X)
|
||||
assert Trace(q).doit(deep=False).arg == q
|
||||
q = MatMul(X, 2*X)
|
||||
assert Trace(q).doit(deep=False).arg == q
|
||||
|
||||
|
||||
def test_trace_constant_factor():
|
||||
# Issue 9052: gave 2*Trace(MatMul(A)) instead of 2*Trace(A)
|
||||
assert trace(2*A) == 2*Trace(A)
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
assert trace(MatMul(2, X)) == 10
|
||||
|
||||
|
||||
def test_trace_rewrite():
|
||||
assert trace(A).rewrite(Sum) == Sum(A[i, i], (i, 0, n - 1))
|
||||
assert trace(eye(3)).rewrite(Sum) == 3
|
||||
|
||||
|
||||
def test_trace_normalize():
|
||||
assert Trace(B*A) != Trace(A*B)
|
||||
assert Trace(B*A)._normalize() == Trace(A*B)
|
||||
assert Trace(B*A.T)._normalize() == Trace(A*B.T)
|
||||
|
||||
|
||||
def test_trace_as_explicit():
|
||||
raises(ValueError, lambda: Trace(A).as_explicit())
|
||||
|
||||
X = MatrixSymbol("X", 3, 3)
|
||||
assert Trace(X).as_explicit() == X[0, 0] + X[1, 1] + X[2, 2]
|
||||
assert Trace(eye(3)).as_explicit() == 3
|
||||
@@ -0,0 +1,69 @@
|
||||
from sympy.functions import adjoint, conjugate, transpose
|
||||
from sympy.matrices.expressions import MatrixSymbol, Adjoint, trace, Transpose
|
||||
from sympy.matrices import eye, Matrix
|
||||
from sympy.assumptions.ask import Q
|
||||
from sympy.assumptions.refine import refine
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import symbols
|
||||
|
||||
n, m, l, k, p = symbols('n m l k p', integer=True)
|
||||
A = MatrixSymbol('A', n, m)
|
||||
B = MatrixSymbol('B', m, l)
|
||||
C = MatrixSymbol('C', n, n)
|
||||
|
||||
|
||||
def test_transpose():
|
||||
Sq = MatrixSymbol('Sq', n, n)
|
||||
|
||||
assert transpose(A) == Transpose(A)
|
||||
assert Transpose(A).shape == (m, n)
|
||||
assert Transpose(A*B).shape == (l, n)
|
||||
assert transpose(Transpose(A)) == A
|
||||
assert isinstance(Transpose(Transpose(A)), Transpose)
|
||||
|
||||
assert adjoint(Transpose(A)) == Adjoint(Transpose(A))
|
||||
assert conjugate(Transpose(A)) == Adjoint(A)
|
||||
|
||||
assert Transpose(eye(3)).doit() == eye(3)
|
||||
|
||||
assert Transpose(S(5)).doit() == S(5)
|
||||
|
||||
assert Transpose(Matrix([[1, 2], [3, 4]])).doit() == Matrix([[1, 3], [2, 4]])
|
||||
|
||||
assert transpose(trace(Sq)) == trace(Sq)
|
||||
assert trace(Transpose(Sq)) == trace(Sq)
|
||||
|
||||
assert Transpose(Sq)[0, 1] == Sq[1, 0]
|
||||
|
||||
assert Transpose(A*B).doit() == Transpose(B) * Transpose(A)
|
||||
|
||||
|
||||
def test_transpose_MatAdd_MatMul():
|
||||
# Issue 16807
|
||||
from sympy.functions.elementary.trigonometric import cos
|
||||
|
||||
x = symbols('x')
|
||||
M = MatrixSymbol('M', 3, 3)
|
||||
N = MatrixSymbol('N', 3, 3)
|
||||
|
||||
assert (N + (cos(x) * M)).T == cos(x)*M.T + N.T
|
||||
|
||||
|
||||
def test_refine():
|
||||
assert refine(C.T, Q.symmetric(C)) == C
|
||||
|
||||
|
||||
def test_transpose1x1():
|
||||
m = MatrixSymbol('m', 1, 1)
|
||||
assert m == refine(m.T)
|
||||
assert m == refine(m.T.T)
|
||||
|
||||
def test_issue_9817():
|
||||
from sympy.matrices.expressions import Identity
|
||||
v = MatrixSymbol('v', 3, 1)
|
||||
A = MatrixSymbol('A', 3, 3)
|
||||
x = Matrix([i + 1 for i in range(3)])
|
||||
X = Identity(3)
|
||||
quadratic = v.T * A * v
|
||||
subbed = quadratic.xreplace({v:x, A:X})
|
||||
assert subbed.as_explicit() == Matrix([[14]])
|
||||
@@ -0,0 +1,167 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.expr import Expr, ExprBuilder
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sorting import default_sort_key
|
||||
from sympy.core.symbol import uniquely_named_symbol
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
|
||||
|
||||
class Trace(Expr):
|
||||
"""Matrix Trace
|
||||
|
||||
Represents the trace of a matrix expression.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Trace, eye
|
||||
>>> A = MatrixSymbol('A', 3, 3)
|
||||
>>> Trace(A)
|
||||
Trace(A)
|
||||
>>> Trace(eye(3))
|
||||
Trace(Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]]))
|
||||
>>> Trace(eye(3)).simplify()
|
||||
3
|
||||
"""
|
||||
is_Trace = True
|
||||
is_commutative = True
|
||||
|
||||
def __new__(cls, mat):
|
||||
mat = sympify(mat)
|
||||
|
||||
if not mat.is_Matrix:
|
||||
raise TypeError("input to Trace, %s, is not a matrix" % str(mat))
|
||||
|
||||
if mat.is_square is False:
|
||||
raise NonSquareMatrixError("Trace of a non-square matrix")
|
||||
|
||||
return Basic.__new__(cls, mat)
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self
|
||||
|
||||
def _eval_derivative(self, v):
|
||||
from sympy.concrete.summations import Sum
|
||||
from .matexpr import MatrixElement
|
||||
if isinstance(v, MatrixElement):
|
||||
return self.rewrite(Sum).diff(v)
|
||||
expr = self.doit()
|
||||
if isinstance(expr, Trace):
|
||||
# Avoid looping infinitely:
|
||||
raise NotImplementedError
|
||||
return expr._eval_derivative(v)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
from sympy.tensor.array.expressions.array_expressions import ArrayTensorProduct, ArrayContraction
|
||||
r = self.args[0]._eval_derivative_matrix_lines(x)
|
||||
for lr in r:
|
||||
if lr.higher == 1:
|
||||
lr.higher = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
lr._lines[0],
|
||||
lr._lines[1],
|
||||
]
|
||||
),
|
||||
(1, 3),
|
||||
],
|
||||
validator=ArrayContraction._validate
|
||||
)
|
||||
else:
|
||||
# This is not a matrix line:
|
||||
lr.higher = ExprBuilder(
|
||||
ArrayContraction,
|
||||
[
|
||||
ExprBuilder(
|
||||
ArrayTensorProduct,
|
||||
[
|
||||
lr._lines[0],
|
||||
lr._lines[1],
|
||||
lr.higher,
|
||||
]
|
||||
),
|
||||
(1, 3), (0, 2)
|
||||
]
|
||||
)
|
||||
lr._lines = [S.One, S.One]
|
||||
lr._first_pointer_parent = lr._lines
|
||||
lr._second_pointer_parent = lr._lines
|
||||
lr._first_pointer_index = 0
|
||||
lr._second_pointer_index = 1
|
||||
return r
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
def doit(self, **hints):
|
||||
if hints.get('deep', True):
|
||||
arg = self.arg.doit(**hints)
|
||||
result = arg._eval_trace()
|
||||
if result is not None:
|
||||
return result
|
||||
else:
|
||||
return Trace(arg)
|
||||
else:
|
||||
# _eval_trace would go too deep here
|
||||
if isinstance(self.arg, MatrixBase):
|
||||
return trace(self.arg)
|
||||
else:
|
||||
return Trace(self.arg)
|
||||
|
||||
def as_explicit(self):
|
||||
return Trace(self.arg.as_explicit()).doit()
|
||||
|
||||
def _normalize(self):
|
||||
# Normalization of trace of matrix products. Use transposition and
|
||||
# cyclic properties of traces to make sure the arguments of the matrix
|
||||
# product are sorted and the first argument is not a transposition.
|
||||
from sympy.matrices.expressions.matmul import MatMul
|
||||
from sympy.matrices.expressions.transpose import Transpose
|
||||
trace_arg = self.arg
|
||||
if isinstance(trace_arg, MatMul):
|
||||
|
||||
def get_arg_key(x):
|
||||
a = trace_arg.args[x]
|
||||
if isinstance(a, Transpose):
|
||||
a = a.arg
|
||||
return default_sort_key(a)
|
||||
|
||||
indmin = min(range(len(trace_arg.args)), key=get_arg_key)
|
||||
if isinstance(trace_arg.args[indmin], Transpose):
|
||||
trace_arg = Transpose(trace_arg).doit()
|
||||
indmin = min(range(len(trace_arg.args)), key=lambda x: default_sort_key(trace_arg.args[x]))
|
||||
trace_arg = MatMul.fromiter(trace_arg.args[indmin:] + trace_arg.args[:indmin])
|
||||
return Trace(trace_arg)
|
||||
return self
|
||||
|
||||
def _eval_rewrite_as_Sum(self, expr, **kwargs):
|
||||
from sympy.concrete.summations import Sum
|
||||
i = uniquely_named_symbol('i', [expr])
|
||||
s = Sum(self.arg[i, i], (i, 0, self.arg.rows - 1))
|
||||
return s.doit()
|
||||
|
||||
|
||||
def trace(expr):
|
||||
"""Trace of a Matrix. Sum of the diagonal elements.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import trace, Symbol, MatrixSymbol, eye
|
||||
>>> n = Symbol('n')
|
||||
>>> X = MatrixSymbol('X', n, n) # A square matrix
|
||||
>>> trace(2*X)
|
||||
2*Trace(X)
|
||||
>>> trace(eye(3))
|
||||
3
|
||||
"""
|
||||
return Trace(expr).doit()
|
||||
@@ -0,0 +1,103 @@
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.matrices.expressions.matexpr import MatrixExpr
|
||||
|
||||
|
||||
class Transpose(MatrixExpr):
|
||||
"""
|
||||
The transpose of a matrix expression.
|
||||
|
||||
This is a symbolic object that simply stores its argument without
|
||||
evaluating it. To actually compute the transpose, use the ``transpose()``
|
||||
function, or the ``.T`` attribute of matrices.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import MatrixSymbol, Transpose, transpose
|
||||
>>> A = MatrixSymbol('A', 3, 5)
|
||||
>>> B = MatrixSymbol('B', 5, 3)
|
||||
>>> Transpose(A)
|
||||
A.T
|
||||
>>> A.T == transpose(A) == Transpose(A)
|
||||
True
|
||||
>>> Transpose(A*B)
|
||||
(A*B).T
|
||||
>>> transpose(A*B)
|
||||
B.T*A.T
|
||||
|
||||
"""
|
||||
is_Transpose = True
|
||||
|
||||
def doit(self, **hints):
|
||||
arg = self.arg
|
||||
if hints.get('deep', True) and isinstance(arg, Basic):
|
||||
arg = arg.doit(**hints)
|
||||
_eval_transpose = getattr(arg, '_eval_transpose', None)
|
||||
if _eval_transpose is not None:
|
||||
result = _eval_transpose()
|
||||
return result if result is not None else Transpose(arg)
|
||||
else:
|
||||
return Transpose(arg)
|
||||
|
||||
@property
|
||||
def arg(self):
|
||||
return self.args[0]
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self.arg.shape[::-1]
|
||||
|
||||
def _entry(self, i, j, expand=False, **kwargs):
|
||||
return self.arg._entry(j, i, expand=expand, **kwargs)
|
||||
|
||||
def _eval_adjoint(self):
|
||||
return self.arg.conjugate()
|
||||
|
||||
def _eval_conjugate(self):
|
||||
return self.arg.adjoint()
|
||||
|
||||
def _eval_transpose(self):
|
||||
return self.arg
|
||||
|
||||
def _eval_trace(self):
|
||||
from .trace import Trace
|
||||
return Trace(self.arg) # Trace(X.T) => Trace(X)
|
||||
|
||||
def _eval_determinant(self):
|
||||
from sympy.matrices.expressions.determinant import det
|
||||
return det(self.arg)
|
||||
|
||||
def _eval_derivative(self, x):
|
||||
# x is a scalar:
|
||||
return self.arg._eval_derivative(x)
|
||||
|
||||
def _eval_derivative_matrix_lines(self, x):
|
||||
lines = self.args[0]._eval_derivative_matrix_lines(x)
|
||||
return [i.transpose() for i in lines]
|
||||
|
||||
|
||||
def transpose(expr):
|
||||
"""Matrix transpose"""
|
||||
return Transpose(expr).doit(deep=False)
|
||||
|
||||
|
||||
from sympy.assumptions.ask import ask, Q
|
||||
from sympy.assumptions.refine import handlers_dict
|
||||
|
||||
|
||||
def refine_Transpose(expr, assumptions):
|
||||
"""
|
||||
>>> from sympy import MatrixSymbol, Q, assuming, refine
|
||||
>>> X = MatrixSymbol('X', 2, 2)
|
||||
>>> X.T
|
||||
X.T
|
||||
>>> with assuming(Q.symmetric(X)):
|
||||
... print(refine(X.T))
|
||||
X
|
||||
"""
|
||||
if ask(Q.symmetric(expr), assumptions):
|
||||
return expr.arg
|
||||
|
||||
return expr
|
||||
|
||||
handlers_dict['Transpose'] = refine_Transpose
|
||||
@@ -0,0 +1,279 @@
|
||||
from sympy.utilities.iterables import \
|
||||
flatten, connected_components, strongly_connected_components
|
||||
from .exceptions import NonSquareMatrixError
|
||||
|
||||
|
||||
def _connected_components(M):
|
||||
"""Returns the list of connected vertices of the graph when
|
||||
a square matrix is viewed as a weighted graph.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([
|
||||
... [66, 0, 0, 68, 0, 0, 0, 0, 67],
|
||||
... [0, 55, 0, 0, 0, 0, 54, 53, 0],
|
||||
... [0, 0, 0, 0, 1, 2, 0, 0, 0],
|
||||
... [86, 0, 0, 88, 0, 0, 0, 0, 87],
|
||||
... [0, 0, 10, 0, 11, 12, 0, 0, 0],
|
||||
... [0, 0, 20, 0, 21, 22, 0, 0, 0],
|
||||
... [0, 45, 0, 0, 0, 0, 44, 43, 0],
|
||||
... [0, 35, 0, 0, 0, 0, 34, 33, 0],
|
||||
... [76, 0, 0, 78, 0, 0, 0, 0, 77]])
|
||||
>>> A.connected_components()
|
||||
[[0, 3, 8], [1, 6, 7], [2, 4, 5]]
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
Even if any symbolic elements of the matrix can be indeterminate
|
||||
to be zero mathematically, this only takes the account of the
|
||||
structural aspect of the matrix, so they will considered to be
|
||||
nonzero.
|
||||
"""
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError
|
||||
|
||||
V = range(M.rows)
|
||||
E = sorted(M.todok().keys())
|
||||
return connected_components((V, E))
|
||||
|
||||
|
||||
def _strongly_connected_components(M):
|
||||
"""Returns the list of strongly connected vertices of the graph when
|
||||
a square matrix is viewed as a weighted graph.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([
|
||||
... [44, 0, 0, 0, 43, 0, 45, 0, 0],
|
||||
... [0, 66, 62, 61, 0, 68, 0, 60, 67],
|
||||
... [0, 0, 22, 21, 0, 0, 0, 20, 0],
|
||||
... [0, 0, 12, 11, 0, 0, 0, 10, 0],
|
||||
... [34, 0, 0, 0, 33, 0, 35, 0, 0],
|
||||
... [0, 86, 82, 81, 0, 88, 0, 80, 87],
|
||||
... [54, 0, 0, 0, 53, 0, 55, 0, 0],
|
||||
... [0, 0, 2, 1, 0, 0, 0, 0, 0],
|
||||
... [0, 76, 72, 71, 0, 78, 0, 70, 77]])
|
||||
>>> A.strongly_connected_components()
|
||||
[[0, 4, 6], [2, 3, 7], [1, 5, 8]]
|
||||
"""
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError
|
||||
|
||||
# RepMatrix uses the more efficient DomainMatrix.scc() method
|
||||
rep = getattr(M, '_rep', None)
|
||||
if rep is not None:
|
||||
return rep.scc()
|
||||
|
||||
V = range(M.rows)
|
||||
E = sorted(M.todok().keys())
|
||||
return strongly_connected_components((V, E))
|
||||
|
||||
|
||||
def _connected_components_decomposition(M):
|
||||
"""Decomposes a square matrix into block diagonal form only
|
||||
using the permutations.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The decomposition is in a form of $A = P^{-1} B P$ where $P$ is a
|
||||
permutation matrix and $B$ is a block diagonal matrix.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
P, B : PermutationMatrix, BlockDiagMatrix
|
||||
*P* is a permutation matrix for the similarity transform
|
||||
as in the explanation. And *B* is the block diagonal matrix of
|
||||
the result of the permutation.
|
||||
|
||||
If you would like to get the diagonal blocks from the
|
||||
BlockDiagMatrix, see
|
||||
:meth:`~sympy.matrices.expressions.blockmatrix.BlockDiagMatrix.get_diag_blocks`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, pprint
|
||||
>>> A = Matrix([
|
||||
... [66, 0, 0, 68, 0, 0, 0, 0, 67],
|
||||
... [0, 55, 0, 0, 0, 0, 54, 53, 0],
|
||||
... [0, 0, 0, 0, 1, 2, 0, 0, 0],
|
||||
... [86, 0, 0, 88, 0, 0, 0, 0, 87],
|
||||
... [0, 0, 10, 0, 11, 12, 0, 0, 0],
|
||||
... [0, 0, 20, 0, 21, 22, 0, 0, 0],
|
||||
... [0, 45, 0, 0, 0, 0, 44, 43, 0],
|
||||
... [0, 35, 0, 0, 0, 0, 34, 33, 0],
|
||||
... [76, 0, 0, 78, 0, 0, 0, 0, 77]])
|
||||
|
||||
>>> P, B = A.connected_components_decomposition()
|
||||
>>> pprint(P)
|
||||
PermutationMatrix((1 3)(2 8 5 7 4 6))
|
||||
>>> pprint(B)
|
||||
[[66 68 67] ]
|
||||
[[ ] ]
|
||||
[[86 88 87] 0 0 ]
|
||||
[[ ] ]
|
||||
[[76 78 77] ]
|
||||
[ ]
|
||||
[ [55 54 53] ]
|
||||
[ [ ] ]
|
||||
[ 0 [45 44 43] 0 ]
|
||||
[ [ ] ]
|
||||
[ [35 34 33] ]
|
||||
[ ]
|
||||
[ [0 1 2 ]]
|
||||
[ [ ]]
|
||||
[ 0 0 [10 11 12]]
|
||||
[ [ ]]
|
||||
[ [20 21 22]]
|
||||
|
||||
>>> P = P.as_explicit()
|
||||
>>> B = B.as_explicit()
|
||||
>>> P.T*B*P == A
|
||||
True
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This problem corresponds to the finding of the connected components
|
||||
of a graph, when a matrix is viewed as a weighted graph.
|
||||
"""
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.matrices.expressions.blockmatrix import BlockDiagMatrix
|
||||
from sympy.matrices.expressions.permutation import PermutationMatrix
|
||||
|
||||
iblocks = M.connected_components()
|
||||
|
||||
p = Permutation(flatten(iblocks))
|
||||
P = PermutationMatrix(p)
|
||||
|
||||
blocks = []
|
||||
for b in iblocks:
|
||||
blocks.append(M[b, b])
|
||||
B = BlockDiagMatrix(*blocks)
|
||||
return P, B
|
||||
|
||||
|
||||
def _strongly_connected_components_decomposition(M, lower=True):
|
||||
"""Decomposes a square matrix into block triangular form only
|
||||
using the permutations.
|
||||
|
||||
Explanation
|
||||
===========
|
||||
|
||||
The decomposition is in a form of $A = P^{-1} B P$ where $P$ is a
|
||||
permutation matrix and $B$ is a block diagonal matrix.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
lower : bool
|
||||
Makes $B$ lower block triangular when ``True``.
|
||||
Otherwise, makes $B$ upper block triangular.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
P, B : PermutationMatrix, BlockMatrix
|
||||
*P* is a permutation matrix for the similarity transform
|
||||
as in the explanation. And *B* is the block triangular matrix of
|
||||
the result of the permutation.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, pprint
|
||||
>>> A = Matrix([
|
||||
... [44, 0, 0, 0, 43, 0, 45, 0, 0],
|
||||
... [0, 66, 62, 61, 0, 68, 0, 60, 67],
|
||||
... [0, 0, 22, 21, 0, 0, 0, 20, 0],
|
||||
... [0, 0, 12, 11, 0, 0, 0, 10, 0],
|
||||
... [34, 0, 0, 0, 33, 0, 35, 0, 0],
|
||||
... [0, 86, 82, 81, 0, 88, 0, 80, 87],
|
||||
... [54, 0, 0, 0, 53, 0, 55, 0, 0],
|
||||
... [0, 0, 2, 1, 0, 0, 0, 0, 0],
|
||||
... [0, 76, 72, 71, 0, 78, 0, 70, 77]])
|
||||
|
||||
A lower block triangular decomposition:
|
||||
|
||||
>>> P, B = A.strongly_connected_components_decomposition()
|
||||
>>> pprint(P)
|
||||
PermutationMatrix((8)(1 4 3 2 6)(5 7))
|
||||
>>> pprint(B)
|
||||
[[44 43 45] [0 0 0] [0 0 0] ]
|
||||
[[ ] [ ] [ ] ]
|
||||
[[34 33 35] [0 0 0] [0 0 0] ]
|
||||
[[ ] [ ] [ ] ]
|
||||
[[54 53 55] [0 0 0] [0 0 0] ]
|
||||
[ ]
|
||||
[ [0 0 0] [22 21 20] [0 0 0] ]
|
||||
[ [ ] [ ] [ ] ]
|
||||
[ [0 0 0] [12 11 10] [0 0 0] ]
|
||||
[ [ ] [ ] [ ] ]
|
||||
[ [0 0 0] [2 1 0 ] [0 0 0] ]
|
||||
[ ]
|
||||
[ [0 0 0] [62 61 60] [66 68 67]]
|
||||
[ [ ] [ ] [ ]]
|
||||
[ [0 0 0] [82 81 80] [86 88 87]]
|
||||
[ [ ] [ ] [ ]]
|
||||
[ [0 0 0] [72 71 70] [76 78 77]]
|
||||
|
||||
>>> P = P.as_explicit()
|
||||
>>> B = B.as_explicit()
|
||||
>>> P.T * B * P == A
|
||||
True
|
||||
|
||||
An upper block triangular decomposition:
|
||||
|
||||
>>> P, B = A.strongly_connected_components_decomposition(lower=False)
|
||||
>>> pprint(P)
|
||||
PermutationMatrix((0 1 5 7 4 3 2 8 6))
|
||||
>>> pprint(B)
|
||||
[[66 68 67] [62 61 60] [0 0 0] ]
|
||||
[[ ] [ ] [ ] ]
|
||||
[[86 88 87] [82 81 80] [0 0 0] ]
|
||||
[[ ] [ ] [ ] ]
|
||||
[[76 78 77] [72 71 70] [0 0 0] ]
|
||||
[ ]
|
||||
[ [0 0 0] [22 21 20] [0 0 0] ]
|
||||
[ [ ] [ ] [ ] ]
|
||||
[ [0 0 0] [12 11 10] [0 0 0] ]
|
||||
[ [ ] [ ] [ ] ]
|
||||
[ [0 0 0] [2 1 0 ] [0 0 0] ]
|
||||
[ ]
|
||||
[ [0 0 0] [0 0 0] [44 43 45]]
|
||||
[ [ ] [ ] [ ]]
|
||||
[ [0 0 0] [0 0 0] [34 33 35]]
|
||||
[ [ ] [ ] [ ]]
|
||||
[ [0 0 0] [0 0 0] [54 53 55]]
|
||||
|
||||
>>> P = P.as_explicit()
|
||||
>>> B = B.as_explicit()
|
||||
>>> P.T * B * P == A
|
||||
True
|
||||
"""
|
||||
from sympy.combinatorics.permutations import Permutation
|
||||
from sympy.matrices.expressions.blockmatrix import BlockMatrix
|
||||
from sympy.matrices.expressions.permutation import PermutationMatrix
|
||||
|
||||
iblocks = M.strongly_connected_components()
|
||||
if not lower:
|
||||
iblocks = list(reversed(iblocks))
|
||||
|
||||
p = Permutation(flatten(iblocks))
|
||||
P = PermutationMatrix(p)
|
||||
|
||||
rows = []
|
||||
for a in iblocks:
|
||||
cols = []
|
||||
for b in iblocks:
|
||||
cols.append(M[a, b])
|
||||
rows.append(cols)
|
||||
B = BlockMatrix(rows)
|
||||
return P, B
|
||||
@@ -0,0 +1,196 @@
|
||||
from mpmath.matrices.matrices import _matrix
|
||||
|
||||
from sympy.core import Basic, Dict, Tuple
|
||||
from sympy.core.numbers import Integer
|
||||
from sympy.core.cache import cacheit
|
||||
from sympy.core.sympify import _sympy_converter as sympify_converter, _sympify
|
||||
from sympy.matrices.dense import DenseMatrix
|
||||
from sympy.matrices.expressions import MatrixExpr
|
||||
from sympy.matrices.matrixbase import MatrixBase
|
||||
from sympy.matrices.repmatrix import RepMatrix
|
||||
from sympy.matrices.sparse import SparseRepMatrix
|
||||
from sympy.multipledispatch import dispatch
|
||||
|
||||
|
||||
def sympify_matrix(arg):
|
||||
return arg.as_immutable()
|
||||
|
||||
|
||||
sympify_converter[MatrixBase] = sympify_matrix
|
||||
|
||||
|
||||
def sympify_mpmath_matrix(arg):
|
||||
mat = [_sympify(x) for x in arg]
|
||||
return ImmutableDenseMatrix(arg.rows, arg.cols, mat)
|
||||
|
||||
|
||||
sympify_converter[_matrix] = sympify_mpmath_matrix
|
||||
|
||||
|
||||
class ImmutableRepMatrix(RepMatrix, MatrixExpr): # type: ignore
|
||||
"""Immutable matrix based on RepMatrix
|
||||
|
||||
Uses DomainMAtrix as the internal representation.
|
||||
"""
|
||||
|
||||
#
|
||||
# This is a subclass of RepMatrix that adds/overrides some methods to make
|
||||
# the instances Basic and immutable. ImmutableRepMatrix is a superclass for
|
||||
# both ImmutableDenseMatrix and ImmutableSparseMatrix.
|
||||
#
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
return cls._new(*args, **kwargs)
|
||||
|
||||
__hash__ = MatrixExpr.__hash__
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
@property
|
||||
def cols(self):
|
||||
return self._cols
|
||||
|
||||
@property
|
||||
def rows(self):
|
||||
return self._rows
|
||||
|
||||
@property
|
||||
def shape(self):
|
||||
return self._rows, self._cols
|
||||
|
||||
def as_immutable(self):
|
||||
return self
|
||||
|
||||
def _entry(self, i, j, **kwargs):
|
||||
return self[i, j]
|
||||
|
||||
def __setitem__(self, *args):
|
||||
raise TypeError("Cannot set values of {}".format(self.__class__))
|
||||
|
||||
def is_diagonalizable(self, reals_only=False, **kwargs):
|
||||
return super().is_diagonalizable(
|
||||
reals_only=reals_only, **kwargs)
|
||||
|
||||
is_diagonalizable.__doc__ = SparseRepMatrix.is_diagonalizable.__doc__
|
||||
is_diagonalizable = cacheit(is_diagonalizable)
|
||||
|
||||
def analytic_func(self, f, x):
|
||||
return self.as_mutable().analytic_func(f, x).as_immutable()
|
||||
|
||||
|
||||
class ImmutableDenseMatrix(DenseMatrix, ImmutableRepMatrix): # type: ignore
|
||||
"""Create an immutable version of a matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import eye, ImmutableMatrix
|
||||
>>> ImmutableMatrix(eye(3))
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
>>> _[0, 0] = 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Cannot set values of ImmutableDenseMatrix
|
||||
"""
|
||||
|
||||
# MatrixExpr is set as NotIterable, but we want explicit matrices to be
|
||||
# iterable
|
||||
_iterable = True
|
||||
_class_priority = 8
|
||||
_op_priority = 10.001
|
||||
|
||||
@classmethod
|
||||
def _new(cls, *args, **kwargs):
|
||||
if len(args) == 1 and isinstance(args[0], ImmutableDenseMatrix):
|
||||
return args[0]
|
||||
if kwargs.get('copy', True) is False:
|
||||
if len(args) != 3:
|
||||
raise TypeError("'copy=False' requires a matrix be initialized as rows,cols,[list]")
|
||||
rows, cols, flat_list = args
|
||||
else:
|
||||
rows, cols, flat_list = cls._handle_creation_inputs(*args, **kwargs)
|
||||
flat_list = list(flat_list) # create a shallow copy
|
||||
|
||||
rep = cls._flat_list_to_DomainMatrix(rows, cols, flat_list)
|
||||
|
||||
return cls._fromrep(rep)
|
||||
|
||||
@classmethod
|
||||
def _fromrep(cls, rep):
|
||||
rows, cols = rep.shape
|
||||
flat_list = rep.to_sympy().to_list_flat()
|
||||
obj = Basic.__new__(cls,
|
||||
Integer(rows),
|
||||
Integer(cols),
|
||||
Tuple(*flat_list, sympify=False))
|
||||
obj._rows = rows
|
||||
obj._cols = cols
|
||||
obj._rep = rep
|
||||
return obj
|
||||
|
||||
|
||||
# make sure ImmutableDenseMatrix is aliased as ImmutableMatrix
|
||||
ImmutableMatrix = ImmutableDenseMatrix
|
||||
|
||||
|
||||
class ImmutableSparseMatrix(SparseRepMatrix, ImmutableRepMatrix): # type:ignore
|
||||
"""Create an immutable version of a sparse matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import eye, ImmutableSparseMatrix
|
||||
>>> ImmutableSparseMatrix(1, 1, {})
|
||||
Matrix([[0]])
|
||||
>>> ImmutableSparseMatrix(eye(3))
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
>>> _[0, 0] = 42
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
TypeError: Cannot set values of ImmutableSparseMatrix
|
||||
>>> _.shape
|
||||
(3, 3)
|
||||
"""
|
||||
is_Matrix = True
|
||||
_class_priority = 9
|
||||
|
||||
@classmethod
|
||||
def _new(cls, *args, **kwargs):
|
||||
rows, cols, smat = cls._handle_creation_inputs(*args, **kwargs)
|
||||
|
||||
rep = cls._smat_to_DomainMatrix(rows, cols, smat)
|
||||
|
||||
return cls._fromrep(rep)
|
||||
|
||||
@classmethod
|
||||
def _fromrep(cls, rep):
|
||||
rows, cols = rep.shape
|
||||
smat = rep.to_sympy().to_dok()
|
||||
obj = Basic.__new__(cls, Integer(rows), Integer(cols), Dict(smat))
|
||||
obj._rows = rows
|
||||
obj._cols = cols
|
||||
obj._rep = rep
|
||||
return obj
|
||||
|
||||
|
||||
@dispatch(ImmutableDenseMatrix, ImmutableDenseMatrix)
|
||||
def _eval_is_eq(lhs, rhs): # noqa:F811
|
||||
"""Helper method for Equality with matrices.sympy.
|
||||
|
||||
Relational automatically converts matrices to ImmutableDenseMatrix
|
||||
instances, so this method only applies here. Returns True if the
|
||||
matrices are definitively the same, False if they are definitively
|
||||
different, and None if undetermined (e.g. if they contain Symbols).
|
||||
Returning None triggers default handling of Equalities.
|
||||
|
||||
"""
|
||||
if lhs.shape != rhs.shape:
|
||||
return False
|
||||
return (lhs - rhs).is_zero_matrix
|
||||
@@ -0,0 +1,524 @@
|
||||
from sympy.polys.matrices.exceptions import DMNonInvertibleMatrixError
|
||||
from sympy.polys.domains import EX
|
||||
|
||||
from .exceptions import MatrixError, NonSquareMatrixError, NonInvertibleMatrixError
|
||||
from .utilities import _iszero
|
||||
|
||||
|
||||
def _pinv_full_rank(M):
|
||||
"""Subroutine for full row or column rank matrices.
|
||||
|
||||
For full row rank matrices, inverse of ``A * A.H`` Exists.
|
||||
For full column rank matrices, inverse of ``A.H * A`` Exists.
|
||||
|
||||
This routine can apply for both cases by checking the shape
|
||||
and have small decision.
|
||||
"""
|
||||
|
||||
if M.is_zero_matrix:
|
||||
return M.H
|
||||
|
||||
if M.rows >= M.cols:
|
||||
return M.H.multiply(M).inv().multiply(M.H)
|
||||
else:
|
||||
return M.H.multiply(M.multiply(M.H).inv())
|
||||
|
||||
def _pinv_rank_decomposition(M):
|
||||
"""Subroutine for rank decomposition
|
||||
|
||||
With rank decompositions, `A` can be decomposed into two full-
|
||||
rank matrices, and each matrix can take pseudoinverse
|
||||
individually.
|
||||
"""
|
||||
|
||||
if M.is_zero_matrix:
|
||||
return M.H
|
||||
|
||||
B, C = M.rank_decomposition()
|
||||
|
||||
Bp = _pinv_full_rank(B)
|
||||
Cp = _pinv_full_rank(C)
|
||||
|
||||
return Cp.multiply(Bp)
|
||||
|
||||
def _pinv_diagonalization(M):
|
||||
"""Subroutine using diagonalization
|
||||
|
||||
This routine can sometimes fail if SymPy's eigenvalue
|
||||
computation is not reliable.
|
||||
"""
|
||||
|
||||
if M.is_zero_matrix:
|
||||
return M.H
|
||||
|
||||
A = M
|
||||
AH = M.H
|
||||
|
||||
try:
|
||||
if M.rows >= M.cols:
|
||||
P, D = AH.multiply(A).diagonalize(normalize=True)
|
||||
D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x)
|
||||
|
||||
return P.multiply(D_pinv).multiply(P.H).multiply(AH)
|
||||
|
||||
else:
|
||||
P, D = A.multiply(AH).diagonalize(
|
||||
normalize=True)
|
||||
D_pinv = D.applyfunc(lambda x: 0 if _iszero(x) else 1 / x)
|
||||
|
||||
return AH.multiply(P).multiply(D_pinv).multiply(P.H)
|
||||
|
||||
except MatrixError:
|
||||
raise NotImplementedError(
|
||||
'pinv for rank-deficient matrices where '
|
||||
'diagonalization of A.H*A fails is not supported yet.')
|
||||
|
||||
def _pinv(M, method='RD'):
|
||||
"""Calculate the Moore-Penrose pseudoinverse of the matrix.
|
||||
|
||||
The Moore-Penrose pseudoinverse exists and is unique for any matrix.
|
||||
If the matrix is invertible, the pseudoinverse is the same as the
|
||||
inverse.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
method : String, optional
|
||||
Specifies the method for computing the pseudoinverse.
|
||||
|
||||
If ``'RD'``, Rank-Decomposition will be used.
|
||||
|
||||
If ``'ED'``, Diagonalization will be used.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Computing pseudoinverse by rank decomposition :
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([[1, 2, 3], [4, 5, 6]])
|
||||
>>> A.pinv()
|
||||
Matrix([
|
||||
[-17/18, 4/9],
|
||||
[ -1/9, 1/9],
|
||||
[ 13/18, -2/9]])
|
||||
|
||||
Computing pseudoinverse by diagonalization :
|
||||
|
||||
>>> B = A.pinv(method='ED')
|
||||
>>> B.simplify()
|
||||
>>> B
|
||||
Matrix([
|
||||
[-17/18, 4/9],
|
||||
[ -1/9, 1/9],
|
||||
[ 13/18, -2/9]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
pinv_solve
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse
|
||||
|
||||
"""
|
||||
|
||||
# Trivial case: pseudoinverse of all-zero matrix is its transpose.
|
||||
if M.is_zero_matrix:
|
||||
return M.H
|
||||
|
||||
if method == 'RD':
|
||||
return _pinv_rank_decomposition(M)
|
||||
elif method == 'ED':
|
||||
return _pinv_diagonalization(M)
|
||||
else:
|
||||
raise ValueError('invalid pinv method %s' % repr(method))
|
||||
|
||||
|
||||
def _verify_invertible(M, iszerofunc=_iszero):
|
||||
"""Initial check to see if a matrix is invertible. Raises or returns
|
||||
determinant for use in _inv_ADJ."""
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("A Matrix must be square to invert.")
|
||||
|
||||
d = M.det(method='berkowitz')
|
||||
zero = d.equals(0)
|
||||
|
||||
if zero is None: # if equals() can't decide, will rref be able to?
|
||||
ok = M.rref(simplify=True)[0]
|
||||
zero = any(iszerofunc(ok[j, j]) for j in range(ok.rows))
|
||||
|
||||
if zero:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
return d
|
||||
|
||||
def _inv_ADJ(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using the adjugate matrix and a determinant.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_GE
|
||||
inverse_LU
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
"""
|
||||
|
||||
d = _verify_invertible(M, iszerofunc=iszerofunc)
|
||||
|
||||
return M.adjugate() / d
|
||||
|
||||
def _inv_GE(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using Gaussian elimination.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_LU
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
"""
|
||||
|
||||
from .dense import Matrix
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("A Matrix must be square to invert.")
|
||||
|
||||
big = Matrix.hstack(M.as_mutable(), Matrix.eye(M.rows))
|
||||
red = big.rref(iszerofunc=iszerofunc, simplify=True)[0]
|
||||
|
||||
if any(iszerofunc(red[j, j]) for j in range(red.rows)):
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
return M._new(red[:, big.rows:])
|
||||
|
||||
def _inv_LU(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using LU decomposition.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
"""
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("A Matrix must be square to invert.")
|
||||
if M.free_symbols:
|
||||
_verify_invertible(M, iszerofunc=iszerofunc)
|
||||
|
||||
return M.LUsolve(M.eye(M.rows), iszerofunc=_iszero)
|
||||
|
||||
def _inv_CH(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using cholesky decomposition.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_LU
|
||||
inverse_LDL
|
||||
"""
|
||||
|
||||
_verify_invertible(M, iszerofunc=iszerofunc)
|
||||
|
||||
return M.cholesky_solve(M.eye(M.rows))
|
||||
|
||||
def _inv_LDL(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using LDL decomposition.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_LU
|
||||
inverse_CH
|
||||
"""
|
||||
|
||||
_verify_invertible(M, iszerofunc=iszerofunc)
|
||||
|
||||
return M.LDLsolve(M.eye(M.rows))
|
||||
|
||||
def _inv_QR(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using QR decomposition.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
"""
|
||||
|
||||
_verify_invertible(M, iszerofunc=iszerofunc)
|
||||
|
||||
return M.QRsolve(M.eye(M.rows))
|
||||
|
||||
def _try_DM(M, use_EX=False):
|
||||
"""Try to convert a matrix to a ``DomainMatrix``."""
|
||||
dM = M.to_DM()
|
||||
K = dM.domain
|
||||
|
||||
# Return DomainMatrix if a domain is found. Only use EX if use_EX=True.
|
||||
if not use_EX and K.is_EXRAW:
|
||||
return None
|
||||
elif K.is_EXRAW:
|
||||
return dM.convert_to(EX)
|
||||
else:
|
||||
return dM
|
||||
|
||||
|
||||
def _use_exact_domain(dom):
|
||||
"""Check whether to convert to an exact domain."""
|
||||
# DomainMatrix can handle RR and CC with partial pivoting. Other inexact
|
||||
# domains like RR[a,b,...] can only be handled by converting to an exact
|
||||
# domain like QQ[a,b,...]
|
||||
if dom.is_RR or dom.is_CC:
|
||||
return False
|
||||
else:
|
||||
return not dom.is_Exact
|
||||
|
||||
|
||||
def _inv_DM(dM, cancel=True):
|
||||
"""Calculates the inverse using ``DomainMatrix``.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
sympy.polys.matrices.domainmatrix.DomainMatrix.inv
|
||||
"""
|
||||
m, n = dM.shape
|
||||
dom = dM.domain
|
||||
|
||||
if m != n:
|
||||
raise NonSquareMatrixError("A Matrix must be square to invert.")
|
||||
|
||||
# Convert RR[a,b,...] to QQ[a,b,...]
|
||||
use_exact = _use_exact_domain(dom)
|
||||
|
||||
if use_exact:
|
||||
dom_exact = dom.get_exact()
|
||||
dM = dM.convert_to(dom_exact)
|
||||
|
||||
try:
|
||||
dMi, den = dM.inv_den()
|
||||
except DMNonInvertibleMatrixError:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
if use_exact:
|
||||
dMi = dMi.convert_to(dom)
|
||||
den = dom.convert_from(den, dom_exact)
|
||||
|
||||
if cancel:
|
||||
# Convert to field and cancel with the denominator.
|
||||
if not dMi.domain.is_Field:
|
||||
dMi = dMi.to_field()
|
||||
Mi = (dMi / den).to_Matrix()
|
||||
else:
|
||||
# Convert to Matrix and divide without cancelling
|
||||
Mi = dMi.to_Matrix() / dMi.domain.to_sympy(den)
|
||||
|
||||
return Mi
|
||||
|
||||
def _inv_block(M, iszerofunc=_iszero):
|
||||
"""Calculates the inverse using BLOCKWISE inversion.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inv
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
"""
|
||||
from sympy.matrices.expressions.blockmatrix import BlockMatrix
|
||||
i = M.shape[0]
|
||||
if i <= 20 :
|
||||
return M.inv(method="LU", iszerofunc=_iszero)
|
||||
A = M[:i // 2, :i //2]
|
||||
B = M[:i // 2, i // 2:]
|
||||
C = M[i // 2:, :i // 2]
|
||||
D = M[i // 2:, i // 2:]
|
||||
try:
|
||||
D_inv = _inv_block(D)
|
||||
except NonInvertibleMatrixError:
|
||||
return M.inv(method="LU", iszerofunc=_iszero)
|
||||
B_D_i = B*D_inv
|
||||
BDC = B_D_i*C
|
||||
A_n = A - BDC
|
||||
try:
|
||||
A_n = _inv_block(A_n)
|
||||
except NonInvertibleMatrixError:
|
||||
return M.inv(method="LU", iszerofunc=_iszero)
|
||||
B_n = -A_n*B_D_i
|
||||
dc = D_inv*C
|
||||
C_n = -dc*A_n
|
||||
D_n = D_inv + dc*-B_n
|
||||
nn = BlockMatrix([[A_n, B_n], [C_n, D_n]]).as_explicit()
|
||||
return nn
|
||||
|
||||
def _inv(M, method=None, iszerofunc=_iszero, try_block_diag=False):
|
||||
"""
|
||||
Return the inverse of a matrix using the method indicated. The default
|
||||
is DM if a suitable domain is found or otherwise GE for dense matrices
|
||||
LDL for sparse matrices.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
method : ('DM', 'DMNC', 'GE', 'LU', 'ADJ', 'CH', 'LDL', 'QR')
|
||||
|
||||
iszerofunc : function, optional
|
||||
Zero-testing function to use.
|
||||
|
||||
try_block_diag : bool, optional
|
||||
If True then will try to form block diagonal matrices using the
|
||||
method get_diag_blocks(), invert these individually, and then
|
||||
reconstruct the full inverse matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import SparseMatrix, Matrix
|
||||
>>> A = SparseMatrix([
|
||||
... [ 2, -1, 0],
|
||||
... [-1, 2, -1],
|
||||
... [ 0, 0, 2]])
|
||||
>>> A.inv('CH')
|
||||
Matrix([
|
||||
[2/3, 1/3, 1/6],
|
||||
[1/3, 2/3, 1/3],
|
||||
[ 0, 0, 1/2]])
|
||||
>>> A.inv(method='LDL') # use of 'method=' is optional
|
||||
Matrix([
|
||||
[2/3, 1/3, 1/6],
|
||||
[1/3, 2/3, 1/3],
|
||||
[ 0, 0, 1/2]])
|
||||
>>> A * _
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
>>> A = Matrix(A)
|
||||
>>> A.inv('CH')
|
||||
Matrix([
|
||||
[2/3, 1/3, 1/6],
|
||||
[1/3, 2/3, 1/3],
|
||||
[ 0, 0, 1/2]])
|
||||
>>> A.inv('ADJ') == A.inv('GE') == A.inv('LU') == A.inv('CH') == A.inv('LDL') == A.inv('QR')
|
||||
True
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
According to the ``method`` keyword, it calls the appropriate method:
|
||||
|
||||
DM .... Use DomainMatrix ``inv_den`` method
|
||||
DMNC .... Use DomainMatrix ``inv_den`` method without cancellation
|
||||
GE .... inverse_GE(); default for dense matrices
|
||||
LU .... inverse_LU()
|
||||
ADJ ... inverse_ADJ()
|
||||
CH ... inverse_CH()
|
||||
LDL ... inverse_LDL(); default for sparse matrices
|
||||
QR ... inverse_QR()
|
||||
|
||||
Note, the GE and LU methods may require the matrix to be simplified
|
||||
before it is inverted in order to properly detect zeros during
|
||||
pivoting. In difficult cases a custom zero detection function can
|
||||
be provided by setting the ``iszerofunc`` argument to a function that
|
||||
should return True if its argument is zero. The ADJ routine computes
|
||||
the determinant and uses that to detect singular matrices in addition
|
||||
to testing for zeros on the diagonal.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
inverse_ADJ
|
||||
inverse_GE
|
||||
inverse_LU
|
||||
inverse_CH
|
||||
inverse_LDL
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError
|
||||
If the determinant of the matrix is zero.
|
||||
"""
|
||||
|
||||
from sympy.matrices import diag, SparseMatrix
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("A Matrix must be square to invert.")
|
||||
|
||||
if try_block_diag:
|
||||
blocks = M.get_diag_blocks()
|
||||
r = []
|
||||
|
||||
for block in blocks:
|
||||
r.append(block.inv(method=method, iszerofunc=iszerofunc))
|
||||
|
||||
return diag(*r)
|
||||
|
||||
# Default: Use DomainMatrix if the domain is not EX.
|
||||
# If DM is requested explicitly then use it even if the domain is EX.
|
||||
if method is None and iszerofunc is _iszero:
|
||||
dM = _try_DM(M, use_EX=False)
|
||||
if dM is not None:
|
||||
method = 'DM'
|
||||
elif method in ("DM", "DMNC"):
|
||||
dM = _try_DM(M, use_EX=True)
|
||||
|
||||
# A suitable domain was not found, fall back to GE for dense matrices
|
||||
# and LDL for sparse matrices.
|
||||
if method is None:
|
||||
if isinstance(M, SparseMatrix):
|
||||
method = 'LDL'
|
||||
else:
|
||||
method = 'GE'
|
||||
|
||||
if method == "DM":
|
||||
rv = _inv_DM(dM)
|
||||
elif method == "DMNC":
|
||||
rv = _inv_DM(dM, cancel=False)
|
||||
elif method == "GE":
|
||||
rv = M.inverse_GE(iszerofunc=iszerofunc)
|
||||
elif method == "LU":
|
||||
rv = M.inverse_LU(iszerofunc=iszerofunc)
|
||||
elif method == "ADJ":
|
||||
rv = M.inverse_ADJ(iszerofunc=iszerofunc)
|
||||
elif method == "CH":
|
||||
rv = M.inverse_CH(iszerofunc=iszerofunc)
|
||||
elif method == "LDL":
|
||||
rv = M.inverse_LDL(iszerofunc=iszerofunc)
|
||||
elif method == "QR":
|
||||
rv = M.inverse_QR(iszerofunc=iszerofunc)
|
||||
elif method == "BLOCK":
|
||||
rv = M.inverse_BLOCK(iszerofunc=iszerofunc)
|
||||
else:
|
||||
raise ValueError("Inversion method unrecognized")
|
||||
|
||||
return M._new(rv)
|
||||
@@ -0,0 +1,97 @@
|
||||
# sympy.matrices.kind
|
||||
|
||||
from sympy.core.kind import Kind, _NumberKind, NumberKind
|
||||
from sympy.core.mul import Mul
|
||||
|
||||
|
||||
class MatrixKind(Kind):
|
||||
"""
|
||||
Kind for all matrices in SymPy.
|
||||
|
||||
Basic class for this kind is ``MatrixBase`` and ``MatrixExpr``,
|
||||
but any expression representing the matrix can have this.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
element_kind : Kind
|
||||
Kind of the element. Default is
|
||||
:class:`sympy.core.kind.NumberKind`,
|
||||
which means that the matrix contains only numbers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
Any instance of matrix class has kind ``MatrixKind``:
|
||||
|
||||
>>> from sympy import MatrixSymbol
|
||||
>>> A = MatrixSymbol('A', 2, 2)
|
||||
>>> A.kind
|
||||
MatrixKind(NumberKind)
|
||||
|
||||
An expression representing a matrix may not be an instance of
|
||||
the Matrix class, but it will have kind ``MatrixKind``:
|
||||
|
||||
>>> from sympy import MatrixExpr, Integral
|
||||
>>> from sympy.abc import x
|
||||
>>> intM = Integral(A, x)
|
||||
>>> isinstance(intM, MatrixExpr)
|
||||
False
|
||||
>>> intM.kind
|
||||
MatrixKind(NumberKind)
|
||||
|
||||
Use ``isinstance()`` to check for ``MatrixKind`` without specifying the
|
||||
element kind. Use ``is`` to check the kind including the element kind:
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.core import NumberKind
|
||||
>>> from sympy.matrices import MatrixKind
|
||||
>>> M = Matrix([1, 2])
|
||||
>>> isinstance(M.kind, MatrixKind)
|
||||
True
|
||||
>>> M.kind is MatrixKind(NumberKind)
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.core.kind.NumberKind
|
||||
sympy.core.kind.UndefinedKind
|
||||
sympy.core.containers.TupleKind
|
||||
sympy.sets.sets.SetKind
|
||||
|
||||
"""
|
||||
def __new__(cls, element_kind=NumberKind):
|
||||
obj = super().__new__(cls, element_kind)
|
||||
obj.element_kind = element_kind
|
||||
return obj
|
||||
|
||||
def __repr__(self):
|
||||
return "MatrixKind(%s)" % self.element_kind
|
||||
|
||||
|
||||
@Mul._kind_dispatcher.register(_NumberKind, MatrixKind)
|
||||
def num_mat_mul(k1, k2):
|
||||
"""
|
||||
Return MatrixKind. The element kind is selected by recursive dispatching.
|
||||
Do not need to dispatch in reversed order because KindDispatcher
|
||||
searches for this automatically.
|
||||
"""
|
||||
# Deal with Mul._kind_dispatcher's commutativity
|
||||
# XXX: this function is called with either k1 or k2 as MatrixKind because
|
||||
# the Mul kind dispatcher is commutative. Maybe it shouldn't be. Need to
|
||||
# swap the args here because NumberKind does not have an element_kind
|
||||
# attribute.
|
||||
if not isinstance(k2, MatrixKind):
|
||||
k1, k2 = k2, k1
|
||||
elemk = Mul._kind_dispatcher(k1, k2.element_kind)
|
||||
return MatrixKind(elemk)
|
||||
|
||||
|
||||
@Mul._kind_dispatcher.register(MatrixKind, MatrixKind)
|
||||
def mat_mat_mul(k1, k2):
|
||||
"""
|
||||
Return MatrixKind. The element kind is selected by recursive dispatching.
|
||||
"""
|
||||
elemk = Mul._kind_dispatcher(k1.element_kind, k2.element_kind)
|
||||
return MatrixKind(elemk)
|
||||
@@ -0,0 +1,687 @@
|
||||
#
|
||||
# A module consisting of deprecated matrix classes. New code should not be
|
||||
# added here.
|
||||
#
|
||||
from sympy.core.basic import Basic
|
||||
from sympy.core.symbol import Dummy
|
||||
|
||||
from .common import MatrixCommon
|
||||
|
||||
from .exceptions import NonSquareMatrixError
|
||||
|
||||
from .utilities import _iszero, _is_zero_after_expand_mul, _simplify
|
||||
|
||||
from .determinant import (
|
||||
_find_reasonable_pivot, _find_reasonable_pivot_naive,
|
||||
_adjugate, _charpoly, _cofactor, _cofactor_matrix, _per,
|
||||
_det, _det_bareiss, _det_berkowitz, _det_bird, _det_laplace, _det_LU,
|
||||
_minor, _minor_submatrix)
|
||||
|
||||
from .reductions import _is_echelon, _echelon_form, _rank, _rref
|
||||
from .subspaces import _columnspace, _nullspace, _rowspace, _orthogonalize
|
||||
|
||||
from .eigen import (
|
||||
_eigenvals, _eigenvects,
|
||||
_bidiagonalize, _bidiagonal_decomposition,
|
||||
_is_diagonalizable, _diagonalize,
|
||||
_is_positive_definite, _is_positive_semidefinite,
|
||||
_is_negative_definite, _is_negative_semidefinite, _is_indefinite,
|
||||
_jordan_form, _left_eigenvects, _singular_values)
|
||||
|
||||
|
||||
# This class was previously defined in this module, but was moved to
|
||||
# sympy.matrices.matrixbase. We import it here for backwards compatibility in
|
||||
# case someone was importing it from here.
|
||||
from .matrixbase import MatrixBase
|
||||
|
||||
|
||||
__doctest_requires__ = {
|
||||
('MatrixEigen.is_indefinite',
|
||||
'MatrixEigen.is_negative_definite',
|
||||
'MatrixEigen.is_negative_semidefinite',
|
||||
'MatrixEigen.is_positive_definite',
|
||||
'MatrixEigen.is_positive_semidefinite'): ['matplotlib'],
|
||||
}
|
||||
|
||||
|
||||
class MatrixDeterminant(MatrixCommon):
|
||||
"""Provides basic matrix determinant operations. Should not be instantiated
|
||||
directly. See ``determinant.py`` for their implementations."""
|
||||
|
||||
def _eval_det_bareiss(self, iszerofunc=_is_zero_after_expand_mul):
|
||||
return _det_bareiss(self, iszerofunc=iszerofunc)
|
||||
|
||||
def _eval_det_berkowitz(self):
|
||||
return _det_berkowitz(self)
|
||||
|
||||
def _eval_det_lu(self, iszerofunc=_iszero, simpfunc=None):
|
||||
return _det_LU(self, iszerofunc=iszerofunc, simpfunc=simpfunc)
|
||||
|
||||
def _eval_det_bird(self):
|
||||
return _det_bird(self)
|
||||
|
||||
def _eval_det_laplace(self):
|
||||
return _det_laplace(self)
|
||||
|
||||
def _eval_determinant(self): # for expressions.determinant.Determinant
|
||||
return _det(self)
|
||||
|
||||
def adjugate(self, method="berkowitz"):
|
||||
return _adjugate(self, method=method)
|
||||
|
||||
def charpoly(self, x='lambda', simplify=_simplify):
|
||||
return _charpoly(self, x=x, simplify=simplify)
|
||||
|
||||
def cofactor(self, i, j, method="berkowitz"):
|
||||
return _cofactor(self, i, j, method=method)
|
||||
|
||||
def cofactor_matrix(self, method="berkowitz"):
|
||||
return _cofactor_matrix(self, method=method)
|
||||
|
||||
def det(self, method="bareiss", iszerofunc=None):
|
||||
return _det(self, method=method, iszerofunc=iszerofunc)
|
||||
|
||||
def per(self):
|
||||
return _per(self)
|
||||
|
||||
def minor(self, i, j, method="berkowitz"):
|
||||
return _minor(self, i, j, method=method)
|
||||
|
||||
def minor_submatrix(self, i, j):
|
||||
return _minor_submatrix(self, i, j)
|
||||
|
||||
_find_reasonable_pivot.__doc__ = _find_reasonable_pivot.__doc__
|
||||
_find_reasonable_pivot_naive.__doc__ = _find_reasonable_pivot_naive.__doc__
|
||||
_eval_det_bareiss.__doc__ = _det_bareiss.__doc__
|
||||
_eval_det_berkowitz.__doc__ = _det_berkowitz.__doc__
|
||||
_eval_det_bird.__doc__ = _det_bird.__doc__
|
||||
_eval_det_laplace.__doc__ = _det_laplace.__doc__
|
||||
_eval_det_lu.__doc__ = _det_LU.__doc__
|
||||
_eval_determinant.__doc__ = _det.__doc__
|
||||
adjugate.__doc__ = _adjugate.__doc__
|
||||
charpoly.__doc__ = _charpoly.__doc__
|
||||
cofactor.__doc__ = _cofactor.__doc__
|
||||
cofactor_matrix.__doc__ = _cofactor_matrix.__doc__
|
||||
det.__doc__ = _det.__doc__
|
||||
per.__doc__ = _per.__doc__
|
||||
minor.__doc__ = _minor.__doc__
|
||||
minor_submatrix.__doc__ = _minor_submatrix.__doc__
|
||||
|
||||
|
||||
class MatrixReductions(MatrixDeterminant):
|
||||
"""Provides basic matrix row/column operations. Should not be instantiated
|
||||
directly. See ``reductions.py`` for some of their implementations."""
|
||||
|
||||
def echelon_form(self, iszerofunc=_iszero, simplify=False, with_pivots=False):
|
||||
return _echelon_form(self, iszerofunc=iszerofunc, simplify=simplify,
|
||||
with_pivots=with_pivots)
|
||||
|
||||
@property
|
||||
def is_echelon(self):
|
||||
return _is_echelon(self)
|
||||
|
||||
def rank(self, iszerofunc=_iszero, simplify=False):
|
||||
return _rank(self, iszerofunc=iszerofunc, simplify=simplify)
|
||||
|
||||
def rref_rhs(self, rhs):
|
||||
"""Return reduced row-echelon form of matrix, matrix showing
|
||||
rhs after reduction steps. ``rhs`` must have the same number
|
||||
of rows as ``self``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, symbols
|
||||
>>> r1, r2 = symbols('r1 r2')
|
||||
>>> Matrix([[1, 1], [2, 1]]).rref_rhs(Matrix([r1, r2]))
|
||||
(Matrix([
|
||||
[1, 0],
|
||||
[0, 1]]), Matrix([
|
||||
[ -r1 + r2],
|
||||
[2*r1 - r2]]))
|
||||
"""
|
||||
r, _ = _rref(self.hstack(self, self.eye(self.rows), rhs))
|
||||
return r[:, :self.cols], r[:, -rhs.cols:]
|
||||
|
||||
def rref(self, iszerofunc=_iszero, simplify=False, pivots=True,
|
||||
normalize_last=True):
|
||||
return _rref(self, iszerofunc=iszerofunc, simplify=simplify,
|
||||
pivots=pivots, normalize_last=normalize_last)
|
||||
|
||||
echelon_form.__doc__ = _echelon_form.__doc__
|
||||
is_echelon.__doc__ = _is_echelon.__doc__
|
||||
rank.__doc__ = _rank.__doc__
|
||||
rref.__doc__ = _rref.__doc__
|
||||
|
||||
def _normalize_op_args(self, op, col, k, col1, col2, error_str="col"):
|
||||
"""Validate the arguments for a row/column operation. ``error_str``
|
||||
can be one of "row" or "col" depending on the arguments being parsed."""
|
||||
if op not in ["n->kn", "n<->m", "n->n+km"]:
|
||||
raise ValueError("Unknown {} operation '{}'. Valid col operations "
|
||||
"are 'n->kn', 'n<->m', 'n->n+km'".format(error_str, op))
|
||||
|
||||
# define self_col according to error_str
|
||||
self_cols = self.cols if error_str == 'col' else self.rows
|
||||
|
||||
# normalize and validate the arguments
|
||||
if op == "n->kn":
|
||||
col = col if col is not None else col1
|
||||
if col is None or k is None:
|
||||
raise ValueError("For a {0} operation 'n->kn' you must provide the "
|
||||
"kwargs `{0}` and `k`".format(error_str))
|
||||
if not 0 <= col < self_cols:
|
||||
raise ValueError("This matrix does not have a {} '{}'".format(error_str, col))
|
||||
|
||||
elif op == "n<->m":
|
||||
# we need two cols to swap. It does not matter
|
||||
# how they were specified, so gather them together and
|
||||
# remove `None`
|
||||
cols = {col, k, col1, col2}.difference([None])
|
||||
if len(cols) > 2:
|
||||
# maybe the user left `k` by mistake?
|
||||
cols = {col, col1, col2}.difference([None])
|
||||
if len(cols) != 2:
|
||||
raise ValueError("For a {0} operation 'n<->m' you must provide the "
|
||||
"kwargs `{0}1` and `{0}2`".format(error_str))
|
||||
col1, col2 = cols
|
||||
if not 0 <= col1 < self_cols:
|
||||
raise ValueError("This matrix does not have a {} '{}'".format(error_str, col1))
|
||||
if not 0 <= col2 < self_cols:
|
||||
raise ValueError("This matrix does not have a {} '{}'".format(error_str, col2))
|
||||
|
||||
elif op == "n->n+km":
|
||||
col = col1 if col is None else col
|
||||
col2 = col1 if col2 is None else col2
|
||||
if col is None or col2 is None or k is None:
|
||||
raise ValueError("For a {0} operation 'n->n+km' you must provide the "
|
||||
"kwargs `{0}`, `k`, and `{0}2`".format(error_str))
|
||||
if col == col2:
|
||||
raise ValueError("For a {0} operation 'n->n+km' `{0}` and `{0}2` must "
|
||||
"be different.".format(error_str))
|
||||
if not 0 <= col < self_cols:
|
||||
raise ValueError("This matrix does not have a {} '{}'".format(error_str, col))
|
||||
if not 0 <= col2 < self_cols:
|
||||
raise ValueError("This matrix does not have a {} '{}'".format(error_str, col2))
|
||||
|
||||
else:
|
||||
raise ValueError('invalid operation %s' % repr(op))
|
||||
|
||||
return op, col, k, col1, col2
|
||||
|
||||
def _eval_col_op_multiply_col_by_const(self, col, k):
|
||||
def entry(i, j):
|
||||
if j == col:
|
||||
return k * self[i, j]
|
||||
return self[i, j]
|
||||
return self._new(self.rows, self.cols, entry)
|
||||
|
||||
def _eval_col_op_swap(self, col1, col2):
|
||||
def entry(i, j):
|
||||
if j == col1:
|
||||
return self[i, col2]
|
||||
elif j == col2:
|
||||
return self[i, col1]
|
||||
return self[i, j]
|
||||
return self._new(self.rows, self.cols, entry)
|
||||
|
||||
def _eval_col_op_add_multiple_to_other_col(self, col, k, col2):
|
||||
def entry(i, j):
|
||||
if j == col:
|
||||
return self[i, j] + k * self[i, col2]
|
||||
return self[i, j]
|
||||
return self._new(self.rows, self.cols, entry)
|
||||
|
||||
def _eval_row_op_swap(self, row1, row2):
|
||||
def entry(i, j):
|
||||
if i == row1:
|
||||
return self[row2, j]
|
||||
elif i == row2:
|
||||
return self[row1, j]
|
||||
return self[i, j]
|
||||
return self._new(self.rows, self.cols, entry)
|
||||
|
||||
def _eval_row_op_multiply_row_by_const(self, row, k):
|
||||
def entry(i, j):
|
||||
if i == row:
|
||||
return k * self[i, j]
|
||||
return self[i, j]
|
||||
return self._new(self.rows, self.cols, entry)
|
||||
|
||||
def _eval_row_op_add_multiple_to_other_row(self, row, k, row2):
|
||||
def entry(i, j):
|
||||
if i == row:
|
||||
return self[i, j] + k * self[row2, j]
|
||||
return self[i, j]
|
||||
return self._new(self.rows, self.cols, entry)
|
||||
|
||||
def elementary_col_op(self, op="n->kn", col=None, k=None, col1=None, col2=None):
|
||||
"""Performs the elementary column operation `op`.
|
||||
|
||||
`op` may be one of
|
||||
|
||||
* ``"n->kn"`` (column n goes to k*n)
|
||||
* ``"n<->m"`` (swap column n and column m)
|
||||
* ``"n->n+km"`` (column n goes to column n + k*column m)
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
op : string; the elementary row operation
|
||||
col : the column to apply the column operation
|
||||
k : the multiple to apply in the column operation
|
||||
col1 : one column of a column swap
|
||||
col2 : second column of a column swap or column "m" in the column operation
|
||||
"n->n+km"
|
||||
"""
|
||||
|
||||
op, col, k, col1, col2 = self._normalize_op_args(op, col, k, col1, col2, "col")
|
||||
|
||||
# now that we've validated, we're all good to dispatch
|
||||
if op == "n->kn":
|
||||
return self._eval_col_op_multiply_col_by_const(col, k)
|
||||
if op == "n<->m":
|
||||
return self._eval_col_op_swap(col1, col2)
|
||||
if op == "n->n+km":
|
||||
return self._eval_col_op_add_multiple_to_other_col(col, k, col2)
|
||||
|
||||
def elementary_row_op(self, op="n->kn", row=None, k=None, row1=None, row2=None):
|
||||
"""Performs the elementary row operation `op`.
|
||||
|
||||
`op` may be one of
|
||||
|
||||
* ``"n->kn"`` (row n goes to k*n)
|
||||
* ``"n<->m"`` (swap row n and row m)
|
||||
* ``"n->n+km"`` (row n goes to row n + k*row m)
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
op : string; the elementary row operation
|
||||
row : the row to apply the row operation
|
||||
k : the multiple to apply in the row operation
|
||||
row1 : one row of a row swap
|
||||
row2 : second row of a row swap or row "m" in the row operation
|
||||
"n->n+km"
|
||||
"""
|
||||
|
||||
op, row, k, row1, row2 = self._normalize_op_args(op, row, k, row1, row2, "row")
|
||||
|
||||
# now that we've validated, we're all good to dispatch
|
||||
if op == "n->kn":
|
||||
return self._eval_row_op_multiply_row_by_const(row, k)
|
||||
if op == "n<->m":
|
||||
return self._eval_row_op_swap(row1, row2)
|
||||
if op == "n->n+km":
|
||||
return self._eval_row_op_add_multiple_to_other_row(row, k, row2)
|
||||
|
||||
|
||||
class MatrixSubspaces(MatrixReductions):
|
||||
"""Provides methods relating to the fundamental subspaces of a matrix.
|
||||
Should not be instantiated directly. See ``subspaces.py`` for their
|
||||
implementations."""
|
||||
|
||||
def columnspace(self, simplify=False):
|
||||
return _columnspace(self, simplify=simplify)
|
||||
|
||||
def nullspace(self, simplify=False, iszerofunc=_iszero):
|
||||
return _nullspace(self, simplify=simplify, iszerofunc=iszerofunc)
|
||||
|
||||
def rowspace(self, simplify=False):
|
||||
return _rowspace(self, simplify=simplify)
|
||||
|
||||
# This is a classmethod but is converted to such later in order to allow
|
||||
# assignment of __doc__ since that does not work for already wrapped
|
||||
# classmethods in Python 3.6.
|
||||
def orthogonalize(cls, *vecs, **kwargs):
|
||||
return _orthogonalize(cls, *vecs, **kwargs)
|
||||
|
||||
columnspace.__doc__ = _columnspace.__doc__
|
||||
nullspace.__doc__ = _nullspace.__doc__
|
||||
rowspace.__doc__ = _rowspace.__doc__
|
||||
orthogonalize.__doc__ = _orthogonalize.__doc__
|
||||
|
||||
orthogonalize = classmethod(orthogonalize) # type:ignore
|
||||
|
||||
|
||||
class MatrixEigen(MatrixSubspaces):
|
||||
"""Provides basic matrix eigenvalue/vector operations.
|
||||
Should not be instantiated directly. See ``eigen.py`` for their
|
||||
implementations."""
|
||||
|
||||
def eigenvals(self, error_when_incomplete=True, **flags):
|
||||
return _eigenvals(self, error_when_incomplete=error_when_incomplete, **flags)
|
||||
|
||||
def eigenvects(self, error_when_incomplete=True, iszerofunc=_iszero, **flags):
|
||||
return _eigenvects(self, error_when_incomplete=error_when_incomplete,
|
||||
iszerofunc=iszerofunc, **flags)
|
||||
|
||||
def is_diagonalizable(self, reals_only=False, **kwargs):
|
||||
return _is_diagonalizable(self, reals_only=reals_only, **kwargs)
|
||||
|
||||
def diagonalize(self, reals_only=False, sort=False, normalize=False):
|
||||
return _diagonalize(self, reals_only=reals_only, sort=sort,
|
||||
normalize=normalize)
|
||||
|
||||
def bidiagonalize(self, upper=True):
|
||||
return _bidiagonalize(self, upper=upper)
|
||||
|
||||
def bidiagonal_decomposition(self, upper=True):
|
||||
return _bidiagonal_decomposition(self, upper=upper)
|
||||
|
||||
@property
|
||||
def is_positive_definite(self):
|
||||
return _is_positive_definite(self)
|
||||
|
||||
@property
|
||||
def is_positive_semidefinite(self):
|
||||
return _is_positive_semidefinite(self)
|
||||
|
||||
@property
|
||||
def is_negative_definite(self):
|
||||
return _is_negative_definite(self)
|
||||
|
||||
@property
|
||||
def is_negative_semidefinite(self):
|
||||
return _is_negative_semidefinite(self)
|
||||
|
||||
@property
|
||||
def is_indefinite(self):
|
||||
return _is_indefinite(self)
|
||||
|
||||
def jordan_form(self, calc_transform=True, **kwargs):
|
||||
return _jordan_form(self, calc_transform=calc_transform, **kwargs)
|
||||
|
||||
def left_eigenvects(self, **flags):
|
||||
return _left_eigenvects(self, **flags)
|
||||
|
||||
def singular_values(self):
|
||||
return _singular_values(self)
|
||||
|
||||
eigenvals.__doc__ = _eigenvals.__doc__
|
||||
eigenvects.__doc__ = _eigenvects.__doc__
|
||||
is_diagonalizable.__doc__ = _is_diagonalizable.__doc__
|
||||
diagonalize.__doc__ = _diagonalize.__doc__
|
||||
is_positive_definite.__doc__ = _is_positive_definite.__doc__
|
||||
is_positive_semidefinite.__doc__ = _is_positive_semidefinite.__doc__
|
||||
is_negative_definite.__doc__ = _is_negative_definite.__doc__
|
||||
is_negative_semidefinite.__doc__ = _is_negative_semidefinite.__doc__
|
||||
is_indefinite.__doc__ = _is_indefinite.__doc__
|
||||
jordan_form.__doc__ = _jordan_form.__doc__
|
||||
left_eigenvects.__doc__ = _left_eigenvects.__doc__
|
||||
singular_values.__doc__ = _singular_values.__doc__
|
||||
bidiagonalize.__doc__ = _bidiagonalize.__doc__
|
||||
bidiagonal_decomposition.__doc__ = _bidiagonal_decomposition.__doc__
|
||||
|
||||
|
||||
class MatrixCalculus(MatrixCommon):
|
||||
"""Provides calculus-related matrix operations."""
|
||||
|
||||
def diff(self, *args, evaluate=True, **kwargs):
|
||||
"""Calculate the derivative of each element in the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.abc import x, y
|
||||
>>> M = Matrix([[x, y], [1, 0]])
|
||||
>>> M.diff(x)
|
||||
Matrix([
|
||||
[1, 0],
|
||||
[0, 0]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
integrate
|
||||
limit
|
||||
"""
|
||||
# XXX this should be handled here rather than in Derivative
|
||||
from sympy.tensor.array.array_derivatives import ArrayDerivative
|
||||
deriv = ArrayDerivative(self, *args, evaluate=evaluate)
|
||||
# XXX This can rather changed to always return immutable matrix
|
||||
if not isinstance(self, Basic) and evaluate:
|
||||
return deriv.as_mutable()
|
||||
return deriv
|
||||
|
||||
def _eval_derivative(self, arg):
|
||||
return self.applyfunc(lambda x: x.diff(arg))
|
||||
|
||||
def integrate(self, *args, **kwargs):
|
||||
"""Integrate each element of the matrix. ``args`` will
|
||||
be passed to the ``integrate`` function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.abc import x, y
|
||||
>>> M = Matrix([[x, y], [1, 0]])
|
||||
>>> M.integrate((x, ))
|
||||
Matrix([
|
||||
[x**2/2, x*y],
|
||||
[ x, 0]])
|
||||
>>> M.integrate((x, 0, 2))
|
||||
Matrix([
|
||||
[2, 2*y],
|
||||
[2, 0]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
limit
|
||||
diff
|
||||
"""
|
||||
return self.applyfunc(lambda x: x.integrate(*args, **kwargs))
|
||||
|
||||
def jacobian(self, X):
|
||||
"""Calculates the Jacobian matrix (derivative of a vector-valued function).
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
``self`` : vector of expressions representing functions f_i(x_1, ..., x_n).
|
||||
X : set of x_i's in order, it can be a list or a Matrix
|
||||
|
||||
Both ``self`` and X can be a row or a column matrix in any order
|
||||
(i.e., jacobian() should always work).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import sin, cos, Matrix
|
||||
>>> from sympy.abc import rho, phi
|
||||
>>> X = Matrix([rho*cos(phi), rho*sin(phi), rho**2])
|
||||
>>> Y = Matrix([rho, phi])
|
||||
>>> X.jacobian(Y)
|
||||
Matrix([
|
||||
[cos(phi), -rho*sin(phi)],
|
||||
[sin(phi), rho*cos(phi)],
|
||||
[ 2*rho, 0]])
|
||||
>>> X = Matrix([rho*cos(phi), rho*sin(phi)])
|
||||
>>> X.jacobian(Y)
|
||||
Matrix([
|
||||
[cos(phi), -rho*sin(phi)],
|
||||
[sin(phi), rho*cos(phi)]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
hessian
|
||||
wronskian
|
||||
"""
|
||||
if not isinstance(X, MatrixBase):
|
||||
X = self._new(X)
|
||||
# Both X and ``self`` can be a row or a column matrix, so we need to make
|
||||
# sure all valid combinations work, but everything else fails:
|
||||
if self.shape[0] == 1:
|
||||
m = self.shape[1]
|
||||
elif self.shape[1] == 1:
|
||||
m = self.shape[0]
|
||||
else:
|
||||
raise TypeError("``self`` must be a row or a column matrix")
|
||||
if X.shape[0] == 1:
|
||||
n = X.shape[1]
|
||||
elif X.shape[1] == 1:
|
||||
n = X.shape[0]
|
||||
else:
|
||||
raise TypeError("X must be a row or a column matrix")
|
||||
|
||||
# m is the number of functions and n is the number of variables
|
||||
# computing the Jacobian is now easy:
|
||||
return self._new(m, n, lambda j, i: self[j].diff(X[i]))
|
||||
|
||||
def limit(self, *args):
|
||||
"""Calculate the limit of each element in the matrix.
|
||||
``args`` will be passed to the ``limit`` function.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.abc import x, y
|
||||
>>> M = Matrix([[x, y], [1, 0]])
|
||||
>>> M.limit(x, 2)
|
||||
Matrix([
|
||||
[2, y],
|
||||
[1, 0]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
integrate
|
||||
diff
|
||||
"""
|
||||
return self.applyfunc(lambda x: x.limit(*args))
|
||||
|
||||
|
||||
# https://github.com/sympy/sympy/pull/12854
|
||||
class MatrixDeprecated(MatrixCommon):
|
||||
"""A class to house deprecated matrix methods."""
|
||||
def berkowitz_charpoly(self, x=Dummy('lambda'), simplify=_simplify):
|
||||
return self.charpoly(x=x)
|
||||
|
||||
def berkowitz_det(self):
|
||||
"""Computes determinant using Berkowitz method.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
det
|
||||
berkowitz
|
||||
"""
|
||||
return self.det(method='berkowitz')
|
||||
|
||||
def berkowitz_eigenvals(self, **flags):
|
||||
"""Computes eigenvalues of a Matrix using Berkowitz method.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
berkowitz
|
||||
"""
|
||||
return self.eigenvals(**flags)
|
||||
|
||||
def berkowitz_minors(self):
|
||||
"""Computes principal minors using Berkowitz method.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
berkowitz
|
||||
"""
|
||||
sign, minors = self.one, []
|
||||
|
||||
for poly in self.berkowitz():
|
||||
minors.append(sign * poly[-1])
|
||||
sign = -sign
|
||||
|
||||
return tuple(minors)
|
||||
|
||||
def berkowitz(self):
|
||||
from sympy.matrices import zeros
|
||||
berk = ((1,),)
|
||||
if not self:
|
||||
return berk
|
||||
|
||||
if not self.is_square:
|
||||
raise NonSquareMatrixError()
|
||||
|
||||
A, N = self, self.rows
|
||||
transforms = [0] * (N - 1)
|
||||
|
||||
for n in range(N, 1, -1):
|
||||
T, k = zeros(n + 1, n), n - 1
|
||||
|
||||
R, C = -A[k, :k], A[:k, k]
|
||||
A, a = A[:k, :k], -A[k, k]
|
||||
|
||||
items = [C]
|
||||
|
||||
for i in range(0, n - 2):
|
||||
items.append(A * items[i])
|
||||
|
||||
for i, B in enumerate(items):
|
||||
items[i] = (R * B)[0, 0]
|
||||
|
||||
items = [self.one, a] + items
|
||||
|
||||
for i in range(n):
|
||||
T[i:, i] = items[:n - i + 1]
|
||||
|
||||
transforms[k - 1] = T
|
||||
|
||||
polys = [self._new([self.one, -A[0, 0]])]
|
||||
|
||||
for i, T in enumerate(transforms):
|
||||
polys.append(T * polys[i])
|
||||
|
||||
return berk + tuple(map(tuple, polys))
|
||||
|
||||
def cofactorMatrix(self, method="berkowitz"):
|
||||
return self.cofactor_matrix(method=method)
|
||||
|
||||
def det_bareis(self):
|
||||
return _det_bareiss(self)
|
||||
|
||||
def det_LU_decomposition(self):
|
||||
"""Compute matrix determinant using LU decomposition.
|
||||
|
||||
|
||||
Note that this method fails if the LU decomposition itself
|
||||
fails. In particular, if the matrix has no inverse this method
|
||||
will fail.
|
||||
|
||||
TODO: Implement algorithm for sparse matrices (SFF),
|
||||
https://www.eecis.udel.edu/~saunders/papers/sffge/it5.ps
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
|
||||
det
|
||||
det_bareiss
|
||||
berkowitz_det
|
||||
"""
|
||||
return self.det(method='lu')
|
||||
|
||||
def jordan_cell(self, eigenval, n):
|
||||
return self.jordan_block(size=n, eigenvalue=eigenval)
|
||||
|
||||
def jordan_cells(self, calc_transformation=True):
|
||||
P, J = self.jordan_form()
|
||||
return P, J.get_diag_blocks()
|
||||
|
||||
def minorEntry(self, i, j, method="berkowitz"):
|
||||
return self.minor(i, j, method=method)
|
||||
|
||||
def minorMatrix(self, i, j):
|
||||
return self.minor_submatrix(i, j)
|
||||
|
||||
def permuteBkwd(self, perm):
|
||||
"""Permute the rows of the matrix with the given permutation in reverse."""
|
||||
return self.permute_rows(perm, direction='backward')
|
||||
|
||||
def permuteFwd(self, perm):
|
||||
"""Permute the rows of the matrix with the given permutation."""
|
||||
return self.permute_rows(perm, direction='forward')
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,156 @@
|
||||
'''Functions returning normal forms of matrices'''
|
||||
|
||||
from sympy.polys.domains.integerring import ZZ
|
||||
from sympy.polys.polytools import Poly
|
||||
from sympy.polys.matrices import DomainMatrix
|
||||
from sympy.polys.matrices.normalforms import (
|
||||
smith_normal_form as _snf,
|
||||
is_smith_normal_form as _is_snf,
|
||||
smith_normal_decomp as _snd,
|
||||
invariant_factors as _invf,
|
||||
hermite_normal_form as _hnf,
|
||||
)
|
||||
|
||||
|
||||
def _to_domain(m, domain=None):
|
||||
"""Convert Matrix to DomainMatrix"""
|
||||
# XXX: deprecated support for RawMatrix:
|
||||
ring = getattr(m, "ring", None)
|
||||
m = m.applyfunc(lambda e: e.as_expr() if isinstance(e, Poly) else e)
|
||||
|
||||
dM = DomainMatrix.from_Matrix(m)
|
||||
|
||||
domain = domain or ring
|
||||
if domain is not None:
|
||||
dM = dM.convert_to(domain)
|
||||
return dM
|
||||
|
||||
|
||||
def smith_normal_form(m, domain=None):
|
||||
'''
|
||||
Return the Smith Normal Form of a matrix `m` over the ring `domain`.
|
||||
This will only work if the ring is a principal ideal domain.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, ZZ
|
||||
>>> from sympy.matrices.normalforms import smith_normal_form
|
||||
>>> m = Matrix([[12, 6, 4], [3, 9, 6], [2, 16, 14]])
|
||||
>>> print(smith_normal_form(m, domain=ZZ))
|
||||
Matrix([[1, 0, 0], [0, 10, 0], [0, 0, 30]])
|
||||
|
||||
'''
|
||||
dM = _to_domain(m, domain)
|
||||
return _snf(dM).to_Matrix()
|
||||
|
||||
|
||||
def is_smith_normal_form(m, domain=None):
|
||||
'''
|
||||
Checks that the matrix is in Smith Normal Form
|
||||
'''
|
||||
dM = _to_domain(m, domain)
|
||||
return _is_snf(dM)
|
||||
|
||||
|
||||
def smith_normal_decomp(m, domain=None):
|
||||
'''
|
||||
Return the Smith Normal Decomposition of a matrix `m` over the ring
|
||||
`domain`. This will only work if the ring is a principal ideal domain.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, ZZ
|
||||
>>> from sympy.matrices.normalforms import smith_normal_decomp
|
||||
>>> m = Matrix([[12, 6, 4], [3, 9, 6], [2, 16, 14]])
|
||||
>>> a, s, t = smith_normal_decomp(m, domain=ZZ)
|
||||
>>> assert a == s * m * t
|
||||
'''
|
||||
dM = _to_domain(m, domain)
|
||||
a, s, t = _snd(dM)
|
||||
return a.to_Matrix(), s.to_Matrix(), t.to_Matrix()
|
||||
|
||||
|
||||
def invariant_factors(m, domain=None):
|
||||
'''
|
||||
Return the tuple of abelian invariants for a matrix `m`
|
||||
(as in the Smith-Normal form)
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Smith_normal_form#Algorithm
|
||||
.. [2] https://web.archive.org/web/20200331143852/https://sierra.nmsu.edu/morandi/notes/SmithNormalForm.pdf
|
||||
|
||||
'''
|
||||
dM = _to_domain(m, domain)
|
||||
factors = _invf(dM)
|
||||
factors = tuple(dM.domain.to_sympy(f) for f in factors)
|
||||
# XXX: deprecated.
|
||||
if hasattr(m, "ring"):
|
||||
if m.ring.is_PolynomialRing:
|
||||
K = m.ring
|
||||
to_poly = lambda f: Poly(f, K.symbols, domain=K.domain)
|
||||
factors = tuple(to_poly(f) for f in factors)
|
||||
return factors
|
||||
|
||||
|
||||
def hermite_normal_form(A, *, D=None, check_rank=False):
|
||||
r"""
|
||||
Compute the Hermite Normal Form of a Matrix *A* of integers.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.matrices.normalforms import hermite_normal_form
|
||||
>>> m = Matrix([[12, 6, 4], [3, 9, 6], [2, 16, 14]])
|
||||
>>> print(hermite_normal_form(m))
|
||||
Matrix([[10, 0, 2], [0, 15, 3], [0, 0, 2]])
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : $m \times n$ ``Matrix`` of integers.
|
||||
|
||||
D : int, optional
|
||||
Let $W$ be the HNF of *A*. If known in advance, a positive integer *D*
|
||||
being any multiple of $\det(W)$ may be provided. In this case, if *A*
|
||||
also has rank $m$, then we may use an alternative algorithm that works
|
||||
mod *D* in order to prevent coefficient explosion.
|
||||
|
||||
check_rank : boolean, optional (default=False)
|
||||
The basic assumption is that, if you pass a value for *D*, then
|
||||
you already believe that *A* has rank $m$, so we do not waste time
|
||||
checking it for you. If you do want this to be checked (and the
|
||||
ordinary, non-modulo *D* algorithm to be used if the check fails), then
|
||||
set *check_rank* to ``True``.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
``Matrix``
|
||||
The HNF of matrix *A*.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
DMDomainError
|
||||
If the domain of the matrix is not :ref:`ZZ`.
|
||||
|
||||
DMShapeError
|
||||
If the mod *D* algorithm is used but the matrix has more rows than
|
||||
columns.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] Cohen, H. *A Course in Computational Algebraic Number Theory.*
|
||||
(See Algorithms 2.4.5 and 2.4.8.)
|
||||
|
||||
"""
|
||||
# Accept any of Python int, SymPy Integer, and ZZ itself:
|
||||
if D is not None and not ZZ.of_type(D):
|
||||
D = ZZ(int(D))
|
||||
return _hnf(A._rep, D=D, check_rank=check_rank).to_Matrix()
|
||||
@@ -0,0 +1,387 @@
|
||||
from types import FunctionType
|
||||
|
||||
from sympy.polys.polyerrors import CoercionFailed
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
|
||||
from .utilities import _get_intermediate_simp, _iszero, _dotprodsimp, _simplify
|
||||
from .determinant import _find_reasonable_pivot
|
||||
|
||||
|
||||
def _row_reduce_list(mat, rows, cols, one, iszerofunc, simpfunc,
|
||||
normalize_last=True, normalize=True, zero_above=True):
|
||||
"""Row reduce a flat list representation of a matrix and return a tuple
|
||||
(rref_matrix, pivot_cols, swaps) where ``rref_matrix`` is a flat list,
|
||||
``pivot_cols`` are the pivot columns and ``swaps`` are any row swaps that
|
||||
were used in the process of row reduction.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
mat : list
|
||||
list of matrix elements, must be ``rows`` * ``cols`` in length
|
||||
|
||||
rows, cols : integer
|
||||
number of rows and columns in flat list representation
|
||||
|
||||
one : SymPy object
|
||||
represents the value one, from ``Matrix.one``
|
||||
|
||||
iszerofunc : determines if an entry can be used as a pivot
|
||||
|
||||
simpfunc : used to simplify elements and test if they are
|
||||
zero if ``iszerofunc`` returns `None`
|
||||
|
||||
normalize_last : indicates where all row reduction should
|
||||
happen in a fraction-free manner and then the rows are
|
||||
normalized (so that the pivots are 1), or whether
|
||||
rows should be normalized along the way (like the naive
|
||||
row reduction algorithm)
|
||||
|
||||
normalize : whether pivot rows should be normalized so that
|
||||
the pivot value is 1
|
||||
|
||||
zero_above : whether entries above the pivot should be zeroed.
|
||||
If ``zero_above=False``, an echelon matrix will be returned.
|
||||
"""
|
||||
|
||||
def get_col(i):
|
||||
return mat[i::cols]
|
||||
|
||||
def row_swap(i, j):
|
||||
mat[i*cols:(i + 1)*cols], mat[j*cols:(j + 1)*cols] = \
|
||||
mat[j*cols:(j + 1)*cols], mat[i*cols:(i + 1)*cols]
|
||||
|
||||
def cross_cancel(a, i, b, j):
|
||||
"""Does the row op row[i] = a*row[i] - b*row[j]"""
|
||||
q = (j - i)*cols
|
||||
for p in range(i*cols, (i + 1)*cols):
|
||||
mat[p] = isimp(a*mat[p] - b*mat[p + q])
|
||||
|
||||
isimp = _get_intermediate_simp(_dotprodsimp)
|
||||
piv_row, piv_col = 0, 0
|
||||
pivot_cols = []
|
||||
swaps = []
|
||||
|
||||
# use a fraction free method to zero above and below each pivot
|
||||
while piv_col < cols and piv_row < rows:
|
||||
pivot_offset, pivot_val, \
|
||||
assumed_nonzero, newly_determined = _find_reasonable_pivot(
|
||||
get_col(piv_col)[piv_row:], iszerofunc, simpfunc)
|
||||
|
||||
# _find_reasonable_pivot may have simplified some things
|
||||
# in the process. Let's not let them go to waste
|
||||
for (offset, val) in newly_determined:
|
||||
offset += piv_row
|
||||
mat[offset*cols + piv_col] = val
|
||||
|
||||
if pivot_offset is None:
|
||||
piv_col += 1
|
||||
continue
|
||||
|
||||
pivot_cols.append(piv_col)
|
||||
if pivot_offset != 0:
|
||||
row_swap(piv_row, pivot_offset + piv_row)
|
||||
swaps.append((piv_row, pivot_offset + piv_row))
|
||||
|
||||
# if we aren't normalizing last, we normalize
|
||||
# before we zero the other rows
|
||||
if normalize_last is False:
|
||||
i, j = piv_row, piv_col
|
||||
mat[i*cols + j] = one
|
||||
for p in range(i*cols + j + 1, (i + 1)*cols):
|
||||
mat[p] = isimp(mat[p] / pivot_val)
|
||||
# after normalizing, the pivot value is 1
|
||||
pivot_val = one
|
||||
|
||||
# zero above and below the pivot
|
||||
for row in range(rows):
|
||||
# don't zero our current row
|
||||
if row == piv_row:
|
||||
continue
|
||||
# don't zero above the pivot unless we're told.
|
||||
if zero_above is False and row < piv_row:
|
||||
continue
|
||||
# if we're already a zero, don't do anything
|
||||
val = mat[row*cols + piv_col]
|
||||
if iszerofunc(val):
|
||||
continue
|
||||
|
||||
cross_cancel(pivot_val, row, val, piv_row)
|
||||
piv_row += 1
|
||||
|
||||
# normalize each row
|
||||
if normalize_last is True and normalize is True:
|
||||
for piv_i, piv_j in enumerate(pivot_cols):
|
||||
pivot_val = mat[piv_i*cols + piv_j]
|
||||
mat[piv_i*cols + piv_j] = one
|
||||
for p in range(piv_i*cols + piv_j + 1, (piv_i + 1)*cols):
|
||||
mat[p] = isimp(mat[p] / pivot_val)
|
||||
|
||||
return mat, tuple(pivot_cols), tuple(swaps)
|
||||
|
||||
|
||||
# This functions is a candidate for caching if it gets implemented for matrices.
|
||||
def _row_reduce(M, iszerofunc, simpfunc, normalize_last=True,
|
||||
normalize=True, zero_above=True):
|
||||
|
||||
mat, pivot_cols, swaps = _row_reduce_list(list(M), M.rows, M.cols, M.one,
|
||||
iszerofunc, simpfunc, normalize_last=normalize_last,
|
||||
normalize=normalize, zero_above=zero_above)
|
||||
|
||||
return M._new(M.rows, M.cols, mat), pivot_cols, swaps
|
||||
|
||||
|
||||
def _is_echelon(M, iszerofunc=_iszero):
|
||||
"""Returns `True` if the matrix is in echelon form. That is, all rows of
|
||||
zeros are at the bottom, and below each leading non-zero in a row are
|
||||
exclusively zeros."""
|
||||
|
||||
if M.rows <= 0 or M.cols <= 0:
|
||||
return True
|
||||
|
||||
zeros_below = all(iszerofunc(t) for t in M[1:, 0])
|
||||
|
||||
if iszerofunc(M[0, 0]):
|
||||
return zeros_below and _is_echelon(M[:, 1:], iszerofunc)
|
||||
|
||||
return zeros_below and _is_echelon(M[1:, 1:], iszerofunc)
|
||||
|
||||
|
||||
def _echelon_form(M, iszerofunc=_iszero, simplify=False, with_pivots=False):
|
||||
"""Returns a matrix row-equivalent to ``M`` that is in echelon form. Note
|
||||
that echelon form of a matrix is *not* unique, however, properties like the
|
||||
row space and the null space are preserved.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix([[1, 2], [3, 4]])
|
||||
>>> M.echelon_form()
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[0, -2]])
|
||||
"""
|
||||
|
||||
simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify
|
||||
|
||||
mat, pivots, _ = _row_reduce(M, iszerofunc, simpfunc,
|
||||
normalize_last=True, normalize=False, zero_above=False)
|
||||
|
||||
if with_pivots:
|
||||
return mat, pivots
|
||||
|
||||
return mat
|
||||
|
||||
|
||||
# This functions is a candidate for caching if it gets implemented for matrices.
|
||||
def _rank(M, iszerofunc=_iszero, simplify=False):
|
||||
"""Returns the rank of a matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.abc import x
|
||||
>>> m = Matrix([[1, 2], [x, 1 - 1/x]])
|
||||
>>> m.rank()
|
||||
2
|
||||
>>> n = Matrix(3, 3, range(1, 10))
|
||||
>>> n.rank()
|
||||
2
|
||||
"""
|
||||
|
||||
def _permute_complexity_right(M, iszerofunc):
|
||||
"""Permute columns with complicated elements as
|
||||
far right as they can go. Since the ``sympy`` row reduction
|
||||
algorithms start on the left, having complexity right-shifted
|
||||
speeds things up.
|
||||
|
||||
Returns a tuple (mat, perm) where perm is a permutation
|
||||
of the columns to perform to shift the complex columns right, and mat
|
||||
is the permuted matrix."""
|
||||
|
||||
def complexity(i):
|
||||
# the complexity of a column will be judged by how many
|
||||
# element's zero-ness cannot be determined
|
||||
return sum(1 if iszerofunc(e) is None else 0 for e in M[:, i])
|
||||
|
||||
complex = [(complexity(i), i) for i in range(M.cols)]
|
||||
perm = [j for (i, j) in sorted(complex)]
|
||||
|
||||
return (M.permute(perm, orientation='cols'), perm)
|
||||
|
||||
simpfunc = simplify if isinstance(simplify, FunctionType) else _simplify
|
||||
|
||||
# for small matrices, we compute the rank explicitly
|
||||
# if is_zero on elements doesn't answer the question
|
||||
# for small matrices, we fall back to the full routine.
|
||||
if M.rows <= 0 or M.cols <= 0:
|
||||
return 0
|
||||
|
||||
if M.rows <= 1 or M.cols <= 1:
|
||||
zeros = [iszerofunc(x) for x in M]
|
||||
|
||||
if False in zeros:
|
||||
return 1
|
||||
|
||||
if M.rows == 2 and M.cols == 2:
|
||||
zeros = [iszerofunc(x) for x in M]
|
||||
|
||||
if False not in zeros and None not in zeros:
|
||||
return 0
|
||||
|
||||
d = M.det()
|
||||
|
||||
if iszerofunc(d) and False in zeros:
|
||||
return 1
|
||||
if iszerofunc(d) is False:
|
||||
return 2
|
||||
|
||||
mat, _ = _permute_complexity_right(M, iszerofunc=iszerofunc)
|
||||
_, pivots, _ = _row_reduce(mat, iszerofunc, simpfunc, normalize_last=True,
|
||||
normalize=False, zero_above=False)
|
||||
|
||||
return len(pivots)
|
||||
|
||||
|
||||
def _to_DM_ZZ_QQ(M):
|
||||
# We have to test for _rep here because there are tests that otherwise fail
|
||||
# with e.g. "AttributeError: 'SubspaceOnlyMatrix' object has no attribute
|
||||
# '_rep'." There is almost certainly no value in such tests. The
|
||||
# presumption seems to be that someone could create a new class by
|
||||
# inheriting some of the Matrix classes and not the full set that is used
|
||||
# by the standard Matrix class but if anyone tried that it would fail in
|
||||
# many ways.
|
||||
if not hasattr(M, '_rep'):
|
||||
return None
|
||||
|
||||
rep = M._rep
|
||||
K = rep.domain
|
||||
|
||||
if K.is_ZZ:
|
||||
return rep
|
||||
elif K.is_QQ:
|
||||
try:
|
||||
return rep.convert_to(ZZ)
|
||||
except CoercionFailed:
|
||||
return rep
|
||||
else:
|
||||
if not all(e.is_Rational for e in M):
|
||||
return None
|
||||
try:
|
||||
return rep.convert_to(ZZ)
|
||||
except CoercionFailed:
|
||||
return rep.convert_to(QQ)
|
||||
|
||||
|
||||
def _rref_dm(dM):
|
||||
"""Compute the reduced row echelon form of a DomainMatrix."""
|
||||
K = dM.domain
|
||||
|
||||
if K.is_ZZ:
|
||||
dM_rref, den, pivots = dM.rref_den(keep_domain=False)
|
||||
dM_rref = dM_rref.to_field() / den
|
||||
elif K.is_QQ:
|
||||
dM_rref, pivots = dM.rref()
|
||||
else:
|
||||
assert False # pragma: no cover
|
||||
|
||||
M_rref = dM_rref.to_Matrix()
|
||||
|
||||
return M_rref, pivots
|
||||
|
||||
|
||||
def _rref(M, iszerofunc=_iszero, simplify=False, pivots=True,
|
||||
normalize_last=True):
|
||||
"""Return reduced row-echelon form of matrix and indices
|
||||
of pivot vars.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
iszerofunc : Function
|
||||
A function used for detecting whether an element can
|
||||
act as a pivot. ``lambda x: x.is_zero`` is used by default.
|
||||
|
||||
simplify : Function
|
||||
A function used to simplify elements when looking for a pivot.
|
||||
By default SymPy's ``simplify`` is used.
|
||||
|
||||
pivots : True or False
|
||||
If ``True``, a tuple containing the row-reduced matrix and a tuple
|
||||
of pivot columns is returned. If ``False`` just the row-reduced
|
||||
matrix is returned.
|
||||
|
||||
normalize_last : True or False
|
||||
If ``True``, no pivots are normalized to `1` until after all
|
||||
entries above and below each pivot are zeroed. This means the row
|
||||
reduction algorithm is fraction free until the very last step.
|
||||
If ``False``, the naive row reduction procedure is used where
|
||||
each pivot is normalized to be `1` before row operations are
|
||||
used to zero above and below the pivot.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> from sympy.abc import x
|
||||
>>> m = Matrix([[1, 2], [x, 1 - 1/x]])
|
||||
>>> m.rref()
|
||||
(Matrix([
|
||||
[1, 0],
|
||||
[0, 1]]), (0, 1))
|
||||
>>> rref_matrix, rref_pivots = m.rref()
|
||||
>>> rref_matrix
|
||||
Matrix([
|
||||
[1, 0],
|
||||
[0, 1]])
|
||||
>>> rref_pivots
|
||||
(0, 1)
|
||||
|
||||
``iszerofunc`` can correct rounding errors in matrices with float
|
||||
values. In the following example, calling ``rref()`` leads to
|
||||
floating point errors, incorrectly row reducing the matrix.
|
||||
``iszerofunc= lambda x: abs(x) < 1e-9`` sets sufficiently small numbers
|
||||
to zero, avoiding this error.
|
||||
|
||||
>>> m = Matrix([[0.9, -0.1, -0.2, 0], [-0.8, 0.9, -0.4, 0], [-0.1, -0.8, 0.6, 0]])
|
||||
>>> m.rref()
|
||||
(Matrix([
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0]]), (0, 1, 2))
|
||||
>>> m.rref(iszerofunc=lambda x:abs(x)<1e-9)
|
||||
(Matrix([
|
||||
[1, 0, -0.301369863013699, 0],
|
||||
[0, 1, -0.712328767123288, 0],
|
||||
[0, 0, 0, 0]]), (0, 1))
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
The default value of ``normalize_last=True`` can provide significant
|
||||
speedup to row reduction, especially on matrices with symbols. However,
|
||||
if you depend on the form row reduction algorithm leaves entries
|
||||
of the matrix, set ``normalize_last=False``
|
||||
"""
|
||||
# Try to use DomainMatrix for ZZ or QQ
|
||||
dM = _to_DM_ZZ_QQ(M)
|
||||
|
||||
if dM is not None:
|
||||
# Use DomainMatrix for ZZ or QQ
|
||||
mat, pivot_cols = _rref_dm(dM)
|
||||
else:
|
||||
# Use the generic Matrix routine.
|
||||
if isinstance(simplify, FunctionType):
|
||||
simpfunc = simplify
|
||||
else:
|
||||
simpfunc = _simplify
|
||||
|
||||
mat, pivot_cols, _ = _row_reduce(M, iszerofunc, simpfunc,
|
||||
normalize_last, normalize=True, zero_above=True)
|
||||
|
||||
if pivots:
|
||||
return mat, pivot_cols
|
||||
else:
|
||||
return mat
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,942 @@
|
||||
from sympy.core.function import expand_mul
|
||||
from sympy.core.symbol import Dummy, uniquely_named_symbol, symbols
|
||||
from sympy.utilities.iterables import numbered_symbols
|
||||
|
||||
from .exceptions import ShapeError, NonSquareMatrixError, NonInvertibleMatrixError
|
||||
from .eigen import _fuzzy_positive_definite
|
||||
from .utilities import _get_intermediate_simp, _iszero
|
||||
|
||||
|
||||
def _diagonal_solve(M, rhs):
|
||||
"""Solves ``Ax = B`` efficiently, where A is a diagonal Matrix,
|
||||
with non-zero diagonal entries.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, eye
|
||||
>>> A = eye(2)*2
|
||||
>>> B = Matrix([[1, 2], [3, 4]])
|
||||
>>> A.diagonal_solve(B) == B/2
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
if not M.is_diagonal():
|
||||
raise TypeError("Matrix should be diagonal")
|
||||
if rhs.rows != M.rows:
|
||||
raise TypeError("Size mismatch")
|
||||
|
||||
return M._new(
|
||||
rhs.rows, rhs.cols, lambda i, j: rhs[i, j] / M[i, i])
|
||||
|
||||
|
||||
def _lower_triangular_solve(M, rhs):
|
||||
"""Solves ``Ax = B``, where A is a lower triangular matrix.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
from .dense import MutableDenseMatrix
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("Matrix must be square.")
|
||||
if rhs.rows != M.rows:
|
||||
raise ShapeError("Matrices size mismatch.")
|
||||
if not M.is_lower:
|
||||
raise ValueError("Matrix must be lower triangular.")
|
||||
|
||||
dps = _get_intermediate_simp()
|
||||
X = MutableDenseMatrix.zeros(M.rows, rhs.cols)
|
||||
|
||||
for j in range(rhs.cols):
|
||||
for i in range(M.rows):
|
||||
if M[i, i] == 0:
|
||||
raise TypeError("Matrix must be non-singular.")
|
||||
|
||||
X[i, j] = dps((rhs[i, j] - sum(M[i, k]*X[k, j]
|
||||
for k in range(i))) / M[i, i])
|
||||
|
||||
return M._new(X)
|
||||
|
||||
def _lower_triangular_solve_sparse(M, rhs):
|
||||
"""Solves ``Ax = B``, where A is a lower triangular matrix.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("Matrix must be square.")
|
||||
if rhs.rows != M.rows:
|
||||
raise ShapeError("Matrices size mismatch.")
|
||||
if not M.is_lower:
|
||||
raise ValueError("Matrix must be lower triangular.")
|
||||
|
||||
dps = _get_intermediate_simp()
|
||||
rows = [[] for i in range(M.rows)]
|
||||
|
||||
for i, j, v in M.row_list():
|
||||
if i > j:
|
||||
rows[i].append((j, v))
|
||||
|
||||
X = rhs.as_mutable()
|
||||
|
||||
for j in range(rhs.cols):
|
||||
for i in range(rhs.rows):
|
||||
for u, v in rows[i]:
|
||||
X[i, j] -= v*X[u, j]
|
||||
|
||||
X[i, j] = dps(X[i, j] / M[i, i])
|
||||
|
||||
return M._new(X)
|
||||
|
||||
|
||||
def _upper_triangular_solve(M, rhs):
|
||||
"""Solves ``Ax = B``, where A is an upper triangular matrix.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
lower_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
from .dense import MutableDenseMatrix
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("Matrix must be square.")
|
||||
if rhs.rows != M.rows:
|
||||
raise ShapeError("Matrix size mismatch.")
|
||||
if not M.is_upper:
|
||||
raise TypeError("Matrix is not upper triangular.")
|
||||
|
||||
dps = _get_intermediate_simp()
|
||||
X = MutableDenseMatrix.zeros(M.rows, rhs.cols)
|
||||
|
||||
for j in range(rhs.cols):
|
||||
for i in reversed(range(M.rows)):
|
||||
if M[i, i] == 0:
|
||||
raise ValueError("Matrix must be non-singular.")
|
||||
|
||||
X[i, j] = dps((rhs[i, j] - sum(M[i, k]*X[k, j]
|
||||
for k in range(i + 1, M.rows))) / M[i, i])
|
||||
|
||||
return M._new(X)
|
||||
|
||||
def _upper_triangular_solve_sparse(M, rhs):
|
||||
"""Solves ``Ax = B``, where A is an upper triangular matrix.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
lower_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
if not M.is_square:
|
||||
raise NonSquareMatrixError("Matrix must be square.")
|
||||
if rhs.rows != M.rows:
|
||||
raise ShapeError("Matrix size mismatch.")
|
||||
if not M.is_upper:
|
||||
raise TypeError("Matrix is not upper triangular.")
|
||||
|
||||
dps = _get_intermediate_simp()
|
||||
rows = [[] for i in range(M.rows)]
|
||||
|
||||
for i, j, v in M.row_list():
|
||||
if i < j:
|
||||
rows[i].append((j, v))
|
||||
|
||||
X = rhs.as_mutable()
|
||||
|
||||
for j in range(rhs.cols):
|
||||
for i in reversed(range(rhs.rows)):
|
||||
for u, v in reversed(rows[i]):
|
||||
X[i, j] -= v*X[u, j]
|
||||
|
||||
X[i, j] = dps(X[i, j] / M[i, i])
|
||||
|
||||
return M._new(X)
|
||||
|
||||
|
||||
def _cholesky_solve(M, rhs):
|
||||
"""Solves ``Ax = B`` using Cholesky decomposition,
|
||||
for a general square non-singular matrix.
|
||||
For a non-square matrix with rows > cols,
|
||||
the least squares solution is returned.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
if M.rows < M.cols:
|
||||
raise NotImplementedError(
|
||||
'Under-determined System. Try M.gauss_jordan_solve(rhs)')
|
||||
|
||||
hermitian = True
|
||||
reform = False
|
||||
|
||||
if M.is_symmetric():
|
||||
hermitian = False
|
||||
elif not M.is_hermitian:
|
||||
reform = True
|
||||
|
||||
if reform or _fuzzy_positive_definite(M) is False:
|
||||
H = M.H
|
||||
M = H.multiply(M)
|
||||
rhs = H.multiply(rhs)
|
||||
hermitian = not M.is_symmetric()
|
||||
|
||||
L = M.cholesky(hermitian=hermitian)
|
||||
Y = L.lower_triangular_solve(rhs)
|
||||
|
||||
if hermitian:
|
||||
return (L.H).upper_triangular_solve(Y)
|
||||
else:
|
||||
return (L.T).upper_triangular_solve(Y)
|
||||
|
||||
|
||||
def _LDLsolve(M, rhs):
|
||||
"""Solves ``Ax = B`` using LDL decomposition,
|
||||
for a general square and non-singular matrix.
|
||||
|
||||
For a non-square matrix with rows > cols,
|
||||
the least squares solution is returned.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, eye
|
||||
>>> A = eye(2)*2
|
||||
>>> B = Matrix([[1, 2], [3, 4]])
|
||||
>>> A.LDLsolve(B) == B/2
|
||||
True
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.LDLdecomposition
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
if M.rows < M.cols:
|
||||
raise NotImplementedError(
|
||||
'Under-determined System. Try M.gauss_jordan_solve(rhs)')
|
||||
|
||||
hermitian = True
|
||||
reform = False
|
||||
|
||||
if M.is_symmetric():
|
||||
hermitian = False
|
||||
elif not M.is_hermitian:
|
||||
reform = True
|
||||
|
||||
if reform or _fuzzy_positive_definite(M) is False:
|
||||
H = M.H
|
||||
M = H.multiply(M)
|
||||
rhs = H.multiply(rhs)
|
||||
hermitian = not M.is_symmetric()
|
||||
|
||||
L, D = M.LDLdecomposition(hermitian=hermitian)
|
||||
Y = L.lower_triangular_solve(rhs)
|
||||
Z = D.diagonal_solve(Y)
|
||||
|
||||
if hermitian:
|
||||
return (L.H).upper_triangular_solve(Z)
|
||||
else:
|
||||
return (L.T).upper_triangular_solve(Z)
|
||||
|
||||
|
||||
def _LUsolve(M, rhs, iszerofunc=_iszero):
|
||||
"""Solve the linear system ``Ax = rhs`` for ``x`` where ``A = M``.
|
||||
|
||||
This is for symbolic matrices, for real or complex ones use
|
||||
mpmath.lu_solve or mpmath.qr_solve.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
QRsolve
|
||||
pinv_solve
|
||||
LUdecomposition
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
if rhs.rows != M.rows:
|
||||
raise ShapeError(
|
||||
"``M`` and ``rhs`` must have the same number of rows.")
|
||||
|
||||
m = M.rows
|
||||
n = M.cols
|
||||
|
||||
if m < n:
|
||||
raise NotImplementedError("Underdetermined systems not supported.")
|
||||
|
||||
try:
|
||||
A, perm = M.LUdecomposition_Simple(
|
||||
iszerofunc=iszerofunc, rankcheck=True)
|
||||
except ValueError:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
dps = _get_intermediate_simp()
|
||||
b = rhs.permute_rows(perm).as_mutable()
|
||||
|
||||
# forward substitution, all diag entries are scaled to 1
|
||||
for i in range(m):
|
||||
for j in range(min(i, n)):
|
||||
scale = A[i, j]
|
||||
b.zip_row_op(i, j, lambda x, y: dps(x - scale * y))
|
||||
|
||||
# consistency check for overdetermined systems
|
||||
if m > n:
|
||||
for i in range(n, m):
|
||||
for j in range(b.cols):
|
||||
if not iszerofunc(b[i, j]):
|
||||
raise ValueError("The system is inconsistent.")
|
||||
|
||||
b = b[0:n, :] # truncate zero rows if consistent
|
||||
|
||||
# backward substitution
|
||||
for i in range(n - 1, -1, -1):
|
||||
for j in range(i + 1, n):
|
||||
scale = A[i, j]
|
||||
b.zip_row_op(i, j, lambda x, y: dps(x - scale * y))
|
||||
|
||||
scale = A[i, i]
|
||||
b.row_op(i, lambda x, _: dps(scale**-1 * x))
|
||||
|
||||
return rhs.__class__(b)
|
||||
|
||||
|
||||
def _QRsolve(M, b):
|
||||
"""Solve the linear system ``Ax = b``.
|
||||
|
||||
``M`` is the matrix ``A``, the method argument is the vector
|
||||
``b``. The method returns the solution vector ``x``. If ``b`` is a
|
||||
matrix, the system is solved for each column of ``b`` and the
|
||||
return value is a matrix of the same shape as ``b``.
|
||||
|
||||
This method is slower (approximately by a factor of 2) but
|
||||
more stable for floating-point arithmetic than the LUsolve method.
|
||||
However, LUsolve usually uses an exact arithmetic, so you do not need
|
||||
to use QRsolve.
|
||||
|
||||
This is mainly for educational purposes and symbolic matrices, for real
|
||||
(or complex) matrices use mpmath.qr_solve.
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
pinv_solve
|
||||
QRdecomposition
|
||||
cramer_solve
|
||||
"""
|
||||
|
||||
dps = _get_intermediate_simp(expand_mul, expand_mul)
|
||||
Q, R = M.QRdecomposition()
|
||||
y = Q.T * b
|
||||
|
||||
# back substitution to solve R*x = y:
|
||||
# We build up the result "backwards" in the vector 'x' and reverse it
|
||||
# only in the end.
|
||||
x = []
|
||||
n = R.rows
|
||||
|
||||
for j in range(n - 1, -1, -1):
|
||||
tmp = y[j, :]
|
||||
|
||||
for k in range(j + 1, n):
|
||||
tmp -= R[j, k] * x[n - 1 - k]
|
||||
|
||||
tmp = dps(tmp)
|
||||
|
||||
x.append(tmp / R[j, j])
|
||||
|
||||
return M.vstack(*x[::-1])
|
||||
|
||||
|
||||
def _gauss_jordan_solve(M, B, freevar=False):
|
||||
"""
|
||||
Solves ``Ax = B`` using Gauss Jordan elimination.
|
||||
|
||||
There may be zero, one, or infinite solutions. If one solution
|
||||
exists, it will be returned. If infinite solutions exist, it will
|
||||
be returned parametrically. If no solutions exist, It will throw
|
||||
ValueError.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
B : Matrix
|
||||
The right hand side of the equation to be solved for. Must have
|
||||
the same number of rows as matrix A.
|
||||
|
||||
freevar : boolean, optional
|
||||
Flag, when set to `True` will return the indices of the free
|
||||
variables in the solutions (column Matrix), for a system that is
|
||||
undetermined (e.g. A has more columns than rows), for which
|
||||
infinite solutions are possible, in terms of arbitrary
|
||||
values of free variables. Default `False`.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
x : Matrix
|
||||
The matrix that will satisfy ``Ax = B``. Will have as many rows as
|
||||
matrix A has columns, and as many columns as matrix B.
|
||||
|
||||
params : Matrix
|
||||
If the system is underdetermined (e.g. A has more columns than
|
||||
rows), infinite solutions are possible, in terms of arbitrary
|
||||
parameters. These arbitrary parameters are returned as params
|
||||
Matrix.
|
||||
|
||||
free_var_index : List, optional
|
||||
If the system is underdetermined (e.g. A has more columns than
|
||||
rows), infinite solutions are possible, in terms of arbitrary
|
||||
values of free variables. Then the indices of the free variables
|
||||
in the solutions (column Matrix) are returned by free_var_index,
|
||||
if the flag `freevar` is set to `True`.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([[1, 2, 1, 1], [1, 2, 2, -1], [2, 4, 0, 6]])
|
||||
>>> B = Matrix([7, 12, 4])
|
||||
>>> sol, params = A.gauss_jordan_solve(B)
|
||||
>>> sol
|
||||
Matrix([
|
||||
[-2*tau0 - 3*tau1 + 2],
|
||||
[ tau0],
|
||||
[ 2*tau1 + 5],
|
||||
[ tau1]])
|
||||
>>> params
|
||||
Matrix([
|
||||
[tau0],
|
||||
[tau1]])
|
||||
>>> taus_zeroes = { tau:0 for tau in params }
|
||||
>>> sol_unique = sol.xreplace(taus_zeroes)
|
||||
>>> sol_unique
|
||||
Matrix([
|
||||
[2],
|
||||
[0],
|
||||
[5],
|
||||
[0]])
|
||||
|
||||
|
||||
>>> A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]])
|
||||
>>> B = Matrix([3, 6, 9])
|
||||
>>> sol, params = A.gauss_jordan_solve(B)
|
||||
>>> sol
|
||||
Matrix([
|
||||
[-1],
|
||||
[ 2],
|
||||
[ 0]])
|
||||
>>> params
|
||||
Matrix(0, 1, [])
|
||||
|
||||
>>> A = Matrix([[2, -7], [-1, 4]])
|
||||
>>> B = Matrix([[-21, 3], [12, -2]])
|
||||
>>> sol, params = A.gauss_jordan_solve(B)
|
||||
>>> sol
|
||||
Matrix([
|
||||
[0, -2],
|
||||
[3, -1]])
|
||||
>>> params
|
||||
Matrix(0, 2, [])
|
||||
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([[1, 2, 1, 1], [1, 2, 2, -1], [2, 4, 0, 6]])
|
||||
>>> B = Matrix([7, 12, 4])
|
||||
>>> sol, params, freevars = A.gauss_jordan_solve(B, freevar=True)
|
||||
>>> sol
|
||||
Matrix([
|
||||
[-2*tau0 - 3*tau1 + 2],
|
||||
[ tau0],
|
||||
[ 2*tau1 + 5],
|
||||
[ tau1]])
|
||||
>>> params
|
||||
Matrix([
|
||||
[tau0],
|
||||
[tau1]])
|
||||
>>> freevars
|
||||
[1, 3]
|
||||
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Gaussian_elimination
|
||||
|
||||
"""
|
||||
|
||||
from sympy.matrices import Matrix, zeros
|
||||
|
||||
cls = M.__class__
|
||||
aug = M.hstack(M.copy(), B.copy())
|
||||
B_cols = B.cols
|
||||
row, col = aug[:, :-B_cols].shape
|
||||
|
||||
# solve by reduced row echelon form
|
||||
A, pivots = aug.rref(simplify=True)
|
||||
A, v = A[:, :-B_cols], A[:, -B_cols:]
|
||||
pivots = list(filter(lambda p: p < col, pivots))
|
||||
rank = len(pivots)
|
||||
|
||||
# Get index of free symbols (free parameters)
|
||||
# non-pivots columns are free variables
|
||||
free_var_index = [c for c in range(A.cols) if c not in pivots]
|
||||
|
||||
# Bring to block form
|
||||
permutation = Matrix(pivots + free_var_index).T
|
||||
|
||||
# check for existence of solutions
|
||||
# rank of aug Matrix should be equal to rank of coefficient matrix
|
||||
if not v[rank:, :].is_zero_matrix:
|
||||
raise ValueError("Linear system has no solution")
|
||||
|
||||
# Free parameters
|
||||
# what are current unnumbered free symbol names?
|
||||
name = uniquely_named_symbol('tau', [aug],
|
||||
compare=lambda i: str(i).rstrip('1234567890'),
|
||||
modify=lambda s: '_' + s).name
|
||||
gen = numbered_symbols(name)
|
||||
tau = Matrix([next(gen) for k in range((col - rank)*B_cols)]).reshape(
|
||||
col - rank, B_cols)
|
||||
|
||||
# Full parametric solution
|
||||
V = A[:rank, free_var_index]
|
||||
vt = v[:rank, :]
|
||||
free_sol = tau.vstack(vt - V * tau, tau)
|
||||
|
||||
# Undo permutation
|
||||
sol = zeros(col, B_cols)
|
||||
|
||||
for k in range(col):
|
||||
sol[permutation[k], :] = free_sol[k,:]
|
||||
|
||||
sol, tau = cls(sol), cls(tau)
|
||||
|
||||
if freevar:
|
||||
return sol, tau, free_var_index
|
||||
else:
|
||||
return sol, tau
|
||||
|
||||
|
||||
def _pinv_solve(M, B, arbitrary_matrix=None):
|
||||
"""Solve ``Ax = B`` using the Moore-Penrose pseudoinverse.
|
||||
|
||||
There may be zero, one, or infinite solutions. If one solution
|
||||
exists, it will be returned. If infinite solutions exist, one will
|
||||
be returned based on the value of arbitrary_matrix. If no solutions
|
||||
exist, the least-squares solution is returned.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
B : Matrix
|
||||
The right hand side of the equation to be solved for. Must have
|
||||
the same number of rows as matrix A.
|
||||
arbitrary_matrix : Matrix
|
||||
If the system is underdetermined (e.g. A has more columns than
|
||||
rows), infinite solutions are possible, in terms of an arbitrary
|
||||
matrix. This parameter may be set to a specific matrix to use
|
||||
for that purpose; if so, it must be the same shape as x, with as
|
||||
many rows as matrix A has columns, and as many columns as matrix
|
||||
B. If left as None, an appropriate matrix containing dummy
|
||||
symbols in the form of ``wn_m`` will be used, with n and m being
|
||||
row and column position of each symbol.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
x : Matrix
|
||||
The matrix that will satisfy ``Ax = B``. Will have as many rows as
|
||||
matrix A has columns, and as many columns as matrix B.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([[1, 2, 3], [4, 5, 6]])
|
||||
>>> B = Matrix([7, 8])
|
||||
>>> A.pinv_solve(B)
|
||||
Matrix([
|
||||
[ _w0_0/6 - _w1_0/3 + _w2_0/6 - 55/18],
|
||||
[-_w0_0/3 + 2*_w1_0/3 - _w2_0/3 + 1/9],
|
||||
[ _w0_0/6 - _w1_0/3 + _w2_0/6 + 59/18]])
|
||||
>>> A.pinv_solve(B, arbitrary_matrix=Matrix([0, 0, 0]))
|
||||
Matrix([
|
||||
[-55/18],
|
||||
[ 1/9],
|
||||
[ 59/18]])
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.dense.DenseMatrix.lower_triangular_solve
|
||||
sympy.matrices.dense.DenseMatrix.upper_triangular_solve
|
||||
gauss_jordan_solve
|
||||
cholesky_solve
|
||||
diagonal_solve
|
||||
LDLsolve
|
||||
LUsolve
|
||||
QRsolve
|
||||
pinv
|
||||
|
||||
Notes
|
||||
=====
|
||||
|
||||
This may return either exact solutions or least squares solutions.
|
||||
To determine which, check ``A * A.pinv() * B == B``. It will be
|
||||
True if exact solutions exist, and False if only a least-squares
|
||||
solution exists. Be aware that the left hand side of that equation
|
||||
may need to be simplified to correctly compare to the right hand
|
||||
side.
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Moore-Penrose_pseudoinverse#Obtaining_all_solutions_of_a_linear_system
|
||||
|
||||
"""
|
||||
|
||||
from sympy.matrices import eye
|
||||
|
||||
A = M
|
||||
A_pinv = M.pinv()
|
||||
|
||||
if arbitrary_matrix is None:
|
||||
rows, cols = A.cols, B.cols
|
||||
w = symbols('w:{}_:{}'.format(rows, cols), cls=Dummy)
|
||||
arbitrary_matrix = M.__class__(cols, rows, w).T
|
||||
|
||||
return A_pinv.multiply(B) + (eye(A.cols) -
|
||||
A_pinv.multiply(A)).multiply(arbitrary_matrix)
|
||||
|
||||
|
||||
def _cramer_solve(M, rhs, det_method="laplace"):
|
||||
"""Solves system of linear equations using Cramer's rule.
|
||||
|
||||
This method is relatively inefficient compared to other methods.
|
||||
However it only uses a single division, assuming a division-free determinant
|
||||
method is provided. This is helpful to minimize the chance of divide-by-zero
|
||||
cases in symbolic solutions to linear systems.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
M : Matrix
|
||||
The matrix representing the left hand side of the equation.
|
||||
rhs : Matrix
|
||||
The matrix representing the right hand side of the equation.
|
||||
det_method : str or callable
|
||||
The method to use to calculate the determinant of the matrix.
|
||||
The default is ``'laplace'``. If a callable is passed, it should take a
|
||||
single argument, the matrix, and return the determinant of the matrix.
|
||||
|
||||
Returns
|
||||
=======
|
||||
x : Matrix
|
||||
The matrix that will satisfy ``Ax = B``. Will have as many rows as
|
||||
matrix A has columns, and as many columns as matrix B.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> A = Matrix([[0, -6, 1], [0, -6, -1], [-5, -2, 3]])
|
||||
>>> B = Matrix([[-30, -9], [-18, -27], [-26, 46]])
|
||||
>>> x = A.cramer_solve(B)
|
||||
>>> x
|
||||
Matrix([
|
||||
[ 0, -5],
|
||||
[ 4, 3],
|
||||
[-6, 9]])
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Cramer%27s_rule#Explicit_formulas_for_small_systems
|
||||
|
||||
"""
|
||||
from .dense import zeros
|
||||
|
||||
def entry(i, j):
|
||||
return rhs[i, sol] if j == col else M[i, j]
|
||||
|
||||
if det_method == "bird":
|
||||
from .determinant import _det_bird
|
||||
det = _det_bird
|
||||
elif det_method == "laplace":
|
||||
from .determinant import _det_laplace
|
||||
det = _det_laplace
|
||||
elif isinstance(det_method, str):
|
||||
det = lambda matrix: matrix.det(method=det_method)
|
||||
else:
|
||||
det = det_method
|
||||
det_M = det(M)
|
||||
x = zeros(*rhs.shape)
|
||||
for sol in range(rhs.shape[1]):
|
||||
for col in range(rhs.shape[0]):
|
||||
x[col, sol] = det(M.__class__(*M.shape, entry)) / det_M
|
||||
return M.__class__(x)
|
||||
|
||||
|
||||
def _solve(M, rhs, method='GJ'):
|
||||
"""Solves linear equation where the unique solution exists.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
rhs : Matrix
|
||||
Vector representing the right hand side of the linear equation.
|
||||
|
||||
method : string, optional
|
||||
If set to ``'GJ'`` or ``'GE'``, the Gauss-Jordan elimination will be
|
||||
used, which is implemented in the routine ``gauss_jordan_solve``.
|
||||
|
||||
If set to ``'LU'``, ``LUsolve`` routine will be used.
|
||||
|
||||
If set to ``'QR'``, ``QRsolve`` routine will be used.
|
||||
|
||||
If set to ``'PINV'``, ``pinv_solve`` routine will be used.
|
||||
|
||||
If set to ``'CRAMER'``, ``cramer_solve`` routine will be used.
|
||||
|
||||
It also supports the methods available for special linear systems
|
||||
|
||||
For positive definite systems:
|
||||
|
||||
If set to ``'CH'``, ``cholesky_solve`` routine will be used.
|
||||
|
||||
If set to ``'LDL'``, ``LDLsolve`` routine will be used.
|
||||
|
||||
To use a different method and to compute the solution via the
|
||||
inverse, use a method defined in the .inv() docstring.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
solutions : Matrix
|
||||
Vector representing the solution.
|
||||
|
||||
Raises
|
||||
======
|
||||
|
||||
ValueError
|
||||
If there is not a unique solution then a ``ValueError`` will be
|
||||
raised.
|
||||
|
||||
If ``M`` is not square, a ``ValueError`` and a different routine
|
||||
for solving the system will be suggested.
|
||||
"""
|
||||
|
||||
if method in ('GJ', 'GE'):
|
||||
try:
|
||||
soln, param = M.gauss_jordan_solve(rhs)
|
||||
|
||||
if param:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible. "
|
||||
"Try ``M.gauss_jordan_solve(rhs)`` to obtain a parametric solution.")
|
||||
|
||||
except ValueError:
|
||||
raise NonInvertibleMatrixError("Matrix det == 0; not invertible.")
|
||||
|
||||
return soln
|
||||
|
||||
elif method == 'LU':
|
||||
return M.LUsolve(rhs)
|
||||
elif method == 'CH':
|
||||
return M.cholesky_solve(rhs)
|
||||
elif method == 'QR':
|
||||
return M.QRsolve(rhs)
|
||||
elif method == 'LDL':
|
||||
return M.LDLsolve(rhs)
|
||||
elif method == 'PINV':
|
||||
return M.pinv_solve(rhs)
|
||||
elif method == 'CRAMER':
|
||||
return M.cramer_solve(rhs)
|
||||
else:
|
||||
return M.inv(method=method).multiply(rhs)
|
||||
|
||||
|
||||
def _solve_least_squares(M, rhs, method='CH'):
|
||||
"""Return the least-square fit to the data.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
rhs : Matrix
|
||||
Vector representing the right hand side of the linear equation.
|
||||
|
||||
method : string or boolean, optional
|
||||
If set to ``'CH'``, ``cholesky_solve`` routine will be used.
|
||||
|
||||
If set to ``'LDL'``, ``LDLsolve`` routine will be used.
|
||||
|
||||
If set to ``'QR'``, ``QRsolve`` routine will be used.
|
||||
|
||||
If set to ``'PINV'``, ``pinv_solve`` routine will be used.
|
||||
|
||||
Otherwise, the conjugate of ``M`` will be used to create a system
|
||||
of equations that is passed to ``solve`` along with the hint
|
||||
defined by ``method``.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
solutions : Matrix
|
||||
Vector representing the solution.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix, ones
|
||||
>>> A = Matrix([1, 2, 3])
|
||||
>>> B = Matrix([2, 3, 4])
|
||||
>>> S = Matrix(A.row_join(B))
|
||||
>>> S
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
[3, 4]])
|
||||
|
||||
If each line of S represent coefficients of Ax + By
|
||||
and x and y are [2, 3] then S*xy is:
|
||||
|
||||
>>> r = S*Matrix([2, 3]); r
|
||||
Matrix([
|
||||
[ 8],
|
||||
[13],
|
||||
[18]])
|
||||
|
||||
But let's add 1 to the middle value and then solve for the
|
||||
least-squares value of xy:
|
||||
|
||||
>>> xy = S.solve_least_squares(Matrix([8, 14, 18])); xy
|
||||
Matrix([
|
||||
[ 5/3],
|
||||
[10/3]])
|
||||
|
||||
The error is given by S*xy - r:
|
||||
|
||||
>>> S*xy - r
|
||||
Matrix([
|
||||
[1/3],
|
||||
[1/3],
|
||||
[1/3]])
|
||||
>>> _.norm().n(2)
|
||||
0.58
|
||||
|
||||
If a different xy is used, the norm will be higher:
|
||||
|
||||
>>> xy += ones(2, 1)/10
|
||||
>>> (S*xy - r).norm().n(2)
|
||||
1.5
|
||||
|
||||
"""
|
||||
|
||||
if method == 'CH':
|
||||
return M.cholesky_solve(rhs)
|
||||
elif method == 'QR':
|
||||
return M.QRsolve(rhs)
|
||||
elif method == 'LDL':
|
||||
return M.LDLsolve(rhs)
|
||||
elif method == 'PINV':
|
||||
return M.pinv_solve(rhs)
|
||||
else:
|
||||
t = M.H
|
||||
return (t * M).solve(t * rhs, method=method)
|
||||
@@ -0,0 +1,473 @@
|
||||
from collections.abc import Callable
|
||||
|
||||
from sympy.core.containers import Dict
|
||||
from sympy.utilities.exceptions import sympy_deprecation_warning
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
from sympy.utilities.misc import as_int
|
||||
|
||||
from .matrixbase import MatrixBase
|
||||
from .repmatrix import MutableRepMatrix, RepMatrix
|
||||
|
||||
from .utilities import _iszero
|
||||
|
||||
from .decompositions import (
|
||||
_liupc, _row_structure_symbolic_cholesky, _cholesky_sparse,
|
||||
_LDLdecomposition_sparse)
|
||||
|
||||
from .solvers import (
|
||||
_lower_triangular_solve_sparse, _upper_triangular_solve_sparse)
|
||||
|
||||
|
||||
class SparseRepMatrix(RepMatrix):
|
||||
"""
|
||||
A sparse matrix (a matrix with a large number of zero elements).
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import SparseMatrix, ones
|
||||
>>> SparseMatrix(2, 2, range(4))
|
||||
Matrix([
|
||||
[0, 1],
|
||||
[2, 3]])
|
||||
>>> SparseMatrix(2, 2, {(1, 1): 2})
|
||||
Matrix([
|
||||
[0, 0],
|
||||
[0, 2]])
|
||||
|
||||
A SparseMatrix can be instantiated from a ragged list of lists:
|
||||
|
||||
>>> SparseMatrix([[1, 2, 3], [1, 2], [1]])
|
||||
Matrix([
|
||||
[1, 2, 3],
|
||||
[1, 2, 0],
|
||||
[1, 0, 0]])
|
||||
|
||||
For safety, one may include the expected size and then an error
|
||||
will be raised if the indices of any element are out of range or
|
||||
(for a flat list) if the total number of elements does not match
|
||||
the expected shape:
|
||||
|
||||
>>> SparseMatrix(2, 2, [1, 2])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: List length (2) != rows*columns (4)
|
||||
|
||||
Here, an error is not raised because the list is not flat and no
|
||||
element is out of range:
|
||||
|
||||
>>> SparseMatrix(2, 2, [[1, 2]])
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[0, 0]])
|
||||
|
||||
But adding another element to the first (and only) row will cause
|
||||
an error to be raised:
|
||||
|
||||
>>> SparseMatrix(2, 2, [[1, 2, 3]])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: The location (0, 2) is out of designated range: (1, 1)
|
||||
|
||||
To autosize the matrix, pass None for rows:
|
||||
|
||||
>>> SparseMatrix(None, [[1, 2, 3]])
|
||||
Matrix([[1, 2, 3]])
|
||||
>>> SparseMatrix(None, {(1, 1): 1, (3, 3): 3})
|
||||
Matrix([
|
||||
[0, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 0, 0],
|
||||
[0, 0, 0, 3]])
|
||||
|
||||
Values that are themselves a Matrix are automatically expanded:
|
||||
|
||||
>>> SparseMatrix(4, 4, {(1, 1): ones(2)})
|
||||
Matrix([
|
||||
[0, 0, 0, 0],
|
||||
[0, 1, 1, 0],
|
||||
[0, 1, 1, 0],
|
||||
[0, 0, 0, 0]])
|
||||
|
||||
A ValueError is raised if the expanding matrix tries to overwrite
|
||||
a different element already present:
|
||||
|
||||
>>> SparseMatrix(3, 3, {(0, 0): ones(2), (1, 1): 2})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: collision at (1, 1)
|
||||
|
||||
See Also
|
||||
========
|
||||
DenseMatrix
|
||||
MutableSparseMatrix
|
||||
ImmutableSparseMatrix
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def _handle_creation_inputs(cls, *args, **kwargs):
|
||||
if len(args) == 1 and isinstance(args[0], MatrixBase):
|
||||
rows = args[0].rows
|
||||
cols = args[0].cols
|
||||
smat = args[0].todok()
|
||||
return rows, cols, smat
|
||||
|
||||
smat = {}
|
||||
# autosizing
|
||||
if len(args) == 2 and args[0] is None:
|
||||
args = [None, None, args[1]]
|
||||
|
||||
if len(args) == 3:
|
||||
r, c = args[:2]
|
||||
if r is c is None:
|
||||
rows = cols = None
|
||||
elif None in (r, c):
|
||||
raise ValueError(
|
||||
'Pass rows=None and no cols for autosizing.')
|
||||
else:
|
||||
rows, cols = as_int(args[0]), as_int(args[1])
|
||||
|
||||
if isinstance(args[2], Callable):
|
||||
op = args[2]
|
||||
|
||||
if None in (rows, cols):
|
||||
raise ValueError(
|
||||
"{} and {} must be integers for this "
|
||||
"specification.".format(rows, cols))
|
||||
|
||||
row_indices = [cls._sympify(i) for i in range(rows)]
|
||||
col_indices = [cls._sympify(j) for j in range(cols)]
|
||||
|
||||
for i in row_indices:
|
||||
for j in col_indices:
|
||||
value = cls._sympify(op(i, j))
|
||||
if value != cls.zero:
|
||||
smat[i, j] = value
|
||||
|
||||
return rows, cols, smat
|
||||
|
||||
elif isinstance(args[2], (dict, Dict)):
|
||||
def update(i, j, v):
|
||||
# update smat and make sure there are no collisions
|
||||
if v:
|
||||
if (i, j) in smat and v != smat[i, j]:
|
||||
raise ValueError(
|
||||
"There is a collision at {} for {} and {}."
|
||||
.format((i, j), v, smat[i, j])
|
||||
)
|
||||
smat[i, j] = v
|
||||
|
||||
# manual copy, copy.deepcopy() doesn't work
|
||||
for (r, c), v in args[2].items():
|
||||
if isinstance(v, MatrixBase):
|
||||
for (i, j), vv in v.todok().items():
|
||||
update(r + i, c + j, vv)
|
||||
elif isinstance(v, (list, tuple)):
|
||||
_, _, smat = cls._handle_creation_inputs(v, **kwargs)
|
||||
for i, j in smat:
|
||||
update(r + i, c + j, smat[i, j])
|
||||
else:
|
||||
v = cls._sympify(v)
|
||||
update(r, c, cls._sympify(v))
|
||||
|
||||
elif is_sequence(args[2]):
|
||||
flat = not any(is_sequence(i) for i in args[2])
|
||||
if not flat:
|
||||
_, _, smat = \
|
||||
cls._handle_creation_inputs(args[2], **kwargs)
|
||||
else:
|
||||
flat_list = args[2]
|
||||
if len(flat_list) != rows * cols:
|
||||
raise ValueError(
|
||||
"The length of the flat list ({}) does not "
|
||||
"match the specified size ({} * {})."
|
||||
.format(len(flat_list), rows, cols)
|
||||
)
|
||||
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
value = flat_list[i*cols + j]
|
||||
value = cls._sympify(value)
|
||||
if value != cls.zero:
|
||||
smat[i, j] = value
|
||||
|
||||
if rows is None: # autosizing
|
||||
keys = smat.keys()
|
||||
rows = max(r for r, _ in keys) + 1 if keys else 0
|
||||
cols = max(c for _, c in keys) + 1 if keys else 0
|
||||
|
||||
else:
|
||||
for i, j in smat.keys():
|
||||
if i and i >= rows or j and j >= cols:
|
||||
raise ValueError(
|
||||
"The location {} is out of the designated range"
|
||||
"[{}, {}]x[{}, {}]"
|
||||
.format((i, j), 0, rows - 1, 0, cols - 1)
|
||||
)
|
||||
|
||||
return rows, cols, smat
|
||||
|
||||
elif len(args) == 1 and isinstance(args[0], (list, tuple)):
|
||||
# list of values or lists
|
||||
v = args[0]
|
||||
c = 0
|
||||
for i, row in enumerate(v):
|
||||
if not isinstance(row, (list, tuple)):
|
||||
row = [row]
|
||||
for j, vv in enumerate(row):
|
||||
if vv != cls.zero:
|
||||
smat[i, j] = cls._sympify(vv)
|
||||
c = max(c, len(row))
|
||||
rows = len(v) if c else 0
|
||||
cols = c
|
||||
return rows, cols, smat
|
||||
|
||||
else:
|
||||
# handle full matrix forms with _handle_creation_inputs
|
||||
rows, cols, mat = super()._handle_creation_inputs(*args)
|
||||
for i in range(rows):
|
||||
for j in range(cols):
|
||||
value = mat[cols*i + j]
|
||||
if value != cls.zero:
|
||||
smat[i, j] = value
|
||||
|
||||
return rows, cols, smat
|
||||
|
||||
@property
|
||||
def _smat(self):
|
||||
|
||||
sympy_deprecation_warning(
|
||||
"""
|
||||
The private _smat attribute of SparseMatrix is deprecated. Use the
|
||||
.todok() method instead.
|
||||
""",
|
||||
deprecated_since_version="1.9",
|
||||
active_deprecations_target="deprecated-private-matrix-attributes"
|
||||
)
|
||||
|
||||
return self.todok()
|
||||
|
||||
def _eval_inverse(self, **kwargs):
|
||||
return self.inv(method=kwargs.get('method', 'LDL'),
|
||||
iszerofunc=kwargs.get('iszerofunc', _iszero),
|
||||
try_block_diag=kwargs.get('try_block_diag', False))
|
||||
|
||||
def applyfunc(self, f):
|
||||
"""Apply a function to each element of the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import SparseMatrix
|
||||
>>> m = SparseMatrix(2, 2, lambda i, j: i*2+j)
|
||||
>>> m
|
||||
Matrix([
|
||||
[0, 1],
|
||||
[2, 3]])
|
||||
>>> m.applyfunc(lambda i: 2*i)
|
||||
Matrix([
|
||||
[0, 2],
|
||||
[4, 6]])
|
||||
|
||||
"""
|
||||
if not callable(f):
|
||||
raise TypeError("`f` must be callable.")
|
||||
|
||||
# XXX: This only applies the function to the nonzero elements of the
|
||||
# matrix so is inconsistent with DenseMatrix.applyfunc e.g.
|
||||
# zeros(2, 2).applyfunc(lambda x: x + 1)
|
||||
dok = {}
|
||||
for k, v in self.todok().items():
|
||||
fv = f(v)
|
||||
if fv != 0:
|
||||
dok[k] = fv
|
||||
|
||||
return self._new(self.rows, self.cols, dok)
|
||||
|
||||
def as_immutable(self):
|
||||
"""Returns an Immutable version of this Matrix."""
|
||||
from .immutable import ImmutableSparseMatrix
|
||||
return ImmutableSparseMatrix(self)
|
||||
|
||||
def as_mutable(self):
|
||||
"""Returns a mutable version of this matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import ImmutableMatrix
|
||||
>>> X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
>>> Y = X.as_mutable()
|
||||
>>> Y[1, 1] = 5 # Can set values in Y
|
||||
>>> Y
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[3, 5]])
|
||||
"""
|
||||
return MutableSparseMatrix(self)
|
||||
|
||||
def col_list(self):
|
||||
"""Returns a column-sorted list of non-zero elements of the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import SparseMatrix
|
||||
>>> a=SparseMatrix(((1, 2), (3, 4)))
|
||||
>>> a
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[3, 4]])
|
||||
>>> a.CL
|
||||
[(0, 0, 1), (1, 0, 3), (0, 1, 2), (1, 1, 4)]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.sparse.SparseMatrix.row_list
|
||||
"""
|
||||
return [tuple(k + (self[k],)) for k in sorted(self.todok().keys(), key=lambda k: list(reversed(k)))]
|
||||
|
||||
def nnz(self):
|
||||
"""Returns the number of non-zero elements in Matrix."""
|
||||
return len(self.todok())
|
||||
|
||||
def row_list(self):
|
||||
"""Returns a row-sorted list of non-zero elements of the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import SparseMatrix
|
||||
>>> a = SparseMatrix(((1, 2), (3, 4)))
|
||||
>>> a
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[3, 4]])
|
||||
>>> a.RL
|
||||
[(0, 0, 1), (0, 1, 2), (1, 0, 3), (1, 1, 4)]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
sympy.matrices.sparse.SparseMatrix.col_list
|
||||
"""
|
||||
return [tuple(k + (self[k],)) for k in
|
||||
sorted(self.todok().keys(), key=list)]
|
||||
|
||||
def scalar_multiply(self, scalar):
|
||||
"Scalar element-wise multiplication"
|
||||
return scalar * self
|
||||
|
||||
def solve_least_squares(self, rhs, method='LDL'):
|
||||
"""Return the least-square fit to the data.
|
||||
|
||||
By default the cholesky_solve routine is used (method='CH'); other
|
||||
methods of matrix inversion can be used. To find out which are
|
||||
available, see the docstring of the .inv() method.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import SparseMatrix, Matrix, ones
|
||||
>>> A = Matrix([1, 2, 3])
|
||||
>>> B = Matrix([2, 3, 4])
|
||||
>>> S = SparseMatrix(A.row_join(B))
|
||||
>>> S
|
||||
Matrix([
|
||||
[1, 2],
|
||||
[2, 3],
|
||||
[3, 4]])
|
||||
|
||||
If each line of S represent coefficients of Ax + By
|
||||
and x and y are [2, 3] then S*xy is:
|
||||
|
||||
>>> r = S*Matrix([2, 3]); r
|
||||
Matrix([
|
||||
[ 8],
|
||||
[13],
|
||||
[18]])
|
||||
|
||||
But let's add 1 to the middle value and then solve for the
|
||||
least-squares value of xy:
|
||||
|
||||
>>> xy = S.solve_least_squares(Matrix([8, 14, 18])); xy
|
||||
Matrix([
|
||||
[ 5/3],
|
||||
[10/3]])
|
||||
|
||||
The error is given by S*xy - r:
|
||||
|
||||
>>> S*xy - r
|
||||
Matrix([
|
||||
[1/3],
|
||||
[1/3],
|
||||
[1/3]])
|
||||
>>> _.norm().n(2)
|
||||
0.58
|
||||
|
||||
If a different xy is used, the norm will be higher:
|
||||
|
||||
>>> xy += ones(2, 1)/10
|
||||
>>> (S*xy - r).norm().n(2)
|
||||
1.5
|
||||
|
||||
"""
|
||||
t = self.T
|
||||
return (t*self).inv(method=method)*t*rhs
|
||||
|
||||
def solve(self, rhs, method='LDL'):
|
||||
"""Return solution to self*soln = rhs using given inversion method.
|
||||
|
||||
For a list of possible inversion methods, see the .inv() docstring.
|
||||
"""
|
||||
if not self.is_square:
|
||||
if self.rows < self.cols:
|
||||
raise ValueError('Under-determined system.')
|
||||
elif self.rows > self.cols:
|
||||
raise ValueError('For over-determined system, M, having '
|
||||
'more rows than columns, try M.solve_least_squares(rhs).')
|
||||
else:
|
||||
return self.inv(method=method).multiply(rhs)
|
||||
|
||||
RL = property(row_list, None, None, "Alternate faster representation")
|
||||
CL = property(col_list, None, None, "Alternate faster representation")
|
||||
|
||||
def liupc(self):
|
||||
return _liupc(self)
|
||||
|
||||
def row_structure_symbolic_cholesky(self):
|
||||
return _row_structure_symbolic_cholesky(self)
|
||||
|
||||
def cholesky(self, hermitian=True):
|
||||
return _cholesky_sparse(self, hermitian=hermitian)
|
||||
|
||||
def LDLdecomposition(self, hermitian=True):
|
||||
return _LDLdecomposition_sparse(self, hermitian=hermitian)
|
||||
|
||||
def lower_triangular_solve(self, rhs):
|
||||
return _lower_triangular_solve_sparse(self, rhs)
|
||||
|
||||
def upper_triangular_solve(self, rhs):
|
||||
return _upper_triangular_solve_sparse(self, rhs)
|
||||
|
||||
liupc.__doc__ = _liupc.__doc__
|
||||
row_structure_symbolic_cholesky.__doc__ = _row_structure_symbolic_cholesky.__doc__
|
||||
cholesky.__doc__ = _cholesky_sparse.__doc__
|
||||
LDLdecomposition.__doc__ = _LDLdecomposition_sparse.__doc__
|
||||
lower_triangular_solve.__doc__ = lower_triangular_solve.__doc__
|
||||
upper_triangular_solve.__doc__ = upper_triangular_solve.__doc__
|
||||
|
||||
|
||||
class MutableSparseMatrix(SparseRepMatrix, MutableRepMatrix):
|
||||
|
||||
@classmethod
|
||||
def _new(cls, *args, **kwargs):
|
||||
rows, cols, smat = cls._handle_creation_inputs(*args, **kwargs)
|
||||
|
||||
rep = cls._smat_to_DomainMatrix(rows, cols, smat)
|
||||
|
||||
return cls._fromrep(rep)
|
||||
|
||||
|
||||
SparseMatrix = MutableSparseMatrix
|
||||
@@ -0,0 +1,300 @@
|
||||
from sympy.core.containers import Dict
|
||||
from sympy.core.symbol import Dummy
|
||||
from sympy.utilities.iterables import is_sequence
|
||||
from sympy.utilities.misc import as_int, filldedent
|
||||
|
||||
from .sparse import MutableSparseMatrix as SparseMatrix
|
||||
|
||||
|
||||
def _doktocsr(dok):
|
||||
"""Converts a sparse matrix to Compressed Sparse Row (CSR) format.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
A : contains non-zero elements sorted by key (row, column)
|
||||
JA : JA[i] is the column corresponding to A[i]
|
||||
IA : IA[i] contains the index in A for the first non-zero element
|
||||
of row[i]. Thus IA[i+1] - IA[i] gives number of non-zero
|
||||
elements row[i]. The length of IA is always 1 more than the
|
||||
number of rows in the matrix.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices.sparsetools import _doktocsr
|
||||
>>> from sympy import SparseMatrix, diag
|
||||
>>> m = SparseMatrix(diag(1, 2, 3))
|
||||
>>> m[2, 0] = -1
|
||||
>>> _doktocsr(m)
|
||||
[[1, 2, -1, 3], [0, 1, 0, 2], [0, 1, 2, 4], [3, 3]]
|
||||
|
||||
"""
|
||||
row, JA, A = [list(i) for i in zip(*dok.row_list())]
|
||||
IA = [0]*((row[0] if row else 0) + 1)
|
||||
for i, r in enumerate(row):
|
||||
IA.extend([i]*(r - row[i - 1])) # if i = 0 nothing is extended
|
||||
IA.extend([len(A)]*(dok.rows - len(IA) + 1))
|
||||
shape = [dok.rows, dok.cols]
|
||||
return [A, JA, IA, shape]
|
||||
|
||||
|
||||
def _csrtodok(csr):
|
||||
"""Converts a CSR representation to DOK representation.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy.matrices.sparsetools import _csrtodok
|
||||
>>> _csrtodok([[5, 8, 3, 6], [0, 1, 2, 1], [0, 0, 2, 3, 4], [4, 3]])
|
||||
Matrix([
|
||||
[0, 0, 0],
|
||||
[5, 8, 0],
|
||||
[0, 0, 3],
|
||||
[0, 6, 0]])
|
||||
|
||||
"""
|
||||
smat = {}
|
||||
A, JA, IA, shape = csr
|
||||
for i in range(len(IA) - 1):
|
||||
indices = slice(IA[i], IA[i + 1])
|
||||
for l, m in zip(A[indices], JA[indices]):
|
||||
smat[i, m] = l
|
||||
return SparseMatrix(*shape, smat)
|
||||
|
||||
|
||||
def banded(*args, **kwargs):
|
||||
"""Returns a SparseMatrix from the given dictionary describing
|
||||
the diagonals of the matrix. The keys are positive for upper
|
||||
diagonals and negative for those below the main diagonal. The
|
||||
values may be:
|
||||
|
||||
* expressions or single-argument functions,
|
||||
|
||||
* lists or tuples of values,
|
||||
|
||||
* matrices
|
||||
|
||||
Unless dimensions are given, the size of the returned matrix will
|
||||
be large enough to contain the largest non-zero value provided.
|
||||
|
||||
kwargs
|
||||
======
|
||||
|
||||
rows : rows of the resulting matrix; computed if
|
||||
not given.
|
||||
|
||||
cols : columns of the resulting matrix; computed if
|
||||
not given.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import banded, ones, Matrix
|
||||
>>> from sympy.abc import x
|
||||
|
||||
If explicit values are given in tuples,
|
||||
the matrix will autosize to contain all values, otherwise
|
||||
a single value is filled onto the entire diagonal:
|
||||
|
||||
>>> banded({1: (1, 2, 3), -1: (4, 5, 6), 0: x})
|
||||
Matrix([
|
||||
[x, 1, 0, 0],
|
||||
[4, x, 2, 0],
|
||||
[0, 5, x, 3],
|
||||
[0, 0, 6, x]])
|
||||
|
||||
A function accepting a single argument can be used to fill the
|
||||
diagonal as a function of diagonal index (which starts at 0).
|
||||
The size (or shape) of the matrix must be given to obtain more
|
||||
than a 1x1 matrix:
|
||||
|
||||
>>> s = lambda d: (1 + d)**2
|
||||
>>> banded(5, {0: s, 2: s, -2: 2})
|
||||
Matrix([
|
||||
[1, 0, 1, 0, 0],
|
||||
[0, 4, 0, 4, 0],
|
||||
[2, 0, 9, 0, 9],
|
||||
[0, 2, 0, 16, 0],
|
||||
[0, 0, 2, 0, 25]])
|
||||
|
||||
The diagonal of matrices placed on a diagonal will coincide
|
||||
with the indicated diagonal:
|
||||
|
||||
>>> vert = Matrix([1, 2, 3])
|
||||
>>> banded({0: vert}, cols=3)
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[2, 1, 0],
|
||||
[3, 2, 1],
|
||||
[0, 3, 2],
|
||||
[0, 0, 3]])
|
||||
|
||||
>>> banded(4, {0: ones(2)})
|
||||
Matrix([
|
||||
[1, 1, 0, 0],
|
||||
[1, 1, 0, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 1, 1]])
|
||||
|
||||
Errors are raised if the designated size will not hold
|
||||
all values an integral number of times. Here, the rows
|
||||
are designated as odd (but an even number is required to
|
||||
hold the off-diagonal 2x2 ones):
|
||||
|
||||
>>> banded({0: 2, 1: ones(2)}, rows=5)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError:
|
||||
sequence does not fit an integral number of times in the matrix
|
||||
|
||||
And here, an even number of rows is given...but the square
|
||||
matrix has an even number of columns, too. As we saw
|
||||
in the previous example, an odd number is required:
|
||||
|
||||
>>> banded(4, {0: 2, 1: ones(2)}) # trying to make 4x4 and cols must be odd
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError:
|
||||
sequence does not fit an integral number of times in the matrix
|
||||
|
||||
A way around having to count rows is to enclosing matrix elements
|
||||
in a tuple and indicate the desired number of them to the right:
|
||||
|
||||
>>> banded({0: 2, 2: (ones(2),)*3})
|
||||
Matrix([
|
||||
[2, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 2, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 2, 0, 1, 1, 0, 0],
|
||||
[0, 0, 0, 2, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 2, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0, 2, 1, 1]])
|
||||
|
||||
An error will be raised if more than one value
|
||||
is written to a given entry. Here, the ones overlap
|
||||
with the main diagonal if they are placed on the
|
||||
first diagonal:
|
||||
|
||||
>>> banded({0: (2,)*5, 1: (ones(2),)*3})
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: collision at (1, 1)
|
||||
|
||||
By placing a 0 at the bottom left of the 2x2 matrix of
|
||||
ones, the collision is avoided:
|
||||
|
||||
>>> u2 = Matrix([
|
||||
... [1, 1],
|
||||
... [0, 1]])
|
||||
>>> banded({0: [2]*5, 1: [u2]*3})
|
||||
Matrix([
|
||||
[2, 1, 1, 0, 0, 0, 0],
|
||||
[0, 2, 1, 0, 0, 0, 0],
|
||||
[0, 0, 2, 1, 1, 0, 0],
|
||||
[0, 0, 0, 2, 1, 0, 0],
|
||||
[0, 0, 0, 0, 2, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 1]])
|
||||
"""
|
||||
try:
|
||||
if len(args) not in (1, 2, 3):
|
||||
raise TypeError
|
||||
if not isinstance(args[-1], (dict, Dict)):
|
||||
raise TypeError
|
||||
if len(args) == 1:
|
||||
rows = kwargs.get('rows', None)
|
||||
cols = kwargs.get('cols', None)
|
||||
if rows is not None:
|
||||
rows = as_int(rows)
|
||||
if cols is not None:
|
||||
cols = as_int(cols)
|
||||
elif len(args) == 2:
|
||||
rows = cols = as_int(args[0])
|
||||
else:
|
||||
rows, cols = map(as_int, args[:2])
|
||||
# fails with ValueError if any keys are not ints
|
||||
_ = all(as_int(k) for k in args[-1])
|
||||
except (ValueError, TypeError):
|
||||
raise TypeError(filldedent(
|
||||
'''unrecognized input to banded:
|
||||
expecting [[row,] col,] {int: value}'''))
|
||||
def rc(d):
|
||||
# return row,col coord of diagonal start
|
||||
r = -d if d < 0 else 0
|
||||
c = 0 if r else d
|
||||
return r, c
|
||||
smat = {}
|
||||
undone = []
|
||||
tba = Dummy()
|
||||
# first handle objects with size
|
||||
for d, v in args[-1].items():
|
||||
r, c = rc(d)
|
||||
# note: only list and tuple are recognized since this
|
||||
# will allow other Basic objects like Tuple
|
||||
# into the matrix if so desired
|
||||
if isinstance(v, (list, tuple)):
|
||||
extra = 0
|
||||
for i, vi in enumerate(v):
|
||||
i += extra
|
||||
if is_sequence(vi):
|
||||
vi = SparseMatrix(vi)
|
||||
smat[r + i, c + i] = vi
|
||||
extra += min(vi.shape) - 1
|
||||
else:
|
||||
smat[r + i, c + i] = vi
|
||||
elif is_sequence(v):
|
||||
v = SparseMatrix(v)
|
||||
rv, cv = v.shape
|
||||
if rows and cols:
|
||||
nr, xr = divmod(rows - r, rv)
|
||||
nc, xc = divmod(cols - c, cv)
|
||||
x = xr or xc
|
||||
do = min(nr, nc)
|
||||
elif rows:
|
||||
do, x = divmod(rows - r, rv)
|
||||
elif cols:
|
||||
do, x = divmod(cols - c, cv)
|
||||
else:
|
||||
do = 1
|
||||
x = 0
|
||||
if x:
|
||||
raise ValueError(filldedent('''
|
||||
sequence does not fit an integral number of times
|
||||
in the matrix'''))
|
||||
j = min(v.shape)
|
||||
for i in range(do):
|
||||
smat[r, c] = v
|
||||
r += j
|
||||
c += j
|
||||
elif v:
|
||||
smat[r, c] = tba
|
||||
undone.append((d, v))
|
||||
s = SparseMatrix(None, smat) # to expand matrices
|
||||
smat = s.todok()
|
||||
# check for dim errors here
|
||||
if rows is not None and rows < s.rows:
|
||||
raise ValueError('Designated rows %s < needed %s' % (rows, s.rows))
|
||||
if cols is not None and cols < s.cols:
|
||||
raise ValueError('Designated cols %s < needed %s' % (cols, s.cols))
|
||||
if rows is cols is None:
|
||||
rows = s.rows
|
||||
cols = s.cols
|
||||
elif rows is not None and cols is None:
|
||||
cols = max(rows, s.cols)
|
||||
elif cols is not None and rows is None:
|
||||
rows = max(cols, s.rows)
|
||||
def update(i, j, v):
|
||||
# update smat and make sure there are
|
||||
# no collisions
|
||||
if v:
|
||||
if (i, j) in smat and smat[i, j] not in (tba, v):
|
||||
raise ValueError('collision at %s' % ((i, j),))
|
||||
smat[i, j] = v
|
||||
if undone:
|
||||
for d, vi in undone:
|
||||
r, c = rc(d)
|
||||
v = vi if callable(vi) else lambda _: vi
|
||||
i = 0
|
||||
while r + i < rows and c + i < cols:
|
||||
update(r + i, c + i, v(i))
|
||||
i += 1
|
||||
return SparseMatrix(rows, cols, smat)
|
||||
@@ -0,0 +1,174 @@
|
||||
from .utilities import _iszero
|
||||
|
||||
|
||||
def _columnspace(M, simplify=False):
|
||||
"""Returns a list of vectors (Matrix objects) that span columnspace of ``M``
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6])
|
||||
>>> M
|
||||
Matrix([
|
||||
[ 1, 3, 0],
|
||||
[-2, -6, 0],
|
||||
[ 3, 9, 6]])
|
||||
>>> M.columnspace()
|
||||
[Matrix([
|
||||
[ 1],
|
||||
[-2],
|
||||
[ 3]]), Matrix([
|
||||
[0],
|
||||
[0],
|
||||
[6]])]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
nullspace
|
||||
rowspace
|
||||
"""
|
||||
|
||||
reduced, pivots = M.echelon_form(simplify=simplify, with_pivots=True)
|
||||
|
||||
return [M.col(i) for i in pivots]
|
||||
|
||||
|
||||
def _nullspace(M, simplify=False, iszerofunc=_iszero):
|
||||
"""Returns list of vectors (Matrix objects) that span nullspace of ``M``
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6])
|
||||
>>> M
|
||||
Matrix([
|
||||
[ 1, 3, 0],
|
||||
[-2, -6, 0],
|
||||
[ 3, 9, 6]])
|
||||
>>> M.nullspace()
|
||||
[Matrix([
|
||||
[-3],
|
||||
[ 1],
|
||||
[ 0]])]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
columnspace
|
||||
rowspace
|
||||
"""
|
||||
|
||||
reduced, pivots = M.rref(iszerofunc=iszerofunc, simplify=simplify)
|
||||
|
||||
free_vars = [i for i in range(M.cols) if i not in pivots]
|
||||
basis = []
|
||||
|
||||
for free_var in free_vars:
|
||||
# for each free variable, we will set it to 1 and all others
|
||||
# to 0. Then, we will use back substitution to solve the system
|
||||
vec = [M.zero] * M.cols
|
||||
vec[free_var] = M.one
|
||||
|
||||
for piv_row, piv_col in enumerate(pivots):
|
||||
vec[piv_col] -= reduced[piv_row, free_var]
|
||||
|
||||
basis.append(vec)
|
||||
|
||||
return [M._new(M.cols, 1, b) for b in basis]
|
||||
|
||||
|
||||
def _rowspace(M, simplify=False):
|
||||
"""Returns a list of vectors that span the row space of ``M``.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import Matrix
|
||||
>>> M = Matrix(3, 3, [1, 3, 0, -2, -6, 0, 3, 9, 6])
|
||||
>>> M
|
||||
Matrix([
|
||||
[ 1, 3, 0],
|
||||
[-2, -6, 0],
|
||||
[ 3, 9, 6]])
|
||||
>>> M.rowspace()
|
||||
[Matrix([[1, 3, 0]]), Matrix([[0, 0, 6]])]
|
||||
"""
|
||||
|
||||
reduced, pivots = M.echelon_form(simplify=simplify, with_pivots=True)
|
||||
|
||||
return [reduced.row(i) for i in range(len(pivots))]
|
||||
|
||||
|
||||
def _orthogonalize(cls, *vecs, normalize=False, rankcheck=False):
|
||||
"""Apply the Gram-Schmidt orthogonalization procedure
|
||||
to vectors supplied in ``vecs``.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
vecs
|
||||
vectors to be made orthogonal
|
||||
|
||||
normalize : bool
|
||||
If ``True``, return an orthonormal basis.
|
||||
|
||||
rankcheck : bool
|
||||
If ``True``, the computation does not stop when encountering
|
||||
linearly dependent vectors.
|
||||
|
||||
If ``False``, it will raise ``ValueError`` when any zero
|
||||
or linearly dependent vectors are found.
|
||||
|
||||
Returns
|
||||
=======
|
||||
|
||||
list
|
||||
List of orthogonal (or orthonormal) basis vectors.
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
>>> from sympy import I, Matrix
|
||||
>>> v = [Matrix([1, I]), Matrix([1, -I])]
|
||||
>>> Matrix.orthogonalize(*v)
|
||||
[Matrix([
|
||||
[1],
|
||||
[I]]), Matrix([
|
||||
[ 1],
|
||||
[-I]])]
|
||||
|
||||
See Also
|
||||
========
|
||||
|
||||
MatrixBase.QRdecomposition
|
||||
|
||||
References
|
||||
==========
|
||||
|
||||
.. [1] https://en.wikipedia.org/wiki/Gram%E2%80%93Schmidt_process
|
||||
"""
|
||||
from .decompositions import _QRdecomposition_optional
|
||||
|
||||
if not vecs:
|
||||
return []
|
||||
|
||||
all_row_vecs = (vecs[0].rows == 1)
|
||||
|
||||
vecs = [x.vec() for x in vecs]
|
||||
M = cls.hstack(*vecs)
|
||||
Q, R = _QRdecomposition_optional(M, normalize=normalize)
|
||||
|
||||
if rankcheck and Q.cols < len(vecs):
|
||||
raise ValueError("GramSchmidt: vector set not linearly independent")
|
||||
|
||||
ret = []
|
||||
for i in range(Q.cols):
|
||||
if all_row_vecs:
|
||||
col = cls(Q[:, i].T)
|
||||
else:
|
||||
col = cls(Q[:, i])
|
||||
ret.append(col)
|
||||
return ret
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,474 @@
|
||||
from sympy.core.function import expand_mul
|
||||
from sympy.core.numbers import I, Rational
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.matrices import Matrix, zeros, eye, SparseMatrix
|
||||
from sympy.abc import x, y, z
|
||||
from sympy.testing.pytest import raises, slow
|
||||
from sympy.testing.matrices import allclose
|
||||
|
||||
|
||||
def test_LUdecomp():
|
||||
testmat = Matrix([[0, 2, 5, 3],
|
||||
[3, 3, 7, 4],
|
||||
[8, 4, 0, 2],
|
||||
[-2, 6, 3, 4]])
|
||||
L, U, p = testmat.LUdecomposition()
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - testmat == zeros(4)
|
||||
|
||||
testmat = Matrix([[6, -2, 7, 4],
|
||||
[0, 3, 6, 7],
|
||||
[1, -2, 7, 4],
|
||||
[-9, 2, 6, 3]])
|
||||
L, U, p = testmat.LUdecomposition()
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - testmat == zeros(4)
|
||||
|
||||
# non-square
|
||||
testmat = Matrix([[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9],
|
||||
[10, 11, 12]])
|
||||
L, U, p = testmat.LUdecomposition(rankcheck=False)
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - testmat == zeros(4, 3)
|
||||
|
||||
# square and singular
|
||||
testmat = Matrix([[1, 2, 3],
|
||||
[2, 4, 6],
|
||||
[4, 5, 6]])
|
||||
L, U, p = testmat.LUdecomposition(rankcheck=False)
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - testmat == zeros(3)
|
||||
|
||||
M = Matrix(((1, x, 1), (2, y, 0), (y, 0, z)))
|
||||
L, U, p = M.LUdecomposition()
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - M == zeros(3)
|
||||
|
||||
mL = Matrix((
|
||||
(1, 0, 0),
|
||||
(2, 3, 0),
|
||||
))
|
||||
assert mL.is_lower is True
|
||||
assert mL.is_upper is False
|
||||
mU = Matrix((
|
||||
(1, 2, 3),
|
||||
(0, 4, 5),
|
||||
))
|
||||
assert mU.is_lower is False
|
||||
assert mU.is_upper is True
|
||||
|
||||
# test FF LUdecomp
|
||||
M = Matrix([[1, 3, 3],
|
||||
[3, 2, 6],
|
||||
[3, 2, 2]])
|
||||
P, L, Dee, U = M.LUdecompositionFF()
|
||||
assert P*M == L*Dee.inv()*U
|
||||
|
||||
M = Matrix([[1, 2, 3, 4],
|
||||
[3, -1, 2, 3],
|
||||
[3, 1, 3, -2],
|
||||
[6, -1, 0, 2]])
|
||||
P, L, Dee, U = M.LUdecompositionFF()
|
||||
assert P*M == L*Dee.inv()*U
|
||||
|
||||
M = Matrix([[0, 0, 1],
|
||||
[2, 3, 0],
|
||||
[3, 1, 4]])
|
||||
P, L, Dee, U = M.LUdecompositionFF()
|
||||
assert P*M == L*Dee.inv()*U
|
||||
|
||||
# issue 15794
|
||||
M = Matrix(
|
||||
[[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
[7, 8, 9]]
|
||||
)
|
||||
raises(ValueError, lambda : M.LUdecomposition_Simple(rankcheck=True))
|
||||
|
||||
def test_singular_value_decompositionD():
|
||||
A = Matrix([[1, 2], [2, 1]])
|
||||
U, S, V = A.singular_value_decomposition()
|
||||
assert U * S * V.T == A
|
||||
assert U.T * U == eye(U.cols)
|
||||
assert V.T * V == eye(V.cols)
|
||||
|
||||
B = Matrix([[1, 2]])
|
||||
U, S, V = B.singular_value_decomposition()
|
||||
|
||||
assert U * S * V.T == B
|
||||
assert U.T * U == eye(U.cols)
|
||||
assert V.T * V == eye(V.cols)
|
||||
|
||||
C = Matrix([
|
||||
[1, 0, 0, 0, 2],
|
||||
[0, 0, 3, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 2, 0, 0, 0],
|
||||
])
|
||||
|
||||
U, S, V = C.singular_value_decomposition()
|
||||
|
||||
assert U * S * V.T == C
|
||||
assert U.T * U == eye(U.cols)
|
||||
assert V.T * V == eye(V.cols)
|
||||
|
||||
D = Matrix([[Rational(1, 3), sqrt(2)], [0, Rational(1, 4)]])
|
||||
U, S, V = D.singular_value_decomposition()
|
||||
assert simplify(U.T * U) == eye(U.cols)
|
||||
assert simplify(V.T * V) == eye(V.cols)
|
||||
assert simplify(U * S * V.T) == D
|
||||
|
||||
|
||||
def test_QR():
|
||||
A = Matrix([[1, 2], [2, 3]])
|
||||
Q, S = A.QRdecomposition()
|
||||
R = Rational
|
||||
assert Q == Matrix([
|
||||
[ 5**R(-1, 2), (R(2)/5)*(R(1)/5)**R(-1, 2)],
|
||||
[2*5**R(-1, 2), (-R(1)/5)*(R(1)/5)**R(-1, 2)]])
|
||||
assert S == Matrix([[5**R(1, 2), 8*5**R(-1, 2)], [0, (R(1)/5)**R(1, 2)]])
|
||||
assert Q*S == A
|
||||
assert Q.T * Q == eye(2)
|
||||
|
||||
A = Matrix([[1, 1, 1], [1, 1, 3], [2, 3, 4]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[12, 0, -51], [6, 0, 167], [-4, 0, 24]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
x = Symbol('x')
|
||||
A = Matrix([x])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q == Matrix([x / Abs(x)])
|
||||
assert R == Matrix([Abs(x)])
|
||||
|
||||
A = Matrix([[x, 0], [0, x]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q == x / Abs(x) * Matrix([[1, 0], [0, 1]])
|
||||
assert R == Abs(x) * Matrix([[1, 0], [0, 1]])
|
||||
|
||||
|
||||
def test_QR_non_square():
|
||||
# Narrow (cols < rows) matrices
|
||||
A = Matrix([[9, 0, 26], [12, 0, -7], [0, 4, 4], [0, -3, -3]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[1, -1, 4], [1, 4, -2], [1, 4, 2], [1, -1, 0]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix(2, 1, [1, 2])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
# Wide (cols > rows) matrices
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[1, 2, 3, 4], [1, 4, 9, 16], [1, 8, 27, 64]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix(1, 2, [1, 2])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
def test_QR_trivial():
|
||||
# Rank deficient matrices
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
# Zero rank matrices
|
||||
A = Matrix([[0, 0, 0]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[0, 0, 0]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[0, 0, 0], [0, 0, 0]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[0, 0, 0], [0, 0, 0]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
# Rank deficient matrices with zero norm from beginning columns
|
||||
A = Matrix([[0, 0, 0], [1, 2, 3]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[0, 0, 0, 0], [1, 2, 3, 4], [0, 0, 0, 0]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[0, 0, 0, 0], [1, 2, 3, 4], [0, 0, 0, 0], [2, 4, 6, 8]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
A = Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0], [1, 2, 3]]).T
|
||||
Q, R = A.QRdecomposition()
|
||||
assert Q.T * Q == eye(Q.cols)
|
||||
assert R.is_upper
|
||||
assert A == Q*R
|
||||
|
||||
|
||||
def test_QR_float():
|
||||
A = Matrix([[1, 1], [1, 1.01]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert allclose(Q * R, A)
|
||||
assert allclose(Q * Q.T, Matrix.eye(2))
|
||||
assert allclose(Q.T * Q, Matrix.eye(2))
|
||||
|
||||
A = Matrix([[1, 1], [1, 1.001]])
|
||||
Q, R = A.QRdecomposition()
|
||||
assert allclose(Q * R, A)
|
||||
assert allclose(Q * Q.T, Matrix.eye(2))
|
||||
assert allclose(Q.T * Q, Matrix.eye(2))
|
||||
|
||||
|
||||
def test_LUdecomposition_Simple_iszerofunc():
|
||||
# Test if callable passed to matrices.LUdecomposition_Simple() as iszerofunc keyword argument is used inside
|
||||
# matrices.LUdecomposition_Simple()
|
||||
magic_string = "I got passed in!"
|
||||
def goofyiszero(value):
|
||||
raise ValueError(magic_string)
|
||||
|
||||
try:
|
||||
lu, p = Matrix([[1, 0], [0, 1]]).LUdecomposition_Simple(iszerofunc=goofyiszero)
|
||||
except ValueError as err:
|
||||
assert magic_string == err.args[0]
|
||||
return
|
||||
|
||||
assert False
|
||||
|
||||
def test_LUdecomposition_iszerofunc():
|
||||
# Test if callable passed to matrices.LUdecomposition() as iszerofunc keyword argument is used inside
|
||||
# matrices.LUdecomposition_Simple()
|
||||
magic_string = "I got passed in!"
|
||||
def goofyiszero(value):
|
||||
raise ValueError(magic_string)
|
||||
|
||||
try:
|
||||
l, u, p = Matrix([[1, 0], [0, 1]]).LUdecomposition(iszerofunc=goofyiszero)
|
||||
except ValueError as err:
|
||||
assert magic_string == err.args[0]
|
||||
return
|
||||
|
||||
assert False
|
||||
|
||||
def test_LDLdecomposition():
|
||||
raises(NonSquareMatrixError, lambda: Matrix((1, 2)).LDLdecomposition())
|
||||
raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).LDLdecomposition())
|
||||
raises(ValueError, lambda: Matrix(((5 + I, 0), (0, 1))).LDLdecomposition())
|
||||
raises(ValueError, lambda: Matrix(((1, 5), (5, 1))).LDLdecomposition())
|
||||
raises(ValueError, lambda: Matrix(((1, 2), (3, 4))).LDLdecomposition(hermitian=False))
|
||||
A = Matrix(((1, 5), (5, 1)))
|
||||
L, D = A.LDLdecomposition(hermitian=False)
|
||||
assert L * D * L.T == A
|
||||
A = Matrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11)))
|
||||
L, D = A.LDLdecomposition()
|
||||
assert L * D * L.T == A
|
||||
assert L.is_lower
|
||||
assert L == Matrix([[1, 0, 0], [ Rational(3, 5), 1, 0], [Rational(-1, 5), Rational(1, 3), 1]])
|
||||
assert D.is_diagonal()
|
||||
assert D == Matrix([[25, 0, 0], [0, 9, 0], [0, 0, 9]])
|
||||
A = Matrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11)))
|
||||
L, D = A.LDLdecomposition()
|
||||
assert expand_mul(L * D * L.H) == A
|
||||
assert L.expand() == Matrix([[1, 0, 0], [I/2, 1, 0], [S.Half - I/2, 0, 1]])
|
||||
assert D.expand() == Matrix(((4, 0, 0), (0, 1, 0), (0, 0, 9)))
|
||||
|
||||
raises(NonSquareMatrixError, lambda: SparseMatrix((1, 2)).LDLdecomposition())
|
||||
raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).LDLdecomposition())
|
||||
raises(ValueError, lambda: SparseMatrix(((5 + I, 0), (0, 1))).LDLdecomposition())
|
||||
raises(ValueError, lambda: SparseMatrix(((1, 5), (5, 1))).LDLdecomposition())
|
||||
raises(ValueError, lambda: SparseMatrix(((1, 2), (3, 4))).LDLdecomposition(hermitian=False))
|
||||
A = SparseMatrix(((1, 5), (5, 1)))
|
||||
L, D = A.LDLdecomposition(hermitian=False)
|
||||
assert L * D * L.T == A
|
||||
A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11)))
|
||||
L, D = A.LDLdecomposition()
|
||||
assert L * D * L.T == A
|
||||
assert L.is_lower
|
||||
assert L == Matrix([[1, 0, 0], [ Rational(3, 5), 1, 0], [Rational(-1, 5), Rational(1, 3), 1]])
|
||||
assert D.is_diagonal()
|
||||
assert D == Matrix([[25, 0, 0], [0, 9, 0], [0, 0, 9]])
|
||||
A = SparseMatrix(((4, -2*I, 2 + 2*I), (2*I, 2, -1 + I), (2 - 2*I, -1 - I, 11)))
|
||||
L, D = A.LDLdecomposition()
|
||||
assert expand_mul(L * D * L.H) == A
|
||||
assert L == Matrix(((1, 0, 0), (I/2, 1, 0), (S.Half - I/2, 0, 1)))
|
||||
assert D == Matrix(((4, 0, 0), (0, 1, 0), (0, 0, 9)))
|
||||
|
||||
def test_pinv_succeeds_with_rank_decomposition_method():
|
||||
# Test rank decomposition method of pseudoinverse succeeding
|
||||
As = [Matrix([
|
||||
[61, 89, 55, 20, 71, 0],
|
||||
[62, 96, 85, 85, 16, 0],
|
||||
[69, 56, 17, 4, 54, 0],
|
||||
[10, 54, 91, 41, 71, 0],
|
||||
[ 7, 30, 10, 48, 90, 0],
|
||||
[0,0,0,0,0,0]])]
|
||||
for A in As:
|
||||
A_pinv = A.pinv(method="RD")
|
||||
AAp = A * A_pinv
|
||||
ApA = A_pinv * A
|
||||
assert simplify(AAp * A) == A
|
||||
assert simplify(ApA * A_pinv) == A_pinv
|
||||
assert AAp.H == AAp
|
||||
assert ApA.H == ApA
|
||||
|
||||
def test_rank_decomposition():
|
||||
a = Matrix(0, 0, [])
|
||||
c, f = a.rank_decomposition()
|
||||
assert f.is_echelon
|
||||
assert c.cols == f.rows == a.rank()
|
||||
assert c * f == a
|
||||
|
||||
a = Matrix(1, 1, [5])
|
||||
c, f = a.rank_decomposition()
|
||||
assert f.is_echelon
|
||||
assert c.cols == f.rows == a.rank()
|
||||
assert c * f == a
|
||||
|
||||
a = Matrix(3, 3, [1, 2, 3, 1, 2, 3, 1, 2, 3])
|
||||
c, f = a.rank_decomposition()
|
||||
assert f.is_echelon
|
||||
assert c.cols == f.rows == a.rank()
|
||||
assert c * f == a
|
||||
|
||||
a = Matrix([
|
||||
[0, 0, 1, 2, 2, -5, 3],
|
||||
[-1, 5, 2, 2, 1, -7, 5],
|
||||
[0, 0, -2, -3, -3, 8, -5],
|
||||
[-1, 5, 0, -1, -2, 1, 0]])
|
||||
c, f = a.rank_decomposition()
|
||||
assert f.is_echelon
|
||||
assert c.cols == f.rows == a.rank()
|
||||
assert c * f == a
|
||||
|
||||
|
||||
@slow
|
||||
def test_upper_hessenberg_decomposition():
|
||||
A = Matrix([
|
||||
[1, 0, sqrt(3)],
|
||||
[sqrt(2), Rational(1, 2), 2],
|
||||
[1, Rational(1, 4), 3],
|
||||
])
|
||||
H, P = A.upper_hessenberg_decomposition()
|
||||
assert simplify(P * P.H) == eye(P.cols)
|
||||
assert simplify(P.H * P) == eye(P.cols)
|
||||
assert H.is_upper_hessenberg
|
||||
assert (simplify(P * H * P.H)) == A
|
||||
|
||||
|
||||
B = Matrix([
|
||||
[1, 2, 10],
|
||||
[8, 2, 5],
|
||||
[3, 12, 34],
|
||||
])
|
||||
H, P = B.upper_hessenberg_decomposition()
|
||||
assert simplify(P * P.H) == eye(P.cols)
|
||||
assert simplify(P.H * P) == eye(P.cols)
|
||||
assert H.is_upper_hessenberg
|
||||
assert simplify(P * H * P.H) == B
|
||||
|
||||
C = Matrix([
|
||||
[1, sqrt(2), 2, 3],
|
||||
[0, 5, 3, 4],
|
||||
[1, 1, 4, sqrt(5)],
|
||||
[0, 2, 2, 3]
|
||||
])
|
||||
|
||||
H, P = C.upper_hessenberg_decomposition()
|
||||
assert simplify(P * P.H) == eye(P.cols)
|
||||
assert simplify(P.H * P) == eye(P.cols)
|
||||
assert H.is_upper_hessenberg
|
||||
assert simplify(P * H * P.H) == C
|
||||
|
||||
D = Matrix([
|
||||
[1, 2, 3],
|
||||
[-3, 5, 6],
|
||||
[4, -8, 9],
|
||||
])
|
||||
H, P = D.upper_hessenberg_decomposition()
|
||||
assert simplify(P * P.H) == eye(P.cols)
|
||||
assert simplify(P.H * P) == eye(P.cols)
|
||||
assert H.is_upper_hessenberg
|
||||
assert simplify(P * H * P.H) == D
|
||||
|
||||
E = Matrix([
|
||||
[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[1, 1, 0, 1],
|
||||
[1, 1, 1, 0]
|
||||
])
|
||||
|
||||
H, P = E.upper_hessenberg_decomposition()
|
||||
assert simplify(P * P.H) == eye(P.cols)
|
||||
assert simplify(P.H * P) == eye(P.cols)
|
||||
assert H.is_upper_hessenberg
|
||||
assert simplify(P * H * P.H) == E
|
||||
@@ -0,0 +1,280 @@
|
||||
import random
|
||||
import pytest
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.polys.polytools import Poly
|
||||
from sympy.matrices import Matrix, eye, ones
|
||||
from sympy.abc import x, y, z
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError
|
||||
from sympy.functions.combinatorial.factorials import factorial, subfactorial
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", [
|
||||
# Evaluating these directly because they are never reached via M.det()
|
||||
Matrix._eval_det_bareiss, Matrix._eval_det_berkowitz,
|
||||
Matrix._eval_det_bird, Matrix._eval_det_laplace, Matrix._eval_det_lu
|
||||
])
|
||||
@pytest.mark.parametrize("M, sol", [
|
||||
(Matrix(), 1),
|
||||
(Matrix([[0]]), 0),
|
||||
(Matrix([[5]]), 5),
|
||||
])
|
||||
def test_eval_determinant(method, M, sol):
|
||||
assert method(M) == sol
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", [
|
||||
"domain-ge", "bareiss", "berkowitz", "bird", "laplace", "lu"])
|
||||
@pytest.mark.parametrize("M, sol", [
|
||||
(Matrix(( (-3, 2),
|
||||
( 8, -5) )), -1),
|
||||
(Matrix(( (x, 1),
|
||||
(y, 2*y) )), 2*x*y - y),
|
||||
(Matrix(( (1, 1, 1),
|
||||
(1, 2, 3),
|
||||
(1, 3, 6) )), 1),
|
||||
(Matrix(( ( 3, -2, 0, 5),
|
||||
(-2, 1, -2, 2),
|
||||
( 0, -2, 5, 0),
|
||||
( 5, 0, 3, 4) )), -289),
|
||||
(Matrix(( ( 1, 2, 3, 4),
|
||||
( 5, 6, 7, 8),
|
||||
( 9, 10, 11, 12),
|
||||
(13, 14, 15, 16) )), 0),
|
||||
(Matrix(( (3, 2, 0, 0, 0),
|
||||
(0, 3, 2, 0, 0),
|
||||
(0, 0, 3, 2, 0),
|
||||
(0, 0, 0, 3, 2),
|
||||
(2, 0, 0, 0, 3) )), 275),
|
||||
(Matrix(( ( 3, 0, 0, 0),
|
||||
(-2, 1, 0, 0),
|
||||
( 0, -2, 5, 0),
|
||||
( 5, 0, 3, 4) )), 60),
|
||||
(Matrix(( ( 1, 0, 0, 0),
|
||||
( 5, 0, 0, 0),
|
||||
( 9, 10, 11, 0),
|
||||
(13, 14, 15, 16) )), 0),
|
||||
(Matrix(( (3, 2, 0, 0, 0),
|
||||
(0, 3, 2, 0, 0),
|
||||
(0, 0, 3, 2, 0),
|
||||
(0, 0, 0, 3, 2),
|
||||
(0, 0, 0, 0, 3) )), 243),
|
||||
(Matrix(( (1, 0, 1, 2, 12),
|
||||
(2, 0, 1, 1, 4),
|
||||
(2, 1, 1, -1, 3),
|
||||
(3, 2, -1, 1, 8),
|
||||
(1, 1, 1, 0, 6) )), -55),
|
||||
(Matrix(( (-5, 2, 3, 4, 5),
|
||||
( 1, -4, 3, 4, 5),
|
||||
( 1, 2, -3, 4, 5),
|
||||
( 1, 2, 3, -2, 5),
|
||||
( 1, 2, 3, 4, -1) )), 11664),
|
||||
(Matrix(( ( 2, 7, -1, 3, 2),
|
||||
( 0, 0, 1, 0, 1),
|
||||
(-2, 0, 7, 0, 2),
|
||||
(-3, -2, 4, 5, 3),
|
||||
( 1, 0, 0, 0, 1) )), 123),
|
||||
(Matrix(( (x, y, z),
|
||||
(1, 0, 0),
|
||||
(y, z, x) )), z**2 - x*y),
|
||||
])
|
||||
def test_determinant(method, M, sol):
|
||||
assert M.det(method=method) == sol
|
||||
|
||||
|
||||
def test_issue_13835():
|
||||
a = symbols('a')
|
||||
M = lambda n: Matrix([[i + a*j for i in range(n)]
|
||||
for j in range(n)])
|
||||
assert M(5).det() == 0
|
||||
assert M(6).det() == 0
|
||||
assert M(7).det() == 0
|
||||
|
||||
|
||||
def test_issue_14517():
|
||||
M = Matrix([
|
||||
[ 0, 10*I, 10*I, 0],
|
||||
[10*I, 0, 0, 10*I],
|
||||
[10*I, 0, 5 + 2*I, 10*I],
|
||||
[ 0, 10*I, 10*I, 5 + 2*I]])
|
||||
ev = M.eigenvals()
|
||||
# test one random eigenvalue, the computation is a little slow
|
||||
test_ev = random.choice(list(ev.keys()))
|
||||
assert (M - test_ev*eye(4)).det() == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", [
|
||||
"bareis", "det_lu", "det_LU", "Bareis", "BAREISS", "BERKOWITZ", "LU"])
|
||||
@pytest.mark.parametrize("M, sol", [
|
||||
(Matrix(( ( 3, -2, 0, 5),
|
||||
(-2, 1, -2, 2),
|
||||
( 0, -2, 5, 0),
|
||||
( 5, 0, 3, 4) )), -289),
|
||||
(Matrix(( (-5, 2, 3, 4, 5),
|
||||
( 1, -4, 3, 4, 5),
|
||||
( 1, 2, -3, 4, 5),
|
||||
( 1, 2, 3, -2, 5),
|
||||
( 1, 2, 3, 4, -1) )), 11664),
|
||||
])
|
||||
def test_legacy_det(method, M, sol):
|
||||
# Minimal support for legacy keys for 'method' in det()
|
||||
# Partially copied from test_determinant()
|
||||
assert M.det(method=method) == sol
|
||||
|
||||
|
||||
def eye_Determinant(n):
|
||||
return Matrix(n, n, lambda i, j: int(i == j))
|
||||
|
||||
def zeros_Determinant(n):
|
||||
return Matrix(n, n, lambda i, j: 0)
|
||||
|
||||
def test_det():
|
||||
a = Matrix(2, 3, [1, 2, 3, 4, 5, 6])
|
||||
raises(NonSquareMatrixError, lambda: a.det())
|
||||
|
||||
z = zeros_Determinant(2)
|
||||
ey = eye_Determinant(2)
|
||||
assert z.det() == 0
|
||||
assert ey.det() == 1
|
||||
|
||||
x = Symbol('x')
|
||||
a = Matrix(0, 0, [])
|
||||
b = Matrix(1, 1, [5])
|
||||
c = Matrix(2, 2, [1, 2, 3, 4])
|
||||
d = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 8])
|
||||
e = Matrix(4, 4,
|
||||
[x, 1, 2, 3, 4, 5, 6, 7, 2, 9, 10, 11, 12, 13, 14, 14])
|
||||
from sympy.abc import i, j, k, l, m, n
|
||||
f = Matrix(3, 3, [i, l, m, 0, j, n, 0, 0, k])
|
||||
g = Matrix(3, 3, [i, 0, 0, l, j, 0, m, n, k])
|
||||
h = Matrix(3, 3, [x**3, 0, 0, i, x**-1, 0, j, k, x**-2])
|
||||
# the method keyword for `det` doesn't kick in until 4x4 matrices,
|
||||
# so there is no need to test all methods on smaller ones
|
||||
|
||||
assert a.det() == 1
|
||||
assert b.det() == 5
|
||||
assert c.det() == -2
|
||||
assert d.det() == 3
|
||||
assert e.det() == 4*x - 24
|
||||
assert e.det(method="domain-ge") == 4*x - 24
|
||||
assert e.det(method='bareiss') == 4*x - 24
|
||||
assert e.det(method='berkowitz') == 4*x - 24
|
||||
assert f.det() == i*j*k
|
||||
assert g.det() == i*j*k
|
||||
assert h.det() == 1
|
||||
raises(ValueError, lambda: e.det(iszerofunc="test"))
|
||||
|
||||
def test_permanent():
|
||||
M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
assert M.per() == 450
|
||||
for i in range(1, 12):
|
||||
assert ones(i, i).per() == ones(i, i).T.per() == factorial(i)
|
||||
assert (ones(i, i)-eye(i)).per() == (ones(i, i)-eye(i)).T.per() == subfactorial(i)
|
||||
|
||||
a1, a2, a3, a4, a5 = symbols('a_1 a_2 a_3 a_4 a_5')
|
||||
M = Matrix([a1, a2, a3, a4, a5])
|
||||
assert M.per() == M.T.per() == a1 + a2 + a3 + a4 + a5
|
||||
|
||||
def test_adjugate():
|
||||
x = Symbol('x')
|
||||
e = Matrix(4, 4,
|
||||
[x, 1, 2, 3, 4, 5, 6, 7, 2, 9, 10, 11, 12, 13, 14, 14])
|
||||
|
||||
adj = Matrix([
|
||||
[ 4, -8, 4, 0],
|
||||
[ 76, -14*x - 68, 14*x - 8, -4*x + 24],
|
||||
[-122, 17*x + 142, -21*x + 4, 8*x - 48],
|
||||
[ 48, -4*x - 72, 8*x, -4*x + 24]])
|
||||
assert e.adjugate() == adj
|
||||
assert e.adjugate(method='bareiss') == adj
|
||||
assert e.adjugate(method='berkowitz') == adj
|
||||
assert e.adjugate(method='bird') == adj
|
||||
assert e.adjugate(method='laplace') == adj
|
||||
|
||||
a = Matrix(2, 3, [1, 2, 3, 4, 5, 6])
|
||||
raises(NonSquareMatrixError, lambda: a.adjugate())
|
||||
|
||||
def test_util():
|
||||
R = Rational
|
||||
|
||||
v1 = Matrix(1, 3, [1, 2, 3])
|
||||
v2 = Matrix(1, 3, [3, 4, 5])
|
||||
assert v1.norm() == sqrt(14)
|
||||
assert v1.project(v2) == Matrix(1, 3, [R(39)/25, R(52)/25, R(13)/5])
|
||||
assert Matrix.zeros(1, 2) == Matrix(1, 2, [0, 0])
|
||||
assert ones(1, 2) == Matrix(1, 2, [1, 1])
|
||||
assert v1.copy() == v1
|
||||
# cofactor
|
||||
assert eye(3) == eye(3).cofactor_matrix()
|
||||
test = Matrix([[1, 3, 2], [2, 6, 3], [2, 3, 6]])
|
||||
assert test.cofactor_matrix() == \
|
||||
Matrix([[27, -6, -6], [-12, 2, 3], [-3, 1, 0]])
|
||||
test = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
assert test.cofactor_matrix() == \
|
||||
Matrix([[-3, 6, -3], [6, -12, 6], [-3, 6, -3]])
|
||||
|
||||
def test_cofactor_and_minors():
|
||||
x = Symbol('x')
|
||||
e = Matrix(4, 4,
|
||||
[x, 1, 2, 3, 4, 5, 6, 7, 2, 9, 10, 11, 12, 13, 14, 14])
|
||||
|
||||
m = Matrix([
|
||||
[ x, 1, 3],
|
||||
[ 2, 9, 11],
|
||||
[12, 13, 14]])
|
||||
cm = Matrix([
|
||||
[ 4, 76, -122, 48],
|
||||
[-8, -14*x - 68, 17*x + 142, -4*x - 72],
|
||||
[ 4, 14*x - 8, -21*x + 4, 8*x],
|
||||
[ 0, -4*x + 24, 8*x - 48, -4*x + 24]])
|
||||
sub = Matrix([
|
||||
[x, 1, 2],
|
||||
[4, 5, 6],
|
||||
[2, 9, 10]])
|
||||
|
||||
assert e.minor_submatrix(1, 2) == m
|
||||
assert e.minor_submatrix(-1, -1) == sub
|
||||
assert e.minor(1, 2) == -17*x - 142
|
||||
assert e.cofactor(1, 2) == 17*x + 142
|
||||
assert e.cofactor_matrix() == cm
|
||||
assert e.cofactor_matrix(method="bareiss") == cm
|
||||
assert e.cofactor_matrix(method="berkowitz") == cm
|
||||
assert e.cofactor_matrix(method="bird") == cm
|
||||
assert e.cofactor_matrix(method="laplace") == cm
|
||||
|
||||
raises(ValueError, lambda: e.cofactor(4, 5))
|
||||
raises(ValueError, lambda: e.minor(4, 5))
|
||||
raises(ValueError, lambda: e.minor_submatrix(4, 5))
|
||||
|
||||
a = Matrix(2, 3, [1, 2, 3, 4, 5, 6])
|
||||
assert a.minor_submatrix(0, 0) == Matrix([[5, 6]])
|
||||
|
||||
raises(ValueError, lambda:
|
||||
Matrix(0, 0, []).minor_submatrix(0, 0))
|
||||
raises(NonSquareMatrixError, lambda: a.cofactor(0, 0))
|
||||
raises(NonSquareMatrixError, lambda: a.minor(0, 0))
|
||||
raises(NonSquareMatrixError, lambda: a.cofactor_matrix())
|
||||
|
||||
def test_charpoly():
|
||||
x, y = Symbol('x'), Symbol('y')
|
||||
z, t = Symbol('z'), Symbol('t')
|
||||
|
||||
from sympy.abc import a,b,c
|
||||
|
||||
m = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
|
||||
assert eye_Determinant(3).charpoly(x) == Poly((x - 1)**3, x)
|
||||
assert eye_Determinant(3).charpoly(y) == Poly((y - 1)**3, y)
|
||||
assert m.charpoly() == Poly(x**3 - 15*x**2 - 18*x, x)
|
||||
raises(NonSquareMatrixError, lambda: Matrix([[1], [2]]).charpoly())
|
||||
n = Matrix(4, 4, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
|
||||
assert n.charpoly() == Poly(x**4, x)
|
||||
|
||||
n = Matrix(4, 4, [45, 0, 0, 0, 0, 23, 0, 0, 0, 0, 87, 0, 0, 0, 0, 12])
|
||||
assert n.charpoly() == Poly(x**4 - 167*x**3 + 8811*x**2 - 173457*x + 1080540, x)
|
||||
|
||||
n = Matrix(3, 3, [x, 0, 0, a, y, 0, b, c, z])
|
||||
assert n.charpoly() == Poly(t**3 - (x+y+z)*t**2 + t*(x*y+y*z+x*z) - x*y*z, t)
|
||||
@@ -0,0 +1,113 @@
|
||||
# Test Matrix/DomainMatrix interaction.
|
||||
|
||||
|
||||
from sympy import GF, ZZ, QQ, EXRAW
|
||||
from sympy.polys.matrices import DomainMatrix, DM
|
||||
|
||||
from sympy import (
|
||||
Matrix,
|
||||
MutableMatrix,
|
||||
ImmutableMatrix,
|
||||
SparseMatrix,
|
||||
MutableDenseMatrix,
|
||||
ImmutableDenseMatrix,
|
||||
MutableSparseMatrix,
|
||||
ImmutableSparseMatrix,
|
||||
)
|
||||
from sympy import symbols, S, sqrt
|
||||
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
x, y = symbols('x y')
|
||||
|
||||
|
||||
MATRIX_TYPES = (
|
||||
Matrix,
|
||||
MutableMatrix,
|
||||
ImmutableMatrix,
|
||||
SparseMatrix,
|
||||
MutableDenseMatrix,
|
||||
ImmutableDenseMatrix,
|
||||
MutableSparseMatrix,
|
||||
ImmutableSparseMatrix,
|
||||
)
|
||||
IMMUTABLE = (
|
||||
ImmutableMatrix,
|
||||
ImmutableDenseMatrix,
|
||||
ImmutableSparseMatrix,
|
||||
)
|
||||
|
||||
|
||||
def DMs(items, domain):
|
||||
return DM(items, domain).to_sparse()
|
||||
|
||||
|
||||
def test_Matrix_rep_domain():
|
||||
|
||||
for Mat in MATRIX_TYPES:
|
||||
|
||||
M = Mat([[1, 2], [3, 4]])
|
||||
assert M._rep == DMs([[1, 2], [3, 4]], ZZ)
|
||||
assert (M / 2)._rep == DMs([[(1,2), 1], [(3,2), 2]], QQ)
|
||||
if not isinstance(M, IMMUTABLE):
|
||||
M[0, 0] = x
|
||||
assert M._rep == DMs([[x, 2], [3, 4]], EXRAW)
|
||||
|
||||
M = Mat([[S(1)/2, 2], [3, 4]])
|
||||
assert M._rep == DMs([[(1,2), 2], [3, 4]], QQ)
|
||||
if not isinstance(M, IMMUTABLE):
|
||||
M[0, 0] = x
|
||||
assert M._rep == DMs([[x, 2], [3, 4]], EXRAW)
|
||||
|
||||
dM = DMs([[1, 2], [3, 4]], ZZ)
|
||||
assert Mat._fromrep(dM)._rep == dM
|
||||
|
||||
# XXX: This is not intended. Perhaps it should be coerced to EXRAW?
|
||||
# The private _fromrep method is never called like this but perhaps it
|
||||
# should be guarded.
|
||||
#
|
||||
# It is not clear how to integrate domains other than ZZ, QQ and EXRAW with
|
||||
# the rest of Matrix or if the public type for this needs to be something
|
||||
# different from Matrix somehow.
|
||||
K = QQ.algebraic_field(sqrt(2))
|
||||
dM = DM([[1, 2], [3, 4]], K)
|
||||
assert Mat._fromrep(dM)._rep.domain == K
|
||||
|
||||
|
||||
def test_Matrix_to_DM():
|
||||
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
assert M.to_DM() == DMs([[1, 2], [3, 4]], ZZ)
|
||||
assert M.to_DM() is not M._rep
|
||||
assert M.to_DM(field=True) == DMs([[1, 2], [3, 4]], QQ)
|
||||
assert M.to_DM(domain=QQ) == DMs([[1, 2], [3, 4]], QQ)
|
||||
assert M.to_DM(domain=QQ[x]) == DMs([[1, 2], [3, 4]], QQ[x])
|
||||
assert M.to_DM(domain=GF(3)) == DMs([[1, 2], [0, 1]], GF(3))
|
||||
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
M[0, 0] = x
|
||||
assert M._rep.domain == EXRAW
|
||||
M[0, 0] = 1
|
||||
assert M.to_DM() == DMs([[1, 2], [3, 4]], ZZ)
|
||||
|
||||
M = Matrix([[S(1)/2, 2], [3, 4]])
|
||||
assert M.to_DM() == DMs([[QQ(1,2), 2], [3, 4]], QQ)
|
||||
|
||||
M = Matrix([[x, 2], [3, 4]])
|
||||
assert M.to_DM() == DMs([[x, 2], [3, 4]], ZZ[x])
|
||||
assert M.to_DM(field=True) == DMs([[x, 2], [3, 4]], ZZ.frac_field(x))
|
||||
|
||||
M = Matrix([[1/x, 2], [3, 4]])
|
||||
assert M.to_DM() == DMs([[1/x, 2], [3, 4]], ZZ.frac_field(x))
|
||||
|
||||
M = Matrix([[1, sqrt(2)], [3, 4]])
|
||||
K = QQ.algebraic_field(sqrt(2))
|
||||
sqrt2 = K.from_sympy(sqrt(2)) # XXX: Maybe K(sqrt(2)) should work
|
||||
M_K = DomainMatrix([[K(1), sqrt2], [K(3), K(4)]], (2, 2), K)
|
||||
assert M.to_DM() == DMs([[1, sqrt(2)], [3, 4]], EXRAW)
|
||||
assert M.to_DM(extension=True) == M_K.to_sparse()
|
||||
|
||||
# Options cannot be used with the domain parameter
|
||||
M = Matrix([[1, 2], [3, 4]])
|
||||
raises(TypeError, lambda: M.to_DM(domain=QQ, field=True))
|
||||
@@ -0,0 +1,712 @@
|
||||
from sympy.core.evalf import N
|
||||
from sympy.core.numbers import (Float, I, Rational)
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin)
|
||||
from sympy.matrices import eye, Matrix
|
||||
from sympy.core.singleton import S
|
||||
from sympy.testing.pytest import raises, XFAIL
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError, MatrixError
|
||||
from sympy.matrices.expressions.fourier import DFT
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
from sympy.testing.pytest import slow
|
||||
from sympy.testing.matrices import allclose
|
||||
|
||||
|
||||
def test_eigen():
|
||||
R = Rational
|
||||
M = Matrix.eye(3)
|
||||
assert M.eigenvals(multiple=False) == {S.One: 3}
|
||||
assert M.eigenvals(multiple=True) == [1, 1, 1]
|
||||
|
||||
assert M.eigenvects() == (
|
||||
[(1, 3, [Matrix([1, 0, 0]),
|
||||
Matrix([0, 1, 0]),
|
||||
Matrix([0, 0, 1])])])
|
||||
|
||||
assert M.left_eigenvects() == (
|
||||
[(1, 3, [Matrix([[1, 0, 0]]),
|
||||
Matrix([[0, 1, 0]]),
|
||||
Matrix([[0, 0, 1]])])])
|
||||
|
||||
M = Matrix([[0, 1, 1],
|
||||
[1, 0, 0],
|
||||
[1, 1, 1]])
|
||||
|
||||
assert M.eigenvals() == {2*S.One: 1, -S.One: 1, S.Zero: 1}
|
||||
|
||||
assert M.eigenvects() == (
|
||||
[
|
||||
(-1, 1, [Matrix([-1, 1, 0])]),
|
||||
( 0, 1, [Matrix([0, -1, 1])]),
|
||||
( 2, 1, [Matrix([R(2, 3), R(1, 3), 1])])
|
||||
])
|
||||
|
||||
assert M.left_eigenvects() == (
|
||||
[
|
||||
(-1, 1, [Matrix([[-2, 1, 1]])]),
|
||||
(0, 1, [Matrix([[-1, -1, 1]])]),
|
||||
(2, 1, [Matrix([[1, 1, 1]])])
|
||||
])
|
||||
|
||||
a = Symbol('a')
|
||||
M = Matrix([[a, 0],
|
||||
[0, 1]])
|
||||
|
||||
assert M.eigenvals() == {a: 1, S.One: 1}
|
||||
|
||||
M = Matrix([[1, -1],
|
||||
[1, 3]])
|
||||
assert M.eigenvects() == ([(2, 2, [Matrix(2, 1, [-1, 1])])])
|
||||
assert M.left_eigenvects() == ([(2, 2, [Matrix([[1, 1]])])])
|
||||
|
||||
M = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
a = R(15, 2)
|
||||
b = 3*33**R(1, 2)
|
||||
c = R(13, 2)
|
||||
d = (R(33, 8) + 3*b/8)
|
||||
e = (R(33, 8) - 3*b/8)
|
||||
|
||||
def NS(e, n):
|
||||
return str(N(e, n))
|
||||
r = [
|
||||
(a - b/2, 1, [Matrix([(12 + 24/(c - b/2))/((c - b/2)*e) + 3/(c - b/2),
|
||||
(6 + 12/(c - b/2))/e, 1])]),
|
||||
( 0, 1, [Matrix([1, -2, 1])]),
|
||||
(a + b/2, 1, [Matrix([(12 + 24/(c + b/2))/((c + b/2)*d) + 3/(c + b/2),
|
||||
(6 + 12/(c + b/2))/d, 1])]),
|
||||
]
|
||||
r1 = [(NS(r[i][0], 2), NS(r[i][1], 2),
|
||||
[NS(j, 2) for j in r[i][2][0]]) for i in range(len(r))]
|
||||
r = M.eigenvects()
|
||||
r2 = [(NS(r[i][0], 2), NS(r[i][1], 2),
|
||||
[NS(j, 2) for j in r[i][2][0]]) for i in range(len(r))]
|
||||
assert sorted(r1) == sorted(r2)
|
||||
|
||||
eps = Symbol('eps', real=True)
|
||||
|
||||
M = Matrix([[abs(eps), I*eps ],
|
||||
[-I*eps, abs(eps) ]])
|
||||
|
||||
assert M.eigenvects() == (
|
||||
[
|
||||
( 0, 1, [Matrix([[-I*eps/abs(eps)], [1]])]),
|
||||
( 2*abs(eps), 1, [ Matrix([[I*eps/abs(eps)], [1]]) ] ),
|
||||
])
|
||||
|
||||
assert M.left_eigenvects() == (
|
||||
[
|
||||
(0, 1, [Matrix([[I*eps/Abs(eps), 1]])]),
|
||||
(2*Abs(eps), 1, [Matrix([[-I*eps/Abs(eps), 1]])])
|
||||
])
|
||||
|
||||
M = Matrix(3, 3, [1, 2, 0, 0, 3, 0, 2, -4, 2])
|
||||
M._eigenvects = M.eigenvects(simplify=False)
|
||||
assert max(i.q for i in M._eigenvects[0][2][0]) > 1
|
||||
M._eigenvects = M.eigenvects(simplify=True)
|
||||
assert max(i.q for i in M._eigenvects[0][2][0]) == 1
|
||||
|
||||
M = Matrix([[Rational(1, 4), 1], [1, 1]])
|
||||
assert M.eigenvects() == [
|
||||
(Rational(5, 8) - sqrt(73)/8, 1, [Matrix([[-sqrt(73)/8 - Rational(3, 8)], [1]])]),
|
||||
(Rational(5, 8) + sqrt(73)/8, 1, [Matrix([[Rational(-3, 8) + sqrt(73)/8], [1]])])]
|
||||
|
||||
# issue 10719
|
||||
assert Matrix([]).eigenvals() == {}
|
||||
assert Matrix([]).eigenvals(multiple=True) == []
|
||||
assert Matrix([]).eigenvects() == []
|
||||
|
||||
# issue 15119
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([[1, 2], [0, 4], [0, 0]]).eigenvals())
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([[1, 0], [3, 4], [5, 6]]).eigenvals())
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([[1, 2, 3], [0, 5, 6]]).eigenvals())
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([[1, 0, 0], [4, 5, 0]]).eigenvals())
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([[1, 2, 3], [0, 5, 6]]).eigenvals(
|
||||
error_when_incomplete = False))
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([[1, 0, 0], [4, 5, 0]]).eigenvals(
|
||||
error_when_incomplete = False))
|
||||
|
||||
m = Matrix([[1, 2], [3, 4]])
|
||||
assert isinstance(m.eigenvals(simplify=True, multiple=False), dict)
|
||||
assert isinstance(m.eigenvals(simplify=True, multiple=True), list)
|
||||
assert isinstance(m.eigenvals(simplify=lambda x: x, multiple=False), dict)
|
||||
assert isinstance(m.eigenvals(simplify=lambda x: x, multiple=True), list)
|
||||
|
||||
|
||||
def test_float_eigenvals():
|
||||
m = Matrix([[1, .6, .6], [.6, .9, .9], [.9, .6, .6]])
|
||||
evals = [
|
||||
Rational(5, 4) - sqrt(385)/20,
|
||||
sqrt(385)/20 + Rational(5, 4),
|
||||
S.Zero]
|
||||
|
||||
n_evals = m.eigenvals(rational=True, multiple=True)
|
||||
n_evals = sorted(n_evals)
|
||||
s_evals = [x.evalf() for x in evals]
|
||||
s_evals = sorted(s_evals)
|
||||
|
||||
for x, y in zip(n_evals, s_evals):
|
||||
assert abs(x-y) < 10**-9
|
||||
|
||||
|
||||
@XFAIL
|
||||
def test_eigen_vects():
|
||||
m = Matrix(2, 2, [1, 0, 0, I])
|
||||
raises(NotImplementedError, lambda: m.is_diagonalizable(True))
|
||||
# !!! bug because of eigenvects() or roots(x**2 + (-1 - I)*x + I, x)
|
||||
# see issue 5292
|
||||
assert not m.is_diagonalizable(True)
|
||||
raises(MatrixError, lambda: m.diagonalize(True))
|
||||
(P, D) = m.diagonalize(True)
|
||||
|
||||
def test_issue_8240():
|
||||
# Eigenvalues of large triangular matrices
|
||||
x, y = symbols('x y')
|
||||
n = 200
|
||||
|
||||
diagonal_variables = [Symbol('x%s' % i) for i in range(n)]
|
||||
M = [[0 for i in range(n)] for j in range(n)]
|
||||
for i in range(n):
|
||||
M[i][i] = diagonal_variables[i]
|
||||
M = Matrix(M)
|
||||
|
||||
eigenvals = M.eigenvals()
|
||||
assert len(eigenvals) == n
|
||||
for i in range(n):
|
||||
assert eigenvals[diagonal_variables[i]] == 1
|
||||
|
||||
eigenvals = M.eigenvals(multiple=True)
|
||||
assert set(eigenvals) == set(diagonal_variables)
|
||||
|
||||
# with multiplicity
|
||||
M = Matrix([[x, 0, 0], [1, y, 0], [2, 3, x]])
|
||||
eigenvals = M.eigenvals()
|
||||
assert eigenvals == {x: 2, y: 1}
|
||||
|
||||
eigenvals = M.eigenvals(multiple=True)
|
||||
assert len(eigenvals) == 3
|
||||
assert eigenvals.count(x) == 2
|
||||
assert eigenvals.count(y) == 1
|
||||
|
||||
|
||||
def test_eigenvals():
|
||||
M = Matrix([[0, 1, 1],
|
||||
[1, 0, 0],
|
||||
[1, 1, 1]])
|
||||
assert M.eigenvals() == {2*S.One: 1, -S.One: 1, S.Zero: 1}
|
||||
|
||||
m = Matrix([
|
||||
[3, 0, 0, 0, -3],
|
||||
[0, -3, -3, 0, 3],
|
||||
[0, 3, 0, 3, 0],
|
||||
[0, 0, 3, 0, 3],
|
||||
[3, 0, 0, 3, 0]])
|
||||
|
||||
# XXX Used dry-run test because arbitrary symbol that appears in
|
||||
# CRootOf may not be unique.
|
||||
assert m.eigenvals()
|
||||
|
||||
|
||||
def test_eigenvects():
|
||||
M = Matrix([[0, 1, 1],
|
||||
[1, 0, 0],
|
||||
[1, 1, 1]])
|
||||
vecs = M.eigenvects()
|
||||
for val, mult, vec_list in vecs:
|
||||
assert len(vec_list) == 1
|
||||
assert M*vec_list[0] == val*vec_list[0]
|
||||
|
||||
|
||||
def test_left_eigenvects():
|
||||
M = Matrix([[0, 1, 1],
|
||||
[1, 0, 0],
|
||||
[1, 1, 1]])
|
||||
vecs = M.left_eigenvects()
|
||||
for val, mult, vec_list in vecs:
|
||||
assert len(vec_list) == 1
|
||||
assert vec_list[0]*M == val*vec_list[0]
|
||||
|
||||
|
||||
@slow
|
||||
def test_bidiagonalize():
|
||||
M = Matrix([[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
assert M.bidiagonalize() == M
|
||||
assert M.bidiagonalize(upper=False) == M
|
||||
assert M.bidiagonalize() == M
|
||||
assert M.bidiagonal_decomposition() == (M, M, M)
|
||||
assert M.bidiagonal_decomposition(upper=False) == (M, M, M)
|
||||
assert M.bidiagonalize() == M
|
||||
|
||||
import random
|
||||
#Real Tests
|
||||
for real_test in range(2):
|
||||
test_values = []
|
||||
row = 2
|
||||
col = 2
|
||||
for _ in range(row * col):
|
||||
value = random.randint(-1000000000, 1000000000)
|
||||
test_values = test_values + [value]
|
||||
# L -> Lower Bidiagonalization
|
||||
# M -> Mutable Matrix
|
||||
# N -> Immutable Matrix
|
||||
# 0 -> Bidiagonalized form
|
||||
# 1,2,3 -> Bidiagonal_decomposition matrices
|
||||
# 4 -> Product of 1 2 3
|
||||
M = Matrix(row, col, test_values)
|
||||
N = ImmutableMatrix(M)
|
||||
|
||||
N1, N2, N3 = N.bidiagonal_decomposition()
|
||||
M1, M2, M3 = M.bidiagonal_decomposition()
|
||||
M0 = M.bidiagonalize()
|
||||
N0 = N.bidiagonalize()
|
||||
|
||||
N4 = N1 * N2 * N3
|
||||
M4 = M1 * M2 * M3
|
||||
|
||||
N2.simplify()
|
||||
N4.simplify()
|
||||
N0.simplify()
|
||||
|
||||
M0.simplify()
|
||||
M2.simplify()
|
||||
M4.simplify()
|
||||
|
||||
LM0 = M.bidiagonalize(upper=False)
|
||||
LM1, LM2, LM3 = M.bidiagonal_decomposition(upper=False)
|
||||
LN0 = N.bidiagonalize(upper=False)
|
||||
LN1, LN2, LN3 = N.bidiagonal_decomposition(upper=False)
|
||||
|
||||
LN4 = LN1 * LN2 * LN3
|
||||
LM4 = LM1 * LM2 * LM3
|
||||
|
||||
LN2.simplify()
|
||||
LN4.simplify()
|
||||
LN0.simplify()
|
||||
|
||||
LM0.simplify()
|
||||
LM2.simplify()
|
||||
LM4.simplify()
|
||||
|
||||
assert M == M4
|
||||
assert M2 == M0
|
||||
assert N == N4
|
||||
assert N2 == N0
|
||||
assert M == LM4
|
||||
assert LM2 == LM0
|
||||
assert N == LN4
|
||||
assert LN2 == LN0
|
||||
|
||||
#Complex Tests
|
||||
for complex_test in range(2):
|
||||
test_values = []
|
||||
size = 2
|
||||
for _ in range(size * size):
|
||||
real = random.randint(-1000000000, 1000000000)
|
||||
comp = random.randint(-1000000000, 1000000000)
|
||||
value = real + comp * I
|
||||
test_values = test_values + [value]
|
||||
M = Matrix(size, size, test_values)
|
||||
N = ImmutableMatrix(M)
|
||||
# L -> Lower Bidiagonalization
|
||||
# M -> Mutable Matrix
|
||||
# N -> Immutable Matrix
|
||||
# 0 -> Bidiagonalized form
|
||||
# 1,2,3 -> Bidiagonal_decomposition matrices
|
||||
# 4 -> Product of 1 2 3
|
||||
N1, N2, N3 = N.bidiagonal_decomposition()
|
||||
M1, M2, M3 = M.bidiagonal_decomposition()
|
||||
M0 = M.bidiagonalize()
|
||||
N0 = N.bidiagonalize()
|
||||
|
||||
N4 = N1 * N2 * N3
|
||||
M4 = M1 * M2 * M3
|
||||
|
||||
N2.simplify()
|
||||
N4.simplify()
|
||||
N0.simplify()
|
||||
|
||||
M0.simplify()
|
||||
M2.simplify()
|
||||
M4.simplify()
|
||||
|
||||
LM0 = M.bidiagonalize(upper=False)
|
||||
LM1, LM2, LM3 = M.bidiagonal_decomposition(upper=False)
|
||||
LN0 = N.bidiagonalize(upper=False)
|
||||
LN1, LN2, LN3 = N.bidiagonal_decomposition(upper=False)
|
||||
|
||||
LN4 = LN1 * LN2 * LN3
|
||||
LM4 = LM1 * LM2 * LM3
|
||||
|
||||
LN2.simplify()
|
||||
LN4.simplify()
|
||||
LN0.simplify()
|
||||
|
||||
LM0.simplify()
|
||||
LM2.simplify()
|
||||
LM4.simplify()
|
||||
|
||||
assert M == M4
|
||||
assert M2 == M0
|
||||
assert N == N4
|
||||
assert N2 == N0
|
||||
assert M == LM4
|
||||
assert LM2 == LM0
|
||||
assert N == LN4
|
||||
assert LN2 == LN0
|
||||
|
||||
M = Matrix(18, 8, range(1, 145))
|
||||
M = M.applyfunc(lambda i: Float(i))
|
||||
assert M.bidiagonal_decomposition()[1] == M.bidiagonalize()
|
||||
assert M.bidiagonal_decomposition(upper=False)[1] == M.bidiagonalize(upper=False)
|
||||
a, b, c = M.bidiagonal_decomposition()
|
||||
diff = a * b * c - M
|
||||
assert abs(max(diff)) < 10**-12
|
||||
|
||||
|
||||
def test_diagonalize():
|
||||
m = Matrix(2, 2, [0, -1, 1, 0])
|
||||
raises(MatrixError, lambda: m.diagonalize(reals_only=True))
|
||||
P, D = m.diagonalize()
|
||||
assert D.is_diagonal()
|
||||
assert D == Matrix([
|
||||
[-I, 0],
|
||||
[ 0, I]])
|
||||
|
||||
# make sure we use floats out if floats are passed in
|
||||
m = Matrix(2, 2, [0, .5, .5, 0])
|
||||
P, D = m.diagonalize()
|
||||
assert all(isinstance(e, Float) for e in D.values())
|
||||
assert all(isinstance(e, Float) for e in P.values())
|
||||
|
||||
_, D2 = m.diagonalize(reals_only=True)
|
||||
assert D == D2
|
||||
|
||||
m = Matrix(
|
||||
[[0, 1, 0, 0], [1, 0, 0, 0.002], [0.002, 0, 0, 1], [0, 0, 1, 0]])
|
||||
P, D = m.diagonalize()
|
||||
assert allclose(P*D, m*P)
|
||||
|
||||
|
||||
def test_is_diagonalizable():
|
||||
a, b, c = symbols('a b c')
|
||||
m = Matrix(2, 2, [a, c, c, b])
|
||||
assert m.is_symmetric()
|
||||
assert m.is_diagonalizable()
|
||||
assert not Matrix(2, 2, [1, 1, 0, 1]).is_diagonalizable()
|
||||
|
||||
m = Matrix(2, 2, [0, -1, 1, 0])
|
||||
assert m.is_diagonalizable()
|
||||
assert not m.is_diagonalizable(reals_only=True)
|
||||
|
||||
|
||||
def test_jordan_form():
|
||||
m = Matrix(3, 2, [-3, 1, -3, 20, 3, 10])
|
||||
raises(NonSquareMatrixError, lambda: m.jordan_form())
|
||||
|
||||
# the next two tests test the cases where the old
|
||||
# algorithm failed due to the fact that the block structure can
|
||||
# *NOT* be determined from algebraic and geometric multiplicity alone
|
||||
# This can be seen most easily when one lets compute the J.c.f. of a matrix that
|
||||
# is in J.c.f already.
|
||||
m = Matrix(4, 4, [2, 1, 0, 0,
|
||||
0, 2, 1, 0,
|
||||
0, 0, 2, 0,
|
||||
0, 0, 0, 2
|
||||
])
|
||||
P, J = m.jordan_form()
|
||||
assert m == J
|
||||
|
||||
m = Matrix(4, 4, [2, 1, 0, 0,
|
||||
0, 2, 0, 0,
|
||||
0, 0, 2, 1,
|
||||
0, 0, 0, 2
|
||||
])
|
||||
P, J = m.jordan_form()
|
||||
assert m == J
|
||||
|
||||
A = Matrix([[ 2, 4, 1, 0],
|
||||
[-4, 2, 0, 1],
|
||||
[ 0, 0, 2, 4],
|
||||
[ 0, 0, -4, 2]])
|
||||
P, J = A.jordan_form()
|
||||
assert simplify(P*J*P.inv()) == A
|
||||
|
||||
assert Matrix(1, 1, [1]).jordan_form() == (Matrix([1]), Matrix([1]))
|
||||
assert Matrix(1, 1, [1]).jordan_form(calc_transform=False) == Matrix([1])
|
||||
|
||||
# If we have eigenvalues in CRootOf form, raise errors
|
||||
m = Matrix([[3, 0, 0, 0, -3], [0, -3, -3, 0, 3], [0, 3, 0, 3, 0], [0, 0, 3, 0, 3], [3, 0, 0, 3, 0]])
|
||||
raises(MatrixError, lambda: m.jordan_form())
|
||||
|
||||
# make sure that if the input has floats, the output does too
|
||||
m = Matrix([
|
||||
[ 0.6875, 0.125 + 0.1875*sqrt(3)],
|
||||
[0.125 + 0.1875*sqrt(3), 0.3125]])
|
||||
P, J = m.jordan_form()
|
||||
assert all(isinstance(x, Float) or x == 0 for x in P)
|
||||
assert all(isinstance(x, Float) or x == 0 for x in J)
|
||||
|
||||
|
||||
def test_singular_values():
|
||||
x = Symbol('x', real=True)
|
||||
|
||||
A = Matrix([[0, 1*I], [2, 0]])
|
||||
# if singular values can be sorted, they should be in decreasing order
|
||||
assert A.singular_values() == [2, 1]
|
||||
|
||||
A = eye(3)
|
||||
A[1, 1] = x
|
||||
A[2, 2] = 5
|
||||
vals = A.singular_values()
|
||||
# since Abs(x) cannot be sorted, test set equality
|
||||
assert set(vals) == {5, 1, Abs(x)}
|
||||
|
||||
A = Matrix([[sin(x), cos(x)], [-cos(x), sin(x)]])
|
||||
vals = [sv.trigsimp() for sv in A.singular_values()]
|
||||
assert vals == [S.One, S.One]
|
||||
|
||||
A = Matrix([
|
||||
[2, 4],
|
||||
[1, 3],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
])
|
||||
assert A.singular_values() == \
|
||||
[sqrt(sqrt(221) + 15), sqrt(15 - sqrt(221))]
|
||||
assert A.T.singular_values() == \
|
||||
[sqrt(sqrt(221) + 15), sqrt(15 - sqrt(221)), 0, 0]
|
||||
|
||||
def test___eq__():
|
||||
assert (Matrix(
|
||||
[[0, 1, 1],
|
||||
[1, 0, 0],
|
||||
[1, 1, 1]]) == {}) is False
|
||||
|
||||
|
||||
def test_definite():
|
||||
# Examples from Gilbert Strang, "Introduction to Linear Algebra"
|
||||
# Positive definite matrices
|
||||
m = Matrix([[2, -1, 0], [-1, 2, -1], [0, -1, 2]])
|
||||
assert m.is_positive_definite == True
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
m = Matrix([[5, 4], [4, 5]])
|
||||
assert m.is_positive_definite == True
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
# Positive semidefinite matrices
|
||||
m = Matrix([[2, -1, -1], [-1, 2, -1], [-1, -1, 2]])
|
||||
assert m.is_positive_definite == False
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
m = Matrix([[1, 2], [2, 4]])
|
||||
assert m.is_positive_definite == False
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
# Examples from Mathematica documentation
|
||||
# Non-hermitian positive definite matrices
|
||||
m = Matrix([[2, 3], [4, 8]])
|
||||
assert m.is_positive_definite == True
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
# Hermetian matrices
|
||||
m = Matrix([[1, 2*I], [-I, 4]])
|
||||
assert m.is_positive_definite == True
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
# Symbolic matrices examples
|
||||
a = Symbol('a', positive=True)
|
||||
b = Symbol('b', negative=True)
|
||||
m = Matrix([[a, 0, 0], [0, a, 0], [0, 0, a]])
|
||||
assert m.is_positive_definite == True
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == False
|
||||
|
||||
m = Matrix([[b, 0, 0], [0, b, 0], [0, 0, b]])
|
||||
assert m.is_positive_definite == False
|
||||
assert m.is_positive_semidefinite == False
|
||||
assert m.is_negative_definite == True
|
||||
assert m.is_negative_semidefinite == True
|
||||
assert m.is_indefinite == False
|
||||
|
||||
m = Matrix([[a, 0], [0, b]])
|
||||
assert m.is_positive_definite == False
|
||||
assert m.is_positive_semidefinite == False
|
||||
assert m.is_negative_definite == False
|
||||
assert m.is_negative_semidefinite == False
|
||||
assert m.is_indefinite == True
|
||||
|
||||
m = Matrix([
|
||||
[0.0228202735623867, 0.00518748979085398,
|
||||
-0.0743036351048907, -0.00709135324903921],
|
||||
[0.00518748979085398, 0.0349045359786350,
|
||||
0.0830317991056637, 0.00233147902806909],
|
||||
[-0.0743036351048907, 0.0830317991056637,
|
||||
1.15859676366277, 0.340359081555988],
|
||||
[-0.00709135324903921, 0.00233147902806909,
|
||||
0.340359081555988, 0.928147644848199]
|
||||
])
|
||||
assert m.is_positive_definite == True
|
||||
assert m.is_positive_semidefinite == True
|
||||
assert m.is_indefinite == False
|
||||
|
||||
# test for issue 19547: https://github.com/sympy/sympy/issues/19547
|
||||
m = Matrix([
|
||||
[0, 0, 0],
|
||||
[0, 1, 2],
|
||||
[0, 2, 1]
|
||||
])
|
||||
assert not m.is_positive_definite
|
||||
assert not m.is_positive_semidefinite
|
||||
|
||||
|
||||
def test_positive_semidefinite_cholesky():
|
||||
from sympy.matrices.eigen import _is_positive_semidefinite_cholesky
|
||||
|
||||
m = Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == True
|
||||
m = Matrix([[0, 0, 0], [0, 5, -10*I], [0, 10*I, 5]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == False
|
||||
m = Matrix([[1, 0, 0], [0, 0, 0], [0, 0, -1]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == False
|
||||
m = Matrix([[0, 1], [1, 0]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == False
|
||||
|
||||
# https://www.value-at-risk.net/cholesky-factorization/
|
||||
m = Matrix([[4, -2, -6], [-2, 10, 9], [-6, 9, 14]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == True
|
||||
m = Matrix([[9, -3, 3], [-3, 2, 1], [3, 1, 6]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == True
|
||||
m = Matrix([[4, -2, 2], [-2, 1, -1], [2, -1, 5]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == True
|
||||
m = Matrix([[1, 2, -1], [2, 5, 1], [-1, 1, 9]])
|
||||
assert _is_positive_semidefinite_cholesky(m) == False
|
||||
|
||||
|
||||
def test_issue_20582():
|
||||
A = Matrix([
|
||||
[5, -5, -3, 2, -7],
|
||||
[-2, -5, 0, 2, 1],
|
||||
[-2, -7, -5, -2, -6],
|
||||
[7, 10, 3, 9, -2],
|
||||
[4, -10, 3, -8, -4]
|
||||
])
|
||||
# XXX Used dry-run test because arbitrary symbol that appears in
|
||||
# CRootOf may not be unique.
|
||||
assert A.eigenvects()
|
||||
|
||||
def test_issue_19210():
|
||||
t = Symbol('t')
|
||||
H = Matrix([[3, 0, 0, 0], [0, 1 , 2, 0], [0, 2, 2, 0], [0, 0, 0, 4]])
|
||||
A = (-I * H * t).jordan_form()
|
||||
assert A == (Matrix([
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, -4/(-1 + sqrt(17)), 4/(1 + sqrt(17))],
|
||||
[0, 0, 1, 1],
|
||||
[1, 0, 0, 0]]), Matrix([
|
||||
[-4*I*t, 0, 0, 0],
|
||||
[ 0, -3*I*t, 0, 0],
|
||||
[ 0, 0, t*(-3*I/2 + sqrt(17)*I/2), 0],
|
||||
[ 0, 0, 0, t*(-sqrt(17)*I/2 - 3*I/2)]]))
|
||||
|
||||
|
||||
def test_issue_20275():
|
||||
# XXX We use complex expansions because complex exponentials are not
|
||||
# recognized by polys.domains
|
||||
A = DFT(3).as_explicit().expand(complex=True)
|
||||
eigenvects = A.eigenvects()
|
||||
assert eigenvects[0] == (
|
||||
-1, 1,
|
||||
[Matrix([[1 - sqrt(3)], [1], [1]])]
|
||||
)
|
||||
assert eigenvects[1] == (
|
||||
1, 1,
|
||||
[Matrix([[1 + sqrt(3)], [1], [1]])]
|
||||
)
|
||||
assert eigenvects[2] == (
|
||||
-I, 1,
|
||||
[Matrix([[0], [-1], [1]])]
|
||||
)
|
||||
|
||||
A = DFT(4).as_explicit().expand(complex=True)
|
||||
eigenvects = A.eigenvects()
|
||||
assert eigenvects[0] == (
|
||||
-1, 1,
|
||||
[Matrix([[-1], [1], [1], [1]])]
|
||||
)
|
||||
assert eigenvects[1] == (
|
||||
1, 2,
|
||||
[Matrix([[1], [0], [1], [0]]), Matrix([[2], [1], [0], [1]])]
|
||||
)
|
||||
assert eigenvects[2] == (
|
||||
-I, 1,
|
||||
[Matrix([[0], [-1], [0], [1]])]
|
||||
)
|
||||
|
||||
# XXX We skip test for some parts of eigenvectors which are very
|
||||
# complicated and fragile under expression tree changes
|
||||
A = DFT(5).as_explicit().expand(complex=True)
|
||||
eigenvects = A.eigenvects()
|
||||
assert eigenvects[0] == (
|
||||
-1, 1,
|
||||
[Matrix([[1 - sqrt(5)], [1], [1], [1], [1]])]
|
||||
)
|
||||
assert eigenvects[1] == (
|
||||
1, 2,
|
||||
[Matrix([[S(1)/2 + sqrt(5)/2], [0], [1], [1], [0]]),
|
||||
Matrix([[S(1)/2 + sqrt(5)/2], [1], [0], [0], [1]])]
|
||||
)
|
||||
|
||||
|
||||
def test_issue_20752():
|
||||
b = symbols('b', nonzero=True)
|
||||
m = Matrix([[0, 0, 0], [0, b, 0], [0, 0, b]])
|
||||
assert m.is_positive_semidefinite is None
|
||||
|
||||
|
||||
def test_issue_25282():
|
||||
dd = sd = [0] * 11 + [1]
|
||||
ds = [2, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0]
|
||||
ss = ds.copy()
|
||||
ss[8] = 2
|
||||
|
||||
def rotate(x, i):
|
||||
return x[i:] + x[:i]
|
||||
|
||||
mat = []
|
||||
for i in range(12):
|
||||
mat.append(rotate(ss, i) + rotate(sd, i))
|
||||
for i in range(12):
|
||||
mat.append(rotate(ds, i) + rotate(dd, i))
|
||||
|
||||
assert sum(Matrix(mat).eigenvals().values()) == 24
|
||||
@@ -0,0 +1,108 @@
|
||||
from sympy.combinatorics import Permutation
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.matrices.expressions import (
|
||||
PermutationMatrix, BlockDiagMatrix, BlockMatrix)
|
||||
|
||||
|
||||
def test_connected_components():
|
||||
a, b, c, d, e, f, g, h, i, j, k, l, m = symbols('a:m')
|
||||
|
||||
M = Matrix([
|
||||
[a, 0, 0, 0, b, 0, 0, 0, 0, 0, c, 0, 0],
|
||||
[0, d, 0, 0, 0, e, 0, 0, 0, 0, 0, f, 0],
|
||||
[0, 0, g, 0, 0, 0, h, 0, 0, 0, 0, 0, i],
|
||||
[0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[m, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, m, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, m, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0],
|
||||
[j, 0, 0, 0, k, 0, 0, 1, 0, 0, l, 0, 0],
|
||||
[0, j, 0, 0, 0, k, 0, 0, 1, 0, 0, l, 0],
|
||||
[0, 0, j, 0, 0, 0, k, 0, 0, 1, 0, 0, l],
|
||||
[0, 0, 0, 0, d, 0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, d, 0, 0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, d, 0, 0, 0, 0, 0, 1]])
|
||||
cc = M.connected_components()
|
||||
assert cc == [[0, 4, 7, 10], [1, 5, 8, 11], [2, 6, 9, 12], [3]]
|
||||
|
||||
P, B = M.connected_components_decomposition()
|
||||
p = Permutation([0, 4, 7, 10, 1, 5, 8, 11, 2, 6, 9, 12, 3])
|
||||
assert P == PermutationMatrix(p)
|
||||
|
||||
B0 = Matrix([
|
||||
[a, b, 0, c],
|
||||
[m, 1, 0, 0],
|
||||
[j, k, 1, l],
|
||||
[0, d, 0, 1]])
|
||||
B1 = Matrix([
|
||||
[d, e, 0, f],
|
||||
[m, 1, 0, 0],
|
||||
[j, k, 1, l],
|
||||
[0, d, 0, 1]])
|
||||
B2 = Matrix([
|
||||
[g, h, 0, i],
|
||||
[m, 1, 0, 0],
|
||||
[j, k, 1, l],
|
||||
[0, d, 0, 1]])
|
||||
B3 = Matrix([[1]])
|
||||
assert B == BlockDiagMatrix(B0, B1, B2, B3)
|
||||
|
||||
|
||||
def test_strongly_connected_components():
|
||||
M = Matrix([
|
||||
[11, 14, 10, 0, 15, 0],
|
||||
[0, 44, 0, 0, 45, 0],
|
||||
[1, 4, 0, 0, 5, 0],
|
||||
[0, 0, 0, 22, 0, 23],
|
||||
[0, 54, 0, 0, 55, 0],
|
||||
[0, 0, 0, 32, 0, 33]])
|
||||
scc = M.strongly_connected_components()
|
||||
assert scc == [[1, 4], [0, 2], [3, 5]]
|
||||
|
||||
P, B = M.strongly_connected_components_decomposition()
|
||||
p = Permutation([1, 4, 0, 2, 3, 5])
|
||||
assert P == PermutationMatrix(p)
|
||||
assert B == BlockMatrix([
|
||||
[
|
||||
Matrix([[44, 45], [54, 55]]),
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix.zeros(2, 2)
|
||||
],
|
||||
[
|
||||
Matrix([[14, 15], [4, 5]]),
|
||||
Matrix([[11, 10], [1, 0]]),
|
||||
Matrix.zeros(2, 2)
|
||||
],
|
||||
[
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix([[22, 23], [32, 33]])
|
||||
]
|
||||
])
|
||||
P = P.as_explicit()
|
||||
B = B.as_explicit()
|
||||
assert P.T * B * P == M
|
||||
|
||||
P, B = M.strongly_connected_components_decomposition(lower=False)
|
||||
p = Permutation([3, 5, 0, 2, 1, 4])
|
||||
assert P == PermutationMatrix(p)
|
||||
assert B == BlockMatrix([
|
||||
[
|
||||
Matrix([[22, 23], [32, 33]]),
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix.zeros(2, 2)
|
||||
],
|
||||
[
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix([[11, 10], [1, 0]]),
|
||||
Matrix([[14, 15], [4, 5]])
|
||||
],
|
||||
[
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix.zeros(2, 2),
|
||||
Matrix([[44, 45], [54, 55]])
|
||||
]
|
||||
])
|
||||
P = P.as_explicit()
|
||||
B = B.as_explicit()
|
||||
assert P.T * B * P == M
|
||||
@@ -0,0 +1,136 @@
|
||||
from itertools import product
|
||||
|
||||
from sympy.core.relational import (Equality, Unequality)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.integrals.integrals import integrate
|
||||
from sympy.matrices.dense import (Matrix, eye, zeros)
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
from sympy.matrices import SparseMatrix
|
||||
from sympy.matrices.immutable import \
|
||||
ImmutableDenseMatrix, ImmutableSparseMatrix
|
||||
from sympy.abc import x, y
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
IM = ImmutableDenseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
ISM = ImmutableSparseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
ieye = ImmutableDenseMatrix(eye(3))
|
||||
|
||||
|
||||
def test_creation():
|
||||
assert IM.shape == ISM.shape == (3, 3)
|
||||
assert IM[1, 2] == ISM[1, 2] == 6
|
||||
assert IM[2, 2] == ISM[2, 2] == 9
|
||||
|
||||
|
||||
def test_immutability():
|
||||
with raises(TypeError):
|
||||
IM[2, 2] = 5
|
||||
with raises(TypeError):
|
||||
ISM[2, 2] = 5
|
||||
|
||||
|
||||
def test_slicing():
|
||||
assert IM[1, :] == ImmutableDenseMatrix([[4, 5, 6]])
|
||||
assert IM[:2, :2] == ImmutableDenseMatrix([[1, 2], [4, 5]])
|
||||
assert ISM[1, :] == ImmutableSparseMatrix([[4, 5, 6]])
|
||||
assert ISM[:2, :2] == ImmutableSparseMatrix([[1, 2], [4, 5]])
|
||||
|
||||
|
||||
def test_subs():
|
||||
A = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
B = ImmutableMatrix([[1, 2], [x, 4]])
|
||||
C = ImmutableMatrix([[-x, x*y], [-(x + y), y**2]])
|
||||
assert B.subs(x, 3) == A
|
||||
assert (x*B).subs(x, 3) == 3*A
|
||||
assert (x*eye(2) + B).subs(x, 3) == 3*eye(2) + A
|
||||
assert C.subs([[x, -1], [y, -2]]) == A
|
||||
assert C.subs([(x, -1), (y, -2)]) == A
|
||||
assert C.subs({x: -1, y: -2}) == A
|
||||
assert C.subs({x: y - 1, y: x - 1}, simultaneous=True) == \
|
||||
ImmutableMatrix([[1 - y, (x - 1)*(y - 1)], [2 - x - y, (x - 1)**2]])
|
||||
|
||||
|
||||
def test_as_immutable():
|
||||
data = [[1, 2], [3, 4]]
|
||||
X = Matrix(data)
|
||||
assert sympify(X) == X.as_immutable() == ImmutableMatrix(data)
|
||||
|
||||
data = {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4}
|
||||
X = SparseMatrix(2, 2, data)
|
||||
assert sympify(X) == X.as_immutable() == ImmutableSparseMatrix(2, 2, data)
|
||||
|
||||
|
||||
def test_function_return_types():
|
||||
# Lets ensure that decompositions of immutable matrices remain immutable
|
||||
# I.e. do MatrixBase methods return the correct class?
|
||||
X = ImmutableMatrix([[1, 2], [3, 4]])
|
||||
Y = ImmutableMatrix([[1], [0]])
|
||||
q, r = X.QRdecomposition()
|
||||
assert (type(q), type(r)) == (ImmutableMatrix, ImmutableMatrix)
|
||||
|
||||
assert type(X.LUsolve(Y)) == ImmutableMatrix
|
||||
assert type(X.QRsolve(Y)) == ImmutableMatrix
|
||||
|
||||
X = ImmutableMatrix([[5, 2], [2, 7]])
|
||||
assert X.T == X
|
||||
assert X.is_symmetric
|
||||
assert type(X.cholesky()) == ImmutableMatrix
|
||||
L, D = X.LDLdecomposition()
|
||||
assert (type(L), type(D)) == (ImmutableMatrix, ImmutableMatrix)
|
||||
|
||||
X = ImmutableMatrix([[1, 2], [2, 1]])
|
||||
assert X.is_diagonalizable()
|
||||
assert X.det() == -3
|
||||
assert X.norm(2) == 3
|
||||
|
||||
assert type(X.eigenvects()[0][2][0]) == ImmutableMatrix
|
||||
|
||||
assert type(zeros(3, 3).as_immutable().nullspace()[0]) == ImmutableMatrix
|
||||
|
||||
X = ImmutableMatrix([[1, 0], [2, 1]])
|
||||
assert type(X.lower_triangular_solve(Y)) == ImmutableMatrix
|
||||
assert type(X.T.upper_triangular_solve(Y)) == ImmutableMatrix
|
||||
|
||||
assert type(X.minor_submatrix(0, 0)) == ImmutableMatrix
|
||||
|
||||
# issue 6279
|
||||
# https://github.com/sympy/sympy/issues/6279
|
||||
# Test that Immutable _op_ Immutable => Immutable and not MatExpr
|
||||
|
||||
|
||||
def test_immutable_evaluation():
|
||||
X = ImmutableMatrix(eye(3))
|
||||
A = ImmutableMatrix(3, 3, range(9))
|
||||
assert isinstance(X + A, ImmutableMatrix)
|
||||
assert isinstance(X * A, ImmutableMatrix)
|
||||
assert isinstance(X * 2, ImmutableMatrix)
|
||||
assert isinstance(2 * X, ImmutableMatrix)
|
||||
assert isinstance(A**2, ImmutableMatrix)
|
||||
|
||||
|
||||
def test_deterimant():
|
||||
assert ImmutableMatrix(4, 4, lambda i, j: i + j).det() == 0
|
||||
|
||||
|
||||
def test_Equality():
|
||||
assert Equality(IM, IM) is S.true
|
||||
assert Unequality(IM, IM) is S.false
|
||||
assert Equality(IM, IM.subs(1, 2)) is S.false
|
||||
assert Unequality(IM, IM.subs(1, 2)) is S.true
|
||||
assert Equality(IM, 2) is S.false
|
||||
assert Unequality(IM, 2) is S.true
|
||||
M = ImmutableMatrix([x, y])
|
||||
assert Equality(M, IM) is S.false
|
||||
assert Unequality(M, IM) is S.true
|
||||
assert Equality(M, M.subs(x, 2)).subs(x, 2) is S.true
|
||||
assert Unequality(M, M.subs(x, 2)).subs(x, 2) is S.false
|
||||
assert Equality(M, M.subs(x, 2)).subs(x, 3) is S.false
|
||||
assert Unequality(M, M.subs(x, 2)).subs(x, 3) is S.true
|
||||
|
||||
|
||||
def test_integrate():
|
||||
intIM = integrate(IM, x)
|
||||
assert intIM.shape == IM.shape
|
||||
assert all(intIM[i, j] == (1 + j + 3*i)*x for i, j in
|
||||
product(range(3), range(3)))
|
||||
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
We have a few different kind of Matrices
|
||||
Matrix, ImmutableMatrix, MatrixExpr
|
||||
|
||||
Here we test the extent to which they cooperate
|
||||
"""
|
||||
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.matrices import (Matrix, MatrixSymbol, eye, Identity,
|
||||
ImmutableMatrix)
|
||||
from sympy.matrices.expressions import MatrixExpr, MatAdd
|
||||
from sympy.matrices.matrixbase import classof
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
SM = MatrixSymbol('X', 3, 3)
|
||||
SV = MatrixSymbol('v', 3, 1)
|
||||
MM = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
IM = ImmutableMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
meye = eye(3)
|
||||
imeye = ImmutableMatrix(eye(3))
|
||||
ideye = Identity(3)
|
||||
a, b, c = symbols('a,b,c')
|
||||
|
||||
|
||||
def test_IM_MM():
|
||||
assert isinstance(MM + IM, ImmutableMatrix)
|
||||
assert isinstance(IM + MM, ImmutableMatrix)
|
||||
assert isinstance(2*IM + MM, ImmutableMatrix)
|
||||
assert MM.equals(IM)
|
||||
|
||||
|
||||
def test_ME_MM():
|
||||
assert isinstance(Identity(3) + MM, MatrixExpr)
|
||||
assert isinstance(SM + MM, MatAdd)
|
||||
assert isinstance(MM + SM, MatAdd)
|
||||
assert (Identity(3) + MM)[1, 1] == 6
|
||||
|
||||
|
||||
def test_equality():
|
||||
a, b, c = Identity(3), eye(3), ImmutableMatrix(eye(3))
|
||||
for x in [a, b, c]:
|
||||
for y in [a, b, c]:
|
||||
assert x.equals(y)
|
||||
|
||||
|
||||
def test_matrix_symbol_MM():
|
||||
X = MatrixSymbol('X', 3, 3)
|
||||
Y = eye(3) + X
|
||||
assert Y[1, 1] == 1 + X[1, 1]
|
||||
|
||||
|
||||
def test_matrix_symbol_vector_matrix_multiplication():
|
||||
A = MM * SV
|
||||
B = IM * SV
|
||||
assert A == B
|
||||
C = (SV.T * MM.T).T
|
||||
assert B == C
|
||||
D = (SV.T * IM.T).T
|
||||
assert C == D
|
||||
|
||||
|
||||
def test_indexing_interactions():
|
||||
assert (a * IM)[1, 1] == 5*a
|
||||
assert (SM + IM)[1, 1] == SM[1, 1] + IM[1, 1]
|
||||
assert (SM * IM)[1, 1] == SM[1, 0]*IM[0, 1] + SM[1, 1]*IM[1, 1] + \
|
||||
SM[1, 2]*IM[2, 1]
|
||||
|
||||
|
||||
def test_classof():
|
||||
A = Matrix(3, 3, range(9))
|
||||
B = ImmutableMatrix(3, 3, range(9))
|
||||
C = MatrixSymbol('C', 3, 3)
|
||||
assert classof(A, A) == Matrix
|
||||
assert classof(B, B) == ImmutableMatrix
|
||||
assert classof(A, B) == ImmutableMatrix
|
||||
assert classof(B, A) == ImmutableMatrix
|
||||
raises(TypeError, lambda: classof(A, C))
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
||||
from sympy.testing.pytest import warns_deprecated_sympy
|
||||
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.polys.polytools import Poly
|
||||
from sympy.matrices import Matrix, randMatrix
|
||||
from sympy.matrices.normalforms import (
|
||||
invariant_factors,
|
||||
smith_normal_form,
|
||||
smith_normal_decomp,
|
||||
hermite_normal_form,
|
||||
is_smith_normal_form,
|
||||
)
|
||||
from sympy.polys.domains import ZZ, QQ
|
||||
from sympy.core.numbers import Integer
|
||||
|
||||
import random
|
||||
|
||||
|
||||
def test_smith_normal():
|
||||
m = Matrix([[12,6,4,8],[3,9,6,12],[2,16,14,28],[20,10,10,20]])
|
||||
smf = Matrix([[1, 0, 0, 0], [0, 10, 0, 0], [0, 0, 30, 0], [0, 0, 0, 0]])
|
||||
assert smith_normal_form(m) == smf
|
||||
|
||||
a, s, t = smith_normal_decomp(m)
|
||||
assert a == s * m * t
|
||||
|
||||
x = Symbol('x')
|
||||
with warns_deprecated_sympy():
|
||||
m = Matrix([[Poly(x-1), Poly(1, x),Poly(-1,x)],
|
||||
[0, Poly(x), Poly(-1,x)],
|
||||
[Poly(0,x),Poly(-1,x),Poly(x)]])
|
||||
invs = 1, x - 1, x**2 - 1
|
||||
assert invariant_factors(m, domain=QQ[x]) == invs
|
||||
|
||||
m = Matrix([[2, 4]])
|
||||
smf = Matrix([[2, 0]])
|
||||
assert smith_normal_form(m) == smf
|
||||
|
||||
prng = random.Random(0)
|
||||
for i in range(6):
|
||||
for j in range(6):
|
||||
for _ in range(10 if i*j else 1):
|
||||
m = randMatrix(i, j, max=5, percent=50, prng=prng)
|
||||
a, s, t = smith_normal_decomp(m)
|
||||
assert a == s * m * t
|
||||
assert is_smith_normal_form(a)
|
||||
s.inv().to_DM(ZZ)
|
||||
t.inv().to_DM(ZZ)
|
||||
|
||||
a, s, t = smith_normal_decomp(m, QQ)
|
||||
assert a == s * m * t
|
||||
assert is_smith_normal_form(a)
|
||||
s.inv()
|
||||
t.inv()
|
||||
|
||||
|
||||
def test_smith_normal_deprecated():
|
||||
from sympy.polys.solvers import RawMatrix as Matrix
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
m = Matrix([[12, 6, 4,8],[3,9,6,12],[2,16,14,28],[20,10,10,20]])
|
||||
setattr(m, 'ring', ZZ)
|
||||
with warns_deprecated_sympy():
|
||||
smf = Matrix([[1, 0, 0, 0], [0, 10, 0, 0], [0, 0, 30, 0], [0, 0, 0, 0]])
|
||||
assert smith_normal_form(m) == smf
|
||||
|
||||
x = Symbol('x')
|
||||
with warns_deprecated_sympy():
|
||||
m = Matrix([[Poly(x-1), Poly(1, x),Poly(-1,x)],
|
||||
[0, Poly(x), Poly(-1,x)],
|
||||
[Poly(0,x),Poly(-1,x),Poly(x)]])
|
||||
setattr(m, 'ring', QQ[x])
|
||||
invs = (Poly(1, x, domain='QQ'), Poly(x - 1, domain='QQ'), Poly(x**2 - 1, domain='QQ'))
|
||||
assert invariant_factors(m) == invs
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
m = Matrix([[2, 4]])
|
||||
setattr(m, 'ring', ZZ)
|
||||
with warns_deprecated_sympy():
|
||||
smf = Matrix([[2, 0]])
|
||||
assert smith_normal_form(m) == smf
|
||||
|
||||
|
||||
def test_hermite_normal():
|
||||
m = Matrix([[2, 7, 17, 29, 41], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]])
|
||||
hnf = Matrix([[1, 0, 0], [0, 2, 1], [0, 0, 1]])
|
||||
assert hermite_normal_form(m) == hnf
|
||||
|
||||
tr_hnf = Matrix([[37, 0, 19], [222, -6, 113], [48, 0, 25], [0, 2, 1], [0, 0, 1]])
|
||||
assert hermite_normal_form(m.transpose()) == tr_hnf
|
||||
|
||||
m = Matrix([[8, 28, 68, 116, 164], [3, 11, 19, 31, 43], [5, 13, 23, 37, 47]])
|
||||
hnf = Matrix([[4, 0, 0], [0, 2, 1], [0, 0, 1]])
|
||||
assert hermite_normal_form(m) == hnf
|
||||
assert hermite_normal_form(m, D=8) == hnf
|
||||
assert hermite_normal_form(m, D=ZZ(8)) == hnf
|
||||
assert hermite_normal_form(m, D=Integer(8)) == hnf
|
||||
|
||||
m = Matrix([[10, 8, 6, 30, 2], [45, 36, 27, 18, 9], [5, 4, 3, 2, 1]])
|
||||
hnf = Matrix([[26, 2], [0, 9], [0, 1]])
|
||||
assert hermite_normal_form(m) == hnf
|
||||
|
||||
m = Matrix([[2, 7], [0, 0], [0, 0]])
|
||||
hnf = Matrix([[1], [0], [0]])
|
||||
assert hermite_normal_form(m) == hnf
|
||||
|
||||
|
||||
def test_issue_23410():
|
||||
A = Matrix([[1, 12], [0, 8], [0, 5]])
|
||||
H = Matrix([[1, 0], [0, 8], [0, 5]])
|
||||
assert hermite_normal_form(A) == H
|
||||
@@ -0,0 +1,351 @@
|
||||
from sympy.core.numbers import I
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.matrices import Matrix, zeros, eye
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.abc import x
|
||||
|
||||
|
||||
# Matrix tests
|
||||
def test_row_op():
|
||||
e = eye(3)
|
||||
|
||||
raises(ValueError, lambda: e.elementary_row_op("abc"))
|
||||
raises(ValueError, lambda: e.elementary_row_op())
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->kn', row=5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->kn', row=-5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=1, row2=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=5, row2=1))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=-5, row2=1))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n<->m', row1=1, row2=-5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=5, row2=1, k=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=-5, row2=1, k=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=-5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_row_op('n->n+km', row1=1, row2=1, k=5))
|
||||
|
||||
# test various ways to set arguments
|
||||
assert e.elementary_row_op("n->kn", 0, 5) == Matrix([[5, 0, 0], [0, 1, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n->kn", 1, 5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n->kn", row=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n->kn", row1=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n<->m", 0, 1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n<->m", row1=0, row2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n<->m", row=0, row2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n->n+km", 0, 5, 1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n->n+km", row=0, k=5, row2=1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]])
|
||||
assert e.elementary_row_op("n->n+km", row1=0, k=5, row2=1) == Matrix([[1, 5, 0], [0, 1, 0], [0, 0, 1]])
|
||||
|
||||
# make sure the matrix doesn't change size
|
||||
a = Matrix(2, 3, [0]*6)
|
||||
assert a.elementary_row_op("n->kn", 1, 5) == Matrix(2, 3, [0]*6)
|
||||
assert a.elementary_row_op("n<->m", 0, 1) == Matrix(2, 3, [0]*6)
|
||||
assert a.elementary_row_op("n->n+km", 0, 5, 1) == Matrix(2, 3, [0]*6)
|
||||
|
||||
|
||||
def test_col_op():
|
||||
e = eye(3)
|
||||
|
||||
raises(ValueError, lambda: e.elementary_col_op("abc"))
|
||||
raises(ValueError, lambda: e.elementary_col_op())
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->kn', col=5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->kn', col=-5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=1, col2=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=5, col2=1))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=-5, col2=1))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n<->m', col1=1, col2=-5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=5, col2=1, k=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=-5, col2=1, k=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=-5, k=5))
|
||||
raises(ValueError, lambda: e.elementary_col_op('n->n+km', col1=1, col2=1, k=5))
|
||||
|
||||
# test various ways to set arguments
|
||||
assert e.elementary_col_op("n->kn", 0, 5) == Matrix([[5, 0, 0], [0, 1, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n->kn", 1, 5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n->kn", col=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n->kn", col1=1, k=5) == Matrix([[1, 0, 0], [0, 5, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n<->m", 0, 1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n<->m", col1=0, col2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n<->m", col=0, col2=1) == Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n->n+km", 0, 5, 1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n->n+km", col=0, k=5, col2=1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]])
|
||||
assert e.elementary_col_op("n->n+km", col1=0, k=5, col2=1) == Matrix([[1, 0, 0], [5, 1, 0], [0, 0, 1]])
|
||||
|
||||
# make sure the matrix doesn't change size
|
||||
a = Matrix(2, 3, [0]*6)
|
||||
assert a.elementary_col_op("n->kn", 1, 5) == Matrix(2, 3, [0]*6)
|
||||
assert a.elementary_col_op("n<->m", 0, 1) == Matrix(2, 3, [0]*6)
|
||||
assert a.elementary_col_op("n->n+km", 0, 5, 1) == Matrix(2, 3, [0]*6)
|
||||
|
||||
|
||||
def test_is_echelon():
|
||||
zro = zeros(3)
|
||||
ident = eye(3)
|
||||
|
||||
assert zro.is_echelon
|
||||
assert ident.is_echelon
|
||||
|
||||
a = Matrix(0, 0, [])
|
||||
assert a.is_echelon
|
||||
|
||||
a = Matrix(2, 3, [3, 2, 1, 0, 0, 6])
|
||||
assert a.is_echelon
|
||||
|
||||
a = Matrix(2, 3, [0, 0, 6, 3, 2, 1])
|
||||
assert not a.is_echelon
|
||||
|
||||
x = Symbol('x')
|
||||
a = Matrix(3, 1, [x, 0, 0])
|
||||
assert a.is_echelon
|
||||
|
||||
a = Matrix(3, 1, [x, x, 0])
|
||||
assert not a.is_echelon
|
||||
|
||||
a = Matrix(3, 3, [0, 0, 0, 1, 2, 3, 0, 0, 0])
|
||||
assert not a.is_echelon
|
||||
|
||||
|
||||
def test_echelon_form():
|
||||
# echelon form is not unique, but the result
|
||||
# must be row-equivalent to the original matrix
|
||||
# and it must be in echelon form.
|
||||
|
||||
a = zeros(3)
|
||||
e = eye(3)
|
||||
|
||||
# we can assume the zero matrix and the identity matrix shouldn't change
|
||||
assert a.echelon_form() == a
|
||||
assert e.echelon_form() == e
|
||||
|
||||
a = Matrix(0, 0, [])
|
||||
assert a.echelon_form() == a
|
||||
|
||||
a = Matrix(1, 1, [5])
|
||||
assert a.echelon_form() == a
|
||||
|
||||
# now we get to the real tests
|
||||
|
||||
def verify_row_null_space(mat, rows, nulls):
|
||||
for v in nulls:
|
||||
assert all(t.is_zero for t in a_echelon*v)
|
||||
for v in rows:
|
||||
if not all(t.is_zero for t in v):
|
||||
assert not all(t.is_zero for t in a_echelon*v.transpose())
|
||||
|
||||
a = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
nulls = [Matrix([
|
||||
[ 1],
|
||||
[-2],
|
||||
[ 1]])]
|
||||
rows = [a[i, :] for i in range(a.rows)]
|
||||
a_echelon = a.echelon_form()
|
||||
assert a_echelon.is_echelon
|
||||
verify_row_null_space(a, rows, nulls)
|
||||
|
||||
|
||||
a = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 8])
|
||||
nulls = []
|
||||
rows = [a[i, :] for i in range(a.rows)]
|
||||
a_echelon = a.echelon_form()
|
||||
assert a_echelon.is_echelon
|
||||
verify_row_null_space(a, rows, nulls)
|
||||
|
||||
a = Matrix(3, 3, [2, 1, 3, 0, 0, 0, 2, 1, 3])
|
||||
nulls = [Matrix([
|
||||
[Rational(-1, 2)],
|
||||
[ 1],
|
||||
[ 0]]),
|
||||
Matrix([
|
||||
[Rational(-3, 2)],
|
||||
[ 0],
|
||||
[ 1]])]
|
||||
rows = [a[i, :] for i in range(a.rows)]
|
||||
a_echelon = a.echelon_form()
|
||||
assert a_echelon.is_echelon
|
||||
verify_row_null_space(a, rows, nulls)
|
||||
|
||||
# this one requires a row swap
|
||||
a = Matrix(3, 3, [2, 1, 3, 0, 0, 0, 1, 1, 3])
|
||||
nulls = [Matrix([
|
||||
[ 0],
|
||||
[ -3],
|
||||
[ 1]])]
|
||||
rows = [a[i, :] for i in range(a.rows)]
|
||||
a_echelon = a.echelon_form()
|
||||
assert a_echelon.is_echelon
|
||||
verify_row_null_space(a, rows, nulls)
|
||||
|
||||
a = Matrix(3, 3, [0, 3, 3, 0, 2, 2, 0, 1, 1])
|
||||
nulls = [Matrix([
|
||||
[1],
|
||||
[0],
|
||||
[0]]),
|
||||
Matrix([
|
||||
[ 0],
|
||||
[-1],
|
||||
[ 1]])]
|
||||
rows = [a[i, :] for i in range(a.rows)]
|
||||
a_echelon = a.echelon_form()
|
||||
assert a_echelon.is_echelon
|
||||
verify_row_null_space(a, rows, nulls)
|
||||
|
||||
a = Matrix(2, 3, [2, 2, 3, 3, 3, 0])
|
||||
nulls = [Matrix([
|
||||
[-1],
|
||||
[1],
|
||||
[0]])]
|
||||
rows = [a[i, :] for i in range(a.rows)]
|
||||
a_echelon = a.echelon_form()
|
||||
assert a_echelon.is_echelon
|
||||
verify_row_null_space(a, rows, nulls)
|
||||
|
||||
|
||||
def test_rref():
|
||||
e = Matrix(0, 0, [])
|
||||
assert e.rref(pivots=False) == e
|
||||
|
||||
e = Matrix(1, 1, [1])
|
||||
a = Matrix(1, 1, [5])
|
||||
assert e.rref(pivots=False) == a.rref(pivots=False) == e
|
||||
|
||||
a = Matrix(3, 1, [1, 2, 3])
|
||||
assert a.rref(pivots=False) == Matrix([[1], [0], [0]])
|
||||
|
||||
a = Matrix(1, 3, [1, 2, 3])
|
||||
assert a.rref(pivots=False) == Matrix([[1, 2, 3]])
|
||||
|
||||
a = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
assert a.rref(pivots=False) == Matrix([
|
||||
[1, 0, -1],
|
||||
[0, 1, 2],
|
||||
[0, 0, 0]])
|
||||
|
||||
a = Matrix(3, 3, [1, 2, 3, 1, 2, 3, 1, 2, 3])
|
||||
b = Matrix(3, 3, [1, 2, 3, 0, 0, 0, 0, 0, 0])
|
||||
c = Matrix(3, 3, [0, 0, 0, 1, 2, 3, 0, 0, 0])
|
||||
d = Matrix(3, 3, [0, 0, 0, 0, 0, 0, 1, 2, 3])
|
||||
assert a.rref(pivots=False) == \
|
||||
b.rref(pivots=False) == \
|
||||
c.rref(pivots=False) == \
|
||||
d.rref(pivots=False) == b
|
||||
|
||||
e = eye(3)
|
||||
z = zeros(3)
|
||||
assert e.rref(pivots=False) == e
|
||||
assert z.rref(pivots=False) == z
|
||||
|
||||
a = Matrix([
|
||||
[ 0, 0, 1, 2, 2, -5, 3],
|
||||
[-1, 5, 2, 2, 1, -7, 5],
|
||||
[ 0, 0, -2, -3, -3, 8, -5],
|
||||
[-1, 5, 0, -1, -2, 1, 0]])
|
||||
mat, pivot_offsets = a.rref()
|
||||
assert mat == Matrix([
|
||||
[1, -5, 0, 0, 1, 1, -1],
|
||||
[0, 0, 1, 0, 0, -1, 1],
|
||||
[0, 0, 0, 1, 1, -2, 1],
|
||||
[0, 0, 0, 0, 0, 0, 0]])
|
||||
assert pivot_offsets == (0, 2, 3)
|
||||
|
||||
a = Matrix([[Rational(1, 19), Rational(1, 5), 2, 3],
|
||||
[ 4, 5, 6, 7],
|
||||
[ 8, 9, 10, 11],
|
||||
[ 12, 13, 14, 15]])
|
||||
assert a.rref(pivots=False) == Matrix([
|
||||
[1, 0, 0, Rational(-76, 157)],
|
||||
[0, 1, 0, Rational(-5, 157)],
|
||||
[0, 0, 1, Rational(238, 157)],
|
||||
[0, 0, 0, 0]])
|
||||
|
||||
x = Symbol('x')
|
||||
a = Matrix(2, 3, [x, 1, 1, sqrt(x), x, 1])
|
||||
for i, j in zip(a.rref(pivots=False),
|
||||
[1, 0, sqrt(x)*(-x + 1)/(-x**Rational(5, 2) + x),
|
||||
0, 1, 1/(sqrt(x) + x + 1)]):
|
||||
assert simplify(i - j).is_zero
|
||||
|
||||
|
||||
def test_rref_rhs():
|
||||
a, b, c, d = symbols('a b c d')
|
||||
A = Matrix([[0, 0], [0, 0], [1, 2], [3, 4]])
|
||||
B = Matrix([a, b, c, d])
|
||||
assert A.rref_rhs(B) == (Matrix([
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
[0, 0],
|
||||
[0, 0]]), Matrix([
|
||||
[ -2*c + d],
|
||||
[3*c/2 - d/2],
|
||||
[ a],
|
||||
[ b]]))
|
||||
|
||||
|
||||
def test_issue_17827():
|
||||
C = Matrix([
|
||||
[3, 4, -1, 1],
|
||||
[9, 12, -3, 3],
|
||||
[0, 2, 1, 3],
|
||||
[2, 3, 0, -2],
|
||||
[0, 3, 3, -5],
|
||||
[8, 15, 0, 6]
|
||||
])
|
||||
# Tests for row/col within valid range
|
||||
D = C.elementary_row_op('n<->m', row1=2, row2=5)
|
||||
E = C.elementary_row_op('n->n+km', row1=5, row2=3, k=-4)
|
||||
F = C.elementary_row_op('n->kn', row=5, k=2)
|
||||
assert(D[5, :] == Matrix([[0, 2, 1, 3]]))
|
||||
assert(E[5, :] == Matrix([[0, 3, 0, 14]]))
|
||||
assert(F[5, :] == Matrix([[16, 30, 0, 12]]))
|
||||
# Tests for row/col out of range
|
||||
raises(ValueError, lambda: C.elementary_row_op('n<->m', row1=2, row2=6))
|
||||
raises(ValueError, lambda: C.elementary_row_op('n->kn', row=7, k=2))
|
||||
raises(ValueError, lambda: C.elementary_row_op('n->n+km', row1=-1, row2=5, k=2))
|
||||
|
||||
def test_rank():
|
||||
m = Matrix([[1, 2], [x, 1 - 1/x]])
|
||||
assert m.rank() == 2
|
||||
n = Matrix(3, 3, range(1, 10))
|
||||
assert n.rank() == 2
|
||||
p = zeros(3)
|
||||
assert p.rank() == 0
|
||||
|
||||
def test_issue_11434():
|
||||
ax, ay, bx, by, cx, cy, dx, dy, ex, ey, t0, t1 = \
|
||||
symbols('a_x a_y b_x b_y c_x c_y d_x d_y e_x e_y t_0 t_1')
|
||||
M = Matrix([[ax, ay, ax*t0, ay*t0, 0],
|
||||
[bx, by, bx*t0, by*t0, 0],
|
||||
[cx, cy, cx*t0, cy*t0, 1],
|
||||
[dx, dy, dx*t0, dy*t0, 1],
|
||||
[ex, ey, 2*ex*t1 - ex*t0, 2*ey*t1 - ey*t0, 0]])
|
||||
assert M.rank() == 4
|
||||
|
||||
def test_rank_regression_from_so():
|
||||
# see:
|
||||
# https://stackoverflow.com/questions/19072700/why-does-sympy-give-me-the-wrong-answer-when-i-row-reduce-a-symbolic-matrix
|
||||
|
||||
nu, lamb = symbols('nu, lambda')
|
||||
A = Matrix([[-3*nu, 1, 0, 0],
|
||||
[ 3*nu, -2*nu - 1, 2, 0],
|
||||
[ 0, 2*nu, (-1*nu) - lamb - 2, 3],
|
||||
[ 0, 0, nu + lamb, -3]])
|
||||
expected_reduced = Matrix([[1, 0, 0, 1/(nu**2*(-lamb - nu))],
|
||||
[0, 1, 0, 3/(nu*(-lamb - nu))],
|
||||
[0, 0, 1, 3/(-lamb - nu)],
|
||||
[0, 0, 0, 0]])
|
||||
expected_pivots = (0, 1, 2)
|
||||
|
||||
reduced, pivots = A.rref()
|
||||
|
||||
assert simplify(expected_reduced - reduced) == zeros(*A.shape)
|
||||
assert pivots == expected_pivots
|
||||
|
||||
def test_issue_15872():
|
||||
A = Matrix([[1, 1, 1, 0], [-2, -1, 0, -1], [0, 0, -1, -1], [0, 0, 2, 1]])
|
||||
B = A - Matrix.eye(4) * I
|
||||
assert B.rank() == 3
|
||||
assert (B**2).rank() == 2
|
||||
assert (B**3).rank() == 2
|
||||
@@ -0,0 +1,62 @@
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.matrices.exceptions import NonSquareMatrixError, NonInvertibleMatrixError
|
||||
|
||||
from sympy import Matrix, Rational
|
||||
|
||||
|
||||
def test_lll():
|
||||
A = Matrix([[1, 0, 0, 0, -20160],
|
||||
[0, 1, 0, 0, 33768],
|
||||
[0, 0, 1, 0, 39578],
|
||||
[0, 0, 0, 1, 47757]])
|
||||
L = Matrix([[ 10, -3, -2, 8, -4],
|
||||
[ 3, -9, 8, 1, -11],
|
||||
[ -3, 13, -9, -3, -9],
|
||||
[-12, -7, -11, 9, -1]])
|
||||
T = Matrix([[ 10, -3, -2, 8],
|
||||
[ 3, -9, 8, 1],
|
||||
[ -3, 13, -9, -3],
|
||||
[-12, -7, -11, 9]])
|
||||
assert A.lll() == L
|
||||
assert A.lll_transform() == (L, T)
|
||||
assert T * A == L
|
||||
|
||||
|
||||
def test_matrix_inv_mod():
|
||||
A = Matrix(2, 1, [1, 0])
|
||||
raises(NonSquareMatrixError, lambda: A.inv_mod(2))
|
||||
A = Matrix(2, 2, [1, 0, 0, 0])
|
||||
raises(NonInvertibleMatrixError, lambda: A.inv_mod(2))
|
||||
A = Matrix(2, 2, [1, 2, 3, 4])
|
||||
Ai = Matrix(2, 2, [1, 1, 0, 1])
|
||||
assert A.inv_mod(3) == Ai
|
||||
A = Matrix(2, 2, [1, 0, 0, 1])
|
||||
assert A.inv_mod(2) == A
|
||||
A = Matrix(3, 3, [1, 2, 3, 4, 5, 6, 7, 8, 9])
|
||||
raises(NonInvertibleMatrixError, lambda: A.inv_mod(5))
|
||||
A = Matrix(3, 3, [5, 1, 3, 2, 6, 0, 2, 1, 1])
|
||||
Ai = Matrix(3, 3, [6, 8, 0, 1, 5, 6, 5, 6, 4])
|
||||
assert A.inv_mod(9) == Ai
|
||||
A = Matrix(3, 3, [1, 6, -3, 4, 1, -5, 3, -5, 5])
|
||||
Ai = Matrix(3, 3, [4, 3, 3, 1, 2, 5, 1, 5, 1])
|
||||
assert A.inv_mod(6) == Ai
|
||||
A = Matrix(3, 3, [1, 6, 1, 4, 1, 5, 3, 2, 5])
|
||||
Ai = Matrix(3, 3, [6, 0, 3, 6, 6, 4, 1, 6, 1])
|
||||
assert A.inv_mod(7) == Ai
|
||||
A = Matrix([[1, 2], [3, Rational(3,4)]])
|
||||
raises(ValueError, lambda: A.inv_mod(2))
|
||||
A = Matrix([[1, 2], [3, 4]])
|
||||
raises(TypeError, lambda: A.inv_mod(Rational(1, 2)))
|
||||
# https://github.com/sympy/sympy/issues/27663
|
||||
M = Matrix([
|
||||
[2, 3, 1, 4],
|
||||
[1, 5, 3, 2],
|
||||
[3, 2, 4, 1],
|
||||
[4, 1, 2, 5],
|
||||
])
|
||||
assert M.inv_mod(26) == Matrix([
|
||||
[7, 21, 10, 10],
|
||||
[1, 7, 19, 3],
|
||||
[14, 1, 15, 1],
|
||||
[25, 23, 3, 12],
|
||||
])
|
||||
@@ -0,0 +1,615 @@
|
||||
import pytest
|
||||
from sympy.core.function import expand_mul
|
||||
from sympy.core.numbers import (I, Rational)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.matrices.exceptions import (ShapeError, NonSquareMatrixError)
|
||||
from sympy.matrices import (
|
||||
ImmutableMatrix, Matrix, eye, ones, ImmutableDenseMatrix, dotprodsimp)
|
||||
from sympy.matrices.determinant import _det_laplace
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.matrices.exceptions import NonInvertibleMatrixError
|
||||
from sympy.polys.matrices.exceptions import DMShapeError
|
||||
from sympy.solvers.solveset import linsolve
|
||||
from sympy.abc import x, y
|
||||
|
||||
def test_issue_17247_expression_blowup_29():
|
||||
M = Matrix(S('''[
|
||||
[ -3/4, 45/32 - 37*I/16, 0, 0],
|
||||
[-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128],
|
||||
[ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0],
|
||||
[ 0, 0, 0, -177/128 - 1369*I/128]]'''))
|
||||
with dotprodsimp(True):
|
||||
assert M.gauss_jordan_solve(ones(4, 1)) == (Matrix(S('''[
|
||||
[ -32549314808672/3306971225785 - 17397006745216*I/3306971225785],
|
||||
[ 67439348256/3306971225785 - 9167503335872*I/3306971225785],
|
||||
[-15091965363354518272/21217636514687010905 + 16890163109293858304*I/21217636514687010905],
|
||||
[ -11328/952745 + 87616*I/952745]]''')), Matrix(0, 1, []))
|
||||
|
||||
def test_issue_17247_expression_blowup_30():
|
||||
M = Matrix(S('''[
|
||||
[ -3/4, 45/32 - 37*I/16, 0, 0],
|
||||
[-149/64 + 49*I/32, -177/128 - 1369*I/128, 0, -2063/256 + 541*I/128],
|
||||
[ 0, 9/4 + 55*I/16, 2473/256 + 137*I/64, 0],
|
||||
[ 0, 0, 0, -177/128 - 1369*I/128]]'''))
|
||||
with dotprodsimp(True):
|
||||
assert M.cholesky_solve(ones(4, 1)) == Matrix(S('''[
|
||||
[ -32549314808672/3306971225785 - 17397006745216*I/3306971225785],
|
||||
[ 67439348256/3306971225785 - 9167503335872*I/3306971225785],
|
||||
[-15091965363354518272/21217636514687010905 + 16890163109293858304*I/21217636514687010905],
|
||||
[ -11328/952745 + 87616*I/952745]]'''))
|
||||
|
||||
# @XFAIL # This calculation hangs with dotprodsimp.
|
||||
# def test_issue_17247_expression_blowup_31():
|
||||
# M = Matrix([
|
||||
# [x + 1, 1 - x, 0, 0],
|
||||
# [1 - x, x + 1, 0, x + 1],
|
||||
# [ 0, 1 - x, x + 1, 0],
|
||||
# [ 0, 0, 0, x + 1]])
|
||||
# with dotprodsimp(True):
|
||||
# assert M.LDLsolve(ones(4, 1)) == Matrix([
|
||||
# [(x + 1)/(4*x)],
|
||||
# [(x - 1)/(4*x)],
|
||||
# [(x + 1)/(4*x)],
|
||||
# [ 1/(x + 1)]])
|
||||
|
||||
|
||||
def test_LUsolve_iszerofunc():
|
||||
# taken from https://github.com/sympy/sympy/issues/24679
|
||||
|
||||
M = Matrix([[(x + 1)**2 - (x**2 + 2*x + 1), x], [x, 0]])
|
||||
b = Matrix([1, 1])
|
||||
is_zero_func = lambda e: False if e._random() else True
|
||||
|
||||
x_exp = Matrix([1/x, (1-(-x**2 - 2*x + (x+1)**2 - 1)/x)/x])
|
||||
|
||||
assert (x_exp - M.LUsolve(b, iszerofunc=is_zero_func)) == Matrix([0, 0])
|
||||
|
||||
|
||||
def test_issue_17247_expression_blowup_32():
|
||||
M = Matrix([
|
||||
[x + 1, 1 - x, 0, 0],
|
||||
[1 - x, x + 1, 0, x + 1],
|
||||
[ 0, 1 - x, x + 1, 0],
|
||||
[ 0, 0, 0, x + 1]])
|
||||
with dotprodsimp(True):
|
||||
assert M.LUsolve(ones(4, 1)) == Matrix([
|
||||
[(x + 1)/(4*x)],
|
||||
[(x - 1)/(4*x)],
|
||||
[(x + 1)/(4*x)],
|
||||
[ 1/(x + 1)]])
|
||||
|
||||
def test_LUsolve():
|
||||
A = Matrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[8, 3, 6]])
|
||||
x = Matrix(3, 1, [3, 7, 5])
|
||||
b = A*x
|
||||
soln = A.LUsolve(b)
|
||||
assert soln == x
|
||||
A = Matrix([[0, -1, 2],
|
||||
[5, 10, 7],
|
||||
[8, 3, 4]])
|
||||
x = Matrix(3, 1, [-1, 2, 5])
|
||||
b = A*x
|
||||
soln = A.LUsolve(b)
|
||||
assert soln == x
|
||||
A = Matrix([[2, 1], [1, 0], [1, 0]]) # issue 14548
|
||||
b = Matrix([3, 1, 1])
|
||||
assert A.LUsolve(b) == Matrix([1, 1])
|
||||
b = Matrix([3, 1, 2]) # inconsistent
|
||||
raises(ValueError, lambda: A.LUsolve(b))
|
||||
A = Matrix([[0, -1, 2],
|
||||
[5, 10, 7],
|
||||
[8, 3, 4],
|
||||
[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[8, 3, 6]])
|
||||
x = Matrix([2, 1, -4])
|
||||
b = A*x
|
||||
soln = A.LUsolve(b)
|
||||
assert soln == x
|
||||
A = Matrix([[0, -1, 2], [5, 10, 7]]) # underdetermined
|
||||
x = Matrix([-1, 2, 0])
|
||||
b = A*x
|
||||
raises(NotImplementedError, lambda: A.LUsolve(b))
|
||||
|
||||
A = Matrix(4, 4, lambda i, j: 1/(i+j+1) if i != 3 else 0)
|
||||
b = Matrix.zeros(4, 1)
|
||||
raises(NonInvertibleMatrixError, lambda: A.LUsolve(b))
|
||||
|
||||
|
||||
def test_LUsolve_noncommutative():
|
||||
a0, a1, a2, a3 = symbols("a:4", commutative=False)
|
||||
b0, b1 = symbols("b:2", commutative=False)
|
||||
A = Matrix([[a0, a1], [a2, a3]])
|
||||
check = A * A.LUsolve(Matrix([b0, b1]))
|
||||
assert check[0, 0].expand() == b0
|
||||
# Because sympy simplification is very limited with noncommutative expressions,
|
||||
# perform an explicit check with the second element
|
||||
assert check[1, 0] == (
|
||||
a2*a0**(-1)*(-a1*(-a2*a0**(-1)*a1 + a3)**(-1)*(-a2*a0**(-1)*b0 + b1) + b0)
|
||||
+ a3*(-a2*a0**(-1)*a1 + a3)**(-1)*(-a2*a0**(-1)*b0 + b1)
|
||||
)
|
||||
|
||||
|
||||
def test_QRsolve():
|
||||
A = Matrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[8, 3, 6]])
|
||||
x = Matrix(3, 1, [3, 7, 5])
|
||||
b = A*x
|
||||
soln = A.QRsolve(b)
|
||||
assert soln == x
|
||||
x = Matrix([[1, 2], [3, 4], [5, 6]])
|
||||
b = A*x
|
||||
soln = A.QRsolve(b)
|
||||
assert soln == x
|
||||
|
||||
A = Matrix([[0, -1, 2],
|
||||
[5, 10, 7],
|
||||
[8, 3, 4]])
|
||||
x = Matrix(3, 1, [-1, 2, 5])
|
||||
b = A*x
|
||||
soln = A.QRsolve(b)
|
||||
assert soln == x
|
||||
x = Matrix([[7, 8], [9, 10], [11, 12]])
|
||||
b = A*x
|
||||
soln = A.QRsolve(b)
|
||||
assert soln == x
|
||||
|
||||
def test_errors():
|
||||
raises(ShapeError, lambda: Matrix([1]).LUsolve(Matrix([[1, 2], [3, 4]])))
|
||||
|
||||
def test_cholesky_solve():
|
||||
A = Matrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[8, 3, 6]])
|
||||
x = Matrix(3, 1, [3, 7, 5])
|
||||
b = A*x
|
||||
soln = A.cholesky_solve(b)
|
||||
assert soln == x
|
||||
A = Matrix([[0, -1, 2],
|
||||
[5, 10, 7],
|
||||
[8, 3, 4]])
|
||||
x = Matrix(3, 1, [-1, 2, 5])
|
||||
b = A*x
|
||||
soln = A.cholesky_solve(b)
|
||||
assert soln == x
|
||||
A = Matrix(((1, 5), (5, 1)))
|
||||
x = Matrix((4, -3))
|
||||
b = A*x
|
||||
soln = A.cholesky_solve(b)
|
||||
assert soln == x
|
||||
A = Matrix(((9, 3*I), (-3*I, 5)))
|
||||
x = Matrix((-2, 1))
|
||||
b = A*x
|
||||
soln = A.cholesky_solve(b)
|
||||
assert expand_mul(soln) == x
|
||||
A = Matrix(((9*I, 3), (-3 + I, 5)))
|
||||
x = Matrix((2 + 3*I, -1))
|
||||
b = A*x
|
||||
soln = A.cholesky_solve(b)
|
||||
assert expand_mul(soln) == x
|
||||
a00, a01, a11, b0, b1 = symbols('a00, a01, a11, b0, b1')
|
||||
A = Matrix(((a00, a01), (a01, a11)))
|
||||
b = Matrix((b0, b1))
|
||||
x = A.cholesky_solve(b)
|
||||
assert simplify(A*x) == b
|
||||
|
||||
|
||||
def test_LDLsolve():
|
||||
A = Matrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[8, 3, 6]])
|
||||
x = Matrix(3, 1, [3, 7, 5])
|
||||
b = A*x
|
||||
soln = A.LDLsolve(b)
|
||||
assert soln == x
|
||||
|
||||
A = Matrix([[0, -1, 2],
|
||||
[5, 10, 7],
|
||||
[8, 3, 4]])
|
||||
x = Matrix(3, 1, [-1, 2, 5])
|
||||
b = A*x
|
||||
soln = A.LDLsolve(b)
|
||||
assert soln == x
|
||||
|
||||
A = Matrix(((9, 3*I), (-3*I, 5)))
|
||||
x = Matrix((-2, 1))
|
||||
b = A*x
|
||||
soln = A.LDLsolve(b)
|
||||
assert expand_mul(soln) == x
|
||||
|
||||
A = Matrix(((9*I, 3), (-3 + I, 5)))
|
||||
x = Matrix((2 + 3*I, -1))
|
||||
b = A*x
|
||||
soln = A.LDLsolve(b)
|
||||
assert expand_mul(soln) == x
|
||||
|
||||
A = Matrix(((9, 3), (3, 9)))
|
||||
x = Matrix((1, 1))
|
||||
b = A * x
|
||||
soln = A.LDLsolve(b)
|
||||
assert expand_mul(soln) == x
|
||||
|
||||
A = Matrix([[-5, -3, -4], [-3, -7, 7]])
|
||||
x = Matrix([[8], [7], [-2]])
|
||||
b = A * x
|
||||
raises(NotImplementedError, lambda: A.LDLsolve(b))
|
||||
|
||||
|
||||
def test_lower_triangular_solve():
|
||||
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([1, 0]).lower_triangular_solve(Matrix([0, 1])))
|
||||
raises(ShapeError,
|
||||
lambda: Matrix([[1, 0], [0, 1]]).lower_triangular_solve(Matrix([1])))
|
||||
raises(ValueError,
|
||||
lambda: Matrix([[2, 1], [1, 2]]).lower_triangular_solve(
|
||||
Matrix([[1, 0], [0, 1]])))
|
||||
|
||||
A = Matrix([[1, 0], [0, 1]])
|
||||
B = Matrix([[x, y], [y, x]])
|
||||
C = Matrix([[4, 8], [2, 9]])
|
||||
|
||||
assert A.lower_triangular_solve(B) == B
|
||||
assert A.lower_triangular_solve(C) == C
|
||||
|
||||
|
||||
def test_upper_triangular_solve():
|
||||
|
||||
raises(NonSquareMatrixError,
|
||||
lambda: Matrix([1, 0]).upper_triangular_solve(Matrix([0, 1])))
|
||||
raises(ShapeError,
|
||||
lambda: Matrix([[1, 0], [0, 1]]).upper_triangular_solve(Matrix([1])))
|
||||
raises(TypeError,
|
||||
lambda: Matrix([[2, 1], [1, 2]]).upper_triangular_solve(
|
||||
Matrix([[1, 0], [0, 1]])))
|
||||
|
||||
A = Matrix([[1, 0], [0, 1]])
|
||||
B = Matrix([[x, y], [y, x]])
|
||||
C = Matrix([[2, 4], [3, 8]])
|
||||
|
||||
assert A.upper_triangular_solve(B) == B
|
||||
assert A.upper_triangular_solve(C) == C
|
||||
|
||||
|
||||
def test_diagonal_solve():
|
||||
raises(TypeError, lambda: Matrix([1, 1]).diagonal_solve(Matrix([1])))
|
||||
A = Matrix([[1, 0], [0, 1]])*2
|
||||
B = Matrix([[x, y], [y, x]])
|
||||
assert A.diagonal_solve(B) == B/2
|
||||
|
||||
A = Matrix([[1, 0], [1, 2]])
|
||||
raises(TypeError, lambda: A.diagonal_solve(B))
|
||||
|
||||
def test_pinv_solve():
|
||||
# Fully determined system (unique result, identical to other solvers).
|
||||
A = Matrix([[1, 5], [7, 9]])
|
||||
B = Matrix([12, 13])
|
||||
assert A.pinv_solve(B) == A.cholesky_solve(B)
|
||||
assert A.pinv_solve(B) == A.LDLsolve(B)
|
||||
assert A.pinv_solve(B) == Matrix([sympify('-43/26'), sympify('71/26')])
|
||||
assert A * A.pinv() * B == B
|
||||
# Fully determined, with two-dimensional B matrix.
|
||||
B = Matrix([[12, 13, 14], [15, 16, 17]])
|
||||
assert A.pinv_solve(B) == A.cholesky_solve(B)
|
||||
assert A.pinv_solve(B) == A.LDLsolve(B)
|
||||
assert A.pinv_solve(B) == Matrix([[-33, -37, -41], [69, 75, 81]]) / 26
|
||||
assert A * A.pinv() * B == B
|
||||
# Underdetermined system (infinite results).
|
||||
A = Matrix([[1, 0, 1], [0, 1, 1]])
|
||||
B = Matrix([5, 7])
|
||||
solution = A.pinv_solve(B)
|
||||
w = {}
|
||||
for s in solution.atoms(Symbol):
|
||||
# Extract dummy symbols used in the solution.
|
||||
w[s.name] = s
|
||||
assert solution == Matrix([[w['w0_0']/3 + w['w1_0']/3 - w['w2_0']/3 + 1],
|
||||
[w['w0_0']/3 + w['w1_0']/3 - w['w2_0']/3 + 3],
|
||||
[-w['w0_0']/3 - w['w1_0']/3 + w['w2_0']/3 + 4]])
|
||||
assert A * A.pinv() * B == B
|
||||
# Overdetermined system (least squares results).
|
||||
A = Matrix([[1, 0], [0, 0], [0, 1]])
|
||||
B = Matrix([3, 2, 1])
|
||||
assert A.pinv_solve(B) == Matrix([3, 1])
|
||||
# Proof the solution is not exact.
|
||||
assert A * A.pinv() * B != B
|
||||
|
||||
def test_pinv_rank_deficient():
|
||||
# Test the four properties of the pseudoinverse for various matrices.
|
||||
As = [Matrix([[1, 1, 1], [2, 2, 2]]),
|
||||
Matrix([[1, 0], [0, 0]]),
|
||||
Matrix([[1, 2], [2, 4], [3, 6]])]
|
||||
|
||||
for A in As:
|
||||
A_pinv = A.pinv(method="RD")
|
||||
AAp = A * A_pinv
|
||||
ApA = A_pinv * A
|
||||
assert simplify(AAp * A) == A
|
||||
assert simplify(ApA * A_pinv) == A_pinv
|
||||
assert AAp.H == AAp
|
||||
assert ApA.H == ApA
|
||||
|
||||
for A in As:
|
||||
A_pinv = A.pinv(method="ED")
|
||||
AAp = A * A_pinv
|
||||
ApA = A_pinv * A
|
||||
assert simplify(AAp * A) == A
|
||||
assert simplify(ApA * A_pinv) == A_pinv
|
||||
assert AAp.H == AAp
|
||||
assert ApA.H == ApA
|
||||
|
||||
# Test solving with rank-deficient matrices.
|
||||
A = Matrix([[1, 0], [0, 0]])
|
||||
# Exact, non-unique solution.
|
||||
B = Matrix([3, 0])
|
||||
solution = A.pinv_solve(B)
|
||||
w1 = solution.atoms(Symbol).pop()
|
||||
assert w1.name == 'w1_0'
|
||||
assert solution == Matrix([3, w1])
|
||||
assert A * A.pinv() * B == B
|
||||
# Least squares, non-unique solution.
|
||||
B = Matrix([3, 1])
|
||||
solution = A.pinv_solve(B)
|
||||
w1 = solution.atoms(Symbol).pop()
|
||||
assert w1.name == 'w1_0'
|
||||
assert solution == Matrix([3, w1])
|
||||
assert A * A.pinv() * B != B
|
||||
|
||||
def test_gauss_jordan_solve():
|
||||
|
||||
# Square, full rank, unique solution
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 10]])
|
||||
b = Matrix([3, 6, 9])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
assert sol == Matrix([[-1], [2], [0]])
|
||||
assert params == Matrix(0, 1, [])
|
||||
|
||||
# Square, full rank, unique solution, B has more columns than rows
|
||||
A = eye(3)
|
||||
B = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
|
||||
sol, params = A.gauss_jordan_solve(B)
|
||||
assert sol == B
|
||||
assert params == Matrix(0, 4, [])
|
||||
|
||||
# Square, reduced rank, parametrized solution
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
b = Matrix([3, 6, 9])
|
||||
sol, params, freevar = A.gauss_jordan_solve(b, freevar=True)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
# Extract dummy symbols used in the solution.
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[w['tau0'] - 1], [-2*w['tau0'] + 2], [w['tau0']]])
|
||||
assert params == Matrix([[w['tau0']]])
|
||||
assert freevar == [2]
|
||||
|
||||
# Square, reduced rank, parametrized solution, B has two columns
|
||||
A = Matrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
B = Matrix([[3, 4], [6, 8], [9, 12]])
|
||||
sol, params, freevar = A.gauss_jordan_solve(B, freevar=True)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
# Extract dummy symbols used in the solution.
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[w['tau0'] - 1, w['tau1'] - Rational(4, 3)],
|
||||
[-2*w['tau0'] + 2, -2*w['tau1'] + Rational(8, 3)],
|
||||
[w['tau0'], w['tau1']],])
|
||||
assert params == Matrix([[w['tau0'], w['tau1']]])
|
||||
assert freevar == [2]
|
||||
|
||||
# Square, reduced rank, parametrized solution
|
||||
A = Matrix([[1, 2, 3], [2, 4, 6], [3, 6, 9]])
|
||||
b = Matrix([0, 0, 0])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[-2*w['tau0'] - 3*w['tau1']],
|
||||
[w['tau0']], [w['tau1']]])
|
||||
assert params == Matrix([[w['tau0']], [w['tau1']]])
|
||||
|
||||
# Square, reduced rank, parametrized solution
|
||||
A = Matrix([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
|
||||
b = Matrix([0, 0, 0])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[w['tau0']], [w['tau1']], [w['tau2']]])
|
||||
assert params == Matrix([[w['tau0']], [w['tau1']], [w['tau2']]])
|
||||
|
||||
# Square, reduced rank, no solution
|
||||
A = Matrix([[1, 2, 3], [2, 4, 6], [3, 6, 9]])
|
||||
b = Matrix([0, 0, 1])
|
||||
raises(ValueError, lambda: A.gauss_jordan_solve(b))
|
||||
|
||||
# Rectangular, tall, full rank, unique solution
|
||||
A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]])
|
||||
b = Matrix([0, 0, 1, 0])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
assert sol == Matrix([[Rational(-1, 2)], [0], [Rational(1, 6)]])
|
||||
assert params == Matrix(0, 1, [])
|
||||
|
||||
# Rectangular, tall, full rank, unique solution, B has less columns than rows
|
||||
A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]])
|
||||
B = Matrix([[0,0], [0, 0], [1, 2], [0, 0]])
|
||||
sol, params = A.gauss_jordan_solve(B)
|
||||
assert sol == Matrix([[Rational(-1, 2), Rational(-2, 2)], [0, 0], [Rational(1, 6), Rational(2, 6)]])
|
||||
assert params == Matrix(0, 2, [])
|
||||
|
||||
# Rectangular, tall, full rank, no solution
|
||||
A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]])
|
||||
b = Matrix([0, 0, 0, 1])
|
||||
raises(ValueError, lambda: A.gauss_jordan_solve(b))
|
||||
|
||||
# Rectangular, tall, full rank, no solution, B has two columns (2nd has no solution)
|
||||
A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]])
|
||||
B = Matrix([[0,0], [0, 0], [1, 0], [0, 1]])
|
||||
raises(ValueError, lambda: A.gauss_jordan_solve(B))
|
||||
|
||||
# Rectangular, tall, full rank, no solution, B has two columns (1st has no solution)
|
||||
A = Matrix([[1, 5, 3], [2, 1, 6], [1, 7, 9], [1, 4, 3]])
|
||||
B = Matrix([[0,0], [0, 0], [0, 1], [1, 0]])
|
||||
raises(ValueError, lambda: A.gauss_jordan_solve(B))
|
||||
|
||||
# Rectangular, tall, reduced rank, parametrized solution
|
||||
A = Matrix([[1, 5, 3], [2, 10, 6], [3, 15, 9], [1, 4, 3]])
|
||||
b = Matrix([0, 0, 0, 1])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[-3*w['tau0'] + 5], [-1], [w['tau0']]])
|
||||
assert params == Matrix([[w['tau0']]])
|
||||
|
||||
# Rectangular, tall, reduced rank, no solution
|
||||
A = Matrix([[1, 5, 3], [2, 10, 6], [3, 15, 9], [1, 4, 3]])
|
||||
b = Matrix([0, 0, 1, 1])
|
||||
raises(ValueError, lambda: A.gauss_jordan_solve(b))
|
||||
|
||||
# Rectangular, wide, full rank, parametrized solution
|
||||
A = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 1, 12]])
|
||||
b = Matrix([1, 1, 1])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[2*w['tau0'] - 1], [-3*w['tau0'] + 1], [0],
|
||||
[w['tau0']]])
|
||||
assert params == Matrix([[w['tau0']]])
|
||||
|
||||
# Rectangular, wide, reduced rank, parametrized solution
|
||||
A = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [2, 4, 6, 8]])
|
||||
b = Matrix([0, 1, 0])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[w['tau0'] + 2*w['tau1'] + S.Half],
|
||||
[-2*w['tau0'] - 3*w['tau1'] - Rational(1, 4)],
|
||||
[w['tau0']], [w['tau1']]])
|
||||
assert params == Matrix([[w['tau0']], [w['tau1']]])
|
||||
# watch out for clashing symbols
|
||||
x0, x1, x2, _x0 = symbols('_tau0 _tau1 _tau2 tau1')
|
||||
M = Matrix([[0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, _x0]])
|
||||
A = M[:, :-1]
|
||||
b = M[:, -1:]
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
assert params == Matrix(3, 1, [x0, x1, x2])
|
||||
assert sol == Matrix(5, 1, [x0, 0, x1, _x0, x2])
|
||||
|
||||
# Rectangular, wide, reduced rank, no solution
|
||||
A = Matrix([[1, 2, 3, 4], [5, 6, 7, 8], [2, 4, 6, 8]])
|
||||
b = Matrix([1, 1, 1])
|
||||
raises(ValueError, lambda: A.gauss_jordan_solve(b))
|
||||
|
||||
# Test for immutable matrix
|
||||
A = ImmutableMatrix([[1, 0], [0, 1]])
|
||||
B = ImmutableMatrix([1, 2])
|
||||
sol, params = A.gauss_jordan_solve(B)
|
||||
assert sol == ImmutableMatrix([1, 2])
|
||||
assert params == ImmutableMatrix(0, 1, [])
|
||||
assert sol.__class__ == ImmutableDenseMatrix
|
||||
assert params.__class__ == ImmutableDenseMatrix
|
||||
|
||||
# Test placement of free variables
|
||||
A = Matrix([[1, 0, 0, 0], [0, 0, 0, 1]])
|
||||
b = Matrix([1, 1])
|
||||
sol, params = A.gauss_jordan_solve(b)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert sol == Matrix([[1], [w['tau0']], [w['tau1']], [1]])
|
||||
assert params == Matrix([[w['tau0']], [w['tau1']]])
|
||||
|
||||
|
||||
def test_linsolve_underdetermined_AND_gauss_jordan_solve():
|
||||
#Test placement of free variables as per issue 19815
|
||||
A = Matrix([[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1]])
|
||||
B = Matrix([1, 2, 1, 1, 1, 1, 1, 2])
|
||||
sol, params = A.gauss_jordan_solve(B)
|
||||
w = {}
|
||||
for s in sol.atoms(Symbol):
|
||||
w[s.name] = s
|
||||
assert params == Matrix([[w['tau0']], [w['tau1']], [w['tau2']],
|
||||
[w['tau3']], [w['tau4']], [w['tau5']]])
|
||||
assert sol == Matrix([[1 - 1*w['tau2']],
|
||||
[w['tau2']],
|
||||
[1 - 1*w['tau0'] + w['tau1']],
|
||||
[w['tau0']],
|
||||
[w['tau3'] + w['tau4']],
|
||||
[-1*w['tau3'] - 1*w['tau4'] - 1*w['tau1']],
|
||||
[1 - 1*w['tau2']],
|
||||
[w['tau1']],
|
||||
[w['tau2']],
|
||||
[w['tau3']],
|
||||
[w['tau4']],
|
||||
[1 - 1*w['tau5']],
|
||||
[w['tau5']],
|
||||
[1]])
|
||||
|
||||
from sympy.abc import j,f
|
||||
# https://github.com/sympy/sympy/issues/20046
|
||||
A = Matrix([
|
||||
[1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
[0, -1, 0, -1, 0, -1, 0, -1, -j],
|
||||
[0, 0, 0, 0, 1, 1, 1, 1, f]
|
||||
])
|
||||
|
||||
sol_1=Matrix(list(linsolve(A))[0])
|
||||
|
||||
tau0, tau1, tau2, tau3, tau4 = symbols('tau:5')
|
||||
|
||||
assert sol_1 == Matrix([[-f - j - tau0 + tau2 + tau4 + 1],
|
||||
[j - tau1 - tau2 - tau4],
|
||||
[tau0],
|
||||
[tau1],
|
||||
[f - tau2 - tau3 - tau4],
|
||||
[tau2],
|
||||
[tau3],
|
||||
[tau4]])
|
||||
|
||||
# https://github.com/sympy/sympy/issues/19815
|
||||
sol_2 = A[:, : -1 ] * sol_1 - A[:, -1 ]
|
||||
assert sol_2 == Matrix([[0], [0], [0]])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("det_method", ["bird", "laplace"])
|
||||
@pytest.mark.parametrize("M, rhs", [
|
||||
(Matrix([[2, 3, 5], [3, 6, 2], [8, 3, 6]]), Matrix(3, 1, [3, 7, 5])),
|
||||
(Matrix([[2, 3, 5], [3, 6, 2], [8, 3, 6]]),
|
||||
Matrix([[1, 2], [3, 4], [5, 6]])),
|
||||
(Matrix(2, 2, symbols("a:4")), Matrix(2, 1, symbols("b:2"))),
|
||||
])
|
||||
def test_cramer_solve(det_method, M, rhs):
|
||||
assert simplify(M.cramer_solve(rhs, det_method=det_method) - M.LUsolve(rhs)
|
||||
) == Matrix.zeros(M.rows, rhs.cols)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("det_method, error", [
|
||||
("bird", DMShapeError), (_det_laplace, NonSquareMatrixError)])
|
||||
def test_cramer_solve_errors(det_method, error):
|
||||
# Non-square matrix
|
||||
A = Matrix([[0, -1, 2], [5, 10, 7]])
|
||||
b = Matrix([-2, 15])
|
||||
raises(error, lambda: A.cramer_solve(b, det_method=det_method))
|
||||
|
||||
|
||||
def test_solve():
|
||||
A = Matrix([[1,2], [2,4]])
|
||||
b = Matrix([[3], [4]])
|
||||
raises(ValueError, lambda: A.solve(b)) #no solution
|
||||
b = Matrix([[ 4], [8]])
|
||||
raises(ValueError, lambda: A.solve(b)) #infinite solution
|
||||
@@ -0,0 +1,745 @@
|
||||
from sympy.core.numbers import (Float, I, Rational)
|
||||
from sympy.core.singleton import S
|
||||
from sympy.core.symbol import (Symbol, symbols)
|
||||
from sympy.functions.elementary.complexes import Abs
|
||||
from sympy.polys.polytools import PurePoly
|
||||
from sympy.matrices import \
|
||||
Matrix, MutableSparseMatrix, ImmutableSparseMatrix, SparseMatrix, eye, \
|
||||
ones, zeros, ShapeError, NonSquareMatrixError
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_sparse_creation():
|
||||
a = SparseMatrix(2, 2, {(0, 0): [[1, 2], [3, 4]]})
|
||||
assert a == SparseMatrix([[1, 2], [3, 4]])
|
||||
a = SparseMatrix(2, 2, {(0, 0): [[1, 2]]})
|
||||
assert a == SparseMatrix([[1, 2], [0, 0]])
|
||||
a = SparseMatrix(2, 2, {(0, 0): [1, 2]})
|
||||
assert a == SparseMatrix([[1, 0], [2, 0]])
|
||||
|
||||
|
||||
def test_sparse_matrix():
|
||||
def sparse_eye(n):
|
||||
return SparseMatrix.eye(n)
|
||||
|
||||
def sparse_zeros(n):
|
||||
return SparseMatrix.zeros(n)
|
||||
|
||||
# creation args
|
||||
raises(TypeError, lambda: SparseMatrix(1, 2))
|
||||
|
||||
a = SparseMatrix((
|
||||
(1, 0),
|
||||
(0, 1)
|
||||
))
|
||||
assert SparseMatrix(a) == a
|
||||
|
||||
from sympy.matrices import MutableDenseMatrix
|
||||
a = MutableSparseMatrix([])
|
||||
b = MutableDenseMatrix([1, 2])
|
||||
assert a.row_join(b) == b
|
||||
assert a.col_join(b) == b
|
||||
assert type(a.row_join(b)) == type(a)
|
||||
assert type(a.col_join(b)) == type(a)
|
||||
|
||||
# make sure 0 x n matrices get stacked correctly
|
||||
sparse_matrices = [SparseMatrix.zeros(0, n) for n in range(4)]
|
||||
assert SparseMatrix.hstack(*sparse_matrices) == Matrix(0, 6, [])
|
||||
sparse_matrices = [SparseMatrix.zeros(n, 0) for n in range(4)]
|
||||
assert SparseMatrix.vstack(*sparse_matrices) == Matrix(6, 0, [])
|
||||
|
||||
# test element assignment
|
||||
a = SparseMatrix((
|
||||
(1, 0),
|
||||
(0, 1)
|
||||
))
|
||||
|
||||
a[3] = 4
|
||||
assert a[1, 1] == 4
|
||||
a[3] = 1
|
||||
|
||||
a[0, 0] = 2
|
||||
assert a == SparseMatrix((
|
||||
(2, 0),
|
||||
(0, 1)
|
||||
))
|
||||
a[1, 0] = 5
|
||||
assert a == SparseMatrix((
|
||||
(2, 0),
|
||||
(5, 1)
|
||||
))
|
||||
a[1, 1] = 0
|
||||
assert a == SparseMatrix((
|
||||
(2, 0),
|
||||
(5, 0)
|
||||
))
|
||||
assert a.todok() == {(0, 0): 2, (1, 0): 5}
|
||||
|
||||
# test_multiplication
|
||||
a = SparseMatrix((
|
||||
(1, 2),
|
||||
(3, 1),
|
||||
(0, 6),
|
||||
))
|
||||
|
||||
b = SparseMatrix((
|
||||
(1, 2),
|
||||
(3, 0),
|
||||
))
|
||||
|
||||
c = a*b
|
||||
assert c[0, 0] == 7
|
||||
assert c[0, 1] == 2
|
||||
assert c[1, 0] == 6
|
||||
assert c[1, 1] == 6
|
||||
assert c[2, 0] == 18
|
||||
assert c[2, 1] == 0
|
||||
|
||||
try:
|
||||
eval('c = a @ b')
|
||||
except SyntaxError:
|
||||
pass
|
||||
else:
|
||||
assert c[0, 0] == 7
|
||||
assert c[0, 1] == 2
|
||||
assert c[1, 0] == 6
|
||||
assert c[1, 1] == 6
|
||||
assert c[2, 0] == 18
|
||||
assert c[2, 1] == 0
|
||||
|
||||
x = Symbol("x")
|
||||
|
||||
c = b * Symbol("x")
|
||||
assert isinstance(c, SparseMatrix)
|
||||
assert c[0, 0] == x
|
||||
assert c[0, 1] == 2*x
|
||||
assert c[1, 0] == 3*x
|
||||
assert c[1, 1] == 0
|
||||
|
||||
c = 5 * b
|
||||
assert isinstance(c, SparseMatrix)
|
||||
assert c[0, 0] == 5
|
||||
assert c[0, 1] == 2*5
|
||||
assert c[1, 0] == 3*5
|
||||
assert c[1, 1] == 0
|
||||
|
||||
#test_power
|
||||
A = SparseMatrix([[2, 3], [4, 5]])
|
||||
assert (A**5)[:] == [6140, 8097, 10796, 14237]
|
||||
A = SparseMatrix([[2, 1, 3], [4, 2, 4], [6, 12, 1]])
|
||||
assert (A**3)[:] == [290, 262, 251, 448, 440, 368, 702, 954, 433]
|
||||
|
||||
# test_creation
|
||||
x = Symbol("x")
|
||||
a = SparseMatrix([[x, 0], [0, 0]])
|
||||
m = a
|
||||
assert m.cols == m.rows
|
||||
assert m.cols == 2
|
||||
assert m[:] == [x, 0, 0, 0]
|
||||
b = SparseMatrix(2, 2, [x, 0, 0, 0])
|
||||
m = b
|
||||
assert m.cols == m.rows
|
||||
assert m.cols == 2
|
||||
assert m[:] == [x, 0, 0, 0]
|
||||
|
||||
assert a == b
|
||||
S = sparse_eye(3)
|
||||
S.row_del(1)
|
||||
assert S == SparseMatrix([
|
||||
[1, 0, 0],
|
||||
[0, 0, 1]])
|
||||
S = sparse_eye(3)
|
||||
S.col_del(1)
|
||||
assert S == SparseMatrix([
|
||||
[1, 0],
|
||||
[0, 0],
|
||||
[0, 1]])
|
||||
S = SparseMatrix.eye(3)
|
||||
S[2, 1] = 2
|
||||
S.col_swap(1, 0)
|
||||
assert S == SparseMatrix([
|
||||
[0, 1, 0],
|
||||
[1, 0, 0],
|
||||
[2, 0, 1]])
|
||||
S.row_swap(0, 1)
|
||||
assert S == SparseMatrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[2, 0, 1]])
|
||||
|
||||
a = SparseMatrix(1, 2, [1, 2])
|
||||
b = a.copy()
|
||||
c = a.copy()
|
||||
assert a[0] == 1
|
||||
a.row_del(0)
|
||||
assert a == SparseMatrix(0, 2, [])
|
||||
b.col_del(1)
|
||||
assert b == SparseMatrix(1, 1, [1])
|
||||
|
||||
assert SparseMatrix([[1, 2, 3], [1, 2], [1]]) == Matrix([
|
||||
[1, 2, 3],
|
||||
[1, 2, 0],
|
||||
[1, 0, 0]])
|
||||
assert SparseMatrix(4, 4, {(1, 1): sparse_eye(2)}) == Matrix([
|
||||
[0, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 1, 0],
|
||||
[0, 0, 0, 0]])
|
||||
raises(ValueError, lambda: SparseMatrix(1, 1, {(1, 1): 1}))
|
||||
assert SparseMatrix(1, 2, [1, 2]).tolist() == [[1, 2]]
|
||||
assert SparseMatrix(2, 2, [1, [2, 3]]).tolist() == [[1, 0], [2, 3]]
|
||||
raises(ValueError, lambda: SparseMatrix(2, 2, [1]))
|
||||
raises(ValueError, lambda: SparseMatrix(1, 1, [[1, 2]]))
|
||||
assert SparseMatrix([.1]).has(Float)
|
||||
# autosizing
|
||||
assert SparseMatrix(None, {(0, 1): 0}).shape == (0, 0)
|
||||
assert SparseMatrix(None, {(0, 1): 1}).shape == (1, 2)
|
||||
assert SparseMatrix(None, None, {(0, 1): 1}).shape == (1, 2)
|
||||
raises(ValueError, lambda: SparseMatrix(None, 1, [[1, 2]]))
|
||||
raises(ValueError, lambda: SparseMatrix(1, None, [[1, 2]]))
|
||||
raises(ValueError, lambda: SparseMatrix(3, 3, {(0, 0): ones(2), (1, 1): 2}))
|
||||
|
||||
# test_determinant
|
||||
x, y = Symbol('x'), Symbol('y')
|
||||
|
||||
assert SparseMatrix(1, 1, [0]).det() == 0
|
||||
|
||||
assert SparseMatrix([[1]]).det() == 1
|
||||
|
||||
assert SparseMatrix(((-3, 2), (8, -5))).det() == -1
|
||||
|
||||
assert SparseMatrix(((x, 1), (y, 2*y))).det() == 2*x*y - y
|
||||
|
||||
assert SparseMatrix(( (1, 1, 1),
|
||||
(1, 2, 3),
|
||||
(1, 3, 6) )).det() == 1
|
||||
|
||||
assert SparseMatrix(( ( 3, -2, 0, 5),
|
||||
(-2, 1, -2, 2),
|
||||
( 0, -2, 5, 0),
|
||||
( 5, 0, 3, 4) )).det() == -289
|
||||
|
||||
assert SparseMatrix(( ( 1, 2, 3, 4),
|
||||
( 5, 6, 7, 8),
|
||||
( 9, 10, 11, 12),
|
||||
(13, 14, 15, 16) )).det() == 0
|
||||
|
||||
assert SparseMatrix(( (3, 2, 0, 0, 0),
|
||||
(0, 3, 2, 0, 0),
|
||||
(0, 0, 3, 2, 0),
|
||||
(0, 0, 0, 3, 2),
|
||||
(2, 0, 0, 0, 3) )).det() == 275
|
||||
|
||||
assert SparseMatrix(( (1, 0, 1, 2, 12),
|
||||
(2, 0, 1, 1, 4),
|
||||
(2, 1, 1, -1, 3),
|
||||
(3, 2, -1, 1, 8),
|
||||
(1, 1, 1, 0, 6) )).det() == -55
|
||||
|
||||
assert SparseMatrix(( (-5, 2, 3, 4, 5),
|
||||
( 1, -4, 3, 4, 5),
|
||||
( 1, 2, -3, 4, 5),
|
||||
( 1, 2, 3, -2, 5),
|
||||
( 1, 2, 3, 4, -1) )).det() == 11664
|
||||
|
||||
assert SparseMatrix(( ( 3, 0, 0, 0),
|
||||
(-2, 1, 0, 0),
|
||||
( 0, -2, 5, 0),
|
||||
( 5, 0, 3, 4) )).det() == 60
|
||||
|
||||
assert SparseMatrix(( ( 1, 0, 0, 0),
|
||||
( 5, 0, 0, 0),
|
||||
( 9, 10, 11, 0),
|
||||
(13, 14, 15, 16) )).det() == 0
|
||||
|
||||
assert SparseMatrix(( (3, 2, 0, 0, 0),
|
||||
(0, 3, 2, 0, 0),
|
||||
(0, 0, 3, 2, 0),
|
||||
(0, 0, 0, 3, 2),
|
||||
(0, 0, 0, 0, 3) )).det() == 243
|
||||
|
||||
assert SparseMatrix(( ( 2, 7, -1, 3, 2),
|
||||
( 0, 0, 1, 0, 1),
|
||||
(-2, 0, 7, 0, 2),
|
||||
(-3, -2, 4, 5, 3),
|
||||
( 1, 0, 0, 0, 1) )).det() == 123
|
||||
|
||||
# test_slicing
|
||||
m0 = sparse_eye(4)
|
||||
assert m0[:3, :3] == sparse_eye(3)
|
||||
assert m0[2:4, 0:2] == sparse_zeros(2)
|
||||
|
||||
m1 = SparseMatrix(3, 3, lambda i, j: i + j)
|
||||
assert m1[0, :] == SparseMatrix(1, 3, (0, 1, 2))
|
||||
assert m1[1:3, 1] == SparseMatrix(2, 1, (2, 3))
|
||||
|
||||
m2 = SparseMatrix(
|
||||
[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]])
|
||||
assert m2[:, -1] == SparseMatrix(4, 1, [3, 7, 11, 15])
|
||||
assert m2[-2:, :] == SparseMatrix([[8, 9, 10, 11], [12, 13, 14, 15]])
|
||||
|
||||
assert SparseMatrix([[1, 2], [3, 4]])[[1], [1]] == Matrix([[4]])
|
||||
|
||||
# test_submatrix_assignment
|
||||
m = sparse_zeros(4)
|
||||
m[2:4, 2:4] = sparse_eye(2)
|
||||
assert m == SparseMatrix([(0, 0, 0, 0),
|
||||
(0, 0, 0, 0),
|
||||
(0, 0, 1, 0),
|
||||
(0, 0, 0, 1)])
|
||||
assert len(m.todok()) == 2
|
||||
m[:2, :2] = sparse_eye(2)
|
||||
assert m == sparse_eye(4)
|
||||
m[:, 0] = SparseMatrix(4, 1, (1, 2, 3, 4))
|
||||
assert m == SparseMatrix([(1, 0, 0, 0),
|
||||
(2, 1, 0, 0),
|
||||
(3, 0, 1, 0),
|
||||
(4, 0, 0, 1)])
|
||||
m[:, :] = sparse_zeros(4)
|
||||
assert m == sparse_zeros(4)
|
||||
m[:, :] = ((1, 2, 3, 4), (5, 6, 7, 8), (9, 10, 11, 12), (13, 14, 15, 16))
|
||||
assert m == SparseMatrix((( 1, 2, 3, 4),
|
||||
( 5, 6, 7, 8),
|
||||
( 9, 10, 11, 12),
|
||||
(13, 14, 15, 16)))
|
||||
m[:2, 0] = [0, 0]
|
||||
assert m == SparseMatrix((( 0, 2, 3, 4),
|
||||
( 0, 6, 7, 8),
|
||||
( 9, 10, 11, 12),
|
||||
(13, 14, 15, 16)))
|
||||
|
||||
# test_reshape
|
||||
m0 = sparse_eye(3)
|
||||
assert m0.reshape(1, 9) == SparseMatrix(1, 9, (1, 0, 0, 0, 1, 0, 0, 0, 1))
|
||||
m1 = SparseMatrix(3, 4, lambda i, j: i + j)
|
||||
assert m1.reshape(4, 3) == \
|
||||
SparseMatrix([(0, 1, 2), (3, 1, 2), (3, 4, 2), (3, 4, 5)])
|
||||
assert m1.reshape(2, 6) == \
|
||||
SparseMatrix([(0, 1, 2, 3, 1, 2), (3, 4, 2, 3, 4, 5)])
|
||||
|
||||
# test_applyfunc
|
||||
m0 = sparse_eye(3)
|
||||
assert m0.applyfunc(lambda x: 2*x) == sparse_eye(3)*2
|
||||
assert m0.applyfunc(lambda x: 0 ) == sparse_zeros(3)
|
||||
|
||||
# test__eval_Abs
|
||||
assert abs(SparseMatrix(((x, 1), (y, 2*y)))) == SparseMatrix(((Abs(x), 1), (Abs(y), 2*Abs(y))))
|
||||
|
||||
# test_LUdecomp
|
||||
testmat = SparseMatrix([[ 0, 2, 5, 3],
|
||||
[ 3, 3, 7, 4],
|
||||
[ 8, 4, 0, 2],
|
||||
[-2, 6, 3, 4]])
|
||||
L, U, p = testmat.LUdecomposition()
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - testmat == sparse_zeros(4)
|
||||
|
||||
testmat = SparseMatrix([[ 6, -2, 7, 4],
|
||||
[ 0, 3, 6, 7],
|
||||
[ 1, -2, 7, 4],
|
||||
[-9, 2, 6, 3]])
|
||||
L, U, p = testmat.LUdecomposition()
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - testmat == sparse_zeros(4)
|
||||
|
||||
x, y, z = Symbol('x'), Symbol('y'), Symbol('z')
|
||||
M = Matrix(((1, x, 1), (2, y, 0), (y, 0, z)))
|
||||
L, U, p = M.LUdecomposition()
|
||||
assert L.is_lower
|
||||
assert U.is_upper
|
||||
assert (L*U).permute_rows(p, 'backward') - M == sparse_zeros(3)
|
||||
|
||||
# test_LUsolve
|
||||
A = SparseMatrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[8, 3, 6]])
|
||||
x = SparseMatrix(3, 1, [3, 7, 5])
|
||||
b = A*x
|
||||
soln = A.LUsolve(b)
|
||||
assert soln == x
|
||||
A = SparseMatrix([[0, -1, 2],
|
||||
[5, 10, 7],
|
||||
[8, 3, 4]])
|
||||
x = SparseMatrix(3, 1, [-1, 2, 5])
|
||||
b = A*x
|
||||
soln = A.LUsolve(b)
|
||||
assert soln == x
|
||||
|
||||
# test_inverse
|
||||
A = sparse_eye(4)
|
||||
assert A.inv() == sparse_eye(4)
|
||||
assert A.inv(method="CH") == sparse_eye(4)
|
||||
assert A.inv(method="LDL") == sparse_eye(4)
|
||||
|
||||
A = SparseMatrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[7, 2, 6]])
|
||||
Ainv = SparseMatrix(Matrix(A).inv())
|
||||
assert A*Ainv == sparse_eye(3)
|
||||
assert A.inv(method="CH") == Ainv
|
||||
assert A.inv(method="LDL") == Ainv
|
||||
|
||||
A = SparseMatrix([[2, 3, 5],
|
||||
[3, 6, 2],
|
||||
[5, 2, 6]])
|
||||
Ainv = SparseMatrix(Matrix(A).inv())
|
||||
assert A*Ainv == sparse_eye(3)
|
||||
assert A.inv(method="CH") == Ainv
|
||||
assert A.inv(method="LDL") == Ainv
|
||||
|
||||
# test_cross
|
||||
v1 = Matrix(1, 3, [1, 2, 3])
|
||||
v2 = Matrix(1, 3, [3, 4, 5])
|
||||
assert v1.cross(v2) == Matrix(1, 3, [-2, 4, -2])
|
||||
assert v1.norm(2)**2 == 14
|
||||
|
||||
# conjugate
|
||||
a = SparseMatrix(((1, 2 + I), (3, 4)))
|
||||
assert a.C == SparseMatrix([
|
||||
[1, 2 - I],
|
||||
[3, 4]
|
||||
])
|
||||
|
||||
# mul
|
||||
assert a*Matrix(2, 2, [1, 0, 0, 1]) == a
|
||||
assert a + Matrix(2, 2, [1, 1, 1, 1]) == SparseMatrix([
|
||||
[2, 3 + I],
|
||||
[4, 5]
|
||||
])
|
||||
|
||||
# col join
|
||||
assert a.col_join(sparse_eye(2)) == SparseMatrix([
|
||||
[1, 2 + I],
|
||||
[3, 4],
|
||||
[1, 0],
|
||||
[0, 1]
|
||||
])
|
||||
|
||||
# row insert
|
||||
assert a.row_insert(2, sparse_eye(2)) == SparseMatrix([
|
||||
[1, 2 + I],
|
||||
[3, 4],
|
||||
[1, 0],
|
||||
[0, 1]
|
||||
])
|
||||
|
||||
# col insert
|
||||
assert a.col_insert(2, SparseMatrix.zeros(2, 1)) == SparseMatrix([
|
||||
[1, 2 + I, 0],
|
||||
[3, 4, 0],
|
||||
])
|
||||
|
||||
# symmetric
|
||||
assert not a.is_symmetric(simplify=False)
|
||||
|
||||
# col op
|
||||
M = SparseMatrix.eye(3)*2
|
||||
M[1, 0] = -1
|
||||
M.col_op(1, lambda v, i: v + 2*M[i, 0])
|
||||
assert M == SparseMatrix([
|
||||
[ 2, 4, 0],
|
||||
[-1, 0, 0],
|
||||
[ 0, 0, 2]
|
||||
])
|
||||
|
||||
# fill
|
||||
M = SparseMatrix.eye(3)
|
||||
M.fill(2)
|
||||
assert M == SparseMatrix([
|
||||
[2, 2, 2],
|
||||
[2, 2, 2],
|
||||
[2, 2, 2],
|
||||
])
|
||||
|
||||
# test_cofactor
|
||||
assert sparse_eye(3) == sparse_eye(3).cofactor_matrix()
|
||||
test = SparseMatrix([[1, 3, 2], [2, 6, 3], [2, 3, 6]])
|
||||
assert test.cofactor_matrix() == \
|
||||
SparseMatrix([[27, -6, -6], [-12, 2, 3], [-3, 1, 0]])
|
||||
test = SparseMatrix([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
|
||||
assert test.cofactor_matrix() == \
|
||||
SparseMatrix([[-3, 6, -3], [6, -12, 6], [-3, 6, -3]])
|
||||
|
||||
# test_jacobian
|
||||
x = Symbol('x')
|
||||
y = Symbol('y')
|
||||
L = SparseMatrix(1, 2, [x**2*y, 2*y**2 + x*y])
|
||||
syms = [x, y]
|
||||
assert L.jacobian(syms) == Matrix([[2*x*y, x**2], [y, 4*y + x]])
|
||||
|
||||
L = SparseMatrix(1, 2, [x, x**2*y**3])
|
||||
assert L.jacobian(syms) == SparseMatrix([[1, 0], [2*x*y**3, x**2*3*y**2]])
|
||||
|
||||
# test_QR
|
||||
A = Matrix([[1, 2], [2, 3]])
|
||||
Q, S = A.QRdecomposition()
|
||||
R = Rational
|
||||
assert Q == Matrix([
|
||||
[ 5**R(-1, 2), (R(2)/5)*(R(1)/5)**R(-1, 2)],
|
||||
[2*5**R(-1, 2), (-R(1)/5)*(R(1)/5)**R(-1, 2)]])
|
||||
assert S == Matrix([
|
||||
[5**R(1, 2), 8*5**R(-1, 2)],
|
||||
[ 0, (R(1)/5)**R(1, 2)]])
|
||||
assert Q*S == A
|
||||
assert Q.T * Q == sparse_eye(2)
|
||||
|
||||
R = Rational
|
||||
# test nullspace
|
||||
# first test reduced row-ech form
|
||||
|
||||
M = SparseMatrix([[5, 7, 2, 1],
|
||||
[1, 6, 2, -1]])
|
||||
out, tmp = M.rref()
|
||||
assert out == Matrix([[1, 0, -R(2)/23, R(13)/23],
|
||||
[0, 1, R(8)/23, R(-6)/23]])
|
||||
|
||||
M = SparseMatrix([[ 1, 3, 0, 2, 6, 3, 1],
|
||||
[-2, -6, 0, -2, -8, 3, 1],
|
||||
[ 3, 9, 0, 0, 6, 6, 2],
|
||||
[-1, -3, 0, 1, 0, 9, 3]])
|
||||
|
||||
out, tmp = M.rref()
|
||||
assert out == Matrix([[1, 3, 0, 0, 2, 0, 0],
|
||||
[0, 0, 0, 1, 2, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, R(1)/3],
|
||||
[0, 0, 0, 0, 0, 0, 0]])
|
||||
# now check the vectors
|
||||
basis = M.nullspace()
|
||||
assert basis[0] == Matrix([-3, 1, 0, 0, 0, 0, 0])
|
||||
assert basis[1] == Matrix([0, 0, 1, 0, 0, 0, 0])
|
||||
assert basis[2] == Matrix([-2, 0, 0, -2, 1, 0, 0])
|
||||
assert basis[3] == Matrix([0, 0, 0, 0, 0, R(-1)/3, 1])
|
||||
|
||||
# test eigen
|
||||
x = Symbol('x')
|
||||
y = Symbol('y')
|
||||
sparse_eye3 = sparse_eye(3)
|
||||
assert sparse_eye3.charpoly(x) == PurePoly((x - 1)**3)
|
||||
assert sparse_eye3.charpoly(y) == PurePoly((y - 1)**3)
|
||||
|
||||
# test values
|
||||
M = Matrix([( 0, 1, -1),
|
||||
( 1, 1, 0),
|
||||
(-1, 0, 1)])
|
||||
vals = M.eigenvals()
|
||||
assert sorted(vals.keys()) == [-1, 1, 2]
|
||||
|
||||
R = Rational
|
||||
M = Matrix([[1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, 1]])
|
||||
assert M.eigenvects() == [(1, 3, [
|
||||
Matrix([1, 0, 0]),
|
||||
Matrix([0, 1, 0]),
|
||||
Matrix([0, 0, 1])])]
|
||||
M = Matrix([[5, 0, 2],
|
||||
[3, 2, 0],
|
||||
[0, 0, 1]])
|
||||
assert M.eigenvects() == [(1, 1, [Matrix([R(-1)/2, R(3)/2, 1])]),
|
||||
(2, 1, [Matrix([0, 1, 0])]),
|
||||
(5, 1, [Matrix([1, 1, 0])])]
|
||||
|
||||
assert M.zeros(3, 5) == SparseMatrix(3, 5, {})
|
||||
A = SparseMatrix(10, 10, {(0, 0): 18, (0, 9): 12, (1, 4): 18, (2, 7): 16, (3, 9): 12, (4, 2): 19, (5, 7): 16, (6, 2): 12, (9, 7): 18})
|
||||
assert A.row_list() == [(0, 0, 18), (0, 9, 12), (1, 4, 18), (2, 7, 16), (3, 9, 12), (4, 2, 19), (5, 7, 16), (6, 2, 12), (9, 7, 18)]
|
||||
assert A.col_list() == [(0, 0, 18), (4, 2, 19), (6, 2, 12), (1, 4, 18), (2, 7, 16), (5, 7, 16), (9, 7, 18), (0, 9, 12), (3, 9, 12)]
|
||||
assert SparseMatrix.eye(2).nnz() == 2
|
||||
|
||||
|
||||
def test_scalar_multiply():
|
||||
assert SparseMatrix([[1, 2]]).scalar_multiply(3) == SparseMatrix([[3, 6]])
|
||||
|
||||
|
||||
def test_transpose():
|
||||
assert SparseMatrix(((1, 2), (3, 4))).transpose() == \
|
||||
SparseMatrix(((1, 3), (2, 4)))
|
||||
|
||||
|
||||
def test_trace():
|
||||
assert SparseMatrix(((1, 2), (3, 4))).trace() == 5
|
||||
assert SparseMatrix(((0, 0), (0, 4))).trace() == 4
|
||||
|
||||
|
||||
def test_CL_RL():
|
||||
assert SparseMatrix(((1, 2), (3, 4))).row_list() == \
|
||||
[(0, 0, 1), (0, 1, 2), (1, 0, 3), (1, 1, 4)]
|
||||
assert SparseMatrix(((1, 2), (3, 4))).col_list() == \
|
||||
[(0, 0, 1), (1, 0, 3), (0, 1, 2), (1, 1, 4)]
|
||||
|
||||
|
||||
def test_add():
|
||||
assert SparseMatrix(((1, 0), (0, 1))) + SparseMatrix(((0, 1), (1, 0))) == \
|
||||
SparseMatrix(((1, 1), (1, 1)))
|
||||
a = SparseMatrix(100, 100, lambda i, j: int(j != 0 and i % j == 0))
|
||||
b = SparseMatrix(100, 100, lambda i, j: int(i != 0 and j % i == 0))
|
||||
assert (len(a.todok()) + len(b.todok()) - len((a + b).todok()) > 0)
|
||||
|
||||
|
||||
def test_errors():
|
||||
raises(ValueError, lambda: SparseMatrix(1.4, 2, lambda i, j: 0))
|
||||
raises(TypeError, lambda: SparseMatrix([1, 2, 3], [1, 2]))
|
||||
raises(ValueError, lambda: SparseMatrix([[1, 2], [3, 4]])[(1, 2, 3)])
|
||||
raises(IndexError, lambda: SparseMatrix([[1, 2], [3, 4]])[5])
|
||||
raises(ValueError, lambda: SparseMatrix([[1, 2], [3, 4]])[1, 2, 3])
|
||||
raises(TypeError,
|
||||
lambda: SparseMatrix([[1, 2], [3, 4]]).copyin_list([0, 1], set()))
|
||||
raises(
|
||||
IndexError, lambda: SparseMatrix([[1, 2], [3, 4]])[1, 2])
|
||||
raises(TypeError, lambda: SparseMatrix([1, 2, 3]).cross(1))
|
||||
raises(IndexError, lambda: SparseMatrix(1, 2, [1, 2])[3])
|
||||
raises(ShapeError,
|
||||
lambda: SparseMatrix(1, 2, [1, 2]) + SparseMatrix(2, 1, [2, 1]))
|
||||
|
||||
|
||||
def test_len():
|
||||
assert not SparseMatrix()
|
||||
assert SparseMatrix() == SparseMatrix([])
|
||||
assert SparseMatrix() == SparseMatrix([[]])
|
||||
|
||||
|
||||
def test_sparse_zeros_sparse_eye():
|
||||
assert SparseMatrix.eye(3) == eye(3, cls=SparseMatrix)
|
||||
assert len(SparseMatrix.eye(3).todok()) == 3
|
||||
assert SparseMatrix.zeros(3) == zeros(3, cls=SparseMatrix)
|
||||
assert len(SparseMatrix.zeros(3).todok()) == 0
|
||||
|
||||
|
||||
def test_copyin():
|
||||
s = SparseMatrix(3, 3, {})
|
||||
s[1, 0] = 1
|
||||
assert s[:, 0] == SparseMatrix(Matrix([0, 1, 0]))
|
||||
assert s[3] == 1
|
||||
assert s[3: 4] == [1]
|
||||
s[1, 1] = 42
|
||||
assert s[1, 1] == 42
|
||||
assert s[1, 1:] == SparseMatrix([[42, 0]])
|
||||
s[1, 1:] = Matrix([[5, 6]])
|
||||
assert s[1, :] == SparseMatrix([[1, 5, 6]])
|
||||
s[1, 1:] = [[42, 43]]
|
||||
assert s[1, :] == SparseMatrix([[1, 42, 43]])
|
||||
s[0, 0] = 17
|
||||
assert s[:, :1] == SparseMatrix([17, 1, 0])
|
||||
s[0, 0] = [1, 1, 1]
|
||||
assert s[:, 0] == SparseMatrix([1, 1, 1])
|
||||
s[0, 0] = Matrix([1, 1, 1])
|
||||
assert s[:, 0] == SparseMatrix([1, 1, 1])
|
||||
s[0, 0] = SparseMatrix([1, 1, 1])
|
||||
assert s[:, 0] == SparseMatrix([1, 1, 1])
|
||||
|
||||
|
||||
def test_sparse_solve():
|
||||
A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11)))
|
||||
assert A.cholesky() == Matrix([
|
||||
[ 5, 0, 0],
|
||||
[ 3, 3, 0],
|
||||
[-1, 1, 3]])
|
||||
assert A.cholesky() * A.cholesky().T == Matrix([
|
||||
[25, 15, -5],
|
||||
[15, 18, 0],
|
||||
[-5, 0, 11]])
|
||||
|
||||
A = SparseMatrix(((25, 15, -5), (15, 18, 0), (-5, 0, 11)))
|
||||
L, D = A.LDLdecomposition()
|
||||
assert 15*L == Matrix([
|
||||
[15, 0, 0],
|
||||
[ 9, 15, 0],
|
||||
[-3, 5, 15]])
|
||||
assert D == Matrix([
|
||||
[25, 0, 0],
|
||||
[ 0, 9, 0],
|
||||
[ 0, 0, 9]])
|
||||
assert L * D * L.T == A
|
||||
|
||||
A = SparseMatrix(((3, 0, 2), (0, 0, 1), (1, 2, 0)))
|
||||
assert A.inv() * A == SparseMatrix(eye(3))
|
||||
|
||||
A = SparseMatrix([
|
||||
[ 2, -1, 0],
|
||||
[-1, 2, -1],
|
||||
[ 0, 0, 2]])
|
||||
ans = SparseMatrix([
|
||||
[Rational(2, 3), Rational(1, 3), Rational(1, 6)],
|
||||
[Rational(1, 3), Rational(2, 3), Rational(1, 3)],
|
||||
[ 0, 0, S.Half]])
|
||||
assert A.inv(method='CH') == ans
|
||||
assert A.inv(method='LDL') == ans
|
||||
assert A * ans == SparseMatrix(eye(3))
|
||||
|
||||
s = A.solve(A[:, 0], 'LDL')
|
||||
assert A*s == A[:, 0]
|
||||
s = A.solve(A[:, 0], 'CH')
|
||||
assert A*s == A[:, 0]
|
||||
A = A.col_join(A)
|
||||
s = A.solve_least_squares(A[:, 0], 'CH')
|
||||
assert A*s == A[:, 0]
|
||||
s = A.solve_least_squares(A[:, 0], 'LDL')
|
||||
assert A*s == A[:, 0]
|
||||
|
||||
|
||||
def test_lower_triangular_solve():
|
||||
raises(NonSquareMatrixError, lambda:
|
||||
SparseMatrix([[1, 2]]).lower_triangular_solve(Matrix([[1, 2]])))
|
||||
raises(ShapeError, lambda:
|
||||
SparseMatrix([[1, 2], [0, 4]]).lower_triangular_solve(Matrix([1])))
|
||||
raises(ValueError, lambda:
|
||||
SparseMatrix([[1, 2], [3, 4]]).lower_triangular_solve(Matrix([[1, 2], [3, 4]])))
|
||||
|
||||
a, b, c, d = symbols('a:d')
|
||||
u, v, w, x = symbols('u:x')
|
||||
|
||||
A = SparseMatrix([[a, 0], [c, d]])
|
||||
B = MutableSparseMatrix([[u, v], [w, x]])
|
||||
C = ImmutableSparseMatrix([[u, v], [w, x]])
|
||||
|
||||
sol = Matrix([[u/a, v/a], [(w - c*u/a)/d, (x - c*v/a)/d]])
|
||||
assert A.lower_triangular_solve(B) == sol
|
||||
assert A.lower_triangular_solve(C) == sol
|
||||
|
||||
|
||||
def test_upper_triangular_solve():
|
||||
raises(NonSquareMatrixError, lambda:
|
||||
SparseMatrix([[1, 2]]).upper_triangular_solve(Matrix([[1, 2]])))
|
||||
raises(ShapeError, lambda:
|
||||
SparseMatrix([[1, 2], [0, 4]]).upper_triangular_solve(Matrix([1])))
|
||||
raises(TypeError, lambda:
|
||||
SparseMatrix([[1, 2], [3, 4]]).upper_triangular_solve(Matrix([[1, 2], [3, 4]])))
|
||||
|
||||
a, b, c, d = symbols('a:d')
|
||||
u, v, w, x = symbols('u:x')
|
||||
|
||||
A = SparseMatrix([[a, b], [0, d]])
|
||||
B = MutableSparseMatrix([[u, v], [w, x]])
|
||||
C = ImmutableSparseMatrix([[u, v], [w, x]])
|
||||
|
||||
sol = Matrix([[(u - b*w/d)/a, (v - b*x/d)/a], [w/d, x/d]])
|
||||
assert A.upper_triangular_solve(B) == sol
|
||||
assert A.upper_triangular_solve(C) == sol
|
||||
|
||||
|
||||
def test_diagonal_solve():
|
||||
a, d = symbols('a d')
|
||||
u, v, w, x = symbols('u:x')
|
||||
|
||||
A = SparseMatrix([[a, 0], [0, d]])
|
||||
B = MutableSparseMatrix([[u, v], [w, x]])
|
||||
C = ImmutableSparseMatrix([[u, v], [w, x]])
|
||||
|
||||
sol = Matrix([[u/a, v/a], [w/d, x/d]])
|
||||
assert A.diagonal_solve(B) == sol
|
||||
assert A.diagonal_solve(C) == sol
|
||||
|
||||
|
||||
def test_hermitian():
|
||||
x = Symbol('x')
|
||||
a = SparseMatrix([[0, I], [-I, 0]])
|
||||
assert a.is_hermitian
|
||||
a = SparseMatrix([[1, I], [-I, 1]])
|
||||
assert a.is_hermitian
|
||||
a[0, 0] = 2*I
|
||||
assert a.is_hermitian is False
|
||||
a[0, 0] = x
|
||||
assert a.is_hermitian is None
|
||||
a[0, 1] = a[1, 0]*I
|
||||
assert a.is_hermitian is False
|
||||
@@ -0,0 +1,132 @@
|
||||
from sympy.matrices.sparsetools import _doktocsr, _csrtodok, banded
|
||||
from sympy.matrices.dense import (Matrix, eye, ones, zeros)
|
||||
from sympy.matrices import SparseMatrix
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_doktocsr():
|
||||
a = SparseMatrix([[1, 2, 0, 0], [0, 3, 9, 0], [0, 1, 4, 0]])
|
||||
b = SparseMatrix(4, 6, [10, 20, 0, 0, 0, 0, 0, 30, 0, 40, 0, 0, 0, 0, 50,
|
||||
60, 70, 0, 0, 0, 0, 0, 0, 80])
|
||||
c = SparseMatrix(4, 4, [0, 0, 0, 0, 0, 12, 0, 2, 15, 0, 12, 0, 0, 0, 0, 4])
|
||||
d = SparseMatrix(10, 10, {(1, 1): 12, (3, 5): 7, (7, 8): 12})
|
||||
e = SparseMatrix([[0, 0, 0], [1, 0, 2], [3, 0, 0]])
|
||||
f = SparseMatrix(7, 8, {(2, 3): 5, (4, 5):12})
|
||||
assert _doktocsr(a) == [[1, 2, 3, 9, 1, 4], [0, 1, 1, 2, 1, 2],
|
||||
[0, 2, 4, 6], [3, 4]]
|
||||
assert _doktocsr(b) == [[10, 20, 30, 40, 50, 60, 70, 80],
|
||||
[0, 1, 1, 3, 2, 3, 4, 5], [0, 2, 4, 7, 8], [4, 6]]
|
||||
assert _doktocsr(c) == [[12, 2, 15, 12, 4], [1, 3, 0, 2, 3],
|
||||
[0, 0, 2, 4, 5], [4, 4]]
|
||||
assert _doktocsr(d) == [[12, 7, 12], [1, 5, 8],
|
||||
[0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3], [10, 10]]
|
||||
assert _doktocsr(e) == [[1, 2, 3], [0, 2, 0], [0, 0, 2, 3], [3, 3]]
|
||||
assert _doktocsr(f) == [[5, 12], [3, 5], [0, 0, 0, 1, 1, 2, 2, 2], [7, 8]]
|
||||
|
||||
|
||||
def test_csrtodok():
|
||||
h = [[5, 7, 5], [2, 1, 3], [0, 1, 1, 3], [3, 4]]
|
||||
g = [[12, 5, 4], [2, 4, 2], [0, 1, 2, 3], [3, 7]]
|
||||
i = [[1, 3, 12], [0, 2, 4], [0, 2, 3], [2, 5]]
|
||||
j = [[11, 15, 12, 15], [2, 4, 1, 2], [0, 1, 1, 2, 3, 4], [5, 8]]
|
||||
k = [[1, 3], [2, 1], [0, 1, 1, 2], [3, 3]]
|
||||
m = _csrtodok(h)
|
||||
assert isinstance(m, SparseMatrix)
|
||||
assert m == SparseMatrix(3, 4,
|
||||
{(0, 2): 5, (2, 1): 7, (2, 3): 5})
|
||||
assert _csrtodok(g) == SparseMatrix(3, 7,
|
||||
{(0, 2): 12, (1, 4): 5, (2, 2): 4})
|
||||
assert _csrtodok(i) == SparseMatrix([[1, 0, 3, 0, 0], [0, 0, 0, 0, 12]])
|
||||
assert _csrtodok(j) == SparseMatrix(5, 8,
|
||||
{(0, 2): 11, (2, 4): 15, (3, 1): 12, (4, 2): 15})
|
||||
assert _csrtodok(k) == SparseMatrix(3, 3, {(0, 2): 1, (2, 1): 3})
|
||||
|
||||
|
||||
def test_banded():
|
||||
raises(TypeError, lambda: banded())
|
||||
raises(TypeError, lambda: banded(1))
|
||||
raises(TypeError, lambda: banded(1, 2))
|
||||
raises(TypeError, lambda: banded(1, 2, 3))
|
||||
raises(TypeError, lambda: banded(1, 2, 3, 4))
|
||||
raises(ValueError, lambda: banded({0: (1, 2)}, rows=1))
|
||||
raises(ValueError, lambda: banded({0: (1, 2)}, cols=1))
|
||||
raises(ValueError, lambda: banded(1, {0: (1, 2)}))
|
||||
raises(ValueError, lambda: banded(2, 1, {0: (1, 2)}))
|
||||
raises(ValueError, lambda: banded(1, 2, {0: (1, 2)}))
|
||||
|
||||
assert isinstance(banded(2, 4, {}), SparseMatrix)
|
||||
assert banded(2, 4, {}) == zeros(2, 4)
|
||||
assert banded({0: 0, 1: 0}) == zeros(0)
|
||||
assert banded({0: Matrix([1, 2])}) == Matrix([1, 2])
|
||||
assert banded({1: [1, 2, 3, 0], -1: [4, 5, 6]}) == \
|
||||
banded({1: (1, 2, 3), -1: (4, 5, 6)}) == \
|
||||
Matrix([
|
||||
[0, 1, 0, 0],
|
||||
[4, 0, 2, 0],
|
||||
[0, 5, 0, 3],
|
||||
[0, 0, 6, 0]])
|
||||
assert banded(3, 4, {-1: 1, 0: 2, 1: 3}) == \
|
||||
Matrix([
|
||||
[2, 3, 0, 0],
|
||||
[1, 2, 3, 0],
|
||||
[0, 1, 2, 3]])
|
||||
s = lambda d: (1 + d)**2
|
||||
assert banded(5, {0: s, 2: s}) == \
|
||||
Matrix([
|
||||
[1, 0, 1, 0, 0],
|
||||
[0, 4, 0, 4, 0],
|
||||
[0, 0, 9, 0, 9],
|
||||
[0, 0, 0, 16, 0],
|
||||
[0, 0, 0, 0, 25]])
|
||||
assert banded(2, {0: 1}) == \
|
||||
Matrix([
|
||||
[1, 0],
|
||||
[0, 1]])
|
||||
assert banded(2, 3, {0: 1}) == \
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[0, 1, 0]])
|
||||
vert = Matrix([1, 2, 3])
|
||||
assert banded({0: vert}, cols=3) == \
|
||||
Matrix([
|
||||
[1, 0, 0],
|
||||
[2, 1, 0],
|
||||
[3, 2, 1],
|
||||
[0, 3, 2],
|
||||
[0, 0, 3]])
|
||||
assert banded(4, {0: ones(2)}) == \
|
||||
Matrix([
|
||||
[1, 1, 0, 0],
|
||||
[1, 1, 0, 0],
|
||||
[0, 0, 1, 1],
|
||||
[0, 0, 1, 1]])
|
||||
raises(ValueError, lambda: banded({0: 2, 1: ones(2)}, rows=5))
|
||||
assert banded({0: 2, 2: (ones(2),)*3}) == \
|
||||
Matrix([
|
||||
[2, 0, 1, 1, 0, 0, 0, 0],
|
||||
[0, 2, 1, 1, 0, 0, 0, 0],
|
||||
[0, 0, 2, 0, 1, 1, 0, 0],
|
||||
[0, 0, 0, 2, 1, 1, 0, 0],
|
||||
[0, 0, 0, 0, 2, 0, 1, 1],
|
||||
[0, 0, 0, 0, 0, 2, 1, 1]])
|
||||
raises(ValueError, lambda: banded({0: (2,)*5, 1: (ones(2),)*3}))
|
||||
u2 = Matrix([[1, 1], [0, 1]])
|
||||
assert banded({0: (2,)*5, 1: (u2,)*3}) == \
|
||||
Matrix([
|
||||
[2, 1, 1, 0, 0, 0, 0],
|
||||
[0, 2, 1, 0, 0, 0, 0],
|
||||
[0, 0, 2, 1, 1, 0, 0],
|
||||
[0, 0, 0, 2, 1, 0, 0],
|
||||
[0, 0, 0, 0, 2, 1, 1],
|
||||
[0, 0, 0, 0, 0, 0, 1]])
|
||||
assert banded({0:(0, ones(2)), 2: 2}) == \
|
||||
Matrix([
|
||||
[0, 0, 2],
|
||||
[0, 1, 1],
|
||||
[0, 1, 1]])
|
||||
raises(ValueError, lambda: banded({0: (0, ones(2)), 1: 2}))
|
||||
assert banded({0: 1}, cols=3) == banded({0: 1}, rows=3) == eye(3)
|
||||
assert banded({1: 1}, rows=3) == Matrix([
|
||||
[0, 1, 0],
|
||||
[0, 0, 1],
|
||||
[0, 0, 0]])
|
||||
@@ -0,0 +1,109 @@
|
||||
from sympy.matrices import Matrix
|
||||
from sympy.core.numbers import Rational
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.solvers import solve
|
||||
|
||||
|
||||
def test_columnspace_one():
|
||||
m = Matrix([[ 1, 2, 0, 2, 5],
|
||||
[-2, -5, 1, -1, -8],
|
||||
[ 0, -3, 3, 4, 1],
|
||||
[ 3, 6, 0, -7, 2]])
|
||||
|
||||
basis = m.columnspace()
|
||||
assert basis[0] == Matrix([1, -2, 0, 3])
|
||||
assert basis[1] == Matrix([2, -5, -3, 6])
|
||||
assert basis[2] == Matrix([2, -1, 4, -7])
|
||||
|
||||
assert len(basis) == 3
|
||||
assert Matrix.hstack(m, *basis).columnspace() == basis
|
||||
|
||||
|
||||
def test_rowspace():
|
||||
m = Matrix([[ 1, 2, 0, 2, 5],
|
||||
[-2, -5, 1, -1, -8],
|
||||
[ 0, -3, 3, 4, 1],
|
||||
[ 3, 6, 0, -7, 2]])
|
||||
|
||||
basis = m.rowspace()
|
||||
assert basis[0] == Matrix([[1, 2, 0, 2, 5]])
|
||||
assert basis[1] == Matrix([[0, -1, 1, 3, 2]])
|
||||
assert basis[2] == Matrix([[0, 0, 0, 5, 5]])
|
||||
|
||||
assert len(basis) == 3
|
||||
|
||||
|
||||
def test_nullspace_one():
|
||||
m = Matrix([[ 1, 2, 0, 2, 5],
|
||||
[-2, -5, 1, -1, -8],
|
||||
[ 0, -3, 3, 4, 1],
|
||||
[ 3, 6, 0, -7, 2]])
|
||||
|
||||
basis = m.nullspace()
|
||||
assert basis[0] == Matrix([-2, 1, 1, 0, 0])
|
||||
assert basis[1] == Matrix([-1, -1, 0, -1, 1])
|
||||
# make sure the null space is really gets zeroed
|
||||
assert all(e.is_zero for e in m*basis[0])
|
||||
assert all(e.is_zero for e in m*basis[1])
|
||||
|
||||
def test_nullspace_second():
|
||||
# first test reduced row-ech form
|
||||
R = Rational
|
||||
|
||||
M = Matrix([[5, 7, 2, 1],
|
||||
[1, 6, 2, -1]])
|
||||
out, tmp = M.rref()
|
||||
assert out == Matrix([[1, 0, -R(2)/23, R(13)/23],
|
||||
[0, 1, R(8)/23, R(-6)/23]])
|
||||
|
||||
M = Matrix([[-5, -1, 4, -3, -1],
|
||||
[ 1, -1, -1, 1, 0],
|
||||
[-1, 0, 0, 0, 0],
|
||||
[ 4, 1, -4, 3, 1],
|
||||
[-2, 0, 2, -2, -1]])
|
||||
assert M*M.nullspace()[0] == Matrix(5, 1, [0]*5)
|
||||
|
||||
M = Matrix([[ 1, 3, 0, 2, 6, 3, 1],
|
||||
[-2, -6, 0, -2, -8, 3, 1],
|
||||
[ 3, 9, 0, 0, 6, 6, 2],
|
||||
[-1, -3, 0, 1, 0, 9, 3]])
|
||||
out, tmp = M.rref()
|
||||
assert out == Matrix([[1, 3, 0, 0, 2, 0, 0],
|
||||
[0, 0, 0, 1, 2, 0, 0],
|
||||
[0, 0, 0, 0, 0, 1, R(1)/3],
|
||||
[0, 0, 0, 0, 0, 0, 0]])
|
||||
|
||||
# now check the vectors
|
||||
basis = M.nullspace()
|
||||
assert basis[0] == Matrix([-3, 1, 0, 0, 0, 0, 0])
|
||||
assert basis[1] == Matrix([0, 0, 1, 0, 0, 0, 0])
|
||||
assert basis[2] == Matrix([-2, 0, 0, -2, 1, 0, 0])
|
||||
assert basis[3] == Matrix([0, 0, 0, 0, 0, R(-1)/3, 1])
|
||||
|
||||
# issue 4797; just see that we can do it when rows > cols
|
||||
M = Matrix([[1, 2], [2, 4], [3, 6]])
|
||||
assert M.nullspace()
|
||||
|
||||
|
||||
def test_columnspace_second():
|
||||
M = Matrix([[ 1, 2, 0, 2, 5],
|
||||
[-2, -5, 1, -1, -8],
|
||||
[ 0, -3, 3, 4, 1],
|
||||
[ 3, 6, 0, -7, 2]])
|
||||
|
||||
# now check the vectors
|
||||
basis = M.columnspace()
|
||||
assert basis[0] == Matrix([1, -2, 0, 3])
|
||||
assert basis[1] == Matrix([2, -5, -3, 6])
|
||||
assert basis[2] == Matrix([2, -1, 4, -7])
|
||||
|
||||
#check by columnspace definition
|
||||
a, b, c, d, e = symbols('a b c d e')
|
||||
X = Matrix([a, b, c, d, e])
|
||||
for i in range(len(basis)):
|
||||
eq=M*X-basis[i]
|
||||
assert len(solve(eq, X)) != 0
|
||||
|
||||
#check if rank-nullity theorem holds
|
||||
assert M.rank() == len(basis)
|
||||
assert len(M.nullspace()) + len(M.columnspace()) == M.cols
|
||||
@@ -0,0 +1,72 @@
|
||||
from contextlib import contextmanager
|
||||
from threading import local
|
||||
|
||||
from sympy.core.function import expand_mul
|
||||
|
||||
|
||||
class DotProdSimpState(local):
|
||||
def __init__(self):
|
||||
self.state = None
|
||||
|
||||
_dotprodsimp_state = DotProdSimpState()
|
||||
|
||||
@contextmanager
|
||||
def dotprodsimp(x):
|
||||
old = _dotprodsimp_state.state
|
||||
|
||||
try:
|
||||
_dotprodsimp_state.state = x
|
||||
yield
|
||||
finally:
|
||||
_dotprodsimp_state.state = old
|
||||
|
||||
|
||||
def _dotprodsimp(expr, withsimp=False):
|
||||
"""Wrapper for simplify.dotprodsimp to avoid circular imports."""
|
||||
from sympy.simplify.simplify import dotprodsimp as dps
|
||||
return dps(expr, withsimp=withsimp)
|
||||
|
||||
|
||||
def _get_intermediate_simp(deffunc=lambda x: x, offfunc=lambda x: x,
|
||||
onfunc=_dotprodsimp, dotprodsimp=None):
|
||||
"""Support function for controlling intermediate simplification. Returns a
|
||||
simplification function according to the global setting of dotprodsimp
|
||||
operation.
|
||||
|
||||
``deffunc`` - Function to be used by default.
|
||||
``offfunc`` - Function to be used if dotprodsimp has been turned off.
|
||||
``onfunc`` - Function to be used if dotprodsimp has been turned on.
|
||||
``dotprodsimp`` - True, False or None. Will be overridden by global
|
||||
_dotprodsimp_state.state if that is not None.
|
||||
"""
|
||||
|
||||
if dotprodsimp is False or _dotprodsimp_state.state is False:
|
||||
return offfunc
|
||||
if dotprodsimp is True or _dotprodsimp_state.state is True:
|
||||
return onfunc
|
||||
|
||||
return deffunc # None, None
|
||||
|
||||
|
||||
def _get_intermediate_simp_bool(default=False, dotprodsimp=None):
|
||||
"""Same as ``_get_intermediate_simp`` but returns bools instead of functions
|
||||
by default."""
|
||||
|
||||
return _get_intermediate_simp(default, False, True, dotprodsimp)
|
||||
|
||||
|
||||
def _iszero(x):
|
||||
"""Returns True if x is zero."""
|
||||
return getattr(x, 'is_zero', None)
|
||||
|
||||
|
||||
def _is_zero_after_expand_mul(x):
|
||||
"""Tests by expand_mul only, suitable for polynomials and rational
|
||||
functions."""
|
||||
return expand_mul(x) == 0
|
||||
|
||||
|
||||
def _simplify(expr):
|
||||
""" Wrapper to avoid circular imports. """
|
||||
from sympy.simplify.simplify import simplify
|
||||
return simplify(expr)
|
||||
Reference in New Issue
Block a user