switching to high quality piper tts and added label translations
This commit is contained in:
@@ -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])
|
||||
Reference in New Issue
Block a user