def remove_ids(mul): """ Remove Identities from a MatMul This is a modified version of sympy.strategies.rm_id. This is necesssary because MatMul may contain both MatrixExprs and Exprs as args. See Also -------- sympy.strategies.rm_id """ # Separate Exprs from MatrixExprs in args factor, mmul = mul.as_coeff_mmul() # Apply standard rm_id for MatMuls result = rm_id(lambda x: x.is_Identity is True)(mmul) if result != mmul: return newmul(factor, *result.args) # Recombine and return else: return mul
else: if not base is None: if exp == 1: args.append(base) else: args.append(base**exp) exp = current_exp base = current_base if exp == 1: args.append(base) else: args.append(base**exp) return newmul(factor, *args) rules = (any_zeros, remove_ids, xxinv, unpack, rm_id(lambda x: x == 1), merge_explicit, factor_in_front, flatten, combine_powers) 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
""" Merge explicit MatrixBase arguments >>> 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(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)))
def canonicalize(x): """Canonicalize the Hadamard product ``x`` with mathematical properties. Examples ======== >>> from sympy.matrices.expressions import MatrixSymbol, HadamardProduct >>> from sympy.matrices.expressions 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): from collections import Counter 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
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
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) rules = (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
elif not isinstance(B_base, MatrixBase) and \ A_base == B_base.inverse(): new_exp = A_exp - B_exp new_args[-1] = MatPow(A_base, new_exp).doit(deep=False) else: new_args.append(B) return newmul(factor, *new_args) rules = ( any_zeros, remove_ids, combine_powers, unpack, rm_id(lambda x: x == 1), merge_explicit, factor_in_front, flatten, ) 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:
>>> 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(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)))
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) rules = ( any_zeros, remove_ids, 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 canonicalize(x): """Canonicalize the Hadamard product ``x`` with mathematical properties. Examples ======== >>> from sympy.matrices.expressions import MatrixSymbol, HadamardProduct >>> from sympy.matrices.expressions import OneMatrix, ZeroMatrix >>> from sympy.matrices.expressions.hadamard import canonicalize >>> 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.*OneMatrix(2, 2) >>> 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) A.**3 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) """ from sympy.core.compatibility import default_sort_key # 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): from collections import Counter 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
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
# 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 rules = (any_zeros, remove_ids, xxinv, unpack, rm_id(lambda x: x == 1), merge_explicit, factor_in_front, flatten) 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