array-api-strict 2.2__py3-none-any.whl → 2.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. array_api_strict/__init__.py +10 -7
  2. array_api_strict/_array_object.py +31 -19
  3. array_api_strict/_creation_functions.py +2 -0
  4. array_api_strict/_data_type_functions.py +34 -3
  5. array_api_strict/_dtypes.py +1 -0
  6. array_api_strict/_elementwise_functions.py +140 -471
  7. array_api_strict/_fft.py +31 -5
  8. array_api_strict/_flags.py +10 -8
  9. array_api_strict/_helpers.py +37 -0
  10. array_api_strict/_linear_algebra_functions.py +1 -1
  11. array_api_strict/_manipulation_functions.py +5 -0
  12. array_api_strict/_searching_functions.py +34 -5
  13. array_api_strict/_statistical_functions.py +42 -5
  14. array_api_strict/_version.py +16 -16
  15. array_api_strict/tests/test_array_object.py +146 -74
  16. array_api_strict/tests/test_creation_functions.py +7 -0
  17. array_api_strict/tests/test_data_type_functions.py +24 -4
  18. array_api_strict/tests/test_elementwise_functions.py +66 -8
  19. array_api_strict/tests/test_flags.py +29 -18
  20. array_api_strict/tests/test_manipulation_functions.py +1 -1
  21. array_api_strict/tests/test_searching_functions.py +44 -0
  22. array_api_strict/tests/test_statistical_functions.py +19 -1
  23. array_api_strict/tests/test_validation.py +1 -1
  24. array_api_strict-2.3.1.dist-info/METADATA +68 -0
  25. array_api_strict-2.3.1.dist-info/RECORD +43 -0
  26. {array_api_strict-2.2.dist-info → array_api_strict-2.3.1.dist-info}/WHEEL +1 -1
  27. array_api_strict-2.2.dist-info/METADATA +0 -37
  28. array_api_strict-2.2.dist-info/RECORD +0 -41
  29. {array_api_strict-2.2.dist-info → array_api_strict-2.3.1.dist-info}/LICENSE +0 -0
  30. {array_api_strict-2.2.dist-info → array_api_strict-2.3.1.dist-info}/top_level.txt +0 -0
array_api_strict/_fft.py CHANGED
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
4
4
 
5
5
  if TYPE_CHECKING:
6
6
  from typing import Union, Optional, Literal
7
- from ._typing import Device
7
+ from ._typing import Device, Dtype as DType
8
8
  from collections.abc import Sequence
9
9
 
10
10
  from ._dtypes import (
@@ -251,7 +251,14 @@ def ihfft(
251
251
  return res
252
252
 
253
253
  @requires_extension('fft')
254
- def fftfreq(n: int, /, *, d: float = 1.0, device: Optional[Device] = None) -> Array:
254
+ def fftfreq(
255
+ n: int,
256
+ /,
257
+ *,
258
+ d: float = 1.0,
259
+ dtype: Optional[DType] = None,
260
+ device: Optional[Device] = None
261
+ ) -> Array:
255
262
  """
256
263
  Array API compatible wrapper for :py:func:`np.fft.fftfreq <numpy.fft.fftfreq>`.
257
264
 
@@ -259,10 +266,23 @@ def fftfreq(n: int, /, *, d: float = 1.0, device: Optional[Device] = None) -> Ar
259
266
  """
260
267
  if device is not None and device not in ALL_DEVICES:
261
268
  raise ValueError(f"Unsupported device {device!r}")
262
- return Array._new(np.fft.fftfreq(n, d=d), device=device)
269
+ if dtype and not dtype in _real_floating_dtypes:
270
+ raise ValueError(f"`dtype` must be a real floating-point type. Got {dtype=}.")
271
+
272
+ np_result = np.fft.fftfreq(n, d=d)
273
+ if dtype:
274
+ np_result = np_result.astype(dtype._np_dtype)
275
+ return Array._new(np_result, device=device)
263
276
 
264
277
  @requires_extension('fft')
265
- def rfftfreq(n: int, /, *, d: float = 1.0, device: Optional[Device] = None) -> Array:
278
+ def rfftfreq(
279
+ n: int,
280
+ /,
281
+ *,
282
+ d: float = 1.0,
283
+ dtype: Optional[DType] = None,
284
+ device: Optional[Device] = None
285
+ ) -> Array:
266
286
  """
267
287
  Array API compatible wrapper for :py:func:`np.fft.rfftfreq <numpy.fft.rfftfreq>`.
268
288
 
@@ -270,7 +290,13 @@ def rfftfreq(n: int, /, *, d: float = 1.0, device: Optional[Device] = None) -> A
270
290
  """
271
291
  if device is not None and device not in ALL_DEVICES:
272
292
  raise ValueError(f"Unsupported device {device!r}")
273
- return Array._new(np.fft.rfftfreq(n, d=d), device=device)
293
+ if dtype and not dtype in _real_floating_dtypes:
294
+ raise ValueError(f"`dtype` must be a real floating-point type. Got {dtype=}.")
295
+
296
+ np_result = np.fft.rfftfreq(n, d=d)
297
+ if dtype:
298
+ np_result = np_result.astype(dtype._np_dtype)
299
+ return Array._new(np_result, device=device)
274
300
 
275
301
  @requires_extension('fft')
276
302
  def fftshift(x: Array, /, *, axes: Union[int, Sequence[int]] = None) -> Array:
@@ -22,11 +22,12 @@ supported_versions = (
22
22
  "2021.12",
23
23
  "2022.12",
24
24
  "2023.12",
25
+ "2024.12"
25
26
  )
26
27
 
27
- draft_version = "2024.12"
28
+ draft_version = "2025.12"
28
29
 
29
- API_VERSION = default_version = "2023.12"
30
+ API_VERSION = default_version = "2024.12"
30
31
 
31
32
  BOOLEAN_INDEXING = True
32
33
 
@@ -169,12 +170,13 @@ def set_array_api_strict_flags(
169
170
  set(default_extensions))
170
171
 
171
172
  # We have to do this separately or it won't get added as the docstring
172
- set_array_api_strict_flags.__doc__ = set_array_api_strict_flags.__doc__.format(
173
- supported_versions=supported_versions,
174
- default_version=default_version,
175
- default_extensions=default_extensions,
176
- draft_version=draft_version,
177
- )
173
+ if set_array_api_strict_flags.__doc__ is not None:
174
+ set_array_api_strict_flags.__doc__ = set_array_api_strict_flags.__doc__.format(
175
+ supported_versions=supported_versions,
176
+ default_version=default_version,
177
+ default_extensions=default_extensions,
178
+ draft_version=draft_version,
179
+ )
178
180
 
179
181
  def get_array_api_strict_flags():
180
182
  """
@@ -0,0 +1,37 @@
1
+ """Private helper routines.
2
+ """
3
+
4
+ from ._flags import get_array_api_strict_flags
5
+ from ._dtypes import _dtype_categories
6
+
7
+ _py_scalars = (bool, int, float, complex)
8
+
9
+
10
+ def _maybe_normalize_py_scalars(x1, x2, dtype_category, func_name):
11
+
12
+ flags = get_array_api_strict_flags()
13
+ if flags["api_version"] < "2024.12":
14
+ # scalars will fail at the call site
15
+ return x1, x2
16
+
17
+ _allowed_dtypes = _dtype_categories[dtype_category]
18
+
19
+ if isinstance(x1, _py_scalars):
20
+ if isinstance(x2, _py_scalars):
21
+ raise TypeError(f"Two scalars not allowed, got {type(x1) = } and {type(x2) =}")
22
+ # x2 must be an array
23
+ if x2.dtype not in _allowed_dtypes:
24
+ raise TypeError(f"Only {dtype_category} dtypes are allowed {func_name}. Got {x2.dtype}.")
25
+ x1 = x2._promote_scalar(x1)
26
+
27
+ elif isinstance(x2, _py_scalars):
28
+ # x1 must be an array
29
+ if x1.dtype not in _allowed_dtypes:
30
+ raise TypeError(f"Only {dtype_category} dtypes are allowed {func_name}. Got {x1.dtype}.")
31
+ x2 = x1._promote_scalar(x2)
32
+ else:
33
+ if x1.dtype not in _allowed_dtypes or x2.dtype not in _allowed_dtypes:
34
+ raise TypeError(f"Only {dtype_category} dtypes are allowed in {func_name}(...). "
35
+ f"Got {x1.dtype} and {x2.dtype}.")
36
+ return x1, x2
37
+
@@ -86,5 +86,5 @@ def vecdot(x1: Array, x2: Array, /, *, axis: int = -1) -> Array:
86
86
  x1_ = np.moveaxis(x1_, axis, -1)
87
87
  x2_ = np.moveaxis(x2_, axis, -1)
88
88
 
89
- res = x1_[..., None, :] @ x2_[..., None]
89
+ res = np.conj(x1_[..., None, :]) @ x2_[..., None]
90
90
  return Array._new(res[..., 0, 0], device=x1.device)
@@ -153,6 +153,11 @@ def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array:
153
153
 
154
154
  See its docstring for more information.
155
155
  """
156
+ if axis is None:
157
+ raise ValueError(
158
+ "squeeze(..., axis=None is not supported. See "
159
+ "https://github.com/data-apis/array-api/pull/100 for a discussion."
160
+ )
156
161
  return Array._new(np.squeeze(x._array, axis=axis), device=x.device)
157
162
 
158
163
 
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from ._array_object import Array
4
- from ._dtypes import _result_type, _real_numeric_dtypes
5
- from ._flags import requires_data_dependent_shapes, requires_api_version
4
+ from ._dtypes import _result_type, _real_numeric_dtypes, bool as _bool
5
+ from ._flags import requires_data_dependent_shapes, requires_api_version, get_array_api_strict_flags
6
+ from ._helpers import _maybe_normalize_py_scalars
6
7
 
7
8
  from typing import TYPE_CHECKING
8
9
  if TYPE_CHECKING:
9
- from typing import Literal, Optional, Tuple
10
+ from typing import Literal, Optional, Tuple, Union
10
11
 
11
12
  import numpy as np
12
13
 
@@ -45,6 +46,24 @@ def nonzero(x: Array, /) -> Tuple[Array, ...]:
45
46
  raise ValueError("nonzero is not allowed on 0-dimensional arrays")
46
47
  return tuple(Array._new(i, device=x.device) for i in np.nonzero(x._array))
47
48
 
49
+
50
+ @requires_api_version('2024.12')
51
+ def count_nonzero(
52
+ x: Array,
53
+ /,
54
+ *,
55
+ axis: Optional[Union[int, Tuple[int, ...]]] = None,
56
+ keepdims: bool = False,
57
+ ) -> Array:
58
+ """
59
+ Array API compatible wrapper for :py:func:`np.count_nonzero <numpy.count_nonzero>`
60
+
61
+ See its docstring for more information.
62
+ """
63
+ arr = np.count_nonzero(x._array, axis=axis, keepdims=keepdims)
64
+ return Array._new(np.asarray(arr), device=x.device)
65
+
66
+
48
67
  @requires_api_version('2023.12')
49
68
  def searchsorted(
50
69
  x1: Array,
@@ -72,17 +91,27 @@ def searchsorted(
72
91
  # x1 must be 1-D, but NumPy already requires this.
73
92
  return Array._new(np.searchsorted(x1._array, x2._array, side=side, sorter=sorter), device=x1.device)
74
93
 
75
- def where(condition: Array, x1: Array, x2: Array, /) -> Array:
94
+ def where(
95
+ condition: Array,
96
+ x1: bool | int | float | complex | Array,
97
+ x2: bool | int | float | complex | Array, /
98
+ ) -> Array:
76
99
  """
77
100
  Array API compatible wrapper for :py:func:`np.where <numpy.where>`.
78
101
 
79
102
  See its docstring for more information.
80
103
  """
104
+ if get_array_api_strict_flags()['api_version'] > '2023.12':
105
+ x1, x2 = _maybe_normalize_py_scalars(x1, x2, "all", "where")
106
+
81
107
  # Call result type here just to raise on disallowed type combinations
82
108
  _result_type(x1.dtype, x2.dtype)
109
+
110
+ if condition.dtype != _bool:
111
+ raise TypeError("`condition` must be have a boolean data type")
83
112
 
84
113
  if len({a.device for a in (condition, x1, x2)}) > 1:
85
- raise ValueError("where inputs must all be on the same device")
114
+ raise ValueError("Inputs to `where` must all use the same device")
86
115
 
87
116
  x1, x2 = Array._normalize_two_args(x1, x2)
88
117
  return Array._new(np.where(condition._array, x1._array, x2._array), device=x1.device)
@@ -3,12 +3,13 @@ from __future__ import annotations
3
3
  from ._dtypes import (
4
4
  _real_floating_dtypes,
5
5
  _real_numeric_dtypes,
6
+ _floating_dtypes,
6
7
  _numeric_dtypes,
7
8
  )
8
9
  from ._array_object import Array
9
10
  from ._dtypes import float32, complex64
10
11
  from ._flags import requires_api_version, get_array_api_strict_flags
11
- from ._creation_functions import zeros
12
+ from ._creation_functions import zeros, ones
12
13
  from ._manipulation_functions import concat
13
14
 
14
15
  from typing import TYPE_CHECKING
@@ -30,7 +31,6 @@ def cumulative_sum(
30
31
  ) -> Array:
31
32
  if x.dtype not in _numeric_dtypes:
32
33
  raise TypeError("Only numeric dtypes are allowed in cumulative_sum")
33
- dt = x.dtype if dtype is None else dtype
34
34
  if dtype is not None:
35
35
  dtype = dtype._np_dtype
36
36
 
@@ -43,9 +43,40 @@ def cumulative_sum(
43
43
  if include_initial:
44
44
  if axis < 0:
45
45
  axis += x.ndim
46
- x = concat([zeros(x.shape[:axis] + (1,) + x.shape[axis + 1:], dtype=dt), x], axis=axis)
46
+ x = concat([zeros(x.shape[:axis] + (1,) + x.shape[axis + 1:], dtype=x.dtype), x], axis=axis)
47
47
  return Array._new(np.cumsum(x._array, axis=axis, dtype=dtype), device=x.device)
48
48
 
49
+
50
+ @requires_api_version('2024.12')
51
+ def cumulative_prod(
52
+ x: Array,
53
+ /,
54
+ *,
55
+ axis: Optional[int] = None,
56
+ dtype: Optional[Dtype] = None,
57
+ include_initial: bool = False,
58
+ ) -> Array:
59
+ if x.dtype not in _numeric_dtypes:
60
+ raise TypeError("Only numeric dtypes are allowed in cumulative_prod")
61
+ if x.ndim == 0:
62
+ raise ValueError("Only ndim >= 1 arrays are allowed in cumulative_prod")
63
+
64
+ if dtype is not None:
65
+ dtype = dtype._np_dtype
66
+
67
+ if axis is None:
68
+ if x.ndim > 1:
69
+ raise ValueError("axis must be specified in cumulative_prod for more than one dimension")
70
+ axis = 0
71
+
72
+ # np.cumprod does not support include_initial
73
+ if include_initial:
74
+ if axis < 0:
75
+ axis += x.ndim
76
+ x = concat([ones(x.shape[:axis] + (1,) + x.shape[axis + 1:], dtype=x.dtype), x], axis=axis)
77
+ return Array._new(np.cumprod(x._array, axis=axis, dtype=dtype), device=x.device)
78
+
79
+
49
80
  def max(
50
81
  x: Array,
51
82
  /,
@@ -65,8 +96,14 @@ def mean(
65
96
  axis: Optional[Union[int, Tuple[int, ...]]] = None,
66
97
  keepdims: bool = False,
67
98
  ) -> Array:
68
- if x.dtype not in _real_floating_dtypes:
69
- raise TypeError("Only real floating-point dtypes are allowed in mean")
99
+
100
+ if get_array_api_strict_flags()['api_version'] > '2023.12':
101
+ allowed_dtypes = _floating_dtypes
102
+ else:
103
+ allowed_dtypes = _real_floating_dtypes
104
+
105
+ if x.dtype not in allowed_dtypes:
106
+ raise TypeError("Only floating-point dtypes are allowed in mean")
70
107
  return Array._new(np.mean(x._array, axis=axis, keepdims=keepdims), device=x.device)
71
108
 
72
109
 
@@ -1,21 +1,21 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
1
3
 
2
- # This file was generated by 'versioneer.py' (0.29) from
3
- # revision-control system data, or from the parent directory name of an
4
- # unpacked source archive. Distribution tarballs contain a pre-generated copy
5
- # of this file.
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
6
5
 
7
- import json
6
+ TYPE_CHECKING = False
7
+ if TYPE_CHECKING:
8
+ from typing import Tuple
9
+ from typing import Union
8
10
 
9
- version_json = '''
10
- {
11
- "date": "2024-11-11T15:30:59-0700",
12
- "dirty": false,
13
- "error": null,
14
- "full-revisionid": "fe4f34ad30ba59c1c981037dd181f6b4daf23b22",
15
- "version": "2.2"
16
- }
17
- ''' # END VERSION_JSON
11
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
12
+ else:
13
+ VERSION_TUPLE = object
18
14
 
15
+ version: str
16
+ __version__: str
17
+ __version_tuple__: VERSION_TUPLE
18
+ version_tuple: VERSION_TUPLE
19
19
 
20
- def get_versions():
21
- return json.loads(version_json)
20
+ __version__ = version = '2.3.1'
21
+ __version_tuple__ = version_tuple = (2, 3, 1)
@@ -5,7 +5,7 @@ from numpy.testing import assert_raises, suppress_warnings
5
5
  import numpy as np
6
6
  import pytest
7
7
 
8
- from .. import ones, asarray, result_type, all, equal
8
+ from .. import ones, arange, reshape, asarray, result_type, all, equal
9
9
  from .._array_object import Array, CPU_DEVICE, Device
10
10
  from .._dtypes import (
11
11
  _all_dtypes,
@@ -45,35 +45,46 @@ def test_validate_index():
45
45
  a = ones((3, 4))
46
46
 
47
47
  # Out of bounds slices are not allowed
48
- assert_raises(IndexError, lambda: a[:4])
49
- assert_raises(IndexError, lambda: a[:-4])
50
- assert_raises(IndexError, lambda: a[:3:-1])
51
- assert_raises(IndexError, lambda: a[:-5:-1])
52
- assert_raises(IndexError, lambda: a[4:])
53
- assert_raises(IndexError, lambda: a[-4:])
54
- assert_raises(IndexError, lambda: a[4::-1])
55
- assert_raises(IndexError, lambda: a[-4::-1])
56
-
57
- assert_raises(IndexError, lambda: a[...,:5])
58
- assert_raises(IndexError, lambda: a[...,:-5])
59
- assert_raises(IndexError, lambda: a[...,:5:-1])
60
- assert_raises(IndexError, lambda: a[...,:-6:-1])
61
- assert_raises(IndexError, lambda: a[...,5:])
62
- assert_raises(IndexError, lambda: a[...,-5:])
63
- assert_raises(IndexError, lambda: a[...,5::-1])
64
- assert_raises(IndexError, lambda: a[...,-5::-1])
48
+ assert_raises(IndexError, lambda: a[:4, 0])
49
+ assert_raises(IndexError, lambda: a[:-4, 0])
50
+ assert_raises(IndexError, lambda: a[:3:-1]) # XXX raises for a wrong reason
51
+ assert_raises(IndexError, lambda: a[:-5:-1, 0])
52
+ assert_raises(IndexError, lambda: a[4:, 0])
53
+ assert_raises(IndexError, lambda: a[-4:, 0])
54
+ assert_raises(IndexError, lambda: a[4::-1, 0])
55
+ assert_raises(IndexError, lambda: a[-4::-1, 0])
56
+
57
+ assert_raises(IndexError, lambda: a[..., :5])
58
+ assert_raises(IndexError, lambda: a[..., :-5])
59
+ assert_raises(IndexError, lambda: a[..., :5:-1])
60
+ assert_raises(IndexError, lambda: a[..., :-6:-1])
61
+ assert_raises(IndexError, lambda: a[..., 5:])
62
+ assert_raises(IndexError, lambda: a[..., -5:])
63
+ assert_raises(IndexError, lambda: a[..., 5::-1])
64
+ assert_raises(IndexError, lambda: a[..., -5::-1])
65
65
 
66
66
  # Boolean indices cannot be part of a larger tuple index
67
- assert_raises(IndexError, lambda: a[a[:,0]==1,0])
68
- assert_raises(IndexError, lambda: a[a[:,0]==1,...])
69
- assert_raises(IndexError, lambda: a[..., a[0]==1])
67
+ assert_raises(IndexError, lambda: a[a[:, 0] == 1, 0])
68
+ assert_raises(IndexError, lambda: a[a[:, 0] == 1, ...])
69
+ assert_raises(IndexError, lambda: a[..., a[0] == 1])
70
70
  assert_raises(IndexError, lambda: a[[True, True, True]])
71
71
  assert_raises(IndexError, lambda: a[(True, True, True),])
72
72
 
73
- # Integer array indices are not allowed (except for 0-D)
74
- idx = asarray([[0, 1]])
75
- assert_raises(IndexError, lambda: a[idx])
76
- assert_raises(IndexError, lambda: a[idx,])
73
+ # Mixing 1D integer array indices with slices, ellipsis or booleans is not allowed
74
+ idx = asarray([0, 1])
75
+ assert_raises(IndexError, lambda: a[..., idx])
76
+ assert_raises(IndexError, lambda: a[:, idx])
77
+ assert_raises(IndexError, lambda: a[asarray([True, True]), idx])
78
+
79
+ # 1D integer array indices must have the same length
80
+ idx1 = asarray([0, 1])
81
+ idx2 = asarray([0, 1, 1])
82
+ assert_raises(IndexError, lambda: a[idx1, idx2])
83
+
84
+ # Non-integer array indices are not allowed
85
+ assert_raises(IndexError, lambda: a[ones(2), 0])
86
+
87
+ # Array-likes (lists, tuples) are not allowed as indices
77
88
  assert_raises(IndexError, lambda: a[[0, 1]])
78
89
  assert_raises(IndexError, lambda: a[(0, 1), (0, 1)])
79
90
  assert_raises(IndexError, lambda: a[[0, 1]])
@@ -87,6 +98,45 @@ def test_validate_index():
87
98
  assert_raises(IndexError, lambda: a[0,])
88
99
  assert_raises(IndexError, lambda: a[0])
89
100
  assert_raises(IndexError, lambda: a[:])
101
+ assert_raises(IndexError, lambda: a[idx])
102
+
103
+
104
+ def test_indexing_arrays():
105
+ # indexing with 1D integer arrays and mixes of integers and 1D integer are allowed
106
+
107
+ # 1D array
108
+ a = arange(5)
109
+ idx = asarray([1, 0, 1, 2, -1])
110
+ a_idx = a[idx]
111
+
112
+ a_idx_loop = asarray([a[idx[i]] for i in range(idx.shape[0])])
113
+ assert all(a_idx == a_idx_loop)
114
+
115
+ # setitem with arrays is not allowed
116
+ with assert_raises(IndexError):
117
+ a[idx] = 42
118
+
119
+ # mixed array and integer indexing
120
+ a = reshape(arange(3*4), (3, 4))
121
+ idx = asarray([1, 0, 1, 2, -1])
122
+ a_idx = a[idx, 1]
123
+
124
+ a_idx_loop = asarray([a[idx[i], 1] for i in range(idx.shape[0])])
125
+ assert all(a_idx == a_idx_loop)
126
+
127
+ # index with two arrays
128
+ a_idx = a[idx, idx]
129
+ a_idx_loop = asarray([a[idx[i], idx[i]] for i in range(idx.shape[0])])
130
+ assert all(a_idx == a_idx_loop)
131
+
132
+ # setitem with arrays is not allowed
133
+ with assert_raises(IndexError):
134
+ a[idx, idx] = 42
135
+
136
+ # smoke test indexing with ndim > 1 arrays
137
+ idx = idx[..., None]
138
+ a[idx, idx]
139
+
90
140
 
91
141
  def test_promoted_scalar_inherits_device():
92
142
  device1 = Device("device1")
@@ -96,12 +146,68 @@ def test_promoted_scalar_inherits_device():
96
146
 
97
147
  assert y.device == device1
98
148
 
149
+
150
+ BIG_INT = int(1e30)
151
+
152
+ def _check_op_array_scalar(dtypes, a, s, func, func_name, BIG_INT=BIG_INT):
153
+ # Test array op scalar. From the spec, the following combinations
154
+ # are supported:
155
+
156
+ # - Python bool for a bool array dtype,
157
+ # - a Python int within the bounds of the given dtype for integer array dtypes,
158
+ # - a Python int or float for real floating-point array dtypes
159
+ # - a Python int, float, or complex for complex floating-point array dtypes
160
+
161
+ # an exception: complex scalar <op> floating array
162
+ scalar_types_for_float = [float, int]
163
+ if not (func_name.startswith("__i")
164
+ or (func_name in ["__floordiv__", "__rfloordiv__", "__mod__", "__rmod__"]
165
+ and type(s) == complex)
166
+ ):
167
+ scalar_types_for_float += [complex]
168
+
169
+ if ((dtypes == "all"
170
+ or dtypes == "numeric" and a.dtype in _numeric_dtypes
171
+ or dtypes == "real numeric" and a.dtype in _real_numeric_dtypes
172
+ or dtypes == "integer" and a.dtype in _integer_dtypes
173
+ or dtypes == "integer or boolean" and a.dtype in _integer_or_boolean_dtypes
174
+ or dtypes == "boolean" and a.dtype in _boolean_dtypes
175
+ or dtypes == "floating-point" and a.dtype in _floating_dtypes
176
+ or dtypes == "real floating-point" and a.dtype in _real_floating_dtypes
177
+ )
178
+ # bool is a subtype of int, which is why we avoid
179
+ # isinstance here.
180
+ and (a.dtype in _boolean_dtypes and type(s) == bool
181
+ or a.dtype in _integer_dtypes and type(s) == int
182
+ or a.dtype in _real_floating_dtypes and type(s) in scalar_types_for_float
183
+ or a.dtype in _complex_floating_dtypes and type(s) in [complex, float, int]
184
+ )):
185
+ if a.dtype in _integer_dtypes and s == BIG_INT:
186
+ with assert_raises(OverflowError):
187
+ func(s)
188
+ return False
189
+
190
+ else:
191
+ # Only test for no error
192
+ with suppress_warnings() as sup:
193
+ # ignore warnings from pow(BIG_INT)
194
+ sup.filter(RuntimeWarning,
195
+ "invalid value encountered in power")
196
+ func(s)
197
+ return True
198
+
199
+ else:
200
+ with assert_raises(TypeError):
201
+ func(s)
202
+ return False
203
+
204
+
99
205
  def test_operators():
100
206
  # For every operator, we test that it works for the required type
101
207
  # combinations and raises TypeError otherwise
102
208
  binary_op_dtypes = {
103
209
  "__add__": "numeric",
104
- "__and__": "integer_or_boolean",
210
+ "__and__": "integer or boolean",
105
211
  "__eq__": "all",
106
212
  "__floordiv__": "real numeric",
107
213
  "__ge__": "real numeric",
@@ -112,12 +218,12 @@ def test_operators():
112
218
  "__mod__": "real numeric",
113
219
  "__mul__": "numeric",
114
220
  "__ne__": "all",
115
- "__or__": "integer_or_boolean",
221
+ "__or__": "integer or boolean",
116
222
  "__pow__": "numeric",
117
223
  "__rshift__": "integer",
118
224
  "__sub__": "numeric",
119
- "__truediv__": "floating",
120
- "__xor__": "integer_or_boolean",
225
+ "__truediv__": "floating-point",
226
+ "__xor__": "integer or boolean",
121
227
  }
122
228
  # Recompute each time because of in-place ops
123
229
  def _array_vals():
@@ -128,8 +234,6 @@ def test_operators():
128
234
  for d in _floating_dtypes:
129
235
  yield asarray(1.0, dtype=d)
130
236
 
131
-
132
- BIG_INT = int(1e30)
133
237
  for op, dtypes in binary_op_dtypes.items():
134
238
  ops = [op]
135
239
  if op not in ["__eq__", "__ne__", "__le__", "__ge__", "__lt__", "__gt__"]:
@@ -139,40 +243,7 @@ def test_operators():
139
243
  for s in [1, 1.0, 1j, BIG_INT, False]:
140
244
  for _op in ops:
141
245
  for a in _array_vals():
142
- # Test array op scalar. From the spec, the following combinations
143
- # are supported:
144
-
145
- # - Python bool for a bool array dtype,
146
- # - a Python int within the bounds of the given dtype for integer array dtypes,
147
- # - a Python int or float for real floating-point array dtypes
148
- # - a Python int, float, or complex for complex floating-point array dtypes
149
-
150
- if ((dtypes == "all"
151
- or dtypes == "numeric" and a.dtype in _numeric_dtypes
152
- or dtypes == "real numeric" and a.dtype in _real_numeric_dtypes
153
- or dtypes == "integer" and a.dtype in _integer_dtypes
154
- or dtypes == "integer_or_boolean" and a.dtype in _integer_or_boolean_dtypes
155
- or dtypes == "boolean" and a.dtype in _boolean_dtypes
156
- or dtypes == "floating" and a.dtype in _floating_dtypes
157
- )
158
- # bool is a subtype of int, which is why we avoid
159
- # isinstance here.
160
- and (a.dtype in _boolean_dtypes and type(s) == bool
161
- or a.dtype in _integer_dtypes and type(s) == int
162
- or a.dtype in _real_floating_dtypes and type(s) in [float, int]
163
- or a.dtype in _complex_floating_dtypes and type(s) in [complex, float, int]
164
- )):
165
- if a.dtype in _integer_dtypes and s == BIG_INT:
166
- assert_raises(OverflowError, lambda: getattr(a, _op)(s))
167
- else:
168
- # Only test for no error
169
- with suppress_warnings() as sup:
170
- # ignore warnings from pow(BIG_INT)
171
- sup.filter(RuntimeWarning,
172
- "invalid value encountered in power")
173
- getattr(a, _op)(s)
174
- else:
175
- assert_raises(TypeError, lambda: getattr(a, _op)(s))
246
+ _check_op_array_scalar(dtypes, a, s, getattr(a, _op), _op)
176
247
 
177
248
  # Test array op array.
178
249
  for _op in ops:
@@ -203,10 +274,10 @@ def test_operators():
203
274
  or (dtypes == "real numeric" and x.dtype in _real_numeric_dtypes and y.dtype in _real_numeric_dtypes)
204
275
  or (dtypes == "numeric" and x.dtype in _numeric_dtypes and y.dtype in _numeric_dtypes)
205
276
  or dtypes == "integer" and x.dtype in _integer_dtypes and y.dtype in _integer_dtypes
206
- or dtypes == "integer_or_boolean" and (x.dtype in _integer_dtypes and y.dtype in _integer_dtypes
277
+ or dtypes == "integer or boolean" and (x.dtype in _integer_dtypes and y.dtype in _integer_dtypes
207
278
  or x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes)
208
279
  or dtypes == "boolean" and x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes
209
- or dtypes == "floating" and x.dtype in _floating_dtypes and y.dtype in _floating_dtypes
280
+ or dtypes == "floating-point" and x.dtype in _floating_dtypes and y.dtype in _floating_dtypes
210
281
  ):
211
282
  getattr(x, _op)(y)
212
283
  else:
@@ -214,7 +285,7 @@ def test_operators():
214
285
 
215
286
  unary_op_dtypes = {
216
287
  "__abs__": "numeric",
217
- "__invert__": "integer_or_boolean",
288
+ "__invert__": "integer or boolean",
218
289
  "__neg__": "numeric",
219
290
  "__pos__": "numeric",
220
291
  }
@@ -223,7 +294,7 @@ def test_operators():
223
294
  if (
224
295
  dtypes == "numeric"
225
296
  and a.dtype in _numeric_dtypes
226
- or dtypes == "integer_or_boolean"
297
+ or dtypes == "integer or boolean"
227
298
  and a.dtype in _integer_or_boolean_dtypes
228
299
  ):
229
300
  # Only test for no error
@@ -438,10 +509,10 @@ def test_array_keys_use_private_array():
438
509
  def test_array_namespace():
439
510
  a = ones((3, 3))
440
511
  assert a.__array_namespace__() == array_api_strict
441
- assert array_api_strict.__array_api_version__ == "2023.12"
512
+ assert array_api_strict.__array_api_version__ == "2024.12"
442
513
 
443
514
  assert a.__array_namespace__(api_version=None) is array_api_strict
444
- assert array_api_strict.__array_api_version__ == "2023.12"
515
+ assert array_api_strict.__array_api_version__ == "2024.12"
445
516
 
446
517
  assert a.__array_namespace__(api_version="2022.12") is array_api_strict
447
518
  assert array_api_strict.__array_api_version__ == "2022.12"
@@ -454,11 +525,12 @@ def test_array_namespace():
454
525
  assert array_api_strict.__array_api_version__ == "2021.12"
455
526
 
456
527
  with pytest.warns(UserWarning):
457
- assert a.__array_namespace__(api_version="2024.12") is array_api_strict
458
- assert array_api_strict.__array_api_version__ == "2024.12"
528
+ assert a.__array_namespace__(api_version="2025.12") is array_api_strict
529
+ assert array_api_strict.__array_api_version__ == "2025.12"
530
+
459
531
 
460
532
  pytest.raises(ValueError, lambda: a.__array_namespace__(api_version="2021.11"))
461
- pytest.raises(ValueError, lambda: a.__array_namespace__(api_version="2025.12"))
533
+ pytest.raises(ValueError, lambda: a.__array_namespace__(api_version="2026.12"))
462
534
 
463
535
  def test_iter():
464
536
  pytest.raises(TypeError, lambda: iter(asarray(3)))