array-api-strict 2.3__py3-none-any.whl → 2.4__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 (35) hide show
  1. array_api_strict/__init__.py +11 -5
  2. array_api_strict/_array_object.py +226 -200
  3. array_api_strict/_constants.py +1 -1
  4. array_api_strict/_creation_functions.py +97 -119
  5. array_api_strict/_data_type_functions.py +73 -64
  6. array_api_strict/_dtypes.py +35 -24
  7. array_api_strict/_elementwise_functions.py +138 -482
  8. array_api_strict/_fft.py +28 -32
  9. array_api_strict/_flags.py +65 -26
  10. array_api_strict/_helpers.py +23 -14
  11. array_api_strict/_indexing_functions.py +3 -8
  12. array_api_strict/_info.py +64 -69
  13. array_api_strict/_linalg.py +57 -53
  14. array_api_strict/_linear_algebra_functions.py +11 -9
  15. array_api_strict/_manipulation_functions.py +30 -30
  16. array_api_strict/_searching_functions.py +22 -33
  17. array_api_strict/_set_functions.py +4 -6
  18. array_api_strict/_sorting_functions.py +6 -6
  19. array_api_strict/_statistical_functions.py +57 -64
  20. array_api_strict/_typing.py +30 -50
  21. array_api_strict/_utility_functions.py +11 -13
  22. array_api_strict/_version.py +2 -2
  23. array_api_strict/py.typed +0 -0
  24. array_api_strict/tests/test_array_object.py +163 -56
  25. array_api_strict/tests/test_creation_functions.py +14 -12
  26. array_api_strict/tests/test_data_type_functions.py +40 -6
  27. array_api_strict/tests/test_elementwise_functions.py +94 -45
  28. array_api_strict/tests/test_searching_functions.py +79 -1
  29. array_api_strict/tests/test_validation.py +1 -3
  30. {array_api_strict-2.3.dist-info → array_api_strict-2.4.dist-info}/METADATA +5 -4
  31. array_api_strict-2.4.dist-info/RECORD +44 -0
  32. {array_api_strict-2.3.dist-info → array_api_strict-2.4.dist-info}/WHEEL +1 -1
  33. array_api_strict-2.3.dist-info/RECORD +0 -43
  34. {array_api_strict-2.3.dist-info → array_api_strict-2.4.dist-info}/LICENSE +0 -0
  35. {array_api_strict-2.3.dist-info → array_api_strict-2.4.dist-info}/top_level.txt +0 -0
@@ -1,33 +1,23 @@
1
- from __future__ import annotations
2
-
1
+ from collections.abc import Sequence
3
2
  from functools import partial
3
+ from typing import Literal, NamedTuple
4
+
5
+ import numpy as np
6
+ import numpy.linalg
4
7
 
5
- from ._dtypes import (
6
- _floating_dtypes,
7
- _numeric_dtypes,
8
- float32,
9
- complex64,
10
- complex128,
11
- )
8
+ from ._array_object import Array
12
9
  from ._data_type_functions import finfo
13
- from ._manipulation_functions import reshape
10
+ from ._dtypes import DType, _floating_dtypes, _numeric_dtypes, complex64, complex128
14
11
  from ._elementwise_functions import conj
15
- from ._array_object import Array
16
- from ._flags import requires_extension, get_array_api_strict_flags
12
+ from ._flags import get_array_api_strict_flags, requires_extension
13
+ from ._manipulation_functions import reshape
14
+ from ._statistical_functions import _np_dtype_sumprod
17
15
 
18
16
  try:
19
- from numpy._core.numeric import normalize_axis_tuple
17
+ from numpy._core.numeric import normalize_axis_tuple # type: ignore[attr-defined]
20
18
  except ImportError:
21
- from numpy.core.numeric import normalize_axis_tuple
22
-
23
- from typing import TYPE_CHECKING
24
- if TYPE_CHECKING:
25
- from ._typing import Literal, Optional, Sequence, Tuple, Union, Dtype
19
+ from numpy.core.numeric import normalize_axis_tuple # type: ignore[no-redef]
26
20
 
27
- from typing import NamedTuple
28
-
29
- import numpy.linalg
30
- import numpy as np
31
21
 
32
22
  class EighResult(NamedTuple):
33
23
  eigenvalues: Array
@@ -175,7 +165,13 @@ def inv(x: Array, /) -> Array:
175
165
  # -np.inf, 'fro', 'nuc']]], but Literal does not support floating-point
176
166
  # literals.
177
167
  @requires_extension('linalg')
178
- def matrix_norm(x: Array, /, *, keepdims: bool = False, ord: Optional[Union[int, float, Literal['fro', 'nuc']]] = 'fro') -> Array: # noqa: F821
168
+ def matrix_norm(
169
+ x: Array,
170
+ /,
171
+ *,
172
+ keepdims: bool = False,
173
+ ord: float | Literal["fro", "nuc"] | None = "fro",
174
+ ) -> Array: # noqa: F821
179
175
  """
180
176
  Array API compatible wrapper for :py:func:`np.linalg.norm <numpy.linalg.norm>`.
181
177
 
@@ -186,7 +182,10 @@ def matrix_norm(x: Array, /, *, keepdims: bool = False, ord: Optional[Union[int,
186
182
  if x.dtype not in _floating_dtypes:
187
183
  raise TypeError('Only floating-point dtypes are allowed in matrix_norm')
188
184
 
189
- return Array._new(np.linalg.norm(x._array, axis=(-2, -1), keepdims=keepdims, ord=ord), device=x.device)
185
+ return Array._new(
186
+ np.linalg.norm(x._array, axis=(-2, -1), keepdims=keepdims, ord=ord),
187
+ device=x.device,
188
+ )
190
189
 
191
190
 
192
191
  @requires_extension('linalg')
@@ -206,7 +205,7 @@ def matrix_power(x: Array, n: int, /) -> Array:
206
205
 
207
206
  # Note: the keyword argument name rtol is different from np.linalg.matrix_rank
208
207
  @requires_extension('linalg')
209
- def matrix_rank(x: Array, /, *, rtol: Optional[Union[float, Array]] = None) -> Array:
208
+ def matrix_rank(x: Array, /, *, rtol: float | Array | None = None) -> Array:
210
209
  """
211
210
  Array API compatible wrapper for :py:func:`np.matrix_rank <numpy.matrix_rank>`.
212
211
 
@@ -218,13 +217,12 @@ def matrix_rank(x: Array, /, *, rtol: Optional[Union[float, Array]] = None) -> A
218
217
  raise np.linalg.LinAlgError("1-dimensional array given. Array must be at least two-dimensional")
219
218
  S = np.linalg.svd(x._array, compute_uv=False)
220
219
  if rtol is None:
221
- tol = S.max(axis=-1, keepdims=True) * max(x.shape[-2:]) * finfo(S.dtype).eps
220
+ tol = S.max(axis=-1, keepdims=True) * max(x.shape[-2:]) * np.finfo(S.dtype).eps
222
221
  else:
223
- if isinstance(rtol, Array):
224
- rtol = rtol._array
222
+ rtol_np = rtol._array if isinstance(rtol, Array) else np.asarray(rtol)
225
223
  # Note: this is different from np.linalg.matrix_rank, which does not multiply
226
224
  # the tolerance by the largest singular value.
227
- tol = S.max(axis=-1, keepdims=True)*np.asarray(rtol)[..., np.newaxis]
225
+ tol = S.max(axis=-1, keepdims=True) * rtol_np[..., np.newaxis]
228
226
  return Array._new(np.count_nonzero(S > tol, axis=-1), device=x.device)
229
227
 
230
228
 
@@ -252,7 +250,7 @@ def outer(x1: Array, x2: Array, /) -> Array:
252
250
 
253
251
  # Note: the keyword argument name rtol is different from np.linalg.pinv
254
252
  @requires_extension('linalg')
255
- def pinv(x: Array, /, *, rtol: Optional[Union[float, Array]] = None) -> Array:
253
+ def pinv(x: Array, /, *, rtol: float | Array | None = None) -> Array:
256
254
  """
257
255
  Array API compatible wrapper for :py:func:`np.linalg.pinv <numpy.linalg.pinv>`.
258
256
 
@@ -267,9 +265,8 @@ def pinv(x: Array, /, *, rtol: Optional[Union[float, Array]] = None) -> Array:
267
265
  # default tolerance by max(M, N).
268
266
  if rtol is None:
269
267
  rtol = max(x.shape[-2:]) * finfo(x.dtype).eps
270
- if isinstance(rtol, Array):
271
- rtol = rtol._array
272
- return Array._new(np.linalg.pinv(x._array, rcond=rtol), device=x.device)
268
+ rtol_np = rtol._array if isinstance(rtol, Array) else rtol
269
+ return Array._new(np.linalg.pinv(x._array, rcond=rtol_np), device=x.device)
273
270
 
274
271
  @requires_extension('linalg')
275
272
  def qr(x: Array, /, *, mode: Literal['reduced', 'complete'] = 'reduced') -> QRResult: # noqa: F821
@@ -312,14 +309,14 @@ def slogdet(x: Array, /) -> SlogdetResult:
312
309
 
313
310
  # To workaround this, the below is the code from np.linalg.solve except
314
311
  # only calling solve1 in the exactly 1D case.
315
- def _solve(a, b):
312
+ def _solve(a: np.ndarray, b: np.ndarray) -> np.ndarray:
316
313
  try:
317
- from numpy.linalg._linalg import (
314
+ from numpy.linalg._linalg import ( # type: ignore[attr-defined]
318
315
  _makearray, _assert_stacked_2d, _assert_stacked_square,
319
316
  _commonType, isComplexType, _raise_linalgerror_singular
320
317
  )
321
318
  except ImportError:
322
- from numpy.linalg.linalg import (
319
+ from numpy.linalg.linalg import ( # type: ignore[attr-defined]
323
320
  _makearray, _assert_stacked_2d, _assert_stacked_square,
324
321
  _commonType, isComplexType, _raise_linalgerror_singular
325
322
  )
@@ -382,14 +379,14 @@ def svd(x: Array, /, *, full_matrices: bool = True) -> SVDResult:
382
379
  # Note: svdvals is not in NumPy (but it is in SciPy). It is equivalent to
383
380
  # np.linalg.svd(compute_uv=False).
384
381
  @requires_extension('linalg')
385
- def svdvals(x: Array, /) -> Union[Array, Tuple[Array, ...]]:
382
+ def svdvals(x: Array, /) -> Array:
386
383
  if x.dtype not in _floating_dtypes:
387
384
  raise TypeError('Only floating-point dtypes are allowed in svdvals')
388
385
  return Array._new(np.linalg.svd(x._array, compute_uv=False), device=x.device)
389
386
 
390
387
  # Note: trace is the numpy top-level namespace, not np.linalg
391
388
  @requires_extension('linalg')
392
- def trace(x: Array, /, *, offset: int = 0, dtype: Optional[Dtype] = None) -> Array:
389
+ def trace(x: Array, /, *, offset: int = 0, dtype: DType | None = None) -> Array:
393
390
  """
394
391
  Array API compatible wrapper for :py:func:`np.trace <numpy.trace>`.
395
392
 
@@ -398,19 +395,13 @@ def trace(x: Array, /, *, offset: int = 0, dtype: Optional[Dtype] = None) -> Arr
398
395
  if x.dtype not in _numeric_dtypes:
399
396
  raise TypeError('Only numeric dtypes are allowed in trace')
400
397
 
401
- # Note: trace() works the same as sum() and prod() (see
402
- # _statistical_functions.py)
403
- if dtype is None:
404
- if get_array_api_strict_flags()['api_version'] < '2023.12':
405
- if x.dtype == float32:
406
- dtype = np.float64
407
- elif x.dtype == complex64:
408
- dtype = np.complex128
409
- else:
410
- dtype = dtype._np_dtype
398
+ # Note: trace() works the same as sum() and prod() (see _statistical_functions.py)
399
+ np_dtype = _np_dtype_sumprod(x, dtype)
400
+
411
401
  # Note: trace always operates on the last two axes, whereas np.trace
412
402
  # operates on the first two axes by default
413
- return Array._new(np.asarray(np.trace(x._array, offset=offset, axis1=-2, axis2=-1, dtype=dtype)), device=x.device)
403
+ res = np.trace(x._array, offset=offset, axis1=-2, axis2=-1, dtype=np_dtype)
404
+ return Array._new(np.asarray(res), device=x.device)
414
405
 
415
406
  # Note: the name here is different from norm(). The array API norm is split
416
407
  # into matrix_norm and vector_norm().
@@ -418,7 +409,14 @@ def trace(x: Array, /, *, offset: int = 0, dtype: Optional[Dtype] = None) -> Arr
418
409
  # The type for ord should be Optional[Union[int, float, Literal[np.inf,
419
410
  # -np.inf]]] but Literal does not support floating-point literals.
420
411
  @requires_extension('linalg')
421
- def vector_norm(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keepdims: bool = False, ord: Optional[Union[int, float]] = 2) -> Array:
412
+ def vector_norm(
413
+ x: Array,
414
+ /,
415
+ *,
416
+ axis: int | tuple[int, ...] | None = None,
417
+ keepdims: bool = False,
418
+ ord: float = 2,
419
+ ) -> Array:
422
420
  """
423
421
  Array API compatible wrapper for :py:func:`np.linalg.norm <numpy.linalg.norm>`.
424
422
 
@@ -456,8 +454,8 @@ def vector_norm(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = No
456
454
  # We can't reuse np.linalg.norm(keepdims) because of the reshape hacks
457
455
  # above to avoid matrix norm logic.
458
456
  shape = list(x.shape)
459
- _axis = normalize_axis_tuple(range(x.ndim) if axis is None else axis, x.ndim)
460
- for i in _axis:
457
+ axis_tup = normalize_axis_tuple(range(x.ndim) if axis is None else axis, x.ndim)
458
+ for i in axis_tup:
461
459
  shape[i] = 1
462
460
  res = reshape(res, tuple(shape))
463
461
 
@@ -480,7 +478,13 @@ def matmul(x1: Array, x2: Array, /) -> Array:
480
478
 
481
479
  # Note: tensordot is the numpy top-level namespace but not in np.linalg
482
480
  @requires_extension('linalg')
483
- def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> Array:
481
+ def tensordot(
482
+ x1: Array,
483
+ x2: Array,
484
+ /,
485
+ *,
486
+ axes: int | tuple[Sequence[int], Sequence[int]] = 2,
487
+ ) -> Array:
484
488
  from ._linear_algebra_functions import tensordot
485
489
  return tensordot(x1, x2, axes=axes)
486
490
 
@@ -4,19 +4,15 @@ them here with wrappers in linalg so that the wrappers can be disabled if the
4
4
  linalg extension is disabled in the flags.
5
5
 
6
6
  """
7
+ from collections.abc import Sequence
7
8
 
8
- from __future__ import annotations
9
+ import numpy as np
10
+ import numpy.linalg
9
11
 
10
- from ._dtypes import _numeric_dtypes
11
12
  from ._array_object import Array
13
+ from ._dtypes import _numeric_dtypes
12
14
  from ._flags import get_array_api_strict_flags
13
15
 
14
- from typing import TYPE_CHECKING
15
- if TYPE_CHECKING:
16
- from ._typing import Sequence, Tuple, Union
17
-
18
- import numpy.linalg
19
- import numpy as np
20
16
 
21
17
  # Note: matmul is the numpy top-level namespace but not in np.linalg
22
18
  def matmul(x1: Array, x2: Array, /) -> Array:
@@ -38,7 +34,13 @@ def matmul(x1: Array, x2: Array, /) -> Array:
38
34
  # Note: tensordot is the numpy top-level namespace but not in np.linalg
39
35
 
40
36
  # Note: axes must be a tuple, unlike np.tensordot where it can be an array or array-like.
41
- def tensordot(x1: Array, x2: Array, /, *, axes: Union[int, Tuple[Sequence[int], Sequence[int]]] = 2) -> Array:
37
+ def tensordot(
38
+ x1: Array,
39
+ x2: Array,
40
+ /,
41
+ *,
42
+ axes: int | tuple[Sequence[int], Sequence[int]] = 2,
43
+ ) -> Array:
42
44
  # Note: the restriction to numeric dtypes only is different from
43
45
  # np.tensordot.
44
46
  if x1.dtype not in _numeric_dtypes or x2.dtype not in _numeric_dtypes:
@@ -1,21 +1,15 @@
1
- from __future__ import annotations
1
+ import numpy as np
2
2
 
3
3
  from ._array_object import Array
4
4
  from ._creation_functions import asarray
5
5
  from ._data_type_functions import astype, result_type
6
6
  from ._dtypes import _integer_dtypes, int64, uint64
7
- from ._flags import requires_api_version, get_array_api_strict_flags
8
-
9
- from typing import TYPE_CHECKING
7
+ from ._flags import get_array_api_strict_flags, requires_api_version
10
8
 
11
- if TYPE_CHECKING:
12
- from typing import List, Optional, Tuple, Union
13
-
14
- import numpy as np
15
9
 
16
10
  # Note: the function name is different here
17
11
  def concat(
18
- arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: Optional[int] = 0
12
+ arrays: tuple[Array, ...] | list[Array], /, *, axis: int | None = 0
19
13
  ) -> Array:
20
14
  """
21
15
  Array API compatible wrapper for :py:func:`np.concatenate <numpy.concatenate>`.
@@ -29,8 +23,11 @@ def concat(
29
23
  raise ValueError("concat inputs must all be on the same device")
30
24
  result_device = arrays[0].device
31
25
 
32
- arrays = tuple(a._array for a in arrays)
33
- return Array._new(np.concatenate(arrays, axis=axis, dtype=dtype._np_dtype), device=result_device)
26
+ np_arrays = tuple(a._array for a in arrays)
27
+ return Array._new(
28
+ np.concatenate(np_arrays, axis=axis, dtype=dtype._np_dtype),
29
+ device=result_device,
30
+ )
34
31
 
35
32
 
36
33
  def expand_dims(x: Array, /, *, axis: int) -> Array:
@@ -42,7 +39,7 @@ def expand_dims(x: Array, /, *, axis: int) -> Array:
42
39
  return Array._new(np.expand_dims(x._array, axis), device=x.device)
43
40
 
44
41
 
45
- def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) -> Array:
42
+ def flip(x: Array, /, *, axis: int | tuple[int, ...] | None = None) -> Array:
46
43
  """
47
44
  Array API compatible wrapper for :py:func:`np.flip <numpy.flip>`.
48
45
 
@@ -53,8 +50,8 @@ def flip(x: Array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None) ->
53
50
  @requires_api_version('2023.12')
54
51
  def moveaxis(
55
52
  x: Array,
56
- source: Union[int, Tuple[int, ...]],
57
- destination: Union[int, Tuple[int, ...]],
53
+ source: int | tuple[int, ...],
54
+ destination: int | tuple[int, ...],
58
55
  /,
59
56
  ) -> Array:
60
57
  """
@@ -66,7 +63,7 @@ def moveaxis(
66
63
 
67
64
  # Note: The function name is different here (see also matrix_transpose).
68
65
  # Unlike transpose(), the axes argument is required.
69
- def permute_dims(x: Array, /, axes: Tuple[int, ...]) -> Array:
66
+ def permute_dims(x: Array, /, axes: tuple[int, ...]) -> Array:
70
67
  """
71
68
  Array API compatible wrapper for :py:func:`np.transpose <numpy.transpose>`.
72
69
 
@@ -77,10 +74,10 @@ def permute_dims(x: Array, /, axes: Tuple[int, ...]) -> Array:
77
74
  @requires_api_version('2023.12')
78
75
  def repeat(
79
76
  x: Array,
80
- repeats: Union[int, Array],
77
+ repeats: int | Array,
81
78
  /,
82
79
  *,
83
- axis: Optional[int] = None,
80
+ axis: int | None = None,
84
81
  ) -> Array:
85
82
  """
86
83
  Array API compatible wrapper for :py:func:`np.repeat <numpy.repeat>`.
@@ -108,17 +105,16 @@ def repeat(
108
105
  repeats = astype(repeats, int64)
109
106
  return Array._new(np.repeat(x._array, repeats._array, axis=axis), device=x.device)
110
107
 
108
+
111
109
  # Note: the optional argument is called 'shape', not 'newshape'
112
- def reshape(x: Array,
113
- /,
114
- shape: Tuple[int, ...],
115
- *,
116
- copy: Optional[bool] = None) -> Array:
110
+ def reshape(x: Array, /, shape: tuple[int, ...], *, copy: bool | None = None) -> Array:
117
111
  """
118
112
  Array API compatible wrapper for :py:func:`np.reshape <numpy.reshape>`.
119
113
 
120
114
  See its docstring for more information.
121
115
  """
116
+ if not isinstance(shape, tuple):
117
+ raise TypeError(f"`shape` must be a tuple of ints; got {shape=} instead.")
122
118
 
123
119
  data = x._array
124
120
  if copy:
@@ -135,19 +131,23 @@ def reshape(x: Array,
135
131
  def roll(
136
132
  x: Array,
137
133
  /,
138
- shift: Union[int, Tuple[int, ...]],
134
+ shift: int | tuple[int, ...],
139
135
  *,
140
- axis: Optional[Union[int, Tuple[int, ...]]] = None,
136
+ axis: int | tuple[int, ...] | None = None,
141
137
  ) -> Array:
142
138
  """
143
139
  Array API compatible wrapper for :py:func:`np.roll <numpy.roll>`.
144
140
 
145
141
  See its docstring for more information.
146
142
  """
143
+ if not isinstance(shift, int | tuple):
144
+ raise ValueError(
145
+ f"`shift` can only be an int or a tuple, got {type(shift)=} instead."
146
+ )
147
147
  return Array._new(np.roll(x._array, shift, axis=axis), device=x.device)
148
148
 
149
149
 
150
- def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array:
150
+ def squeeze(x: Array, /, axis: int | tuple[int, ...]) -> Array:
151
151
  """
152
152
  Array API compatible wrapper for :py:func:`np.squeeze <numpy.squeeze>`.
153
153
 
@@ -161,7 +161,7 @@ def squeeze(x: Array, /, axis: Union[int, Tuple[int, ...]]) -> Array:
161
161
  return Array._new(np.squeeze(x._array, axis=axis), device=x.device)
162
162
 
163
163
 
164
- def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) -> Array:
164
+ def stack(arrays: tuple[Array, ...] | list[Array], /, *, axis: int = 0) -> Array:
165
165
  """
166
166
  Array API compatible wrapper for :py:func:`np.stack <numpy.stack>`.
167
167
 
@@ -172,12 +172,12 @@ def stack(arrays: Union[Tuple[Array, ...], List[Array]], /, *, axis: int = 0) ->
172
172
  if len({a.device for a in arrays}) > 1:
173
173
  raise ValueError("concat inputs must all be on the same device")
174
174
  result_device = arrays[0].device
175
- arrays = tuple(a._array for a in arrays)
176
- return Array._new(np.stack(arrays, axis=axis), device=result_device)
175
+ np_arrays = tuple(a._array for a in arrays)
176
+ return Array._new(np.stack(np_arrays, axis=axis), device=result_device)
177
177
 
178
178
 
179
179
  @requires_api_version('2023.12')
180
- def tile(x: Array, repetitions: Tuple[int, ...], /) -> Array:
180
+ def tile(x: Array, repetitions: tuple[int, ...], /) -> Array:
181
181
  """
182
182
  Array API compatible wrapper for :py:func:`np.tile <numpy.tile>`.
183
183
 
@@ -190,7 +190,7 @@ def tile(x: Array, repetitions: Tuple[int, ...], /) -> Array:
190
190
 
191
191
  # Note: this function is new
192
192
  @requires_api_version('2023.12')
193
- def unstack(x: Array, /, *, axis: int = 0) -> Tuple[Array, ...]:
193
+ def unstack(x: Array, /, *, axis: int = 0) -> tuple[Array, ...]:
194
194
  if not (-x.ndim <= axis < x.ndim):
195
195
  raise ValueError("axis out of range")
196
196
 
@@ -1,17 +1,15 @@
1
- from __future__ import annotations
2
-
3
- from ._array_object import Array
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
-
7
- from typing import TYPE_CHECKING
8
- if TYPE_CHECKING:
9
- from typing import Literal, Optional, Tuple, Union
1
+ from typing import Literal
10
2
 
11
3
  import numpy as np
12
4
 
5
+ from ._array_object import Array
6
+ from ._dtypes import _real_numeric_dtypes, _result_type
7
+ from ._dtypes import bool as _bool
8
+ from ._flags import requires_api_version, requires_data_dependent_shapes
9
+ from ._helpers import _maybe_normalize_py_scalars
10
+
13
11
 
14
- def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array:
12
+ def argmax(x: Array, /, *, axis: int | None = None, keepdims: bool = False) -> Array:
15
13
  """
16
14
  Array API compatible wrapper for :py:func:`np.argmax <numpy.argmax>`.
17
15
 
@@ -22,7 +20,7 @@ def argmax(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -
22
20
  return Array._new(np.asarray(np.argmax(x._array, axis=axis, keepdims=keepdims)), device=x.device)
23
21
 
24
22
 
25
- def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -> Array:
23
+ def argmin(x: Array, /, *, axis: int | None = None, keepdims: bool = False) -> Array:
26
24
  """
27
25
  Array API compatible wrapper for :py:func:`np.argmin <numpy.argmin>`.
28
26
 
@@ -34,7 +32,7 @@ def argmin(x: Array, /, *, axis: Optional[int] = None, keepdims: bool = False) -
34
32
 
35
33
 
36
34
  @requires_data_dependent_shapes
37
- def nonzero(x: Array, /) -> Tuple[Array, ...]:
35
+ def nonzero(x: Array, /) -> tuple[Array, ...]:
38
36
  """
39
37
  Array API compatible wrapper for :py:func:`np.nonzero <numpy.nonzero>`.
40
38
 
@@ -51,7 +49,7 @@ def count_nonzero(
51
49
  x: Array,
52
50
  /,
53
51
  *,
54
- axis: Optional[Union[int, Tuple[int, ...]]] = None,
52
+ axis: int | tuple[int, ...] | None = None,
55
53
  keepdims: bool = False,
56
54
  ) -> Array:
57
55
  """
@@ -70,7 +68,7 @@ def searchsorted(
70
68
  /,
71
69
  *,
72
70
  side: Literal["left", "right"] = "left",
73
- sorter: Optional[Array] = None,
71
+ sorter: Array | None = None,
74
72
  ) -> Array:
75
73
  """
76
74
  Array API compatible wrapper for :py:func:`np.searchsorted <numpy.searchsorted>`.
@@ -83,36 +81,27 @@ def searchsorted(
83
81
  if x1.device != x2.device:
84
82
  raise ValueError(f"Arrays from two different devices ({x1.device} and {x2.device}) can not be combined.")
85
83
 
86
- sorter = sorter._array if sorter is not None else None
84
+ np_sorter = sorter._array if sorter is not None else None
87
85
  # TODO: The sort order of nans and signed zeros is implementation
88
86
  # dependent. Should we error/warn if they are present?
89
87
 
90
88
  # x1 must be 1-D, but NumPy already requires this.
91
- return Array._new(np.searchsorted(x1._array, x2._array, side=side, sorter=sorter), device=x1.device)
89
+ return Array._new(
90
+ np.searchsorted(x1._array, x2._array, side=side, sorter=np_sorter),
91
+ device=x1.device,
92
+ )
92
93
 
93
- def where(
94
- condition: Array,
95
- x1: bool | int | float | complex | Array,
96
- x2: bool | int | float | complex | Array, /
97
- ) -> Array:
94
+
95
+ def where(condition: Array, x1: Array | complex, x2: Array | complex, /) -> Array:
98
96
  """
99
97
  Array API compatible wrapper for :py:func:`np.where <numpy.where>`.
100
98
 
101
99
  See its docstring for more information.
102
100
  """
103
- if get_array_api_strict_flags()['api_version'] > '2023.12':
104
- num_scalars = 0
105
-
106
- if isinstance(x1, (bool, float, complex, int)):
107
- x1 = Array._new(np.asarray(x1), device=condition.device)
108
- num_scalars += 1
109
-
110
- if isinstance(x2, (bool, float, complex, int)):
111
- x2 = Array._new(np.asarray(x2), device=condition.device)
112
- num_scalars += 1
101
+ if not isinstance(condition, Array):
102
+ raise TypeError(f"`condition` must be an Array; got {type(condition)}")
113
103
 
114
- if num_scalars == 2:
115
- raise ValueError("One of x1, x2 arguments must be an array.")
104
+ x1, x2 = _maybe_normalize_py_scalars(x1, x2, "all", "where")
116
105
 
117
106
  # Call result type here just to raise on disallowed type combinations
118
107
  _result_type(x1.dtype, x2.dtype)
@@ -1,13 +1,10 @@
1
- from __future__ import annotations
2
-
3
- from ._array_object import Array
4
-
5
- from ._flags import requires_data_dependent_shapes
6
-
7
1
  from typing import NamedTuple
8
2
 
9
3
  import numpy as np
10
4
 
5
+ from ._array_object import Array
6
+ from ._flags import requires_data_dependent_shapes
7
+
11
8
  # Note: np.unique() is split into four functions in the array API:
12
9
  # unique_all, unique_counts, unique_inverse, and unique_values (this is done
13
10
  # to remove polymorphic return types).
@@ -20,6 +17,7 @@ import numpy as np
20
17
  # Note: The functions here return a namedtuple (np.unique() returns a normal
21
18
  # tuple).
22
19
 
20
+
23
21
  class UniqueAllResult(NamedTuple):
24
22
  values: Array
25
23
  indices: Array
@@ -1,10 +1,10 @@
1
- from __future__ import annotations
1
+ from typing import Literal
2
+
3
+ import numpy as np
2
4
 
3
5
  from ._array_object import Array
4
6
  from ._dtypes import _real_numeric_dtypes
5
7
 
6
- import numpy as np
7
-
8
8
 
9
9
  # Note: the descending keyword argument is new in this function
10
10
  def argsort(
@@ -18,7 +18,7 @@ def argsort(
18
18
  if x.dtype not in _real_numeric_dtypes:
19
19
  raise TypeError("Only real numeric dtypes are allowed in argsort")
20
20
  # Note: this keyword argument is different, and the default is different.
21
- kind = "stable" if stable else "quicksort"
21
+ kind: Literal["stable", "quicksort"] = "stable" if stable else "quicksort"
22
22
  if not descending:
23
23
  res = np.argsort(x._array, axis=axis, kind=kind)
24
24
  else:
@@ -35,6 +35,7 @@ def argsort(
35
35
  res = max_i - res
36
36
  return Array._new(res, device=x.device)
37
37
 
38
+
38
39
  # Note: the descending keyword argument is new in this function
39
40
  def sort(
40
41
  x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True
@@ -47,8 +48,7 @@ def sort(
47
48
  if x.dtype not in _real_numeric_dtypes:
48
49
  raise TypeError("Only real numeric dtypes are allowed in sort")
49
50
  # Note: this keyword argument is different, and the default is different.
50
- kind = "stable" if stable else "quicksort"
51
- res = np.sort(x._array, axis=axis, kind=kind)
51
+ res = np.sort(x._array, axis=axis, kind="stable" if stable else "quicksort")
52
52
  if descending:
53
53
  res = np.flip(res, axis=axis)
54
54
  return Array._new(res, device=x.device)