-
Notifications
You must be signed in to change notification settings - Fork 75
Add support for NumPy 2.0 #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,11 @@ | |
|
||
import copy | ||
from functools import wraps | ||
import warnings | ||
|
||
import numpy as np | ||
|
||
from . import markup | ||
from . import markup, QuantitiesDeprecationWarning | ||
from .dimensionality import Dimensionality, p_dict | ||
from .registry import unit_registry | ||
from .decorators import with_doc | ||
|
@@ -114,15 +115,19 @@ class Quantity(np.ndarray): | |
# TODO: what is an appropriate value? | ||
__array_priority__ = 21 | ||
|
||
def __new__(cls, data, units='', dtype=None, copy=True): | ||
def __new__(cls, data, units='', dtype=None, copy=None): | ||
if copy is not None: | ||
warnings.warn(("The 'copy' argument in Quantity is deprecated and will be removed in the future. " | ||
"The argument has no effect since quantities-0.16.0 (to aid numpy-2.0 support)."), | ||
QuantitiesDeprecationWarning, stacklevel=2) | ||
if isinstance(data, Quantity): | ||
if units: | ||
data = data.rescale(units) | ||
if isinstance(data, unit_registry['UnitQuantity']): | ||
return 1*data | ||
return np.array(data, dtype=dtype, copy=copy, subok=True).view(cls) | ||
return np.asanyarray(data, dtype=dtype).view(cls) | ||
|
||
ret = np.array(data, dtype=dtype, copy=copy).view(cls) | ||
ret = np.asarray(data, dtype=dtype).view(cls) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After these changes we are no longer using the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a good idea. |
||
ret._dimensionality.update(validate_dimensionality(units)) | ||
return ret | ||
|
||
|
@@ -210,15 +215,17 @@ def rescale(self, units=None, dtype=None): | |
dtype = self.dtype | ||
if self.dimensionality == to_dims: | ||
return self.astype(dtype) | ||
to_u = Quantity(1.0, to_dims) | ||
from_u = Quantity(1.0, self.dimensionality) | ||
to_u = Quantity(1.0, to_dims, dtype=dtype) | ||
from_u = Quantity(1.0, self.dimensionality, dtype=dtype) | ||
try: | ||
cf = get_conversion_factor(from_u, to_u) | ||
except AssertionError: | ||
raise ValueError( | ||
'Unable to convert between units of "%s" and "%s"' | ||
%(from_u._dimensionality, to_u._dimensionality) | ||
) | ||
if np.dtype(dtype).kind in 'fc': | ||
cf = np.array(cf, dtype=dtype) | ||
new_magnitude = cf*self.magnitude | ||
dtype = np.result_type(dtype, new_magnitude) | ||
return Quantity(new_magnitude, to_u, dtype=dtype) | ||
|
@@ -272,7 +279,7 @@ def __array_prepare__(self, obj, context=None): | |
uf, objs, huh = context | ||
if uf.__name__.startswith('is'): | ||
return obj | ||
#print self, obj, res, uf, objs | ||
|
||
try: | ||
res._dimensionality = p_dict[uf](*objs) | ||
except KeyError: | ||
|
@@ -283,11 +290,12 @@ def __array_prepare__(self, obj, context=None): | |
) | ||
return res | ||
|
||
def __array_wrap__(self, obj, context=None): | ||
def __array_wrap__(self, obj, context=None, return_scalar=False): | ||
if not isinstance(obj, Quantity): | ||
# backwards compatibility with numpy-1.3 | ||
obj = self.__array_prepare__(obj, context) | ||
return obj | ||
return self.__array_prepare__(obj, context) | ||
else: | ||
return super().__array_wrap__(obj, context, return_scalar) | ||
Comment on lines
+296
to
+298
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm confused by this. The previous array wrap would only do something if the object was not a Quantity if the object was already a quantity it would just return the object without any changes. Here you force the array_wrap even in the case of the object being a Quantity. It seems like for the case of some failing tests the objects are losing their dimensionality and this seems like it could be a place where a Quantity should be returned, no? I'm not super familiar with this deep level of array wrapping so just thought maybe a discussion of this could help point us to the actual test failure issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bjodah just wanted to bump my message. Does this seem like it could be the problem with the tests failing? |
||
|
||
@with_doc(np.ndarray.__add__) | ||
@scale_other_units | ||
|
@@ -476,7 +484,7 @@ def sum(self, axis=None, dtype=None, out=None): | |
ret = self.magnitude.sum(axis, dtype, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -487,8 +495,7 @@ def nansum(self, axis=None, dtype=None, out=None): | |
import numpy as np | ||
return Quantity( | ||
np.nansum(self.magnitude, axis, dtype, out), | ||
self.dimensionality, | ||
copy=False | ||
self.dimensionality | ||
) | ||
|
||
@with_doc(np.ndarray.fill) | ||
|
@@ -523,7 +530,7 @@ def argsort(self, axis=-1, kind='quick', order=None): | |
@with_doc(np.ndarray.searchsorted) | ||
def searchsorted(self,values, side='left'): | ||
if not isinstance (values, Quantity): | ||
values = Quantity(values, copy=False) | ||
values = Quantity(values) | ||
|
||
if values._dimensionality != self._dimensionality: | ||
raise ValueError("values does not have the same units as self") | ||
|
@@ -539,7 +546,7 @@ def max(self, axis=None, out=None): | |
ret = self.magnitude.max(axis, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -553,16 +560,15 @@ def argmax(self, axis=None, out=None): | |
def nanmax(self, axis=None, out=None): | ||
return Quantity( | ||
np.nanmax(self.magnitude), | ||
self.dimensionality, | ||
copy=False | ||
self.dimensionality | ||
) | ||
|
||
@with_doc(np.ndarray.min) | ||
def min(self, axis=None, out=None): | ||
ret = self.magnitude.min(axis, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -572,8 +578,7 @@ def min(self, axis=None, out=None): | |
def nanmin(self, axis=None, out=None): | ||
return Quantity( | ||
np.nanmin(self.magnitude), | ||
self.dimensionality, | ||
copy=False | ||
self.dimensionality | ||
) | ||
|
||
@with_doc(np.ndarray.argmin) | ||
|
@@ -590,10 +595,10 @@ def nanargmax(self,axis=None, out=None): | |
|
||
@with_doc(np.ndarray.ptp) | ||
def ptp(self, axis=None, out=None): | ||
ret = self.magnitude.ptp(axis, None if out is None else out.magnitude) | ||
ret = np.ptp(self.magnitude, axis, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -620,7 +625,7 @@ def clip(self, min=None, max=None, out=None): | |
) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(clipped, dim, copy=False) | ||
return Quantity(clipped, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -631,7 +636,7 @@ def round(self, decimals=0, out=None): | |
ret = self.magnitude.round(decimals, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -642,7 +647,7 @@ def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): | |
ret = self.magnitude.trace(offset, axis1, axis2, dtype, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -652,16 +657,15 @@ def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): | |
def squeeze(self, axis=None): | ||
return Quantity( | ||
self.magnitude.squeeze(axis), | ||
self.dimensionality, | ||
copy=False | ||
self.dimensionality | ||
) | ||
|
||
@with_doc(np.ndarray.mean) | ||
def mean(self, axis=None, dtype=None, out=None): | ||
ret = self.magnitude.mean(axis, dtype, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -672,15 +676,14 @@ def nanmean(self, axis=None, dtype=None, out=None): | |
import numpy as np | ||
return Quantity( | ||
np.nanmean(self.magnitude, axis, dtype, out), | ||
self.dimensionality, | ||
copy=False) | ||
self.dimensionality) | ||
|
||
@with_doc(np.ndarray.var) | ||
def var(self, axis=None, dtype=None, out=None, ddof=0): | ||
ret = self.magnitude.var(axis, dtype, out, ddof) | ||
dim = self._dimensionality**2 | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -691,7 +694,7 @@ def std(self, axis=None, dtype=None, out=None, ddof=0): | |
ret = self.magnitude.std(axis, dtype, out, ddof) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -701,8 +704,7 @@ def std(self, axis=None, dtype=None, out=None, ddof=0): | |
def nanstd(self, axis=None, dtype=None, out=None, ddof=0): | ||
return Quantity( | ||
np.nanstd(self.magnitude, axis, dtype, out, ddof), | ||
self._dimensionality, | ||
copy=False | ||
self._dimensionality | ||
) | ||
|
||
@with_doc(np.ndarray.prod) | ||
|
@@ -715,7 +717,7 @@ def prod(self, axis=None, dtype=None, out=None): | |
ret = self.magnitude.prod(axis, dtype, None if out is None else out.magnitude) | ||
dim = self._dimensionality**power | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -726,7 +728,7 @@ def cumsum(self, axis=None, dtype=None, out=None): | |
ret = self.magnitude.cumsum(axis, dtype, None if out is None else out.magnitude) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if not isinstance(out, Quantity): | ||
raise TypeError("out parameter must be a Quantity") | ||
out._dimensionality = dim | ||
|
@@ -743,7 +745,7 @@ def cumprod(self, axis=None, dtype=None, out=None): | |
ret = self.magnitude.cumprod(axis, dtype, out) | ||
dim = self.dimensionality | ||
if out is None: | ||
return Quantity(ret, dim, copy=False) | ||
return Quantity(ret, dim) | ||
if isinstance(out, Quantity): | ||
out._dimensionality = dim | ||
return out | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now breaking the tests in MNE:
x-ref: mne-tools/mne-python#12815
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you all testing against a development version of NumPy? I don't think @bjodah or I thought about that in these PRs for parsing the numpy version. I guess a version parse instead would be better if you want to defend against dev version testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MNE is testing against stable and the development version of
numpy
, all my projects are also tested against both, and I'm probably not the only one ;)Quick fix in #238 but maybe you want something a bit more elaborate 😃