Repository: fplll/fpylll (language: python)

Issue:
IntegerMatrix.from_matrix does not support numpy arrays
```
import numpy as np
from fpylll import IntegerMatrix
B = np.eye(5)
Bfpy = IntegerMatrix.from_matrix(B)
```

gives

`NotImplementedError: Type '<class 'numpy.float64'>' not supported`

that ultimately comes from set_matrix calling _set ([here](https://github.com/fplll/fpylll/blob/master/src/fpylll/fplll/integer_matrix.pyx)) which calls assign_mpz ([here](https://github.com/fplll/fpylll/blob/65954104ad59fcf2f5240beaa714be9bcdee67d8/src/fpylll/io.pyx#L36C10-L36C20)).

This is not the end of the world, as one can do double list comprehension and cast to a python int -- but it would be nice if from_matrix supported numpy arrays natively.


Discussion / hints:
It's not quite clear to me what should happen here. Would we just round?
Apologies, I meant e.g.

```
import numpy as np
from fpylll import IntegerMatrix
B = np.eye(5, dtype=np.int32)
Bfpy = IntegerMatrix.from_matrix(B)
```

which gives

`NotImplementedError: Type '<class 'numpy.int32'>' not supported`

which seems true for all integer types numpy understands.
I guess the solution is just to do this casting to a python integer "behind the scenes" in these cases?
Yup, I agree with that. Shouldn't be too hard to add.

Fix patch:
diff --git a/src/fpylll/fplll/integer_matrix.pyx b/src/fpylll/fplll/integer_matrix.pyx
index 8764c645..b0682b8a 100644
--- a/src/fpylll/fplll/integer_matrix.pyx
+++ b/src/fpylll/fplll/integer_matrix.pyx
@@ -326,6 +326,8 @@ cdef class IntegerMatrix:
             >>> B[0,0]
             1
 
+        IntegerMatrix also supports numpy's integer types, if numpy is supported.
+        See tests/test_numpy.py for example usage.
         """
         self._type = check_int_type(int_type)
 
diff --git a/src/fpylll/io.pyx b/src/fpylll/io.pyx
index 0d4f9d8c..6aec4a49 100644
--- a/src/fpylll/io.pyx
+++ b/src/fpylll/io.pyx
@@ -12,6 +12,10 @@ from cpython.version cimport PY_MAJOR_VERSION
 from fplll.fplll cimport FT_DEFAULT, FT_DOUBLE, FT_LONG_DOUBLE, FT_DPE, FT_MPFR
 from fplll.fplll cimport ZT_MPZ, ZT_LONG
 
+# Note: this uses fpylll's numpy and not the global numpy package.
+IF HAVE_NUMPY:
+    from .numpy import is_numpy_integer
+
 IF HAVE_QD:
     from fpylll.fplll.fplll cimport FT_DD, FT_QD
 
@@ -36,7 +40,7 @@ cdef int assign_Z_NR_mpz(Z_NR[mpz_t]& t, value) except -1:
 cdef int assign_mpz(mpz_t& t, value) except -1:
     """
     Assign Python integer to Z_NR[mpz_t]
-    """
+    """     
     if isinstance(value, int) and PY_MAJOR_VERSION == 2:
             mpz_set_si(t, PyInt_AS_LONG(value))
             return 0
@@ -49,6 +53,12 @@ cdef int assign_mpz(mpz_t& t, value) except -1:
             mpz_set_pylong(t, value)
             return 0
 
+    IF HAVE_NUMPY:
+        if is_numpy_integer(value):
+            value = long(value)
+            mpz_set_pylong(t, value)
+            return 0
+
     raise NotImplementedError("Type '%s' not supported"%type(value))
 
 cdef object mpz_get_python(mpz_srcptr z):
diff --git a/src/fpylll/numpy.pyx b/src/fpylll/numpy.pyx
index 84970ba5..9196c66d 100644
--- a/src/fpylll/numpy.pyx
+++ b/src/fpylll/numpy.pyx
@@ -15,6 +15,7 @@ IF not HAVE_NUMPY:
 
 import numpy
 from numpy.__init__ cimport ndarray  # TODO: that __init__ shouldn't be needed
+from numpy.__init__ cimport integer as np_integer
 
 def _dump_mu(ndarray[double, ndim=2, mode="c"] mu not None, MatGSO M, int kappa, int block_size):
     u"""
@@ -130,3 +131,11 @@ def dump_r(MatGSO M, int kappa, int block_size):
     r = ndarray(dtype='float64', shape=block_size)
     _dump_r(r, M, kappa, block_size)
     return r
+
+def is_numpy_integer(value):
+    """
+    Return true if value is a numpy integer, false otherwise.
+    :param value: the value to be checked.
+    :returns: True if value is a numpy integer, false otherwise.
+    """
+    return isinstance(value, np_integer)


Test patch:
diff --git a/tests/test_numpy.py b/tests/test_numpy.py
index 5ff06381..3e1728d6 100644
--- a/tests/test_numpy.py
+++ b/tests/test_numpy.py
@@ -36,3 +36,14 @@ def test_dump_r(nrows=10):
 
     for i in range(nrows):
         assert abs(M.get_r(i, i) - r[i]) < 0.001
+
+
+def test_is_numpy_integer(nrows=10):
+    if not have_numpy:
+        return
+
+    import numpy as np
+    B = np.eye(nrows, dtype=np.int32)
+    Bfpy = IntegerMatrix.from_matrix(B)
+    for i in range(nrows):
+        assert Bfpy[i][i] == 1