array-api-strict 2.2__py3-none-any.whl → 2.3__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 +44 -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 +24 -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.dist-info/METADATA +68 -0
  25. array_api_strict-2.3.dist-info/RECORD +43 -0
  26. {array_api_strict-2.2.dist-info → array_api_strict-2.3.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.dist-info}/LICENSE +0 -0
  30. {array_api_strict-2.2.dist-info → array_api_strict-2.3.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 {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,12 @@
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
6
 
7
7
  from typing import TYPE_CHECKING
8
8
  if TYPE_CHECKING:
9
- from typing import Literal, Optional, Tuple
9
+ from typing import Literal, Optional, Tuple, Union
10
10
 
11
11
  import numpy as np
12
12
 
@@ -45,6 +45,24 @@ def nonzero(x: Array, /) -> Tuple[Array, ...]:
45
45
  raise ValueError("nonzero is not allowed on 0-dimensional arrays")
46
46
  return tuple(Array._new(i, device=x.device) for i in np.nonzero(x._array))
47
47
 
48
+
49
+ @requires_api_version('2024.12')
50
+ def count_nonzero(
51
+ x: Array,
52
+ /,
53
+ *,
54
+ axis: Optional[Union[int, Tuple[int, ...]]] = None,
55
+ keepdims: bool = False,
56
+ ) -> Array:
57
+ """
58
+ Array API compatible wrapper for :py:func:`np.count_nonzero <numpy.count_nonzero>`
59
+
60
+ See its docstring for more information.
61
+ """
62
+ arr = np.count_nonzero(x._array, axis=axis, keepdims=keepdims)
63
+ return Array._new(np.asarray(arr), device=x.device)
64
+
65
+
48
66
  @requires_api_version('2023.12')
49
67
  def searchsorted(
50
68
  x1: Array,
@@ -72,17 +90,38 @@ def searchsorted(
72
90
  # x1 must be 1-D, but NumPy already requires this.
73
91
  return Array._new(np.searchsorted(x1._array, x2._array, side=side, sorter=sorter), device=x1.device)
74
92
 
75
- def where(condition: Array, x1: Array, x2: Array, /) -> Array:
93
+ def where(
94
+ condition: Array,
95
+ x1: bool | int | float | complex | Array,
96
+ x2: bool | int | float | complex | Array, /
97
+ ) -> Array:
76
98
  """
77
99
  Array API compatible wrapper for :py:func:`np.where <numpy.where>`.
78
100
 
79
101
  See its docstring for more information.
80
102
  """
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
113
+
114
+ if num_scalars == 2:
115
+ raise ValueError("One of x1, x2 arguments must be an array.")
116
+
81
117
  # Call result type here just to raise on disallowed type combinations
82
118
  _result_type(x1.dtype, x2.dtype)
119
+
120
+ if condition.dtype != _bool:
121
+ raise TypeError("`condition` must be have a boolean data type")
83
122
 
84
123
  if len({a.device for a in (condition, x1, x2)}) > 1:
85
- raise ValueError("where inputs must all be on the same device")
124
+ raise ValueError("Inputs to `where` must all use the same device")
86
125
 
87
126
  x1, x2 = Array._normalize_two_args(x1, x2)
88
127
  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'
21
+ __version_tuple__ = version_tuple = (2, 3)