Repository: sympy/sympy

Issue:
Simplifying expression with 1x1 matrix transpose
```
> m = MatrixSymbol('m', 1, 1)
> simplify(m.T - m) # i would like to see 0 here
> m.equals(m.T) # prove by numerical computation
True
```

Discussion:
andreo: @isuruf could you please review?
jksuom: It should not be necessary to add `if expr.shape == (1, 1)` separately to several assumption handlers, It would probably be sufficient to have that in the `MatrixExpr` handler alone. Besides, all 1x1 matrices are diagonal, and the assumption system can deduce that diagonal matrices are symmetric. Hence, it should be enough to add that information to `AskDiagonalHandler`. In addition to 1x1 matrices, also 0x0 matrices are diagonal, so the test could be something like `if expr.shape[0] == expr.shape[1] <= 1`.
andreo: @jksuom, now empty and 1x1 matrices are diagonal and symmetric.

There is an error when I use expression `if expr.shape[0] == expr.shape[1] <= 1`:

```
  File "sympy/assumptions/handlers/matrices.py", line 392, in MatrixSymbol
    if expr.shape[0] == expr.shape[1] <= 1:
  File "sympy/core/relational.py", line 195, in __nonzero__
    raise TypeError("cannot determine truth value of Relational")
TypeError: cannot determine truth value of Relational
```

I've added the diagonal check in several assumption handlers because otherwise, it can't deduce diagonal property.
jksuom: This should be synchronized with the updated master.

Patch:
diff --git a/sympy/assumptions/handlers/matrices.py b/sympy/assumptions/handlers/matrices.py
index f0e01f8771..55b007a306 100644
--- a/sympy/assumptions/handlers/matrices.py
+++ b/sympy/assumptions/handlers/matrices.py
@@ -38,6 +38,10 @@ def MatMul(expr, assumptions):
         factor, mmul = expr.as_coeff_mmul()
         if all(ask(Q.symmetric(arg), assumptions) for arg in mmul.args):
             return True
+        # TODO: implement sathandlers system for the matrices.
+        # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
+        if ask(Q.diagonal(expr), assumptions):
+            return True
         if len(mmul.args) >= 2 and mmul.args[0] == mmul.args[-1].T:
             if len(mmul.args) == 2:
                 return True
@@ -51,6 +55,10 @@ def MatAdd(expr, assumptions):
     def MatrixSymbol(expr, assumptions):
         if not expr.is_square:
             return False
+        # TODO: implement sathandlers system for the matrices.
+        # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
+        if ask(Q.diagonal(expr), assumptions):
+            return True
         if Q.symmetric(expr) in conjuncts(assumptions):
             return True
 
@@ -66,6 +74,10 @@ def Transpose(expr, assumptions):
 
     @staticmethod
     def MatrixSlice(expr, assumptions):
+        # TODO: implement sathandlers system for the matrices.
+        # Now it duplicates the general fact: Implies(Q.diagonal, Q.symmetric).
+        if ask(Q.diagonal(expr), assumptions):
+            return True
         if not expr.on_diag:
             return None
         else:
@@ -365,8 +377,14 @@ class AskDiagonalHandler(CommonHandler):
     Handler for key 'diagonal'
     """
 
+    @staticmethod
+    def _is_empty_or_1x1(expr):
+        return expr.shape == (0, 0) or expr.shape == (1, 1)
+
     @staticmethod
     def MatMul(expr, assumptions):
+        if AskDiagonalHandler._is_empty_or_1x1(expr):
+            return True
         factor, matrices = expr.as_coeff_matrices()
         if all(ask(Q.diagonal(m), assumptions) for m in matrices):
             return True
@@ -378,6 +396,8 @@ def MatAdd(expr, assumptions):
 
     @staticmethod
     def MatrixSymbol(expr, assumptions):
+        if AskDiagonalHandler._is_empty_or_1x1(expr):
+            return True
         if Q.diagonal(expr) in conjuncts(assumptions):
             return True
 
@@ -393,6 +413,8 @@ def Inverse(expr, assumptions):
 
     @staticmethod
     def MatrixSlice(expr, assumptions):
+        if AskDiagonalHandler._is_empty_or_1x1(expr):
+            return True
         if not expr.on_diag:
             return None
         else:


Test patch:
diff --git a/sympy/assumptions/tests/test_matrices.py b/sympy/assumptions/tests/test_matrices.py
index f58a2ea176..2d57d8982b 100644
--- a/sympy/assumptions/tests/test_matrices.py
+++ b/sympy/assumptions/tests/test_matrices.py
@@ -7,6 +7,11 @@
 X = MatrixSymbol('X', 2, 2)
 Y = MatrixSymbol('Y', 2, 3)
 Z = MatrixSymbol('Z', 2, 2)
+A1x1 = MatrixSymbol('A1x1', 1, 1)
+B1x1 = MatrixSymbol('B1x1', 1, 1)
+C0x0 = MatrixSymbol('C0x0', 0, 0)
+V1 = MatrixSymbol('V1', 2, 1)
+V2 = MatrixSymbol('V2', 2, 1)
 
 def test_square():
     assert ask(Q.square(X))
@@ -46,7 +51,13 @@ def test_symmetric():
     assert ask(Q.symmetric(Y.T*X*Y)) is None
     assert ask(Q.symmetric(Y.T*X*Y), Q.symmetric(X)) is True
     assert ask(Q.symmetric(X*X*X*X*X*X*X*X*X*X), Q.symmetric(X)) is True
-
+    assert ask(Q.symmetric(A1x1)) is True
+    assert ask(Q.symmetric(A1x1 + B1x1)) is True
+    assert ask(Q.symmetric(A1x1 * B1x1)) is True
+    assert ask(Q.symmetric(V1.T*V1)) is True
+    assert ask(Q.symmetric(V1.T*(V1 + V2))) is True
+    assert ask(Q.symmetric(V1.T*(V1 + V2) + A1x1)) is True
+    assert ask(Q.symmetric(MatrixSlice(Y, (0, 1), (1, 2)))) is True
 
 def _test_orthogonal_unitary(predicate):
     assert ask(predicate(X), predicate(X))
@@ -115,6 +126,14 @@ def test_diagonal():
     assert ask(Q.diagonal(X), Q.lower_triangular(X) & Q.upper_triangular(X))
     assert ask(Q.symmetric(X), Q.diagonal(X))
     assert ask(Q.triangular(X), Q.diagonal(X))
+    assert ask(Q.diagonal(C0x0))
+    assert ask(Q.diagonal(A1x1))
+    assert ask(Q.diagonal(A1x1 + B1x1))
+    assert ask(Q.diagonal(A1x1*B1x1))
+    assert ask(Q.diagonal(V1.T*V2))
+    assert ask(Q.diagonal(V1.T*(X + Z)*V1))
+    assert ask(Q.diagonal(MatrixSlice(Y, (0, 1), (1, 2)))) is True
+    assert ask(Q.diagonal(V1.T*(V1 + V2))) is True
 
 
 def test_non_atoms():
diff --git a/sympy/matrices/expressions/tests/test_transpose.py b/sympy/matrices/expressions/tests/test_transpose.py
index ce99a2eb20..27f6d37d87 100644
--- a/sympy/matrices/expressions/tests/test_transpose.py
+++ b/sympy/matrices/expressions/tests/test_transpose.py
@@ -38,3 +38,9 @@ def test_transpose():
 
 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)