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.
@@ -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, Final, Literal, SupportsIndex
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
- obj._array = x
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
- else:
631
- kwargs = {'stream': stream}
632
- if max_version is not _undef:
633
- kwargs['max_version'] = max_version
634
- if dl_device is not _undef:
635
- kwargs['dl_device'] = dl_device
636
- if copy is not _undef:
637
- kwargs['copy'] = copy
638
- return self._array.__dlpack__(**kwargs)
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
- # Note: device support is required for this
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, Device
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 in (None,) + _all_dtypes:
30
- raise ValueError(f"dtype must be one of the supported dtypes, got {dtype!r}")
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
- _check_valid_dtype(dtype)
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
- _check_device(device)
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
- if copy not in [_undef, None]:
221
- raise NotImplementedError("The copy argument to from_dlpack is not yet implemented")
249
+ if hasattr(x, "__dlpack_device__"):
250
+ from ._devices import _device_from_dlpack_device
222
251
 
223
- return Array._new(np.from_dlpack(x), device=device)
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") -> list[Array]:
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
- return [
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) -> list[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
- return [
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
+
@@ -11,9 +11,11 @@ import numpy.typing as npt
11
11
 
12
12
  class DType:
13
13
  _np_dtype: Final[np.dtype[Any]]
14
- __slots__ = ("_np_dtype", "__weakref__")
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: