Skip to content

Commit 572ea0f

Browse files
committed
Make compatible with numpy version 1.26
1 parent e63efad commit 572ea0f

File tree

8 files changed

+140
-42
lines changed

8 files changed

+140
-42
lines changed

src/blosc2/__init__.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,15 +171,81 @@ class Tuner(Enum):
171171
# For array-api compatibility
172172
iinfo = np.iinfo
173173
finfo = np.finfo
174-
isdtype = np.isdtype
174+
175+
176+
def isdtype(a_dtype: np.dtype, kind: str | np.dtype | tuple):
177+
"""
178+
Returns a boolean indicating whether a provided dtype is of a specified data type "kind".
179+
180+
Parameters
181+
----------
182+
dtype: dtype
183+
The input dtype.
184+
185+
kind: str | dtype | Tuple[str, dtype]
186+
Data type kind.
187+
188+
If kind is a dtype, return boolean indicating whether the input dtype is equal to the dtype specified by kind.
189+
190+
If kind is a string, return boolean indicating whether the input dtype is of a specified data type kind.
191+
The following dtype kinds are supporte:
192+
193+
* 'bool': boolean data types (e.g., bool).
194+
195+
* 'signed integer': signed integer data types (e.g., int8, int16, int32, int64).
196+
197+
* 'unsigned integer': unsigned integer data types (e.g., uint8, uint16, uint32, uint64).
198+
199+
* 'integral': integer data types. Shorthand for ('signed integer', 'unsigned integer').
200+
201+
* 'real floating': real-valued floating-point data types (e.g., float32, float64).
202+
203+
* 'complex floating': complex floating-point data types (e.g., complex64, complex128).
204+
205+
* 'numeric': numeric data types. Shorthand for ('integral', 'real floating', 'complex floating').
206+
207+
Returns
208+
-------
209+
out: bool
210+
Boolean indicating whether a provided dtype is of a specified data type kind.
211+
"""
212+
kind = (kind,) if not isinstance(kind, tuple) else kind
213+
for _ in kind:
214+
if a_dtype == kind:
215+
return True
216+
217+
_complex, _signedint, _uint, _rfloat = False, False, False, False
218+
if a_dtype in (complex64, complex128):
219+
_complex = True
220+
if "complex floating" in kind:
221+
return True
222+
if a_dtype == bool_ and "bool" in kind:
223+
return True
224+
if a_dtype in (int8, int16, int32, int64):
225+
_signedint = True
226+
if "signed integer" in kind:
227+
return True
228+
if a_dtype in (uint8, uint16, uint32, uint64):
229+
_uint = True
230+
if "unsigned integer" in kind:
231+
return True
232+
if a_dtype in (float16, float32, float64):
233+
_rfloat = True
234+
if "real floating" in kind:
235+
return True
236+
if "integral" in kind and (_signedint or _uint):
237+
return True
238+
return "numeric" in kind and (
239+
_signedint or _uint or _rfloat or _complex
240+
) # checked everything, otherwise False
241+
175242

176243
# dtypes for array-api
177244
str_ = np.str_
178245
bytes_ = np.bytes_
179246
object_ = np.object_
180247

181248
from numpy import (
182-
bool,
183249
bool_,
184250
complex64,
185251
complex128,
@@ -202,6 +268,8 @@ class Tuner(Enum):
202268
uint64,
203269
)
204270

271+
bool = bool
272+
205273
DEFAULT_COMPLEX = complex128
206274
"""
207275
Default complex floating dtype."""

src/blosc2/lazyexpr.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,7 @@ def slices_eval( # noqa: C901
13821382
# shape_slice in general not equal to final shape:
13831383
# dummy dims (due to ints) will be dealt with by taking final_slice
13841384
shape_slice = ndindex.ndindex(_slice).newshape(shape)
1385-
mask_slice = np.bool([isinstance(i, int) for i in orig_slice])
1385+
mask_slice = np.array([isinstance(i, int) for i in orig_slice], dtype=np.bool_)
13861386
else:
13871387
# # out should always have shape of full array
13881388
# if shape is not None and shape != out.shape:
@@ -1769,7 +1769,7 @@ def reduce_slices( # noqa: C901
17691769

17701770
_slice = _slice.raw
17711771
shape_slice = shape
1772-
mask_slice = np.bool([isinstance(i, int) for i in _slice])
1772+
mask_slice = np.array([isinstance(i, int) for i in _slice], dtype=np.bool_)
17731773
if out is None and _slice != ():
17741774
_slice = tuple(slice(i, i + 1, 1) if isinstance(i, int) else i for i in _slice)
17751775
shape_slice = ndindex.ndindex(_slice).newshape(shape)
@@ -2209,7 +2209,7 @@ def result_type(
22092209
# Follow NumPy rules for scalar-array operations
22102210
# Create small arrays with the same dtypes and let NumPy's type promotion determine the result type
22112211
arrs = [
2212-
value if not hasattr(value, "dtype") else np.array([0], dtype=value.dtype)
2212+
value if (np.isscalar(value) or not hasattr(value, "dtype")) else np.array([0], dtype=value.dtype)
22132213
for value in arrays_and_dtypes
22142214
]
22152215
return np.result_type(*arrs)

src/blosc2/ndarray.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2904,6 +2904,16 @@ def chunkwise_logaddexp(inputs, output, offset):
29042904
return blosc2.lazyudf(chunkwise_logaddexp, (x1, x2), dtype=dtype, shape=x1.shape)
29052905

29062906

2907+
try: # handle different numpy versions
2908+
nplshift = np.bitwise_left_shift
2909+
nprshift = np.bitwise_right_shift
2910+
npbinvert = np.bitwise_invert
2911+
except AttributeError:
2912+
nplshift = np.left_shift
2913+
nprshift = np.right_shift
2914+
npbinvert = np.bitwise_not
2915+
2916+
29072917
class Operand:
29082918
"""Base class for all operands in expressions."""
29092919

@@ -2957,8 +2967,8 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
29572967
np.bitwise_or: "|",
29582968
np.bitwise_xor: "^",
29592969
np.arctan2: "arctan2",
2960-
np.bitwise_left_shift: "<<",
2961-
np.bitwise_right_shift: ">>",
2970+
nplshift: "<<",
2971+
nprshift: ">>",
29622972
np.remainder: "%",
29632973
np.nextafter: "nextafter",
29642974
np.copysign: "copysign",
@@ -2992,7 +3002,7 @@ def __array_ufunc__(self, ufunc, method, *inputs, **kwargs):
29923002
np.conj: "conj",
29933003
np.real: "real",
29943004
np.imag: "imag",
2995-
np.bitwise_invert: "~",
3005+
npbinvert: "~",
29963006
np.isnan: "isnan",
29973007
np.isfinite: "isfinite",
29983008
np.isinf: "isinf",
@@ -3791,11 +3801,11 @@ def get_fselection_numpy(self, key: list | np.ndarray) -> np.ndarray:
37913801
return_index=True,
37923802
return_inverse=True,
37933803
)
3804+
idx_inv = idx_inv if chunked_arr.shape != idx_inv.shape else idx_inv.squeeze(-1)
37943805
unique_chunks = chunked_arr[row_ids]
3795-
idx_order = np.argsort(
3796-
idx_inv.squeeze(-1)
3797-
) # sort by chunks (can't sort by index since larger index could belong to lower chunk)
3806+
# sort by chunks (can't sort by index since larger index could belong to lower chunk)
37983807
# e.g. chunks of (100, 10) means (50, 15) has chunk idx (0,1) but (60,5) has (0, 0)
3808+
idx_order = np.argsort(idx_inv)
37993809
sorted_idxs = arr[idx_order]
38003810
out = np.empty(flat_shape, dtype=self.dtype)
38013811
shape = np.array(shape)

tests/ndarray/test_concatenate.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111

1212
import blosc2
1313

14+
try: # handle different versions of numpy
15+
npconcat = np.concat
16+
except AttributeError:
17+
npconcat = np.concatenate
18+
1419

1520
@pytest.mark.parametrize(
1621
("shape1", "shape2", "dtype", "axis"),
@@ -34,7 +39,7 @@ def test_concat2(shape1, shape2, dtype, axis):
3439
ndarr2 = blosc2.arange(0, int(np.prod(shape2)), 1, dtype=dtype, shape=shape2)
3540
cparams = blosc2.CParams(clevel=1)
3641
result = blosc2.concat([ndarr1, ndarr2], axis=axis, cparams=cparams)
37-
nparray = np.concat([ndarr1[:], ndarr2[:]], axis=axis)
42+
nparray = npconcat([ndarr1[:], ndarr2[:]], axis=axis)
3843
np.testing.assert_almost_equal(result[:], nparray)
3944

4045

@@ -60,7 +65,7 @@ def test_concat3(shape1, shape2, shape3, dtype, axis):
6065
ndarr3 = blosc2.arange(0, int(np.prod(shape3)), 1, dtype=dtype, shape=shape3)
6166
cparams = blosc2.CParams(codec=blosc2.Codec.BLOSCLZ)
6267
result = blosc2.concat([ndarr1, ndarr2, ndarr3], axis=axis, cparams=cparams)
63-
nparray = np.concat([ndarr1[:], ndarr2[:], ndarr3[:]], axis=axis)
68+
nparray = npconcat([ndarr1[:], ndarr2[:], ndarr3[:]], axis=axis)
6469
np.testing.assert_almost_equal(result[:], nparray)
6570

6671

tests/ndarray/test_elementwise_funcs.py

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
UNARY_FUNC_PAIRS.append((np.round, blosc2.round))
3232
UNARY_FUNC_PAIRS.append((np.count_nonzero, blosc2.count_nonzero))
3333

34-
DTYPES = [np.bool_, np.int32, np.int64, np.float32, np.float64, np.complex128]
34+
DTYPES = [blosc2.bool_, blosc2.int32, blosc2.int64, blosc2.float32, blosc2.float64, blosc2.complex128]
3535
SHAPES_CHUNKS = [((10,), (3,)), ((20, 20), (4, 7)), ((10, 13, 13), (3, 5, 2))]
3636

3737

@@ -43,20 +43,20 @@ def test_unary_funcs(np_func, blosc_func, dtype, shape, chunkshape): # noqa : C
4343
a_blosc = blosc2.linspace(
4444
0.01, stop=0.99, num=np.prod(shape), chunks=chunkshape, shape=shape, dtype=dtype
4545
)
46-
if not np.issubdtype(dtype, np.integer):
46+
if not blosc2.isdtype(dtype, "integral"):
4747
a_blosc[tuple(i // 2 for i in shape)] = blosc2.nan
48-
if dtype == np.complex128:
48+
if dtype == blosc2.complex128:
4949
a_blosc = (a_blosc * (1 + 1j)).compute()
5050
a_blosc[tuple(i // 2 for i in shape)] = blosc2.nan + blosc2.nan * 1j
51-
if dtype == np.bool and np_func.__name__ == "arctanh":
51+
if dtype == blosc2.bool_ and np_func.__name__ == "arctanh":
5252
a_blosc = blosc2.zeros(chunks=chunkshape, shape=shape, dtype=dtype)
5353
else:
5454
a_blosc = blosc2.linspace(
5555
1, stop=np.prod(shape), num=np.prod(shape), chunks=chunkshape, shape=shape, dtype=dtype
5656
)
57-
if not np.issubdtype(dtype, np.integer):
57+
if not blosc2.isdtype(dtype, "integral"):
5858
a_blosc[tuple(i // 2 for i in shape)] = blosc2.nan
59-
if dtype == np.complex128:
59+
if dtype == blosc2.complex128:
6060
a_blosc = (
6161
a_blosc
6262
+ blosc2.linspace(
@@ -87,12 +87,16 @@ def test_unary_funcs(np_func, blosc_func, dtype, shape, chunkshape): # noqa : C
8787
# some functions don't support certain dtypes and that's fine
8888
assert True
8989
except ValueError as e:
90-
if np_func.__name__ == "logical_not" and dtype in (np.float32, np.float64, np.complex128):
90+
if np_func.__name__ == "logical_not" and dtype in (
91+
blosc2.float32,
92+
blosc2.float64,
93+
blosc2.complex128,
94+
):
9195
assert True
9296
else:
9397
raise e
9498
except AssertionError as e:
95-
if np_func.__name__ in ("tan", "tanh") and dtype == np.complex128:
99+
if np_func.__name__ in ("tan", "tanh") and dtype == blosc2.complex128:
96100
warnings.showwarning(
97101
"tan and tanh do not give correct NaN location",
98102
UserWarning,
@@ -123,9 +127,9 @@ def test_binary_funcs(np_func, blosc_func, dtype, shape, chunkshape): # noqa :
123127
shape=shape,
124128
dtype=dtype,
125129
)
126-
if not np.issubdtype(dtype, np.integer):
130+
if not blosc2.isdtype(dtype, "integral"):
127131
a_blosc1[tuple(i // 2 for i in shape)] = blosc2.nan
128-
if dtype == np.complex128:
132+
if dtype == blosc2.complex128:
129133
a_blosc1 = (
130134
a_blosc1
131135
+ blosc2.linspace(
@@ -151,23 +155,26 @@ def test_binary_funcs(np_func, blosc_func, dtype, shape, chunkshape): # noqa :
151155
# some functions don't support certain dtypes and that's fine
152156
assert True
153157
except ValueError as e: # shouldn't be allowed for non-booleans
154-
if np_func.__name__ in ("logical_and", "logical_or", "logical_xor", "minimum", "maximum"):
158+
if np_func.__name__ in ("logical_and", "logical_or", "logical_xor"):
159+
assert True
160+
if (
161+
np_func.__name__ in ("less", "less_equal", "greater", "greater_equal", "minimum", "maximum")
162+
and dtype == blosc2.complex128
163+
): # not supported for complex dtypes
155164
assert True
156165
else:
157166
raise e
158-
except NotImplementedError as e: # shouldn't be allowed for non-booleans
167+
except NotImplementedError as e:
159168
if np_func.__name__ in ("left_shift", "right_shift", "floor_divide", "power", "remainder"):
160169
assert True
161170
else:
162171
raise e
163172
except AssertionError as e:
164-
if np_func.__name__ == "power" and np.issubdtype(
165-
dtype, np.integer
173+
if np_func.__name__ == "power" and blosc2.isdtype(
174+
dtype, "integral"
166175
): # overflow causes disagreement, no problem
167176
assert True
168-
elif np_func.__name__ in ("maximum", "minimum") and np.issubdtype(
169-
dtype, np.floating
170-
): # overflow causes disagreement, no problem
177+
elif np_func.__name__ in ("maximum", "minimum") and blosc2.isdtype(dtype, "real floating"):
171178
warnings.showwarning(
172179
"minimum and maximum for numexpr do not match NaN behaviour for numpy",
173180
UserWarning,

tests/ndarray/test_evaluate.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ def test_numpy_funcs(sample_data, func):
100100
a = a[:]
101101
b = b[:]
102102
c = c[:] # ensure that all operands are numpy arrays
103-
npfunc = getattr(np, func)
104-
d_numpy = npfunc(((a**3 + np.sin(a * 2)) < c) & (b > 0), axis=0)
105-
np.testing.assert_equal(d_blosc2, d_numpy)
103+
try:
104+
npfunc = getattr(np, func)
105+
d_numpy = npfunc(((a**3 + np.sin(a * 2)) < c) & (b > 0), axis=0)
106+
np.testing.assert_equal(d_blosc2, d_numpy)
107+
except AttributeError:
108+
pytest.skip("NumPy version has no cumulative_sum function.")

tests/ndarray/test_lazyexpr.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,11 +1375,13 @@ def test_only_ndarrays_or_constructors(obj, getitem, item):
13751375
@pytest.mark.parametrize("func", ["cumsum", "cumulative_sum", "cumprod"])
13761376
def test_numpy_funcs(array_fixture, func):
13771377
a1, a2, a3, a4, na1, na2, na3, na4 = array_fixture
1378-
npfunc = getattr(np, func)
1379-
d_blosc2 = npfunc(((a1**3 + blosc2.sin(na2 * 2)) < a3) & (na2 > 0), axis=0)
1380-
npfunc = getattr(np, func)
1381-
d_numpy = npfunc(((na1**3 + np.sin(na2 * 2)) < na3) & (na2 > 0), axis=0)
1382-
np.testing.assert_equal(d_blosc2, d_numpy)
1378+
try:
1379+
npfunc = getattr(np, func)
1380+
d_blosc2 = npfunc(((a1**3 + blosc2.sin(na2 * 2)) < a3) & (na2 > 0), axis=0)
1381+
d_numpy = npfunc(((na1**3 + np.sin(na2 * 2)) < na3) & (na2 > 0), axis=0)
1382+
np.testing.assert_equal(d_blosc2, d_numpy)
1383+
except AttributeError:
1384+
pytest.skip("NumPy version has no cumulative_sum function.")
13831385

13841386

13851387
# Test the LazyExpr when some operands are missing (e.g. removed file)
@@ -1489,8 +1491,8 @@ def test_chain_persistentexpressions():
14891491
def test_scalar_dtypes(values):
14901492
value1, value2 = values
14911493
dtype1 = (value1 + value2).dtype
1492-
avalue1 = blosc2.asarray(value1) if hasattr(value1, "shape") else value1
1493-
avalue2 = blosc2.asarray(value2) if hasattr(value2, "shape") else value2
1494+
avalue1 = blosc2.asarray(value1) if not np.isscalar(value1) else value1
1495+
avalue2 = blosc2.asarray(value2) if not np.isscalar(value2) else value2
14941496
dtype2 = (avalue1 * avalue2).dtype
14951497
assert dtype1 == dtype2, f"Expected {dtype1} but got {dtype2}"
14961498

tests/ndarray/test_linalg.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -741,8 +741,11 @@ def test_matrix_transpose(shape):
741741
def test_mT(shape):
742742
arr = blosc2.linspace(0, 1, shape=shape)
743743
result = arr.mT
744-
expected = arr[:].mT
745-
np.testing.assert_allclose(result, expected)
744+
try:
745+
expected = arr[:].mT
746+
np.testing.assert_allclose(result, expected)
747+
except AttributeError:
748+
pytest.skip("np.ndarray object in Numpy version {np.__version__} does not have .mT attribute.")
746749

747750

748751
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)