Skip to content

Commit a1974a5

Browse files
committed
Numpy 2.0 compatibility
1 parent bfb90b9 commit a1974a5

File tree

3 files changed

+78
-36
lines changed

3 files changed

+78
-36
lines changed

src/sage/matrix/matrix1.pyx

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import sage.modules.free_module
2525
from sage.structure.coerce cimport coercion_model
2626

2727

28+
_MISSING = object()
29+
30+
2831
cdef class Matrix(Matrix0):
2932
###################################################
3033
# Coercion to Various Systems
@@ -670,7 +673,7 @@ cdef class Matrix(Matrix0):
670673
entries = [[sib(v, 2) for v in row] for row in self.rows()]
671674
return sib.name('matrix')(self.base_ring(), entries)
672675

673-
def numpy(self, dtype=None, copy=True):
676+
def numpy(self, dtype=None, copy=_MISSING):
674677
"""
675678
Return the Numpy matrix associated to this matrix.
676679
@@ -680,14 +683,6 @@ cdef class Matrix(Matrix0):
680683
then the type will be determined as the minimum type required
681684
to hold the objects in the sequence.
682685
683-
- ``copy`` -- if `self` is already an `ndarray`, then this flag
684-
determines whether the data is copied (the default), or whether
685-
the internal array is returned. Note that this is incompatible
686-
with the behavior of ``copy`` argument to ``__array__`` method
687-
in numpy 2.0, see `Adapting to changes in the copy keyword
688-
<https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword>`_.
689-
Currently SageMath is using numpy 1.26.
690-
691686
EXAMPLES::
692687
693688
sage: # needs numpy
@@ -713,7 +708,7 @@ cdef class Matrix(Matrix0):
713708
typecodes::
714709
715710
sage: import numpy # needs numpy
716-
sage: numpy.typecodes.items() # needs numpy
711+
sage: numpy.typecodes.items() # needs numpy # random
717712
[('All', '?bhilqpBHILQPefdgFDGSUVOMm'), ('AllFloat', 'efdgFDG'),
718713
...
719714
@@ -738,40 +733,69 @@ cdef class Matrix(Matrix0):
738733
sage: b.shape
739734
(3, 4)
740735
741-
TESTS:
742-
743-
This ensures the docstring above is correct. It needs to be changed
744-
when numpy version in SageMath is updated to 2.0.0::
736+
TESTS::
745737
746738
sage: # needs numpy
747-
sage: import numpy as np
748-
sage: np.__version__
749-
'1.26.4'
739+
sage: matrix(3, range(12)).numpy(copy=False)
740+
doctest:warning...
741+
DeprecationWarning: passing copy argument to numpy() is deprecated
742+
See https://github.com/sagemath/sage/issues/39152 for details.
743+
array([[ 0, 1, 2, 3],
744+
[ 4, 5, 6, 7],
745+
[ 8, 9, 10, 11]])
750746
"""
747+
if copy is not _MISSING:
748+
from sage.misc.superseded import deprecation
749+
deprecation(39152, "passing copy argument to numpy() is deprecated")
751750
import numpy
752751
return numpy.asarray(self.list(), dtype=dtype).reshape(self.nrows(), self.ncols())
753752

754-
def __array__(self, dtype=None):
753+
def __array__(self, dtype=None, copy=None):
755754
"""
756755
Define the magic ``__array__`` function so that ``numpy.array(m)`` can convert
757756
a matrix ``m`` to a numpy array. See
758757
`Interoperability with NumPy <https://numpy.org/doc/1.26/user/basics.interoperability.html>`_.
759758
760759
Note that subclasses should override :meth:`numpy`, but usually not this method.
761760
762-
SageMath is using Numpy 1.26, so there is no ``copy`` argument.
761+
INPUT:
763762
764-
TESTS:
763+
- ``dtype`` -- the desired data-type for the array. If not given,
764+
then the type will be determined automatically.
765765
766-
This ensures the docstring above is correct. It needs to be changed
767-
when numpy version in SageMath is updated to 2.0.0::
766+
- ``copy`` -- required for numpy 2.0 compatibility.
767+
See <https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword>`_.
768+
Note that ``copy=False`` is not supported.
769+
770+
TESTS::
768771
769772
sage: # needs numpy
770773
sage: import numpy as np
771-
sage: np.__version__
772-
'1.26.4'
773-
"""
774-
return self.numpy(dtype, copy=False)
774+
sage: a = matrix(3, range(12))
775+
sage: if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
776+
....: try:
777+
....: np.array(a, copy=False) # in numpy 2.0, this raises an error
778+
....: except ValueError:
779+
....: pass
780+
....: else:
781+
....: assert False
782+
....: else:
783+
....: b = np.array(a, copy=False) # in numpy 1.26, this means "avoid copy if possible"
784+
....: # https://numpy.org/doc/1.26/reference/generated/numpy.array.html#numpy.array
785+
....: # but no-copy is not supported so it will copy anyway
786+
....: a[0,0] = 1
787+
....: assert b[0,0] == 0
788+
....: b = np.asarray(a)
789+
....: a[0,0] = 2
790+
....: assert b[0,0] == 1
791+
"""
792+
import numpy as np
793+
if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
794+
if copy is False:
795+
raise ValueError("Sage matrix cannot be converted to numpy array without copying")
796+
else:
797+
assert copy is None # numpy versions before 2.0 should not pass copy argument
798+
return self.numpy(dtype)
775799

776800
###################################################
777801
# Construction functions

src/sage/matrix/matrix_mod2_dense.pyx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ cdef class Matrix_mod2_dense(matrix_dense.Matrix_dense): # dense or sparse
565565
return list(C)
566566
return C
567567

568-
def numpy(self, dtype=None, copy=True):
568+
def numpy(self, dtype=None):
569569
"""
570570
Return the Numpy matrix associated to this matrix.
571571
See :meth:`.matrix1.Matrix.numpy`.

src/sage/matrix/matrix_numpy_dense.pyx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ cdef class Matrix_numpy_dense(Matrix_dense):
364364
return False
365365
return True
366366

367-
def numpy(self, dtype=None, copy=True):
367+
def numpy(self, dtype=None):
368368
"""
369369
Return the Numpy matrix associated to this matrix.
370370
@@ -373,9 +373,6 @@ cdef class Matrix_numpy_dense(Matrix_dense):
373373
- ``dtype`` -- the desired data-type for the array. If not given,
374374
then the type will be determined automatically.
375375
376-
- ``copy`` -- boolean (default: ``True``); determines whether the data is copied
377-
(the default), or whether the internal numpy array is returned.
378-
379376
EXAMPLES::
380377
381378
sage: m = matrix(RDF,[[1,2],[3,4]])
@@ -425,8 +422,9 @@ cdef class Matrix_numpy_dense(Matrix_dense):
425422
sage: m.numpy()
426423
array([], shape=(5, 0), dtype=float64)
427424
428-
Test for ``copy``::
425+
Test that a copy is always made::
429426
427+
sage: import numpy as np
430428
sage: m = matrix(RDF,2); m
431429
[0.0 0.0]
432430
[0.0 0.0]
@@ -441,17 +439,37 @@ cdef class Matrix_numpy_dense(Matrix_dense):
441439
sage: n
442440
array([[2., 0.],
443441
[0., 0.]])
444-
sage: n=numpy.asarray(m) # should not copy
442+
sage: n=numpy.asarray(m) # should still copy
445443
sage: m[0,0]=4
446444
sage: n
447-
array([[4., 0.],
445+
array([[3., 0.],
448446
[0., 0.]])
449447
sage: n=numpy.asarray(m, dtype=numpy.int64) # should copy
450448
sage: m[0,0]=5
451449
sage: n
452450
array([[4, 0],
453451
[0, 0]])
454-
sage: n=numpy.array(m, dtype=numpy.int64, copy=False)
452+
453+
::
454+
455+
sage: import numpy as np
456+
sage: a = matrix(RDF, 3, range(12))
457+
sage: if np.lib.NumpyVersion(np.__version__) >= '2.0.0':
458+
....: try:
459+
....: np.array(a, copy=False) # in numpy 2.0, this raises an error
460+
....: except ValueError:
461+
....: pass
462+
....: else:
463+
....: assert False
464+
....: else:
465+
....: b = np.array(a, copy=False) # in numpy 1.26, this means "avoid copy if possible"
466+
....: # https://numpy.org/doc/1.26/reference/generated/numpy.array.html#numpy.array
467+
....: # but no-copy is not supported so it will copy anyway
468+
....: a[0,0] = 1
469+
....: assert b[0,0] == 0
470+
....: b = np.asarray(a)
471+
....: a[0,0] = 2
472+
....: assert b[0,0] == 1
455473
456474
Make sure it's reasonably fast (the temporary numpy array is immediately
457475
destroyed otherwise it consumes 200MB memory)::
@@ -467,7 +485,7 @@ cdef class Matrix_numpy_dense(Matrix_dense):
467485
(3000+0j)
468486
"""
469487
import numpy as np
470-
return np.array(self._matrix_numpy, dtype=dtype, copy=copy)
488+
return np.array(self._matrix_numpy, dtype=dtype)
471489

472490
def _replace_self_with_numpy(self, numpy_matrix):
473491
"""

0 commit comments

Comments
 (0)