array-api-strict 2.4.1__py3-none-any.whl → 2.6__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.
- array_api_strict/__init__.py +4 -2
- array_api_strict/_array_object.py +49 -40
- array_api_strict/_creation_functions.py +106 -39
- array_api_strict/_data_type_functions.py +16 -4
- array_api_strict/_devices.py +136 -0
- array_api_strict/_dtypes.py +3 -1
- array_api_strict/_elementwise_functions.py +6 -4
- array_api_strict/_fft.py +21 -1
- array_api_strict/_flags.py +3 -2
- array_api_strict/_info.py +21 -80
- array_api_strict/_linalg.py +62 -1
- array_api_strict/_manipulation_functions.py +1 -1
- array_api_strict/_searching_functions.py +8 -2
- array_api_strict/_set_functions.py +23 -1
- array_api_strict/_statistical_functions.py +8 -2
- array_api_strict/_version.py +16 -3
- array_api_strict/tests/test_array_object.py +69 -10
- array_api_strict/tests/test_creation_functions.py +161 -2
- array_api_strict/tests/test_device_support.py +49 -7
- array_api_strict/tests/test_elementwise_functions.py +22 -5
- array_api_strict/tests/test_flags.py +11 -11
- array_api_strict/tests/test_searching_functions.py +1 -1
- array_api_strict/tests/test_statistical_functions.py +16 -0
- {array_api_strict-2.4.1.dist-info → array_api_strict-2.6.dist-info}/METADATA +4 -1
- array_api_strict-2.6.dist-info/RECORD +45 -0
- array_api_strict-2.4.1.dist-info/RECORD +0 -44
- {array_api_strict-2.4.1.dist-info → array_api_strict-2.6.dist-info}/LICENSE +0 -0
- {array_api_strict-2.4.1.dist-info → array_api_strict-2.6.dist-info}/WHEEL +0 -0
- {array_api_strict-2.4.1.dist-info → array_api_strict-2.6.dist-info}/top_level.txt +0 -0
array_api_strict/__init__.py
CHANGED
|
@@ -73,6 +73,7 @@ __all__ += [
|
|
|
73
73
|
from ._data_type_functions import (
|
|
74
74
|
astype,
|
|
75
75
|
broadcast_arrays,
|
|
76
|
+
broadcast_shapes,
|
|
76
77
|
broadcast_to,
|
|
77
78
|
can_cast,
|
|
78
79
|
finfo,
|
|
@@ -84,6 +85,7 @@ from ._data_type_functions import (
|
|
|
84
85
|
__all__ += [
|
|
85
86
|
"astype",
|
|
86
87
|
"broadcast_arrays",
|
|
88
|
+
"broadcast_shapes",
|
|
87
89
|
"broadcast_to",
|
|
88
90
|
"can_cast",
|
|
89
91
|
"finfo",
|
|
@@ -299,9 +301,9 @@ from ._searching_functions import argmax, argmin, nonzero, count_nonzero, search
|
|
|
299
301
|
|
|
300
302
|
__all__ += ["argmax", "argmin", "nonzero", "count_nonzero", "searchsorted", "where"]
|
|
301
303
|
|
|
302
|
-
from ._set_functions import unique_all, unique_counts, unique_inverse, unique_values
|
|
304
|
+
from ._set_functions import unique_all, unique_counts, unique_inverse, unique_values, isin
|
|
303
305
|
|
|
304
|
-
__all__ += ["unique_all", "unique_counts", "unique_inverse", "unique_values"]
|
|
306
|
+
__all__ += ["unique_all", "unique_counts", "unique_inverse", "unique_values", "isin"]
|
|
305
307
|
|
|
306
308
|
from ._sorting_functions import argsort, sort
|
|
307
309
|
|
|
@@ -20,7 +20,7 @@ import sys
|
|
|
20
20
|
from collections.abc import Iterator
|
|
21
21
|
from enum import IntEnum
|
|
22
22
|
from types import EllipsisType, ModuleType
|
|
23
|
-
from typing import Any,
|
|
23
|
+
from typing import Any, Literal, SupportsIndex, Callable
|
|
24
24
|
|
|
25
25
|
import numpy as np
|
|
26
26
|
import numpy.typing as npt
|
|
@@ -40,35 +40,14 @@ from ._dtypes import (
|
|
|
40
40
|
_real_to_complex_map,
|
|
41
41
|
_result_type,
|
|
42
42
|
)
|
|
43
|
+
from ._devices import (
|
|
44
|
+
CPU_DEVICE, Device, device_supports_dtype, _normalize_dl_device, _DLPACK_DEVICE_FOR,
|
|
45
|
+
DLDeviceType
|
|
46
|
+
)
|
|
43
47
|
from ._flags import get_array_api_strict_flags, set_array_api_strict_flags
|
|
44
48
|
from ._typing import PyCapsule
|
|
45
49
|
|
|
46
50
|
|
|
47
|
-
class Device:
|
|
48
|
-
_device: Final[str]
|
|
49
|
-
__slots__ = ("_device", "__weakref__")
|
|
50
|
-
|
|
51
|
-
def __init__(self, device: str = "CPU_DEVICE"):
|
|
52
|
-
if device not in ("CPU_DEVICE", "device1", "device2"):
|
|
53
|
-
raise ValueError(f"The device '{device}' is not a valid choice.")
|
|
54
|
-
self._device = device
|
|
55
|
-
|
|
56
|
-
def __repr__(self) -> str:
|
|
57
|
-
return f"array_api_strict.Device('{self._device}')"
|
|
58
|
-
|
|
59
|
-
def __eq__(self, other: object) -> bool:
|
|
60
|
-
if not isinstance(other, Device):
|
|
61
|
-
return False
|
|
62
|
-
return self._device == other._device
|
|
63
|
-
|
|
64
|
-
def __hash__(self) -> int:
|
|
65
|
-
return hash(("Device", self._device))
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
CPU_DEVICE = Device()
|
|
69
|
-
ALL_DEVICES = (CPU_DEVICE, Device("device1"), Device("device2"))
|
|
70
|
-
|
|
71
|
-
|
|
72
51
|
class Array:
|
|
73
52
|
"""
|
|
74
53
|
n-d array object for the array API namespace.
|
|
@@ -113,10 +92,15 @@ class Array:
|
|
|
113
92
|
raise TypeError(
|
|
114
93
|
f"The array_api_strict namespace does not support the dtype '{x.dtype}'"
|
|
115
94
|
)
|
|
116
|
-
|
|
117
|
-
obj._dtype = _dtype
|
|
95
|
+
|
|
118
96
|
if device is None:
|
|
119
97
|
device = CPU_DEVICE
|
|
98
|
+
if not device_supports_dtype(device, _dtype):
|
|
99
|
+
raise ValueError(f"Device {device!r} does not support dtype={_dtype!r}.")
|
|
100
|
+
|
|
101
|
+
obj._array = x
|
|
102
|
+
obj._dtype = _dtype
|
|
103
|
+
|
|
120
104
|
obj._device = device
|
|
121
105
|
return obj
|
|
122
106
|
|
|
@@ -125,6 +109,9 @@ class Array:
|
|
|
125
109
|
raise TypeError(
|
|
126
110
|
"The array_api_strict Array object should not be instantiated directly. Use an array creation function, such as asarray(), instead."
|
|
127
111
|
)
|
|
112
|
+
|
|
113
|
+
def __reduce__(self) -> tuple[Callable, tuple[npt.NDArray[Any], Device]]:
|
|
114
|
+
return (self._new, (self._array, self._device))
|
|
128
115
|
|
|
129
116
|
# These functions are not required by the spec, but are implemented for
|
|
130
117
|
# the sake of usability.
|
|
@@ -397,7 +384,7 @@ class Array:
|
|
|
397
384
|
isinstance(i, SupportsIndex) # i.e. ints
|
|
398
385
|
or isinstance(i, slice)
|
|
399
386
|
or i == Ellipsis
|
|
400
|
-
or i is None
|
|
387
|
+
or (op == "getitem" and i is None) # `None` disallowed in setitem
|
|
401
388
|
or isinstance(i, Array)
|
|
402
389
|
or isinstance(i, np.ndarray)
|
|
403
390
|
):
|
|
@@ -627,22 +614,40 @@ class Array:
|
|
|
627
614
|
raise NotImplementedError("The copy argument to __dlpack__ is not yet implemented")
|
|
628
615
|
|
|
629
616
|
return self._array.__dlpack__(stream=stream)
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
617
|
+
|
|
618
|
+
kwargs: dict[str, Any] = {'stream': stream}
|
|
619
|
+
self_dl_device = _normalize_dl_device(*_DLPACK_DEVICE_FOR[self._device])
|
|
620
|
+
cpu_dl_device = _normalize_dl_device(DLDeviceType.kDLCPU, 0)
|
|
621
|
+
numpy_dl_device: tuple[IntEnum, int] | None = None
|
|
622
|
+
|
|
623
|
+
if dl_device not in [_undef, None]:
|
|
624
|
+
requested = _normalize_dl_device(dl_device[0], dl_device[1])
|
|
625
|
+
if requested == self_dl_device:
|
|
626
|
+
pass
|
|
627
|
+
elif requested == cpu_dl_device and self_dl_device != cpu_dl_device:
|
|
628
|
+
if copy is False:
|
|
629
|
+
raise BufferError(
|
|
630
|
+
"Cannot export array to CPU without copying when copy=False"
|
|
631
|
+
)
|
|
632
|
+
if copy is _undef:
|
|
633
|
+
copy = True
|
|
634
|
+
numpy_dl_device = (DLDeviceType.kDLCPU, 0)
|
|
635
|
+
else:
|
|
636
|
+
raise BufferError("unsupported device requested")
|
|
637
|
+
|
|
638
|
+
if max_version is not _undef:
|
|
639
|
+
kwargs['max_version'] = max_version
|
|
640
|
+
if numpy_dl_device is not None:
|
|
641
|
+
kwargs['dl_device'] = numpy_dl_device
|
|
642
|
+
if copy is not _undef:
|
|
643
|
+
kwargs['copy'] = copy
|
|
644
|
+
return self._array.__dlpack__(**kwargs)
|
|
639
645
|
|
|
640
646
|
def __dlpack_device__(self) -> tuple[IntEnum, int]:
|
|
641
647
|
"""
|
|
642
648
|
Performs the operation __dlpack_device__.
|
|
643
649
|
"""
|
|
644
|
-
|
|
645
|
-
return self._array.__dlpack_device__()
|
|
650
|
+
return _DLPACK_DEVICE_FOR[self._device]
|
|
646
651
|
|
|
647
652
|
def __eq__(self, other: Array | complex, /) -> Array: # type: ignore[override]
|
|
648
653
|
"""
|
|
@@ -957,6 +962,10 @@ class Array:
|
|
|
957
962
|
other = value
|
|
958
963
|
if isinstance(value, (bool, int, float, complex)):
|
|
959
964
|
other = self._promote_scalar(value)
|
|
965
|
+
else:
|
|
966
|
+
if value.device != self.device:
|
|
967
|
+
raise ValueError(f"mismatched devices: {self.device = } != {value.device =}.")
|
|
968
|
+
|
|
960
969
|
dt = _result_type(self.dtype, other.dtype)
|
|
961
970
|
if dt != self.dtype:
|
|
962
971
|
raise TypeError(f"mismatched dtypes: {self.dtype = } and {other.dtype = }")
|
|
@@ -5,7 +5,11 @@ from typing import TYPE_CHECKING, Literal
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
|
-
from ._dtypes import DType, _all_dtypes, _np_dtype
|
|
8
|
+
from ._dtypes import DType, _all_dtypes, _np_dtype, bool as xp_bool
|
|
9
|
+
from ._devices import (
|
|
10
|
+
Device, device_supports_dtype, get_default_dtypes,
|
|
11
|
+
check_device as _check_device
|
|
12
|
+
)
|
|
9
13
|
from ._flags import get_array_api_strict_flags
|
|
10
14
|
from ._typing import NestedSequence, SupportsBufferProtocol, SupportsDLPack
|
|
11
15
|
|
|
@@ -14,7 +18,7 @@ if TYPE_CHECKING:
|
|
|
14
18
|
from typing_extensions import TypeIs
|
|
15
19
|
|
|
16
20
|
# Circular import
|
|
17
|
-
from ._array_object import Array
|
|
21
|
+
from ._array_object import Array
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
class Undef(Enum):
|
|
@@ -24,10 +28,15 @@ class Undef(Enum):
|
|
|
24
28
|
_undef = Undef.UNDEF
|
|
25
29
|
|
|
26
30
|
|
|
27
|
-
def _check_valid_dtype(dtype: DType | None) -> None:
|
|
31
|
+
def _check_valid_dtype(dtype: DType | None, device: Device | None = None) -> None:
|
|
28
32
|
# Note: Only spelling dtypes as the dtype objects is supported.
|
|
29
|
-
if dtype not
|
|
30
|
-
|
|
33
|
+
if dtype is not None:
|
|
34
|
+
if dtype not in _all_dtypes:
|
|
35
|
+
raise ValueError(f"dtype must be one of the supported dtypes, got {dtype!r}")
|
|
36
|
+
|
|
37
|
+
if device is not None:
|
|
38
|
+
if not device_supports_dtype(device, dtype):
|
|
39
|
+
raise ValueError(f"Device {device!r} does not support dtype={dtype!r}.")
|
|
31
40
|
|
|
32
41
|
|
|
33
42
|
def _supports_buffer_protocol(obj: object) -> TypeIs[SupportsBufferProtocol]:
|
|
@@ -38,18 +47,6 @@ def _supports_buffer_protocol(obj: object) -> TypeIs[SupportsBufferProtocol]:
|
|
|
38
47
|
return True
|
|
39
48
|
|
|
40
49
|
|
|
41
|
-
def _check_device(device: Device | None) -> None:
|
|
42
|
-
# _array_object imports in this file are inside the functions to avoid
|
|
43
|
-
# circular imports
|
|
44
|
-
from ._array_object import ALL_DEVICES, Device
|
|
45
|
-
|
|
46
|
-
if device is not None and not isinstance(device, Device):
|
|
47
|
-
raise ValueError(f"Unsupported device {device!r}")
|
|
48
|
-
|
|
49
|
-
if device is not None and device not in ALL_DEVICES:
|
|
50
|
-
raise ValueError(f"Unsupported device {device!r}")
|
|
51
|
-
|
|
52
|
-
|
|
53
50
|
def asarray(
|
|
54
51
|
obj: Array | complex | NestedSequence[complex] | SupportsBufferProtocol,
|
|
55
52
|
/,
|
|
@@ -65,11 +62,12 @@ def asarray(
|
|
|
65
62
|
"""
|
|
66
63
|
from ._array_object import Array
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
_check_device(device)
|
|
66
|
+
_check_valid_dtype(dtype, device)
|
|
69
67
|
_np_dtype = None
|
|
70
68
|
if dtype is not None:
|
|
71
69
|
_np_dtype = dtype._np_dtype
|
|
72
|
-
|
|
70
|
+
|
|
73
71
|
if isinstance(obj, Array) and device is None:
|
|
74
72
|
device = obj.device
|
|
75
73
|
|
|
@@ -108,6 +106,27 @@ def asarray(
|
|
|
108
106
|
raise OverflowError("Integer out of bounds for array dtypes")
|
|
109
107
|
|
|
110
108
|
res = np.array(obj, dtype=_np_dtype, copy=copy)
|
|
109
|
+
|
|
110
|
+
# numpy default dtype may differ; if so, adjust the dtype
|
|
111
|
+
if dtype is None and device is not None:
|
|
112
|
+
res_dtype = DType(res.dtype)
|
|
113
|
+
# The dtype selected by Numpy might not be the default dtype
|
|
114
|
+
# on this device. We thus find the default dtype for the dtype "kind", and
|
|
115
|
+
# cast to the device-appropriate default.
|
|
116
|
+
from ._data_type_functions import isdtype
|
|
117
|
+
if isdtype(res_dtype, "bool"):
|
|
118
|
+
target_dtype = DType("bool")
|
|
119
|
+
elif isdtype(res_dtype, "integral"):
|
|
120
|
+
target_dtype = get_default_dtypes(device)["integral"]
|
|
121
|
+
elif isdtype(res_dtype, "real floating"):
|
|
122
|
+
target_dtype = get_default_dtypes(device)["real floating"]
|
|
123
|
+
elif isdtype(res_dtype, "complex floating"):
|
|
124
|
+
target_dtype = get_default_dtypes(device)["complex floating"]
|
|
125
|
+
else:
|
|
126
|
+
raise ValueError(f"{res_dtype = } not understood.")
|
|
127
|
+
|
|
128
|
+
res = res.astype(target_dtype._np_dtype)
|
|
129
|
+
|
|
111
130
|
return Array._new(res, device=device)
|
|
112
131
|
|
|
113
132
|
|
|
@@ -127,8 +146,13 @@ def arange(
|
|
|
127
146
|
"""
|
|
128
147
|
from ._array_object import Array
|
|
129
148
|
|
|
130
|
-
_check_valid_dtype(dtype)
|
|
131
149
|
_check_device(device)
|
|
150
|
+
_check_valid_dtype(dtype, device)
|
|
151
|
+
if dtype is None:
|
|
152
|
+
if any(isinstance(x, float) for x in (start, stop, step)):
|
|
153
|
+
dtype = get_default_dtypes(device)["real floating"]
|
|
154
|
+
else:
|
|
155
|
+
dtype = get_default_dtypes(device)["integral"]
|
|
132
156
|
|
|
133
157
|
return Array._new(
|
|
134
158
|
np.arange(start, stop, step, dtype=_np_dtype(dtype)),
|
|
@@ -149,8 +173,10 @@ def empty(
|
|
|
149
173
|
"""
|
|
150
174
|
from ._array_object import Array
|
|
151
175
|
|
|
152
|
-
_check_valid_dtype(dtype)
|
|
153
176
|
_check_device(device)
|
|
177
|
+
_check_valid_dtype(dtype, device)
|
|
178
|
+
if dtype is None:
|
|
179
|
+
dtype = get_default_dtypes(device)["real floating"]
|
|
154
180
|
|
|
155
181
|
return Array._new(np.empty(shape, dtype=_np_dtype(dtype)), device=device)
|
|
156
182
|
|
|
@@ -165,10 +191,12 @@ def empty_like(
|
|
|
165
191
|
"""
|
|
166
192
|
from ._array_object import Array
|
|
167
193
|
|
|
168
|
-
_check_valid_dtype(dtype)
|
|
169
194
|
_check_device(device)
|
|
170
195
|
if device is None:
|
|
171
196
|
device = x.device
|
|
197
|
+
if dtype is None:
|
|
198
|
+
dtype = x.dtype
|
|
199
|
+
_check_valid_dtype(dtype, device)
|
|
172
200
|
|
|
173
201
|
return Array._new(np.empty_like(x._array, dtype=_np_dtype(dtype)), device=device)
|
|
174
202
|
|
|
@@ -189,8 +217,10 @@ def eye(
|
|
|
189
217
|
"""
|
|
190
218
|
from ._array_object import Array
|
|
191
219
|
|
|
192
|
-
_check_valid_dtype(dtype)
|
|
193
220
|
_check_device(device)
|
|
221
|
+
_check_valid_dtype(dtype, device)
|
|
222
|
+
if dtype is None:
|
|
223
|
+
dtype = get_default_dtypes(device)["real floating"]
|
|
194
224
|
|
|
195
225
|
return Array._new(
|
|
196
226
|
np.eye(n_rows, M=n_cols, k=k, dtype=_np_dtype(dtype)), device=device
|
|
@@ -212,15 +242,20 @@ def from_dlpack(
|
|
|
212
242
|
if copy is not _undef:
|
|
213
243
|
raise ValueError("The copy argument to from_dlpack requires at least version 2023.12 of the array API")
|
|
214
244
|
|
|
215
|
-
# Going to wait for upstream numpy support
|
|
216
245
|
if device is not _undef:
|
|
217
246
|
_check_device(device)
|
|
218
247
|
else:
|
|
219
248
|
device = None
|
|
220
|
-
|
|
221
|
-
|
|
249
|
+
if hasattr(x, "__dlpack_device__"):
|
|
250
|
+
from ._devices import _device_from_dlpack_device
|
|
222
251
|
|
|
223
|
-
|
|
252
|
+
dl_type, dl_id = x.__dlpack_device__()
|
|
253
|
+
device = _device_from_dlpack_device(dl_type, dl_id)
|
|
254
|
+
if copy in [_undef, None]:
|
|
255
|
+
# numpy 1.26 does not have the copy= arg
|
|
256
|
+
return Array._new(np.from_dlpack(x), device=device)
|
|
257
|
+
|
|
258
|
+
return Array._new(np.from_dlpack(x, copy=copy), device=device)
|
|
224
259
|
|
|
225
260
|
|
|
226
261
|
def full(
|
|
@@ -237,11 +272,22 @@ def full(
|
|
|
237
272
|
"""
|
|
238
273
|
from ._array_object import Array
|
|
239
274
|
|
|
240
|
-
_check_valid_dtype(dtype)
|
|
241
275
|
_check_device(device)
|
|
276
|
+
_check_valid_dtype(dtype, device)
|
|
277
|
+
|
|
278
|
+
if not isinstance(fill_value, bool | int | float | complex):
|
|
279
|
+
msg = f"Expected Python scalar fill_value, got type {type(fill_value)}"
|
|
280
|
+
raise TypeError(msg)
|
|
281
|
+
|
|
282
|
+
if dtype is None:
|
|
283
|
+
if type(fill_value) == bool:
|
|
284
|
+
dtype = xp_bool
|
|
285
|
+
else:
|
|
286
|
+
kind = {
|
|
287
|
+
int: "integral", float: "real floating", complex: "complex floating"
|
|
288
|
+
}[type(fill_value)]
|
|
289
|
+
dtype = get_default_dtypes(device)[kind]
|
|
242
290
|
|
|
243
|
-
if isinstance(fill_value, Array) and fill_value.ndim == 0:
|
|
244
|
-
fill_value = fill_value._array
|
|
245
291
|
res = np.full(shape, fill_value, dtype=_np_dtype(dtype))
|
|
246
292
|
if DType(res.dtype) not in _all_dtypes:
|
|
247
293
|
# This will happen if the fill value is not something that NumPy
|
|
@@ -265,10 +311,16 @@ def full_like(
|
|
|
265
311
|
"""
|
|
266
312
|
from ._array_object import Array
|
|
267
313
|
|
|
268
|
-
_check_valid_dtype(dtype)
|
|
269
314
|
_check_device(device)
|
|
270
315
|
if device is None:
|
|
271
316
|
device = x.device
|
|
317
|
+
if dtype is None:
|
|
318
|
+
dtype = x.dtype
|
|
319
|
+
_check_valid_dtype(dtype, device)
|
|
320
|
+
|
|
321
|
+
if not isinstance(fill_value, bool | int | float | complex):
|
|
322
|
+
msg = f"Expected Python scalar fill_value, got type {type(fill_value)}"
|
|
323
|
+
raise TypeError(msg)
|
|
272
324
|
|
|
273
325
|
res = np.full_like(x._array, fill_value, dtype=_np_dtype(dtype))
|
|
274
326
|
if DType(res.dtype) not in _all_dtypes:
|
|
@@ -295,8 +347,13 @@ def linspace(
|
|
|
295
347
|
"""
|
|
296
348
|
from ._array_object import Array
|
|
297
349
|
|
|
298
|
-
_check_valid_dtype(dtype)
|
|
299
350
|
_check_device(device)
|
|
351
|
+
_check_valid_dtype(dtype, device)
|
|
352
|
+
if dtype is None:
|
|
353
|
+
if isinstance(start, complex) or isinstance(stop, complex):
|
|
354
|
+
dtype = get_default_dtypes(device)["complex floating"]
|
|
355
|
+
else:
|
|
356
|
+
dtype = get_default_dtypes(device)["real floating"]
|
|
300
357
|
|
|
301
358
|
return Array._new(
|
|
302
359
|
np.linspace(start, stop, num, dtype=_np_dtype(dtype), endpoint=endpoint),
|
|
@@ -304,7 +361,7 @@ def linspace(
|
|
|
304
361
|
)
|
|
305
362
|
|
|
306
363
|
|
|
307
|
-
def meshgrid(*arrays: Array, indexing: Literal["xy", "ij"] = "xy") ->
|
|
364
|
+
def meshgrid(*arrays: Array, indexing: Literal["xy", "ij"] = "xy") -> tuple[Array, ...]:
|
|
308
365
|
"""
|
|
309
366
|
Array API compatible wrapper for :py:func:`np.meshgrid <numpy.meshgrid>`.
|
|
310
367
|
|
|
@@ -327,10 +384,12 @@ def meshgrid(*arrays: Array, indexing: Literal["xy", "ij"] = "xy") -> list[Array
|
|
|
327
384
|
else:
|
|
328
385
|
device = None
|
|
329
386
|
|
|
330
|
-
|
|
387
|
+
typ = list if get_array_api_strict_flags()['api_version'] < '2025.12' else tuple
|
|
388
|
+
|
|
389
|
+
return typ(
|
|
331
390
|
Array._new(array, device=device)
|
|
332
391
|
for array in np.meshgrid(*[a._array for a in arrays], indexing=indexing)
|
|
333
|
-
|
|
392
|
+
)
|
|
334
393
|
|
|
335
394
|
|
|
336
395
|
def ones(
|
|
@@ -346,8 +405,10 @@ def ones(
|
|
|
346
405
|
"""
|
|
347
406
|
from ._array_object import Array
|
|
348
407
|
|
|
349
|
-
_check_valid_dtype(dtype)
|
|
350
408
|
_check_device(device)
|
|
409
|
+
_check_valid_dtype(dtype, device)
|
|
410
|
+
if dtype is None:
|
|
411
|
+
dtype = get_default_dtypes(device)["real floating"]
|
|
351
412
|
|
|
352
413
|
return Array._new(np.ones(shape, dtype=_np_dtype(dtype)), device=device)
|
|
353
414
|
|
|
@@ -362,10 +423,12 @@ def ones_like(
|
|
|
362
423
|
"""
|
|
363
424
|
from ._array_object import Array
|
|
364
425
|
|
|
365
|
-
_check_valid_dtype(dtype)
|
|
366
426
|
_check_device(device)
|
|
367
427
|
if device is None:
|
|
368
428
|
device = x.device
|
|
429
|
+
if dtype is None:
|
|
430
|
+
dtype = x.dtype
|
|
431
|
+
_check_valid_dtype(dtype, device)
|
|
369
432
|
|
|
370
433
|
return Array._new(np.ones_like(x._array, dtype=_np_dtype(dtype)), device=device)
|
|
371
434
|
|
|
@@ -411,8 +474,10 @@ def zeros(
|
|
|
411
474
|
"""
|
|
412
475
|
from ._array_object import Array
|
|
413
476
|
|
|
414
|
-
_check_valid_dtype(dtype)
|
|
415
477
|
_check_device(device)
|
|
478
|
+
_check_valid_dtype(dtype, device)
|
|
479
|
+
if dtype is None:
|
|
480
|
+
dtype = get_default_dtypes(device)["real floating"]
|
|
416
481
|
|
|
417
482
|
return Array._new(np.zeros(shape, dtype=_np_dtype(dtype)), device=device)
|
|
418
483
|
|
|
@@ -427,9 +492,11 @@ def zeros_like(
|
|
|
427
492
|
"""
|
|
428
493
|
from ._array_object import Array
|
|
429
494
|
|
|
430
|
-
_check_valid_dtype(dtype)
|
|
431
495
|
_check_device(device)
|
|
432
496
|
if device is None:
|
|
433
497
|
device = x.device
|
|
498
|
+
if dtype is None:
|
|
499
|
+
dtype = x.dtype
|
|
500
|
+
_check_valid_dtype(dtype, device)
|
|
434
501
|
|
|
435
502
|
return Array._new(np.zeros_like(x._array, dtype=_np_dtype(dtype)), device=device)
|
|
@@ -16,7 +16,7 @@ from ._dtypes import (
|
|
|
16
16
|
_signed_integer_dtypes,
|
|
17
17
|
_unsigned_integer_dtypes,
|
|
18
18
|
)
|
|
19
|
-
from ._flags import get_array_api_strict_flags
|
|
19
|
+
from ._flags import get_array_api_strict_flags, requires_api_version
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
# Note: astype is a function, not an array method as in NumPy.
|
|
@@ -49,7 +49,7 @@ def astype(
|
|
|
49
49
|
return Array._new(x._array.astype(dtype=dtype._np_dtype, copy=copy), device=device)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
def broadcast_arrays(*arrays: Array) ->
|
|
52
|
+
def broadcast_arrays(*arrays: Array) -> tuple[Array, ...]:
|
|
53
53
|
"""
|
|
54
54
|
Array API compatible wrapper for :py:func:`np.broadcast_arrays <numpy.broadcast_arrays>`.
|
|
55
55
|
|
|
@@ -57,9 +57,21 @@ def broadcast_arrays(*arrays: Array) -> list[Array]:
|
|
|
57
57
|
"""
|
|
58
58
|
from ._array_object import Array
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
typ = list if get_array_api_strict_flags()['api_version'] < '2025.12' else tuple
|
|
61
|
+
|
|
62
|
+
return typ(
|
|
61
63
|
Array._new(array, device=arrays[0].device) for array in np.broadcast_arrays(*[a._array for a in arrays])
|
|
62
|
-
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@requires_api_version("2025.12")
|
|
68
|
+
def broadcast_shapes(*shapes: tuple[int, ...]) -> tuple[int, ...]:
|
|
69
|
+
"""
|
|
70
|
+
Array API compatible wrapper for :py:func:`np.broadcast_shapes <numpy.broadcast_shapes>`.
|
|
71
|
+
|
|
72
|
+
See its docstring for more information.
|
|
73
|
+
"""
|
|
74
|
+
return np.broadcast_shapes(*shapes)
|
|
63
75
|
|
|
64
76
|
|
|
65
77
|
def broadcast_to(x: Array, /, shape: tuple[int, ...]) -> Array:
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from typing import Final
|
|
2
|
+
from enum import IntEnum
|
|
3
|
+
|
|
4
|
+
from ._dtypes import (
|
|
5
|
+
DType, float32, float64, complex64, complex128, int64,
|
|
6
|
+
_all_dtypes, _boolean_dtypes, _signed_integer_dtypes,
|
|
7
|
+
_unsigned_integer_dtypes, _integer_dtypes, _real_floating_dtypes,
|
|
8
|
+
_complex_floating_dtypes, _numeric_dtypes
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
_ALL_DEVICE_NAMES = ("CPU_DEVICE", "device1", "device2", "no_float64")
|
|
12
|
+
|
|
13
|
+
class Device:
|
|
14
|
+
_device: Final[str]
|
|
15
|
+
__slots__ = ("_device", "__weakref__")
|
|
16
|
+
|
|
17
|
+
def __init__(self, device: str = "CPU_DEVICE"):
|
|
18
|
+
if device not in _ALL_DEVICE_NAMES:
|
|
19
|
+
raise ValueError(f"The device '{device}' is not a valid choice.")
|
|
20
|
+
self._device = device
|
|
21
|
+
|
|
22
|
+
def __repr__(self) -> str:
|
|
23
|
+
return f"array_api_strict.Device('{self._device}')"
|
|
24
|
+
|
|
25
|
+
def __eq__(self, other: object) -> bool:
|
|
26
|
+
if not isinstance(other, Device):
|
|
27
|
+
return False
|
|
28
|
+
return self._device == other._device
|
|
29
|
+
|
|
30
|
+
def __hash__(self) -> int:
|
|
31
|
+
return hash(("Device", self._device))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
CPU_DEVICE = Device()
|
|
35
|
+
NO_FLOAT64_DEVICE = Device("no_float64")
|
|
36
|
+
|
|
37
|
+
ALL_DEVICES = (CPU_DEVICE, Device("device1"), Device("device2"), NO_FLOAT64_DEVICE)
|
|
38
|
+
|
|
39
|
+
class DLDeviceType(IntEnum):
|
|
40
|
+
kDLCPU = 1
|
|
41
|
+
kDLCUDA = 2
|
|
42
|
+
kDLMETAL = 8
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
_DLPACK_DEVICE_FOR: Final[dict[Device, tuple[DLDeviceType, int]]] = {
|
|
46
|
+
CPU_DEVICE: (DLDeviceType.kDLCPU, 0),
|
|
47
|
+
Device("device1"): (DLDeviceType.kDLCUDA, 0),
|
|
48
|
+
Device("device2"): (DLDeviceType.kDLCUDA, 1),
|
|
49
|
+
NO_FLOAT64_DEVICE: (DLDeviceType.kDLMETAL, 0),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_DLPACK_DEVICE_TO_LOGICAL: Final[dict[tuple[int, int], Device]] = {
|
|
53
|
+
(int(device_type), device_id): logical_device
|
|
54
|
+
for logical_device, (device_type, device_id) in _DLPACK_DEVICE_FOR.items()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _normalize_dl_device(device_type: IntEnum | int, device_id: int) -> tuple[int, int]:
|
|
59
|
+
return (int(device_type), device_id)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _device_from_dlpack_device(
|
|
63
|
+
device_type: IntEnum | int, device_id: int
|
|
64
|
+
) -> Device:
|
|
65
|
+
# NB: if the (device_type, device_id) pair not known, raise
|
|
66
|
+
return _DLPACK_DEVICE_TO_LOGICAL[_normalize_dl_device(device_type, device_id)]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def check_device(device: Device | None) -> None:
|
|
70
|
+
if device is not None and not isinstance(device, Device):
|
|
71
|
+
raise ValueError(f"Unsupported device {device!r}")
|
|
72
|
+
|
|
73
|
+
if device is not None and device not in ALL_DEVICES:
|
|
74
|
+
raise ValueError(f"Unsupported device {device!r}")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Helpers for device-specific dtype support
|
|
78
|
+
|
|
79
|
+
def get_default_dtypes(device: Device | None = None) -> dict[str, DType]:
|
|
80
|
+
if device == NO_FLOAT64_DEVICE:
|
|
81
|
+
# mimic an MPS device which does not have float64 at all
|
|
82
|
+
return {
|
|
83
|
+
"real floating": float32,
|
|
84
|
+
"complex floating": complex64,
|
|
85
|
+
"integral": int64,
|
|
86
|
+
"indexing": int64,
|
|
87
|
+
}
|
|
88
|
+
elif device == Device('device2'):
|
|
89
|
+
# mimic a torch CPU device: support float64 but default to float32
|
|
90
|
+
return {
|
|
91
|
+
"real floating": float32,
|
|
92
|
+
"complex floating": complex64,
|
|
93
|
+
"integral": int64,
|
|
94
|
+
"indexing": int64,
|
|
95
|
+
}
|
|
96
|
+
else:
|
|
97
|
+
return {
|
|
98
|
+
"real floating": float64,
|
|
99
|
+
"complex floating": complex128,
|
|
100
|
+
"integral": int64,
|
|
101
|
+
"indexing": int64,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def device_supports_dtype(device: Device | None, dtype: DType |None) -> bool:
|
|
106
|
+
"""True if `device` supports `dtype`, False otherwise."""
|
|
107
|
+
# Device("no_float64") supports all dtypes except float64 and complex128
|
|
108
|
+
if device == NO_FLOAT64_DEVICE:
|
|
109
|
+
return dtype not in (float64, complex128)
|
|
110
|
+
|
|
111
|
+
# All other devices support all dtypes
|
|
112
|
+
return True
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _map_supported(dtypes: list[DType], device: Device) -> dict[str, DType]:
|
|
116
|
+
return {
|
|
117
|
+
dt._canonical_name: dt
|
|
118
|
+
for dt in dtypes
|
|
119
|
+
if device_supports_dtype(device, dt)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# _info.dtypes() maps "kind" -> dict of {name: dtype}
|
|
124
|
+
# Note that "kinds" differ from "categories" above, per the spec.
|
|
125
|
+
|
|
126
|
+
_kind_to_dtypes = {
|
|
127
|
+
None: _all_dtypes,
|
|
128
|
+
"bool": _boolean_dtypes,
|
|
129
|
+
"signed integer": _signed_integer_dtypes,
|
|
130
|
+
"unsigned integer": _unsigned_integer_dtypes,
|
|
131
|
+
"integral": _integer_dtypes,
|
|
132
|
+
"real floating": _real_floating_dtypes,
|
|
133
|
+
"complex floating": _complex_floating_dtypes,
|
|
134
|
+
"numeric": _numeric_dtypes
|
|
135
|
+
}
|
|
136
|
+
|
array_api_strict/_dtypes.py
CHANGED
|
@@ -11,9 +11,11 @@ import numpy.typing as npt
|
|
|
11
11
|
|
|
12
12
|
class DType:
|
|
13
13
|
_np_dtype: Final[np.dtype[Any]]
|
|
14
|
-
|
|
14
|
+
_canonical_name: Final[Any]
|
|
15
|
+
__slots__ = ("_np_dtype", "_canonical_name", "__weakref__")
|
|
15
16
|
|
|
16
17
|
def __init__(self, np_dtype: npt.DTypeLike):
|
|
18
|
+
self._canonical_name = np_dtype
|
|
17
19
|
self._np_dtype = np.dtype(np_dtype)
|
|
18
20
|
|
|
19
21
|
def __repr__(self) -> str:
|