Skip to content

Commit

Permalink
Numpy 2.0 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
user202729 committed Dec 18, 2024
1 parent bfb90b9 commit 271bb0e
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 36 deletions.
76 changes: 50 additions & 26 deletions src/sage/matrix/matrix1.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import sage.modules.free_module
from sage.structure.coerce cimport coercion_model


_MISSING = object()


cdef class Matrix(Matrix0):
###################################################
# Coercion to Various Systems
Expand Down Expand Up @@ -670,7 +673,7 @@ cdef class Matrix(Matrix0):
entries = [[sib(v, 2) for v in row] for row in self.rows()]
return sib.name('matrix')(self.base_ring(), entries)

def numpy(self, dtype=None, copy=True):
def numpy(self, dtype=None, copy=_MISSING):
"""
Return the Numpy matrix associated to this matrix.
Expand All @@ -680,14 +683,6 @@ cdef class Matrix(Matrix0):
then the type will be determined as the minimum type required
to hold the objects in the sequence.
- ``copy`` -- if `self` is already an `ndarray`, then this flag
determines whether the data is copied (the default), or whether
the internal array is returned. Note that this is incompatible
with the behavior of ``copy`` argument to ``__array__`` method
in numpy 2.0, see `Adapting to changes in the copy keyword
<https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword>`_.
Currently SageMath is using numpy 1.26.
EXAMPLES::
sage: # needs numpy
Expand All @@ -713,7 +708,7 @@ cdef class Matrix(Matrix0):
typecodes::
sage: import numpy # needs numpy
sage: numpy.typecodes.items() # needs numpy
sage: numpy.typecodes.items() # needs numpy # random
[('All', '?bhilqpBHILQPefdgFDGSUVOMm'), ('AllFloat', 'efdgFDG'),
...
Expand All @@ -738,40 +733,69 @@ cdef class Matrix(Matrix0):
sage: b.shape
(3, 4)
TESTS:
This ensures the docstring above is correct. It needs to be changed
when numpy version in SageMath is updated to 2.0.0::
TESTS::
sage: # needs numpy
sage: import numpy as np
sage: np.__version__
'1.26.4'
sage: matrix(3, range(12)).numpy(copy=False)
doctest:warning...
DeprecationWarning: passing copy argument to numpy() is deprecated
See https://github.com/sagemath/sage/issues/39152 for details.
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
"""
if copy is not _MISSING:
from sage.misc.superseded import deprecation
deprecation(39152, "passing copy argument to numpy() is deprecated")
import numpy
return numpy.asarray(self.list(), dtype=dtype).reshape(self.nrows(), self.ncols())

def __array__(self, dtype=None):
def __array__(self, dtype=None, copy=None):
"""
Define the magic ``__array__`` function so that ``numpy.array(m)`` can convert
a matrix ``m`` to a numpy array. See
`Interoperability with NumPy <https://numpy.org/doc/1.26/user/basics.interoperability.html>`_.
Note that subclasses should override :meth:`numpy`, but usually not this method.
SageMath is using Numpy 1.26, so there is no ``copy`` argument.
INPUT:
TESTS:
- ``dtype`` -- the desired data-type for the array. If not given,
then the type will be determined automatically.
This ensures the docstring above is correct. It needs to be changed
when numpy version in SageMath is updated to 2.0.0::
- ``copy`` -- required for numpy 2.0 compatibility.
See <https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword>`_.
Note that ``copy=False`` is not supported.
TESTS::
sage: # needs numpy
sage: import numpy as np
sage: np.__version__
'1.26.4'
"""
return self.numpy(dtype, copy=False)
sage: a = matrix(3, range(12))
sage: if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
....: try:
....: np.array(a, copy=False) # in numpy 2.0, this raises an error
....: except ValueError:
....: pass
....: else:
....: assert False
....: else:
....: b = np.array(a, copy=False) # in numpy 1.26, this means "avoid copy if possible"
....: # https://numpy.org/doc/1.26/reference/generated/numpy.array.html#numpy.array
....: # but no-copy is not supported so it will copy anyway
....: a[0,0] = 1
....: assert b[0,0] == 0
....: b = np.asarray(a)
....: a[0,0] = 2
....: assert b[0,0] == 1
"""
import numpy as np
if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
if copy is False:
raise ValueError("Sage matrix cannot be converted to numpy array without copying")
else:
assert copy is None # numpy versions before 2.0 should not pass copy argument
return self.numpy(dtype)

###################################################
# Construction functions
Expand Down
2 changes: 1 addition & 1 deletion src/sage/matrix/matrix_mod2_dense.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -565,7 +565,7 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
return list(C)
return C

def numpy(self, dtype=None, copy=True):
def numpy(self, dtype=None):
"""
Return the Numpy matrix associated to this matrix.
See :meth:`.matrix1.Matrix.numpy`.
Expand Down
41 changes: 32 additions & 9 deletions src/sage/matrix/matrix_numpy_dense.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ cdef class Matrix_numpy_dense(Matrix_dense):
return False
return True

def numpy(self, dtype=None, copy=True):
def numpy(self, dtype=None):
"""
Return the Numpy matrix associated to this matrix.
Expand All @@ -373,9 +373,6 @@ cdef class Matrix_numpy_dense(Matrix_dense):
- ``dtype`` -- the desired data-type for the array. If not given,
then the type will be determined automatically.
- ``copy`` -- boolean (default: ``True``); determines whether the data is copied
(the default), or whether the internal numpy array is returned.
EXAMPLES::
sage: m = matrix(RDF,[[1,2],[3,4]])
Expand Down Expand Up @@ -425,8 +422,9 @@ cdef class Matrix_numpy_dense(Matrix_dense):
sage: m.numpy()
array([], shape=(5, 0), dtype=float64)
Test for ``copy``::
Test that a copy is always made::
sage: import numpy as np
sage: m = matrix(RDF,2); m
[0.0 0.0]
[0.0 0.0]
Expand All @@ -441,17 +439,37 @@ cdef class Matrix_numpy_dense(Matrix_dense):
sage: n
array([[2., 0.],
[0., 0.]])
sage: n=numpy.asarray(m) # should not copy
sage: n=numpy.asarray(m) # should still copy
sage: m[0,0]=4
sage: n
array([[4., 0.],
array([[3., 0.],
[0., 0.]])
sage: n=numpy.asarray(m, dtype=numpy.int64) # should copy
sage: m[0,0]=5
sage: n
array([[4, 0],
[0, 0]])
sage: n=numpy.array(m, dtype=numpy.int64, copy=False)
::
sage: import numpy as np
sage: a = matrix(RDF, 3, range(12))
sage: if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
....: try:
....: np.array(a, copy=False) # in numpy 2.0, this raises an error
....: except ValueError:
....: pass
....: else:
....: assert False
....: else:
....: b = np.array(a, copy=False) # in numpy 1.26, this means "avoid copy if possible"
....: # https://numpy.org/doc/1.26/reference/generated/numpy.array.html#numpy.array
....: # but no-copy is not supported so it will copy anyway
....: a[0,0] = 1
....: assert b[0,0] == 0
....: b = np.asarray(a)
....: a[0,0] = 2
....: assert b[0,0] == 1
Make sure it's reasonably fast (the temporary numpy array is immediately
destroyed otherwise it consumes 200MB memory)::
Expand All @@ -467,7 +485,12 @@ cdef class Matrix_numpy_dense(Matrix_dense):
(3000+0j)
"""
import numpy as np
return np.array(self._matrix_numpy, dtype=dtype, copy=copy)
if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
if copy is False:
raise ValueError("Sage matrix cannot be converted to numpy array without copying")
else:
assert copy is None # numpy versions before 2.0 should not pass copy argument
return np.array(self._matrix_numpy, dtype=dtype)

def _replace_self_with_numpy(self, numpy_matrix):
"""
Expand Down

0 comments on commit 271bb0e

Please sign in to comment.