Repository: sympy/sympy

Issue:
_MatrixWrapper does not wrap iteration of numpy matrix properly
The `_MatrixWrapper` class should make it possible to wrap a numpy matrix and use it like a SymPy Matrix but iteration does not work properly:
```julia
In [1]: from numpy import matrix                                                                                                  

In [2]: from sympy.matrices.common import _MatrixWrapper                                                                          

In [3]: M = matrix([[1, 2], [3, 4]])                                                                                              

In [4]: _MatrixWrapper(M)                                                                                                         
Out[4]: <sympy.matrices.common._MatrixWrapper at 0x119aa55f8>

In [5]: list(_MatrixWrapper(M))                                                                                                   
Out[5]: [matrix([[1, 2]]), matrix([[3, 4]])]

In [6]: M = Matrix([[1, 2], [3, 4]])                                                                                              

In [7]: list(M)                                                                                                                   
Out[7]: [1, 2, 3, 4]
```

Discussion:

Patch:
diff --git a/sympy/matrices/common.py b/sympy/matrices/common.py
index 996a519378..1a39068466 100644
--- a/sympy/matrices/common.py
+++ b/sympy/matrices/common.py
@@ -8,6 +8,7 @@
 
 from collections import defaultdict
 from inspect import isfunction
+from itertools import chain
 
 from sympy.assumptions.refine import refine
 from sympy.core import SympifyError, Add
@@ -2592,36 +2593,53 @@ def shape(self):
 
 
 class _MatrixWrapper(object):
-    """Wrapper class providing the minimum functionality
-    for a matrix-like object: .rows, .cols, .shape, indexability,
-    and iterability.  CommonMatrix math operations should work
-    on matrix-like objects.  For example, wrapping a numpy
-    matrix in a MatrixWrapper allows it to be passed to CommonMatrix.
+    """Wrapper class providing the minimum functionality for a matrix-like
+    object: .rows, .cols, .shape, indexability, and iterability. CommonMatrix
+    math operations should work on matrix-like objects. This one is intended for
+    matrix-like objects which use the same indexing format as SymPy with respect
+    to returning matrix elements instead of rows for non-tuple indexes.
     """
+
+    is_Matrix     = False # needs to be here because of __getattr__
     is_MatrixLike = True
 
-    def __init__(self, mat, shape=None):
+    def __init__(self, mat, shape):
         self.mat = mat
-        self.rows, self.cols = mat.shape if shape is None else shape
-
-    def __getattr__(self, attr):
-        """Most attribute access is passed straight through
-        to the stored matrix"""
-        return getattr(self.mat, attr)
+        self.shape = shape
+        self.rows, self.cols = shape
 
     def __getitem__(self, key):
-        return self.mat.__getitem__(key)
+        if isinstance(key, tuple):
+            return sympify(self.mat.__getitem__(key))
+
+        return sympify(self.mat.__getitem__((key // self.rows, key % self.cols)))
+
+    def __iter__(self): # supports numpy.matrix and numpy.array
+        mat = self.mat
+        cols = self.cols
+
+        return iter(sympify(mat[r, c]) for r in range(self.rows) for c in range(cols))
 
 
 def _matrixify(mat):
     """If `mat` is a Matrix or is matrix-like,
     return a Matrix or MatrixWrapper object.  Otherwise
     `mat` is passed through without modification."""
-    if getattr(mat, 'is_Matrix', False):
+
+    if getattr(mat, 'is_Matrix', False) or getattr(mat, 'is_MatrixLike', False):
         return mat
-    if hasattr(mat, 'shape'):
+
+    shape = None
+
+    if hasattr(mat, 'shape'): # numpy, scipy.sparse
         if len(mat.shape) == 2:
-            return _MatrixWrapper(mat)
+            shape = mat.shape
+    elif hasattr(mat, 'rows') and hasattr(mat, 'cols'): # mpmath
+        shape = (mat.rows, mat.cols)
+
+    if shape:
+        return _MatrixWrapper(mat, shape)
+
     return mat
 
 


Test patch:
diff --git a/sympy/core/tests/test_subs.py b/sympy/core/tests/test_subs.py
index a89530cec2..519f34c522 100644
--- a/sympy/core/tests/test_subs.py
+++ b/sympy/core/tests/test_subs.py
@@ -831,11 +831,3 @@ def test_issue_6976():
     assert (x**4 + x**3 + x**2 + x + sqrt(x)).subs(x**2, y) == \
         y**Rational(1, 4) + y**Rational(3, 2) + sqrt(y) + y**2 + y
     assert x.subs(x**3, y) == y**Rational(1, 3)
-
-
-def test_issue_11746():
-    assert (1/x).subs(x**2, 1) == 1/x
-    assert (1/(x**3)).subs(x**2, 1) == x**(-3)
-    assert (1/(x**4)).subs(x**2, 1) == 1
-    assert (1/(x**3)).subs(x**4, 1) == x**(-3)
-    assert (1/(y**5)).subs(x**5, 1) == y**(-5)
diff --git a/sympy/matrices/tests/test_matrices.py b/sympy/matrices/tests/test_matrices.py
index 895aa6ccfa..c54dd77fdb 100644
--- a/sympy/matrices/tests/test_matrices.py
+++ b/sympy/matrices/tests/test_matrices.py
@@ -3618,6 +3618,43 @@ def test_from_ndarray():
     raises(NotImplementedError, lambda: Matrix(array([[
         [1, 2], [3, 4]], [[5, 6], [7, 8]]])))
 
+def test_17522_numpy():
+    from sympy.matrices.common import _matrixify
+    try:
+        from numpy import array, matrix
+    except ImportError:
+        skip('NumPy must be available to test indexing matrixified NumPy ndarrays and matrices')
+
+    m = _matrixify(array([[1, 2], [3, 4]]))
+    assert m[3] == 4
+    assert list(m) == [1, 2, 3, 4]
+
+    m = _matrixify(matrix([[1, 2], [3, 4]]))
+    assert m[3] == 4
+    assert list(m) == [1, 2, 3, 4]
+
+def test_17522_mpmath():
+    from sympy.matrices.common import _matrixify
+    try:
+        from mpmath import matrix
+    except ImportError:
+        skip('mpmath must be available to test indexing matrixified mpmath matrices')
+
+    m = _matrixify(matrix([[1, 2], [3, 4]]))
+    assert m[3] == 4
+    assert list(m) == [1, 2, 3, 4]
+
+def test_17522_scipy():
+    from sympy.matrices.common import _matrixify
+    try:
+        from scipy.sparse import csr_matrix
+    except ImportError:
+        skip('SciPy must be available to test indexing matrixified SciPy sparse matrices')
+
+    m = _matrixify(csr_matrix([[1, 2], [3, 4]]))
+    assert m[3] == 4
+    assert list(m) == [1, 2, 3, 4]
+
 def test_hermitian():
     a = Matrix([[1, I], [-I, 1]])
     assert a.is_hermitian