123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- 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.common 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
- 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_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_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
|