Repository: sympy/sympy
Pull request: NumPy print support for identity matrices
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234". See
https://github.com/blog/1506-closing-issues-via-pull-requests . Please also
write a comment on that issue linking back to this pull request once it is
open. -->

Fixes #17006 

#### Brief description of what is fixed or changed

Adds support to the NumPy printing for printing identity matrices

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* printing
  * NumPy printer now handles identity matrices
<!-- END RELEASE NOTES -->


Problem statement:
Lambdify misinterprets some matrix expressions
Using lambdify on an expression containing an identity matrix gives us an unexpected result:

```python
>>> import numpy as np
>>> n = symbols('n', integer=True)
>>> A = MatrixSymbol("A", n, n)
>>> a = np.array([[1, 2], [3, 4]])
>>> f = lambdify(A, A + Identity(n))
>>> f(a)
array([[1.+1.j, 2.+1.j],
       [3.+1.j, 4.+1.j]])
```

Instead, the output should be  `array([[2, 2], [3, 5]])`, since we're adding an identity matrix to the array. Inspecting the globals and source code of `f` shows us why we get the result:

```python
>>> import inspect
>>> print(inspect.getsource(f))
def _lambdifygenerated(A):
    return (I + A)
>>> f.__globals__['I']
1j
```

The code printer prints `I`, which is currently being interpreted as a Python built-in complex number. The printer should support printing identity matrices, and signal an error for unsupported expressions that might be misinterpreted.

Hints from issue discussion:
If the shape is an explicit number, we can just print `eye(n)`. For unknown shape, it's harder. We can raise an exception for now. It's better to raise an exception than give a wrong answer.

Commit message: Add string matching test

Patch to review:
diff --git a/sympy/printing/pycode.py b/sympy/printing/pycode.py
index 6ec7bd891c23..0916d935688a 100644
--- a/sympy/printing/pycode.py
+++ b/sympy/printing/pycode.py
@@ -608,6 +608,13 @@ def _print_MatrixBase(self, expr):
             func = self._module_format('numpy.array')
         return "%s(%s)" % (func, self._print(expr.tolist()))
 
+    def _print_Identity(self, expr):
+        shape = expr.shape
+        if all([dim.is_Integer for dim in shape]):
+            return "%s(%s)" % (self._module_format('numpy.eye'), self._print(expr.shape[0]))
+        else:
+            raise NotImplementedError
+
     def _print_BlockMatrix(self, expr):
         return '{0}({1})'.format(self._module_format('numpy.block'),
                                  self._print(expr.args[0].tolist()))
diff --git a/sympy/printing/tests/test_numpy.py b/sympy/printing/tests/test_numpy.py
index 13627064ecb4..239ed67ec06d 100644
--- a/sympy/printing/tests/test_numpy.py
+++ b/sympy/printing/tests/test_numpy.py
@@ -1,6 +1,6 @@
 from sympy import (
     Piecewise, lambdify, Equality, Unequality, Sum, Mod, cbrt, sqrt,
-    MatrixSymbol, BlockMatrix
+    MatrixSymbol, BlockMatrix, Identity
 )
 from sympy import eye
 from sympy.abc import x, i, j, a, b, c, d
@@ -252,3 +252,16 @@ def test_16857():
 
     printer = NumPyPrinter()
     assert printer.doprint(A) == 'numpy.block([[a_1, a_2], [a_3, a_4]])'
+
+
+def test_issue_17006():
+    if not np:
+        skip("NumPy not installed")
+
+    M = MatrixSymbol("M", 2, 2)
+
+    f = lambdify(M, M + Identity(2))
+    ma = np.matrix([[1, 2], [3, 4]])
+    mr = np.matrix([[2, 2], [3, 5]])
+
+    assert (f(ma) == mr).all()
diff --git a/sympy/printing/tests/test_pycode.py b/sympy/printing/tests/test_pycode.py
index 0253d7218f5a..7405b0bfa6b3 100644
--- a/sympy/printing/tests/test_pycode.py
+++ b/sympy/printing/tests/test_pycode.py
@@ -7,7 +7,7 @@
 from sympy.core.numbers import pi
 from sympy.functions import acos, Piecewise, sign
 from sympy.logic import And, Or
-from sympy.matrices import SparseMatrix, MatrixSymbol
+from sympy.matrices import SparseMatrix, MatrixSymbol, Identity
 from sympy.printing.pycode import (
     MpmathPrinter, NumPyPrinter, PythonCodePrinter, pycode, SciPyPrinter
 )
@@ -49,6 +49,7 @@ def test_NumPyPrinter():
     A = MatrixSymbol("A", 2, 2)
     assert p.doprint(A**(-1)) == "numpy.linalg.inv(A)"
     assert p.doprint(A**5) == "numpy.linalg.matrix_power(A, 5)"
+    assert p.doprint(Identity(3)) == "numpy.eye(3)"
 
 
 def test_SciPyPrinter():


Code review comments:
diff_hunk: @@ -608,6 +608,13 @@ def _print_MatrixBase(self, expr):
             func = self._module_format('numpy.array')
         return "%s(%s)" % (func, self._print(expr.tolist()))
 
+    def _print_Identity(self, expr):
+        shape = expr.shape
+        if all([dim.is_Integer for dim in shape]):
+            return "%s(%s)" % (self._module_format('numpy.eye'), self._print(expr.shape[0]))
+        else:
+            raise NotImplementedError
path: sympy/printing/pycode.py
text: @user1:
Add a message to the error