Spaces:
Sleeping
Sleeping
"""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) | |
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 | |