array-api-compat 1.10.0__py3-none-any.whl → 1.11__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_compat/__init__.py +1 -1
- array_api_compat/common/_aliases.py +31 -6
- array_api_compat/common/_fft.py +27 -5
- array_api_compat/common/_helpers.py +131 -43
- array_api_compat/common/_typing.py +3 -0
- array_api_compat/cupy/__init__.py +1 -1
- array_api_compat/cupy/_aliases.py +19 -4
- array_api_compat/cupy/_info.py +1 -1
- array_api_compat/dask/array/__init__.py +1 -1
- array_api_compat/dask/array/_aliases.py +175 -46
- array_api_compat/dask/array/_info.py +1 -1
- array_api_compat/numpy/__init__.py +1 -1
- array_api_compat/numpy/_aliases.py +15 -3
- array_api_compat/numpy/_info.py +1 -1
- array_api_compat/torch/__init__.py +1 -1
- array_api_compat/torch/_aliases.py +63 -6
- array_api_compat/torch/_info.py +1 -1
- {array_api_compat-1.10.0.dist-info → array_api_compat-1.11.dist-info}/METADATA +11 -2
- array_api_compat-1.11.dist-info/RECORD +36 -0
- {array_api_compat-1.10.0.dist-info → array_api_compat-1.11.dist-info}/WHEEL +1 -1
- array_api_compat-1.10.0.dist-info/RECORD +0 -36
- {array_api_compat-1.10.0.dist-info → array_api_compat-1.11.dist-info}/LICENSE +0 -0
- {array_api_compat-1.10.0.dist-info → array_api_compat-1.11.dist-info}/top_level.txt +0 -0
array_api_compat/__init__.py
CHANGED
|
@@ -233,11 +233,6 @@ def unique_values(x: ndarray, /, xp) -> ndarray:
|
|
|
233
233
|
**kwargs,
|
|
234
234
|
)
|
|
235
235
|
|
|
236
|
-
def astype(x: ndarray, dtype: Dtype, /, *, copy: bool = True) -> ndarray:
|
|
237
|
-
if not copy and dtype == x.dtype:
|
|
238
|
-
return x
|
|
239
|
-
return x.astype(dtype=dtype, copy=copy)
|
|
240
|
-
|
|
241
236
|
# These functions have different keyword argument names
|
|
242
237
|
|
|
243
238
|
def std(
|
|
@@ -297,6 +292,36 @@ def cumulative_sum(
|
|
|
297
292
|
)
|
|
298
293
|
return res
|
|
299
294
|
|
|
295
|
+
|
|
296
|
+
def cumulative_prod(
|
|
297
|
+
x: ndarray,
|
|
298
|
+
/,
|
|
299
|
+
xp,
|
|
300
|
+
*,
|
|
301
|
+
axis: Optional[int] = None,
|
|
302
|
+
dtype: Optional[Dtype] = None,
|
|
303
|
+
include_initial: bool = False,
|
|
304
|
+
**kwargs
|
|
305
|
+
) -> ndarray:
|
|
306
|
+
wrapped_xp = array_namespace(x)
|
|
307
|
+
|
|
308
|
+
if axis is None:
|
|
309
|
+
if x.ndim > 1:
|
|
310
|
+
raise ValueError("axis must be specified in cumulative_prod for more than one dimension")
|
|
311
|
+
axis = 0
|
|
312
|
+
|
|
313
|
+
res = xp.cumprod(x, axis=axis, dtype=dtype, **kwargs)
|
|
314
|
+
|
|
315
|
+
# np.cumprod does not support include_initial
|
|
316
|
+
if include_initial:
|
|
317
|
+
initial_shape = list(x.shape)
|
|
318
|
+
initial_shape[axis] = 1
|
|
319
|
+
res = xp.concatenate(
|
|
320
|
+
[wrapped_xp.ones(shape=initial_shape, dtype=res.dtype, device=device(res)), res],
|
|
321
|
+
axis=axis,
|
|
322
|
+
)
|
|
323
|
+
return res
|
|
324
|
+
|
|
300
325
|
# The min and max argument names in clip are different and not optional in numpy, and type
|
|
301
326
|
# promotion behavior is different.
|
|
302
327
|
def clip(
|
|
@@ -549,7 +574,7 @@ __all__ = ['arange', 'empty', 'empty_like', 'eye', 'full', 'full_like',
|
|
|
549
574
|
'linspace', 'ones', 'ones_like', 'zeros', 'zeros_like',
|
|
550
575
|
'UniqueAllResult', 'UniqueCountsResult', 'UniqueInverseResult',
|
|
551
576
|
'unique_all', 'unique_counts', 'unique_inverse', 'unique_values',
|
|
552
|
-
'
|
|
577
|
+
'std', 'var', 'cumulative_sum', 'cumulative_prod','clip', 'permute_dims',
|
|
553
578
|
'reshape', 'argsort', 'sort', 'nonzero', 'ceil', 'floor', 'trunc',
|
|
554
579
|
'matmul', 'matrix_transpose', 'tensordot', 'vecdot', 'isdtype',
|
|
555
580
|
'unstack', 'sign']
|
array_api_compat/common/_fft.py
CHANGED
|
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
from typing import TYPE_CHECKING, Union, Optional, Literal
|
|
4
4
|
|
|
5
5
|
if TYPE_CHECKING:
|
|
6
|
-
from ._typing import Device, ndarray
|
|
6
|
+
from ._typing import Device, ndarray, DType
|
|
7
7
|
from collections.abc import Sequence
|
|
8
8
|
|
|
9
9
|
# Note: NumPy fft functions improperly upcast float32 and complex64 to
|
|
@@ -149,15 +149,37 @@ def ihfft(
|
|
|
149
149
|
return res.astype(xp.complex64)
|
|
150
150
|
return res
|
|
151
151
|
|
|
152
|
-
def fftfreq(
|
|
152
|
+
def fftfreq(
|
|
153
|
+
n: int,
|
|
154
|
+
/,
|
|
155
|
+
xp,
|
|
156
|
+
*,
|
|
157
|
+
d: float = 1.0,
|
|
158
|
+
dtype: Optional[DType] = None,
|
|
159
|
+
device: Optional[Device] = None
|
|
160
|
+
) -> ndarray:
|
|
153
161
|
if device not in ["cpu", None]:
|
|
154
162
|
raise ValueError(f"Unsupported device {device!r}")
|
|
155
|
-
|
|
163
|
+
res = xp.fft.fftfreq(n, d=d)
|
|
164
|
+
if dtype is not None:
|
|
165
|
+
return res.astype(dtype)
|
|
166
|
+
return res
|
|
156
167
|
|
|
157
|
-
def rfftfreq(
|
|
168
|
+
def rfftfreq(
|
|
169
|
+
n: int,
|
|
170
|
+
/,
|
|
171
|
+
xp,
|
|
172
|
+
*,
|
|
173
|
+
d: float = 1.0,
|
|
174
|
+
dtype: Optional[DType] = None,
|
|
175
|
+
device: Optional[Device] = None
|
|
176
|
+
) -> ndarray:
|
|
158
177
|
if device not in ["cpu", None]:
|
|
159
178
|
raise ValueError(f"Unsupported device {device!r}")
|
|
160
|
-
|
|
179
|
+
res = xp.fft.rfftfreq(n, d=d)
|
|
180
|
+
if dtype is not None:
|
|
181
|
+
return res.astype(dtype)
|
|
182
|
+
return res
|
|
161
183
|
|
|
162
184
|
def fftshift(x: ndarray, /, xp, *, axes: Union[int, Sequence[int]] = None) -> ndarray:
|
|
163
185
|
return xp.fft.fftshift(x, axes=axes)
|
|
@@ -11,14 +11,14 @@ from typing import TYPE_CHECKING
|
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from typing import Optional, Union, Any
|
|
14
|
-
from ._typing import Array, Device
|
|
14
|
+
from ._typing import Array, Device, Namespace
|
|
15
15
|
|
|
16
16
|
import sys
|
|
17
17
|
import math
|
|
18
18
|
import inspect
|
|
19
19
|
import warnings
|
|
20
20
|
|
|
21
|
-
def _is_jax_zero_gradient_array(x):
|
|
21
|
+
def _is_jax_zero_gradient_array(x: object) -> bool:
|
|
22
22
|
"""Return True if `x` is a zero-gradient array.
|
|
23
23
|
|
|
24
24
|
These arrays are a design quirk of Jax that may one day be removed.
|
|
@@ -32,7 +32,8 @@ def _is_jax_zero_gradient_array(x):
|
|
|
32
32
|
|
|
33
33
|
return isinstance(x, np.ndarray) and x.dtype == jax.float0
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
|
|
36
|
+
def is_numpy_array(x: object) -> bool:
|
|
36
37
|
"""
|
|
37
38
|
Return True if `x` is a NumPy array.
|
|
38
39
|
|
|
@@ -63,7 +64,8 @@ def is_numpy_array(x):
|
|
|
63
64
|
return (isinstance(x, (np.ndarray, np.generic))
|
|
64
65
|
and not _is_jax_zero_gradient_array(x))
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
|
|
68
|
+
def is_cupy_array(x: object) -> bool:
|
|
67
69
|
"""
|
|
68
70
|
Return True if `x` is a CuPy array.
|
|
69
71
|
|
|
@@ -93,7 +95,8 @@ def is_cupy_array(x):
|
|
|
93
95
|
# TODO: Should we reject ndarray subclasses?
|
|
94
96
|
return isinstance(x, cp.ndarray)
|
|
95
97
|
|
|
96
|
-
|
|
98
|
+
|
|
99
|
+
def is_torch_array(x: object) -> bool:
|
|
97
100
|
"""
|
|
98
101
|
Return True if `x` is a PyTorch tensor.
|
|
99
102
|
|
|
@@ -120,7 +123,8 @@ def is_torch_array(x):
|
|
|
120
123
|
# TODO: Should we reject ndarray subclasses?
|
|
121
124
|
return isinstance(x, torch.Tensor)
|
|
122
125
|
|
|
123
|
-
|
|
126
|
+
|
|
127
|
+
def is_ndonnx_array(x: object) -> bool:
|
|
124
128
|
"""
|
|
125
129
|
Return True if `x` is a ndonnx Array.
|
|
126
130
|
|
|
@@ -147,7 +151,8 @@ def is_ndonnx_array(x):
|
|
|
147
151
|
|
|
148
152
|
return isinstance(x, ndx.Array)
|
|
149
153
|
|
|
150
|
-
|
|
154
|
+
|
|
155
|
+
def is_dask_array(x: object) -> bool:
|
|
151
156
|
"""
|
|
152
157
|
Return True if `x` is a dask.array Array.
|
|
153
158
|
|
|
@@ -174,7 +179,8 @@ def is_dask_array(x):
|
|
|
174
179
|
|
|
175
180
|
return isinstance(x, dask.array.Array)
|
|
176
181
|
|
|
177
|
-
|
|
182
|
+
|
|
183
|
+
def is_jax_array(x: object) -> bool:
|
|
178
184
|
"""
|
|
179
185
|
Return True if `x` is a JAX array.
|
|
180
186
|
|
|
@@ -202,6 +208,7 @@ def is_jax_array(x):
|
|
|
202
208
|
|
|
203
209
|
return isinstance(x, jax.Array) or _is_jax_zero_gradient_array(x)
|
|
204
210
|
|
|
211
|
+
|
|
205
212
|
def is_pydata_sparse_array(x) -> bool:
|
|
206
213
|
"""
|
|
207
214
|
Return True if `x` is an array from the `sparse` package.
|
|
@@ -231,7 +238,8 @@ def is_pydata_sparse_array(x) -> bool:
|
|
|
231
238
|
# TODO: Account for other backends.
|
|
232
239
|
return isinstance(x, sparse.SparseArray)
|
|
233
240
|
|
|
234
|
-
|
|
241
|
+
|
|
242
|
+
def is_array_api_obj(x: object) -> bool:
|
|
235
243
|
"""
|
|
236
244
|
Return True if `x` is an array API compatible array object.
|
|
237
245
|
|
|
@@ -254,10 +262,12 @@ def is_array_api_obj(x):
|
|
|
254
262
|
or is_pydata_sparse_array(x) \
|
|
255
263
|
or hasattr(x, '__array_namespace__')
|
|
256
264
|
|
|
257
|
-
|
|
265
|
+
|
|
266
|
+
def _compat_module_name() -> str:
|
|
258
267
|
assert __name__.endswith('.common._helpers')
|
|
259
268
|
return __name__.removesuffix('.common._helpers')
|
|
260
269
|
|
|
270
|
+
|
|
261
271
|
def is_numpy_namespace(xp) -> bool:
|
|
262
272
|
"""
|
|
263
273
|
Returns True if `xp` is a NumPy namespace.
|
|
@@ -278,6 +288,7 @@ def is_numpy_namespace(xp) -> bool:
|
|
|
278
288
|
"""
|
|
279
289
|
return xp.__name__ in {'numpy', _compat_module_name() + '.numpy'}
|
|
280
290
|
|
|
291
|
+
|
|
281
292
|
def is_cupy_namespace(xp) -> bool:
|
|
282
293
|
"""
|
|
283
294
|
Returns True if `xp` is a CuPy namespace.
|
|
@@ -298,6 +309,7 @@ def is_cupy_namespace(xp) -> bool:
|
|
|
298
309
|
"""
|
|
299
310
|
return xp.__name__ in {'cupy', _compat_module_name() + '.cupy'}
|
|
300
311
|
|
|
312
|
+
|
|
301
313
|
def is_torch_namespace(xp) -> bool:
|
|
302
314
|
"""
|
|
303
315
|
Returns True if `xp` is a PyTorch namespace.
|
|
@@ -319,7 +331,7 @@ def is_torch_namespace(xp) -> bool:
|
|
|
319
331
|
return xp.__name__ in {'torch', _compat_module_name() + '.torch'}
|
|
320
332
|
|
|
321
333
|
|
|
322
|
-
def is_ndonnx_namespace(xp):
|
|
334
|
+
def is_ndonnx_namespace(xp) -> bool:
|
|
323
335
|
"""
|
|
324
336
|
Returns True if `xp` is an NDONNX namespace.
|
|
325
337
|
|
|
@@ -337,7 +349,8 @@ def is_ndonnx_namespace(xp):
|
|
|
337
349
|
"""
|
|
338
350
|
return xp.__name__ == 'ndonnx'
|
|
339
351
|
|
|
340
|
-
|
|
352
|
+
|
|
353
|
+
def is_dask_namespace(xp) -> bool:
|
|
341
354
|
"""
|
|
342
355
|
Returns True if `xp` is a Dask namespace.
|
|
343
356
|
|
|
@@ -357,7 +370,8 @@ def is_dask_namespace(xp):
|
|
|
357
370
|
"""
|
|
358
371
|
return xp.__name__ in {'dask.array', _compat_module_name() + '.dask.array'}
|
|
359
372
|
|
|
360
|
-
|
|
373
|
+
|
|
374
|
+
def is_jax_namespace(xp) -> bool:
|
|
361
375
|
"""
|
|
362
376
|
Returns True if `xp` is a JAX namespace.
|
|
363
377
|
|
|
@@ -378,7 +392,8 @@ def is_jax_namespace(xp):
|
|
|
378
392
|
"""
|
|
379
393
|
return xp.__name__ in {'jax.numpy', 'jax.experimental.array_api'}
|
|
380
394
|
|
|
381
|
-
|
|
395
|
+
|
|
396
|
+
def is_pydata_sparse_namespace(xp) -> bool:
|
|
382
397
|
"""
|
|
383
398
|
Returns True if `xp` is a pydata/sparse namespace.
|
|
384
399
|
|
|
@@ -396,7 +411,8 @@ def is_pydata_sparse_namespace(xp):
|
|
|
396
411
|
"""
|
|
397
412
|
return xp.__name__ == 'sparse'
|
|
398
413
|
|
|
399
|
-
|
|
414
|
+
|
|
415
|
+
def is_array_api_strict_namespace(xp) -> bool:
|
|
400
416
|
"""
|
|
401
417
|
Returns True if `xp` is an array-api-strict namespace.
|
|
402
418
|
|
|
@@ -414,14 +430,16 @@ def is_array_api_strict_namespace(xp):
|
|
|
414
430
|
"""
|
|
415
431
|
return xp.__name__ == 'array_api_strict'
|
|
416
432
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
433
|
+
|
|
434
|
+
def _check_api_version(api_version: str) -> None:
|
|
435
|
+
if api_version in ['2021.12', '2022.12', '2023.12']:
|
|
436
|
+
warnings.warn(f"The {api_version} version of the array API specification was requested but the returned namespace is actually version 2024.12")
|
|
420
437
|
elif api_version is not None and api_version not in ['2021.12', '2022.12',
|
|
421
|
-
'2023.12']:
|
|
422
|
-
raise ValueError("Only the
|
|
438
|
+
'2023.12', '2024.12']:
|
|
439
|
+
raise ValueError("Only the 2024.12 version of the array API specification is currently supported")
|
|
423
440
|
|
|
424
|
-
|
|
441
|
+
|
|
442
|
+
def array_namespace(*xs, api_version=None, use_compat=None) -> Namespace:
|
|
425
443
|
"""
|
|
426
444
|
Get the array API compatible namespace for the arrays `xs`.
|
|
427
445
|
|
|
@@ -433,7 +451,7 @@ def array_namespace(*xs, api_version=None, use_compat=None):
|
|
|
433
451
|
|
|
434
452
|
api_version: str
|
|
435
453
|
The newest version of the spec that you need support for (currently
|
|
436
|
-
the compat library wrapped APIs support
|
|
454
|
+
the compat library wrapped APIs support v2024.12).
|
|
437
455
|
|
|
438
456
|
use_compat: bool or None
|
|
439
457
|
If None (the default), the native namespace will be returned if it is
|
|
@@ -630,24 +648,24 @@ def device(x: Array, /) -> Device:
|
|
|
630
648
|
if is_numpy_array(x):
|
|
631
649
|
return "cpu"
|
|
632
650
|
elif is_dask_array(x):
|
|
633
|
-
# Peek at the metadata of the
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
# Must be on CPU since backed by numpy
|
|
638
|
-
return "cpu"
|
|
639
|
-
except ImportError:
|
|
640
|
-
pass
|
|
651
|
+
# Peek at the metadata of the Dask array to determine type
|
|
652
|
+
if is_numpy_array(x._meta):
|
|
653
|
+
# Must be on CPU since backed by numpy
|
|
654
|
+
return "cpu"
|
|
641
655
|
return _DASK_DEVICE
|
|
642
656
|
elif is_jax_array(x):
|
|
643
|
-
#
|
|
644
|
-
#
|
|
645
|
-
#
|
|
646
|
-
#
|
|
647
|
-
|
|
648
|
-
|
|
657
|
+
# FIXME Jitted JAX arrays do not have a device attribute
|
|
658
|
+
# https://github.com/jax-ml/jax/issues/26000
|
|
659
|
+
# Return None in this case. Note that this workaround breaks
|
|
660
|
+
# the standard and will result in new arrays being created on the
|
|
661
|
+
# default device instead of the same device as the input array(s).
|
|
662
|
+
x_device = getattr(x, 'device', None)
|
|
663
|
+
# Older JAX releases had .device() as a method, which has been replaced
|
|
664
|
+
# with a property in accordance with the standard.
|
|
665
|
+
if inspect.ismethod(x_device):
|
|
666
|
+
return x_device()
|
|
649
667
|
else:
|
|
650
|
-
return
|
|
668
|
+
return x_device
|
|
651
669
|
elif is_pydata_sparse_array(x):
|
|
652
670
|
# `sparse` will gain `.device`, so check for this first.
|
|
653
671
|
x_device = getattr(x, 'device', None)
|
|
@@ -778,8 +796,11 @@ def to_device(x: Array, device: Device, /, *, stream: Optional[Union[int, Any]]
|
|
|
778
796
|
raise ValueError(f"Unsupported device {device!r}")
|
|
779
797
|
elif is_jax_array(x):
|
|
780
798
|
if not hasattr(x, "__array_namespace__"):
|
|
781
|
-
# In JAX v0.4.31 and older, this import adds to_device method to x
|
|
799
|
+
# In JAX v0.4.31 and older, this import adds to_device method to x...
|
|
782
800
|
import jax.experimental.array_api # noqa: F401
|
|
801
|
+
# ... but only on eager JAX. It won't work inside jax.jit.
|
|
802
|
+
if not hasattr(x, "to_device"):
|
|
803
|
+
return x
|
|
783
804
|
return x.to_device(device, stream=stream)
|
|
784
805
|
elif is_pydata_sparse_array(x) and device == _device(x):
|
|
785
806
|
# Perform trivial check to return the same array if
|
|
@@ -788,24 +809,30 @@ def to_device(x: Array, device: Device, /, *, stream: Optional[Union[int, Any]]
|
|
|
788
809
|
return x.to_device(device, stream=stream)
|
|
789
810
|
|
|
790
811
|
|
|
791
|
-
def size(x):
|
|
812
|
+
def size(x: Array) -> int | None:
|
|
792
813
|
"""
|
|
793
814
|
Return the total number of elements of x.
|
|
794
815
|
|
|
795
816
|
This is equivalent to `x.size` according to the `standard
|
|
796
817
|
<https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.size.html>`__.
|
|
818
|
+
|
|
797
819
|
This helper is included because PyTorch defines `size` in an
|
|
798
820
|
:external+torch:meth:`incompatible way <torch.Tensor.size>`.
|
|
799
|
-
|
|
821
|
+
It also fixes dask.array's behaviour which returns nan for unknown sizes, whereas
|
|
822
|
+
the standard requires None.
|
|
800
823
|
"""
|
|
824
|
+
# Lazy API compliant arrays, such as ndonnx, can contain None in their shape
|
|
801
825
|
if None in x.shape:
|
|
802
826
|
return None
|
|
803
|
-
|
|
827
|
+
out = math.prod(x.shape)
|
|
828
|
+
# dask.array.Array.shape can contain NaN
|
|
829
|
+
return None if math.isnan(out) else out
|
|
804
830
|
|
|
805
831
|
|
|
806
|
-
def is_writeable_array(x) -> bool:
|
|
832
|
+
def is_writeable_array(x: object) -> bool:
|
|
807
833
|
"""
|
|
808
834
|
Return False if ``x.__setitem__`` is expected to raise; True otherwise.
|
|
835
|
+
Return False if `x` is not an array API compatible object.
|
|
809
836
|
|
|
810
837
|
Warning
|
|
811
838
|
-------
|
|
@@ -816,7 +843,67 @@ def is_writeable_array(x) -> bool:
|
|
|
816
843
|
return x.flags.writeable
|
|
817
844
|
if is_jax_array(x) or is_pydata_sparse_array(x):
|
|
818
845
|
return False
|
|
819
|
-
return
|
|
846
|
+
return is_array_api_obj(x)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def is_lazy_array(x: object) -> bool:
|
|
850
|
+
"""Return True if x is potentially a future or it may be otherwise impossible or
|
|
851
|
+
expensive to eagerly read its contents, regardless of their size, e.g. by
|
|
852
|
+
calling ``bool(x)`` or ``float(x)``.
|
|
853
|
+
|
|
854
|
+
Return False otherwise; e.g. ``bool(x)`` etc. is guaranteed to succeed and to be
|
|
855
|
+
cheap as long as the array has the right dtype and size.
|
|
856
|
+
|
|
857
|
+
Note
|
|
858
|
+
----
|
|
859
|
+
This function errs on the side of caution for array types that may or may not be
|
|
860
|
+
lazy, e.g. JAX arrays, by always returning True for them.
|
|
861
|
+
"""
|
|
862
|
+
if (
|
|
863
|
+
is_numpy_array(x)
|
|
864
|
+
or is_cupy_array(x)
|
|
865
|
+
or is_torch_array(x)
|
|
866
|
+
or is_pydata_sparse_array(x)
|
|
867
|
+
):
|
|
868
|
+
return False
|
|
869
|
+
|
|
870
|
+
# **JAX note:** while it is possible to determine if you're inside or outside
|
|
871
|
+
# jax.jit by testing the subclass of a jax.Array object, as well as testing bool()
|
|
872
|
+
# as we do below for unknown arrays, this is not recommended by JAX best practices.
|
|
873
|
+
|
|
874
|
+
# **Dask note:** Dask eagerly computes the graph on __bool__, __float__, and so on.
|
|
875
|
+
# This behaviour, while impossible to change without breaking backwards
|
|
876
|
+
# compatibility, is highly detrimental to performance as the whole graph will end
|
|
877
|
+
# up being computed multiple times.
|
|
878
|
+
|
|
879
|
+
if is_jax_array(x) or is_dask_array(x) or is_ndonnx_array(x):
|
|
880
|
+
return True
|
|
881
|
+
|
|
882
|
+
if not is_array_api_obj(x):
|
|
883
|
+
return False
|
|
884
|
+
|
|
885
|
+
# Unknown Array API compatible object. Note that this test may have dire consequences
|
|
886
|
+
# in terms of performance, e.g. for a lazy object that eagerly computes the graph
|
|
887
|
+
# on __bool__ (dask is one such example, which however is special-cased above).
|
|
888
|
+
|
|
889
|
+
# Select a single point of the array
|
|
890
|
+
s = size(x)
|
|
891
|
+
if s is None:
|
|
892
|
+
return True
|
|
893
|
+
xp = array_namespace(x)
|
|
894
|
+
if s > 1:
|
|
895
|
+
x = xp.reshape(x, (-1,))[0]
|
|
896
|
+
# Cast to dtype=bool and deal with size 0 arrays
|
|
897
|
+
x = xp.any(x)
|
|
898
|
+
|
|
899
|
+
try:
|
|
900
|
+
bool(x)
|
|
901
|
+
return False
|
|
902
|
+
# The Array API standard dictactes that __bool__ should raise TypeError if the
|
|
903
|
+
# output cannot be defined.
|
|
904
|
+
# Here we allow for it to raise arbitrary exceptions, e.g. like Dask does.
|
|
905
|
+
except Exception:
|
|
906
|
+
return True
|
|
820
907
|
|
|
821
908
|
|
|
822
909
|
__all__ = [
|
|
@@ -840,6 +927,7 @@ __all__ = [
|
|
|
840
927
|
"is_pydata_sparse_array",
|
|
841
928
|
"is_pydata_sparse_namespace",
|
|
842
929
|
"is_writeable_array",
|
|
930
|
+
"is_lazy_array",
|
|
843
931
|
"size",
|
|
844
932
|
"to_device",
|
|
845
933
|
]
|
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import cupy as cp
|
|
4
4
|
|
|
5
|
-
from ..common import _aliases
|
|
5
|
+
from ..common import _aliases, _helpers
|
|
6
6
|
from .._internal import get_xp
|
|
7
7
|
|
|
8
8
|
from ._info import __array_namespace_info__
|
|
@@ -46,10 +46,10 @@ unique_all = get_xp(cp)(_aliases.unique_all)
|
|
|
46
46
|
unique_counts = get_xp(cp)(_aliases.unique_counts)
|
|
47
47
|
unique_inverse = get_xp(cp)(_aliases.unique_inverse)
|
|
48
48
|
unique_values = get_xp(cp)(_aliases.unique_values)
|
|
49
|
-
astype = _aliases.astype
|
|
50
49
|
std = get_xp(cp)(_aliases.std)
|
|
51
50
|
var = get_xp(cp)(_aliases.var)
|
|
52
51
|
cumulative_sum = get_xp(cp)(_aliases.cumulative_sum)
|
|
52
|
+
cumulative_prod = get_xp(cp)(_aliases.cumulative_prod)
|
|
53
53
|
clip = get_xp(cp)(_aliases.clip)
|
|
54
54
|
permute_dims = get_xp(cp)(_aliases.permute_dims)
|
|
55
55
|
reshape = get_xp(cp)(_aliases.reshape)
|
|
@@ -110,6 +110,21 @@ def asarray(
|
|
|
110
110
|
|
|
111
111
|
return cp.array(obj, dtype=dtype, **kwargs)
|
|
112
112
|
|
|
113
|
+
|
|
114
|
+
def astype(
|
|
115
|
+
x: ndarray,
|
|
116
|
+
dtype: Dtype,
|
|
117
|
+
/,
|
|
118
|
+
*,
|
|
119
|
+
copy: bool = True,
|
|
120
|
+
device: Optional[Device] = None,
|
|
121
|
+
) -> ndarray:
|
|
122
|
+
if device is None:
|
|
123
|
+
return x.astype(dtype=dtype, copy=copy)
|
|
124
|
+
out = _helpers.to_device(x.astype(dtype=dtype, copy=False), device)
|
|
125
|
+
return out.copy() if copy and out is x else out
|
|
126
|
+
|
|
127
|
+
|
|
113
128
|
# These functions are completely new here. If the library already has them
|
|
114
129
|
# (i.e., numpy 2.0), use the library version instead of our wrapper.
|
|
115
130
|
if hasattr(cp, 'vecdot'):
|
|
@@ -127,10 +142,10 @@ if hasattr(cp, 'unstack'):
|
|
|
127
142
|
else:
|
|
128
143
|
unstack = get_xp(cp)(_aliases.unstack)
|
|
129
144
|
|
|
130
|
-
__all__ = _aliases.__all__ + ['__array_namespace_info__', 'asarray', '
|
|
145
|
+
__all__ = _aliases.__all__ + ['__array_namespace_info__', 'asarray', 'astype',
|
|
131
146
|
'acos', 'acosh', 'asin', 'asinh', 'atan',
|
|
132
147
|
'atan2', 'atanh', 'bitwise_left_shift',
|
|
133
148
|
'bitwise_invert', 'bitwise_right_shift',
|
|
134
|
-
'concat', 'pow', 'sign']
|
|
149
|
+
'bool', 'concat', 'pow', 'sign']
|
|
135
150
|
|
|
136
151
|
_all_ignore = ['cp', 'get_xp']
|
array_api_compat/cupy/_info.py
CHANGED
|
@@ -3,7 +3,7 @@ from dask.array import * # noqa: F403
|
|
|
3
3
|
# These imports may overwrite names from the import * above.
|
|
4
4
|
from ._aliases import * # noqa: F403
|
|
5
5
|
|
|
6
|
-
__array_api_version__ = '
|
|
6
|
+
__array_api_version__ = '2024.12'
|
|
7
7
|
|
|
8
8
|
__import__(__package__ + '.linalg')
|
|
9
9
|
__import__(__package__ + '.fft')
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from ...common import _aliases, array_namespace
|
|
5
6
|
|
|
6
7
|
from ..._internal import get_xp
|
|
7
8
|
|
|
@@ -30,36 +31,71 @@ from numpy import (
|
|
|
30
31
|
)
|
|
31
32
|
|
|
32
33
|
from typing import TYPE_CHECKING
|
|
34
|
+
|
|
33
35
|
if TYPE_CHECKING:
|
|
34
36
|
from typing import Optional, Union
|
|
35
37
|
|
|
36
|
-
from ...common._typing import
|
|
38
|
+
from ...common._typing import (
|
|
39
|
+
Device,
|
|
40
|
+
Dtype,
|
|
41
|
+
Array,
|
|
42
|
+
NestedSequence,
|
|
43
|
+
SupportsBufferProtocol,
|
|
44
|
+
)
|
|
37
45
|
|
|
38
46
|
import dask.array as da
|
|
39
47
|
|
|
40
48
|
isdtype = get_xp(np)(_aliases.isdtype)
|
|
41
49
|
unstack = get_xp(da)(_aliases.unstack)
|
|
42
|
-
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# da.astype doesn't respect copy=True
|
|
53
|
+
def astype(
|
|
54
|
+
x: Array,
|
|
55
|
+
dtype: Dtype,
|
|
56
|
+
/,
|
|
57
|
+
*,
|
|
58
|
+
copy: bool = True,
|
|
59
|
+
device: Optional[Device] = None,
|
|
60
|
+
) -> Array:
|
|
61
|
+
"""
|
|
62
|
+
Array API compatibility wrapper for astype().
|
|
63
|
+
|
|
64
|
+
See the corresponding documentation in the array library and/or the array API
|
|
65
|
+
specification for more details.
|
|
66
|
+
"""
|
|
67
|
+
# TODO: respect device keyword?
|
|
68
|
+
|
|
69
|
+
if not copy and dtype == x.dtype:
|
|
70
|
+
return x
|
|
71
|
+
x = x.astype(dtype)
|
|
72
|
+
return x.copy() if copy else x
|
|
73
|
+
|
|
43
74
|
|
|
44
75
|
# Common aliases
|
|
45
76
|
|
|
77
|
+
|
|
46
78
|
# This arange func is modified from the common one to
|
|
47
79
|
# not pass stop/step as keyword arguments, which will cause
|
|
48
80
|
# an error with dask
|
|
49
|
-
|
|
50
|
-
# TODO: delete the xp stuff, it shouldn't be necessary
|
|
51
|
-
def _dask_arange(
|
|
81
|
+
def arange(
|
|
52
82
|
start: Union[int, float],
|
|
53
83
|
/,
|
|
54
84
|
stop: Optional[Union[int, float]] = None,
|
|
55
85
|
step: Union[int, float] = 1,
|
|
56
86
|
*,
|
|
57
|
-
xp,
|
|
58
87
|
dtype: Optional[Dtype] = None,
|
|
59
88
|
device: Optional[Device] = None,
|
|
60
89
|
**kwargs,
|
|
61
90
|
) -> Array:
|
|
62
|
-
|
|
91
|
+
"""
|
|
92
|
+
Array API compatibility wrapper for arange().
|
|
93
|
+
|
|
94
|
+
See the corresponding documentation in the array library and/or the array API
|
|
95
|
+
specification for more details.
|
|
96
|
+
"""
|
|
97
|
+
# TODO: respect device keyword?
|
|
98
|
+
|
|
63
99
|
args = [start]
|
|
64
100
|
if stop is not None:
|
|
65
101
|
args.append(stop)
|
|
@@ -68,13 +104,12 @@ def _dask_arange(
|
|
|
68
104
|
# prepend the default value for start which is 0
|
|
69
105
|
args.insert(0, 0)
|
|
70
106
|
args.append(step)
|
|
71
|
-
return xp.arange(*args, dtype=dtype, **kwargs)
|
|
72
107
|
|
|
73
|
-
arange =
|
|
74
|
-
|
|
108
|
+
return da.arange(*args, dtype=dtype, **kwargs)
|
|
109
|
+
|
|
75
110
|
|
|
76
|
-
linspace = get_xp(da)(_aliases.linspace)
|
|
77
111
|
eye = get_xp(da)(_aliases.eye)
|
|
112
|
+
linspace = get_xp(da)(_aliases.linspace)
|
|
78
113
|
UniqueAllResult = get_xp(da)(_aliases.UniqueAllResult)
|
|
79
114
|
UniqueCountsResult = get_xp(da)(_aliases.UniqueCountsResult)
|
|
80
115
|
UniqueInverseResult = get_xp(da)(_aliases.UniqueInverseResult)
|
|
@@ -86,6 +121,7 @@ permute_dims = get_xp(da)(_aliases.permute_dims)
|
|
|
86
121
|
std = get_xp(da)(_aliases.std)
|
|
87
122
|
var = get_xp(da)(_aliases.var)
|
|
88
123
|
cumulative_sum = get_xp(da)(_aliases.cumulative_sum)
|
|
124
|
+
cumulative_prod = get_xp(da)(_aliases.cumulative_prod)
|
|
89
125
|
empty = get_xp(da)(_aliases.empty)
|
|
90
126
|
empty_like = get_xp(da)(_aliases.empty_like)
|
|
91
127
|
full = get_xp(da)(_aliases.full)
|
|
@@ -97,7 +133,6 @@ zeros_like = get_xp(da)(_aliases.zeros_like)
|
|
|
97
133
|
reshape = get_xp(da)(_aliases.reshape)
|
|
98
134
|
matrix_transpose = get_xp(da)(_aliases.matrix_transpose)
|
|
99
135
|
vecdot = get_xp(da)(_aliases.vecdot)
|
|
100
|
-
|
|
101
136
|
nonzero = get_xp(da)(_aliases.nonzero)
|
|
102
137
|
ceil = get_xp(np)(_aliases.ceil)
|
|
103
138
|
floor = get_xp(np)(_aliases.floor)
|
|
@@ -106,6 +141,7 @@ matmul = get_xp(np)(_aliases.matmul)
|
|
|
106
141
|
tensordot = get_xp(np)(_aliases.tensordot)
|
|
107
142
|
sign = get_xp(np)(_aliases.sign)
|
|
108
143
|
|
|
144
|
+
|
|
109
145
|
# asarray also adds the copy keyword, which is not present in numpy 1.0.
|
|
110
146
|
def asarray(
|
|
111
147
|
obj: Union[
|
|
@@ -120,7 +156,7 @@ def asarray(
|
|
|
120
156
|
*,
|
|
121
157
|
dtype: Optional[Dtype] = None,
|
|
122
158
|
device: Optional[Device] = None,
|
|
123
|
-
copy:
|
|
159
|
+
copy: Optional[Union[bool, np._CopyMode]] = None,
|
|
124
160
|
**kwargs,
|
|
125
161
|
) -> Array:
|
|
126
162
|
"""
|
|
@@ -129,24 +165,25 @@ def asarray(
|
|
|
129
165
|
See the corresponding documentation in the array library and/or the array API
|
|
130
166
|
specification for more details.
|
|
131
167
|
"""
|
|
168
|
+
# TODO: respect device keyword?
|
|
169
|
+
|
|
170
|
+
if isinstance(obj, da.Array):
|
|
171
|
+
if dtype is not None and dtype != obj.dtype:
|
|
172
|
+
if copy is False:
|
|
173
|
+
raise ValueError("Unable to avoid copy when changing dtype")
|
|
174
|
+
obj = obj.astype(dtype)
|
|
175
|
+
return obj.copy() if copy else obj
|
|
176
|
+
|
|
132
177
|
if copy is False:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
else:
|
|
142
|
-
if not isinstance(obj, da.Array) or dtype is not None and obj.dtype != dtype:
|
|
143
|
-
# copy=True to be uniform across dask < 2024.12 and >= 2024.12
|
|
144
|
-
# see https://github.com/dask/dask/pull/11524/
|
|
145
|
-
obj = np.array(obj, dtype=dtype, copy=True)
|
|
146
|
-
return da.from_array(obj)
|
|
147
|
-
return obj
|
|
178
|
+
raise NotImplementedError(
|
|
179
|
+
"Unable to avoid copy when converting a non-dask object to dask"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# copy=None to be uniform across dask < 2024.12 and >= 2024.12
|
|
183
|
+
# see https://github.com/dask/dask/pull/11524/
|
|
184
|
+
obj = np.array(obj, dtype=dtype, copy=True)
|
|
185
|
+
return da.from_array(obj)
|
|
148
186
|
|
|
149
|
-
return da.asarray(obj, dtype=dtype, **kwargs)
|
|
150
187
|
|
|
151
188
|
from dask.array import (
|
|
152
189
|
# Element wise aliases
|
|
@@ -165,49 +202,141 @@ from dask.array import (
|
|
|
165
202
|
concatenate as concat,
|
|
166
203
|
)
|
|
167
204
|
|
|
205
|
+
|
|
168
206
|
# dask.array.clip does not work unless all three arguments are provided.
|
|
169
207
|
# Furthermore, the masking workaround in common._aliases.clip cannot work with
|
|
170
208
|
# dask (meaning uint64 promoting to float64 is going to just be unfixed for
|
|
171
209
|
# now).
|
|
172
|
-
@get_xp(da)
|
|
173
210
|
def clip(
|
|
174
211
|
x: Array,
|
|
175
212
|
/,
|
|
176
213
|
min: Optional[Union[int, float, Array]] = None,
|
|
177
214
|
max: Optional[Union[int, float, Array]] = None,
|
|
178
|
-
*,
|
|
179
|
-
xp,
|
|
180
215
|
) -> Array:
|
|
216
|
+
"""
|
|
217
|
+
Array API compatibility wrapper for clip().
|
|
218
|
+
|
|
219
|
+
See the corresponding documentation in the array library and/or the array API
|
|
220
|
+
specification for more details.
|
|
221
|
+
"""
|
|
222
|
+
|
|
181
223
|
def _isscalar(a):
|
|
182
224
|
return isinstance(a, (int, float, type(None)))
|
|
225
|
+
|
|
183
226
|
min_shape = () if _isscalar(min) else min.shape
|
|
184
227
|
max_shape = () if _isscalar(max) else max.shape
|
|
185
228
|
|
|
186
229
|
# TODO: This won't handle dask unknown shapes
|
|
187
|
-
import numpy as np
|
|
188
230
|
result_shape = np.broadcast_shapes(x.shape, min_shape, max_shape)
|
|
189
231
|
|
|
190
232
|
if min is not None:
|
|
191
|
-
min =
|
|
233
|
+
min = da.broadcast_to(da.asarray(min), result_shape)
|
|
192
234
|
if max is not None:
|
|
193
|
-
max =
|
|
235
|
+
max = da.broadcast_to(da.asarray(max), result_shape)
|
|
194
236
|
|
|
195
237
|
if min is None and max is None:
|
|
196
|
-
return
|
|
238
|
+
return da.positive(x)
|
|
197
239
|
|
|
198
240
|
if min is None:
|
|
199
|
-
return astype(
|
|
241
|
+
return astype(da.minimum(x, max), x.dtype)
|
|
200
242
|
if max is None:
|
|
201
|
-
return astype(
|
|
243
|
+
return astype(da.maximum(x, min), x.dtype)
|
|
244
|
+
|
|
245
|
+
return astype(da.minimum(da.maximum(x, min), max), x.dtype)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _ensure_single_chunk(x: Array, axis: int) -> tuple[Array, Callable[[Array], Array]]:
|
|
249
|
+
"""
|
|
250
|
+
Make sure that Array is not broken into multiple chunks along axis.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
x : Array
|
|
255
|
+
The input Array with a single chunk along axis.
|
|
256
|
+
restore : Callable[Array, Array]
|
|
257
|
+
function to apply to the output to rechunk it back into reasonable chunks
|
|
258
|
+
"""
|
|
259
|
+
if axis < 0:
|
|
260
|
+
axis += x.ndim
|
|
261
|
+
if x.numblocks[axis] < 2:
|
|
262
|
+
return x, lambda x: x
|
|
263
|
+
|
|
264
|
+
# Break chunks on other axes in an attempt to keep chunk size low
|
|
265
|
+
x = x.rechunk({i: -1 if i == axis else "auto" for i in range(x.ndim)})
|
|
266
|
+
|
|
267
|
+
# Rather than reconstructing the original chunks, which can be a
|
|
268
|
+
# very expensive affair, just break down oversized chunks without
|
|
269
|
+
# incurring in any transfers over the network.
|
|
270
|
+
# This has the downside of a risk of overchunking if the array is
|
|
271
|
+
# then used in operations against other arrays that match the
|
|
272
|
+
# original chunking pattern.
|
|
273
|
+
return x, lambda x: x.rechunk()
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def sort(
|
|
277
|
+
x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True
|
|
278
|
+
) -> Array:
|
|
279
|
+
"""
|
|
280
|
+
Array API compatibility layer around the lack of sort() in Dask.
|
|
281
|
+
|
|
282
|
+
Warnings
|
|
283
|
+
--------
|
|
284
|
+
This function temporarily rechunks the array along `axis` to a single chunk.
|
|
285
|
+
This can be extremely inefficient and can lead to out-of-memory errors.
|
|
286
|
+
|
|
287
|
+
See the corresponding documentation in the array library and/or the array API
|
|
288
|
+
specification for more details.
|
|
289
|
+
"""
|
|
290
|
+
x, restore = _ensure_single_chunk(x, axis)
|
|
291
|
+
|
|
292
|
+
meta_xp = array_namespace(x._meta)
|
|
293
|
+
x = da.map_blocks(
|
|
294
|
+
meta_xp.sort,
|
|
295
|
+
x,
|
|
296
|
+
axis=axis,
|
|
297
|
+
meta=x._meta,
|
|
298
|
+
dtype=x.dtype,
|
|
299
|
+
descending=descending,
|
|
300
|
+
stable=stable,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
return restore(x)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def argsort(
|
|
307
|
+
x: Array, /, *, axis: int = -1, descending: bool = False, stable: bool = True
|
|
308
|
+
) -> Array:
|
|
309
|
+
"""
|
|
310
|
+
Array API compatibility layer around the lack of argsort() in Dask.
|
|
311
|
+
|
|
312
|
+
See the corresponding documentation in the array library and/or the array API
|
|
313
|
+
specification for more details.
|
|
314
|
+
|
|
315
|
+
Warnings
|
|
316
|
+
--------
|
|
317
|
+
This function temporarily rechunks the array along `axis` into a single chunk.
|
|
318
|
+
This can be extremely inefficient and can lead to out-of-memory errors.
|
|
319
|
+
"""
|
|
320
|
+
x, restore = _ensure_single_chunk(x, axis)
|
|
202
321
|
|
|
203
|
-
|
|
322
|
+
meta_xp = array_namespace(x._meta)
|
|
323
|
+
dtype = meta_xp.argsort(x._meta).dtype
|
|
324
|
+
meta = meta_xp.astype(x._meta, dtype)
|
|
325
|
+
x = da.map_blocks(
|
|
326
|
+
meta_xp.argsort,
|
|
327
|
+
x,
|
|
328
|
+
axis=axis,
|
|
329
|
+
meta=meta,
|
|
330
|
+
dtype=dtype,
|
|
331
|
+
descending=descending,
|
|
332
|
+
stable=stable,
|
|
333
|
+
)
|
|
204
334
|
|
|
205
|
-
|
|
206
|
-
_da_unsupported = ['sort', 'argsort']
|
|
335
|
+
return restore(x)
|
|
207
336
|
|
|
208
|
-
_common_aliases = [alias for alias in _aliases.__all__ if alias not in _da_unsupported]
|
|
209
337
|
|
|
210
|
-
__all__ =
|
|
338
|
+
__all__ = _aliases.__all__ + [
|
|
339
|
+
'__array_namespace_info__', 'asarray', 'astype', 'acos',
|
|
211
340
|
'acosh', 'asin', 'asinh', 'atan', 'atan2',
|
|
212
341
|
'atanh', 'bitwise_left_shift', 'bitwise_invert',
|
|
213
342
|
'bitwise_right_shift', 'concat', 'pow', 'iinfo', 'finfo', 'can_cast',
|
|
@@ -216,4 +345,4 @@ __all__ = _common_aliases + ['__array_namespace_info__', 'asarray', 'acos',
|
|
|
216
345
|
'complex64', 'complex128', 'iinfo', 'finfo',
|
|
217
346
|
'can_cast', 'result_type']
|
|
218
347
|
|
|
219
|
-
_all_ignore = ["get_xp", "da", "np"]
|
|
348
|
+
_all_ignore = ["Callable", "array_namespace", "get_xp", "da", "np"]
|
|
@@ -46,10 +46,10 @@ unique_all = get_xp(np)(_aliases.unique_all)
|
|
|
46
46
|
unique_counts = get_xp(np)(_aliases.unique_counts)
|
|
47
47
|
unique_inverse = get_xp(np)(_aliases.unique_inverse)
|
|
48
48
|
unique_values = get_xp(np)(_aliases.unique_values)
|
|
49
|
-
astype = _aliases.astype
|
|
50
49
|
std = get_xp(np)(_aliases.std)
|
|
51
50
|
var = get_xp(np)(_aliases.var)
|
|
52
51
|
cumulative_sum = get_xp(np)(_aliases.cumulative_sum)
|
|
52
|
+
cumulative_prod = get_xp(np)(_aliases.cumulative_prod)
|
|
53
53
|
clip = get_xp(np)(_aliases.clip)
|
|
54
54
|
permute_dims = get_xp(np)(_aliases.permute_dims)
|
|
55
55
|
reshape = get_xp(np)(_aliases.reshape)
|
|
@@ -115,6 +115,18 @@ def asarray(
|
|
|
115
115
|
|
|
116
116
|
return np.array(obj, copy=copy, dtype=dtype, **kwargs)
|
|
117
117
|
|
|
118
|
+
|
|
119
|
+
def astype(
|
|
120
|
+
x: ndarray,
|
|
121
|
+
dtype: Dtype,
|
|
122
|
+
/,
|
|
123
|
+
*,
|
|
124
|
+
copy: bool = True,
|
|
125
|
+
device: Optional[Device] = None,
|
|
126
|
+
) -> ndarray:
|
|
127
|
+
return x.astype(dtype=dtype, copy=copy)
|
|
128
|
+
|
|
129
|
+
|
|
118
130
|
# These functions are completely new here. If the library already has them
|
|
119
131
|
# (i.e., numpy 2.0), use the library version instead of our wrapper.
|
|
120
132
|
if hasattr(np, 'vecdot'):
|
|
@@ -132,10 +144,10 @@ if hasattr(np, 'unstack'):
|
|
|
132
144
|
else:
|
|
133
145
|
unstack = get_xp(np)(_aliases.unstack)
|
|
134
146
|
|
|
135
|
-
__all__ = _aliases.__all__ + ['__array_namespace_info__', 'asarray', '
|
|
147
|
+
__all__ = _aliases.__all__ + ['__array_namespace_info__', 'asarray', 'astype',
|
|
136
148
|
'acos', 'acosh', 'asin', 'asinh', 'atan',
|
|
137
149
|
'atan2', 'atanh', 'bitwise_left_shift',
|
|
138
150
|
'bitwise_invert', 'bitwise_right_shift',
|
|
139
|
-
'concat', 'pow']
|
|
151
|
+
'bool', 'concat', 'pow']
|
|
140
152
|
|
|
141
153
|
_all_ignore = ['np', 'get_xp']
|
array_api_compat/numpy/_info.py
CHANGED
|
@@ -8,6 +8,7 @@ from ..common._aliases import (matrix_transpose as _aliases_matrix_transpose,
|
|
|
8
8
|
clip as _aliases_clip,
|
|
9
9
|
unstack as _aliases_unstack,
|
|
10
10
|
cumulative_sum as _aliases_cumulative_sum,
|
|
11
|
+
cumulative_prod as _aliases_cumulative_prod,
|
|
11
12
|
)
|
|
12
13
|
from .._internal import get_xp
|
|
13
14
|
|
|
@@ -30,6 +31,12 @@ _int_dtypes = {
|
|
|
30
31
|
torch.int32,
|
|
31
32
|
torch.int64,
|
|
32
33
|
}
|
|
34
|
+
try:
|
|
35
|
+
# torch >=2.3
|
|
36
|
+
_int_dtypes |= {torch.uint16, torch.uint32, torch.uint64}
|
|
37
|
+
except AttributeError:
|
|
38
|
+
pass
|
|
39
|
+
|
|
33
40
|
|
|
34
41
|
_array_api_dtypes = {
|
|
35
42
|
torch.bool,
|
|
@@ -118,7 +125,11 @@ def _fix_promotion(x1, x2, only_scalar=True):
|
|
|
118
125
|
x1 = x1.to(dtype)
|
|
119
126
|
return x1, x2
|
|
120
127
|
|
|
121
|
-
|
|
128
|
+
|
|
129
|
+
_py_scalars = (bool, int, float, complex)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def result_type(*arrays_and_dtypes: Union[array, Dtype, bool, int, float, complex]) -> Dtype:
|
|
122
133
|
if len(arrays_and_dtypes) == 0:
|
|
123
134
|
raise TypeError("At least one array or dtype must be provided")
|
|
124
135
|
if len(arrays_and_dtypes) == 1:
|
|
@@ -130,6 +141,9 @@ def result_type(*arrays_and_dtypes: Union[array, Dtype]) -> Dtype:
|
|
|
130
141
|
return result_type(arrays_and_dtypes[0], result_type(*arrays_and_dtypes[1:]))
|
|
131
142
|
|
|
132
143
|
x, y = arrays_and_dtypes
|
|
144
|
+
if isinstance(x, _py_scalars) or isinstance(y, _py_scalars):
|
|
145
|
+
return torch.result_type(x, y)
|
|
146
|
+
|
|
133
147
|
xdt = x.dtype if not isinstance(x, torch.dtype) else x
|
|
134
148
|
ydt = y.dtype if not isinstance(y, torch.dtype) else y
|
|
135
149
|
|
|
@@ -204,6 +218,7 @@ def min(x: array, /, *, axis: Optional[Union[int, Tuple[int, ...]]] = None, keep
|
|
|
204
218
|
clip = get_xp(torch)(_aliases_clip)
|
|
205
219
|
unstack = get_xp(torch)(_aliases_unstack)
|
|
206
220
|
cumulative_sum = get_xp(torch)(_aliases_cumulative_sum)
|
|
221
|
+
cumulative_prod = get_xp(torch)(_aliases_cumulative_prod)
|
|
207
222
|
|
|
208
223
|
# torch.sort also returns a tuple
|
|
209
224
|
# https://github.com/pytorch/pytorch/issues/70921
|
|
@@ -498,6 +513,31 @@ def nonzero(x: array, /, **kwargs) -> Tuple[array, ...]:
|
|
|
498
513
|
raise ValueError("nonzero() does not support zero-dimensional arrays")
|
|
499
514
|
return torch.nonzero(x, as_tuple=True, **kwargs)
|
|
500
515
|
|
|
516
|
+
|
|
517
|
+
# torch uses `dim` instead of `axis`
|
|
518
|
+
def diff(
|
|
519
|
+
x: array,
|
|
520
|
+
/,
|
|
521
|
+
*,
|
|
522
|
+
axis: int = -1,
|
|
523
|
+
n: int = 1,
|
|
524
|
+
prepend: Optional[array] = None,
|
|
525
|
+
append: Optional[array] = None,
|
|
526
|
+
) -> array:
|
|
527
|
+
return torch.diff(x, dim=axis, n=n, prepend=prepend, append=append)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
# torch uses `dim` instead of `axis`
|
|
531
|
+
def count_nonzero(
|
|
532
|
+
x: array,
|
|
533
|
+
/,
|
|
534
|
+
*,
|
|
535
|
+
axis: Optional[Union[int, Tuple[int, ...]]] = None,
|
|
536
|
+
keepdims: bool = False,
|
|
537
|
+
) -> array:
|
|
538
|
+
return torch.count_nonzero(x, dim=axis, keepdims=keepdims)
|
|
539
|
+
|
|
540
|
+
|
|
501
541
|
def where(condition: array, x1: array, x2: array, /) -> array:
|
|
502
542
|
x1, x2 = _fix_promotion(x1, x2)
|
|
503
543
|
return torch.where(condition, x1, x2)
|
|
@@ -613,8 +653,19 @@ def triu(x: array, /, *, k: int = 0) -> array:
|
|
|
613
653
|
def expand_dims(x: array, /, *, axis: int = 0) -> array:
|
|
614
654
|
return torch.unsqueeze(x, axis)
|
|
615
655
|
|
|
616
|
-
|
|
617
|
-
|
|
656
|
+
|
|
657
|
+
def astype(
|
|
658
|
+
x: array,
|
|
659
|
+
dtype: Dtype,
|
|
660
|
+
/,
|
|
661
|
+
*,
|
|
662
|
+
copy: bool = True,
|
|
663
|
+
device: Optional[Device] = None,
|
|
664
|
+
) -> array:
|
|
665
|
+
if device is not None:
|
|
666
|
+
return x.to(device, dtype=dtype, copy=copy)
|
|
667
|
+
return x.to(dtype=dtype, copy=copy)
|
|
668
|
+
|
|
618
669
|
|
|
619
670
|
def broadcast_arrays(*arrays: array) -> List[array]:
|
|
620
671
|
shape = torch.broadcast_shapes(*[a.shape for a in arrays])
|
|
@@ -717,6 +768,11 @@ def take(x: array, indices: array, /, *, axis: Optional[int] = None, **kwargs) -
|
|
|
717
768
|
axis = 0
|
|
718
769
|
return torch.index_select(x, axis, indices, **kwargs)
|
|
719
770
|
|
|
771
|
+
|
|
772
|
+
def take_along_axis(x: array, indices: array, /, *, axis: int = -1) -> array:
|
|
773
|
+
return torch.take_along_dim(x, indices, dim=axis)
|
|
774
|
+
|
|
775
|
+
|
|
720
776
|
def sign(x: array, /) -> array:
|
|
721
777
|
# torch sign() does not support complex numbers and does not propagate
|
|
722
778
|
# nans. See https://github.com/data-apis/array-api-compat/issues/136
|
|
@@ -735,11 +791,12 @@ def sign(x: array, /) -> array:
|
|
|
735
791
|
__all__ = ['__array_namespace_info__', 'result_type', 'can_cast',
|
|
736
792
|
'permute_dims', 'bitwise_invert', 'newaxis', 'conj', 'add',
|
|
737
793
|
'atan2', 'bitwise_and', 'bitwise_left_shift', 'bitwise_or',
|
|
738
|
-
'bitwise_right_shift', 'bitwise_xor', 'copysign', '
|
|
794
|
+
'bitwise_right_shift', 'bitwise_xor', 'copysign', 'count_nonzero',
|
|
795
|
+
'diff', 'divide',
|
|
739
796
|
'equal', 'floor_divide', 'greater', 'greater_equal', 'hypot',
|
|
740
797
|
'less', 'less_equal', 'logaddexp', 'maximum', 'minimum',
|
|
741
798
|
'multiply', 'not_equal', 'pow', 'remainder', 'subtract', 'max',
|
|
742
|
-
'min', 'clip', 'unstack', 'cumulative_sum', 'sort', 'prod', 'sum',
|
|
799
|
+
'min', 'clip', 'unstack', 'cumulative_sum', 'cumulative_prod', 'sort', 'prod', 'sum',
|
|
743
800
|
'any', 'all', 'mean', 'std', 'var', 'concat', 'squeeze',
|
|
744
801
|
'broadcast_to', 'flip', 'roll', 'nonzero', 'where', 'reshape',
|
|
745
802
|
'arange', 'eye', 'linspace', 'full', 'ones', 'zeros', 'empty',
|
|
@@ -747,6 +804,6 @@ __all__ = ['__array_namespace_info__', 'result_type', 'can_cast',
|
|
|
747
804
|
'UniqueAllResult', 'UniqueCountsResult', 'UniqueInverseResult',
|
|
748
805
|
'unique_all', 'unique_counts', 'unique_inverse', 'unique_values',
|
|
749
806
|
'matmul', 'matrix_transpose', 'vecdot', 'tensordot', 'isdtype',
|
|
750
|
-
'take', 'sign']
|
|
807
|
+
'take', 'take_along_axis', 'sign']
|
|
751
808
|
|
|
752
809
|
_all_ignore = ['torch', 'get_xp']
|
array_api_compat/torch/_info.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: array_api_compat
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.11
|
|
4
4
|
Summary: A wrapper around NumPy and other array libraries to make them compatible with the Array API standard
|
|
5
5
|
Home-page: https://data-apis.org/array-api-compat/
|
|
6
6
|
Author: Consortium for Python Data API Standards
|
|
@@ -28,6 +28,15 @@ Provides-Extra: dask
|
|
|
28
28
|
Requires-Dist: dask; extra == "dask"
|
|
29
29
|
Provides-Extra: sparse
|
|
30
30
|
Requires-Dist: sparse>=0.15.1; extra == "sparse"
|
|
31
|
+
Dynamic: author
|
|
32
|
+
Dynamic: classifier
|
|
33
|
+
Dynamic: description
|
|
34
|
+
Dynamic: description-content-type
|
|
35
|
+
Dynamic: home-page
|
|
36
|
+
Dynamic: license
|
|
37
|
+
Dynamic: provides-extra
|
|
38
|
+
Dynamic: requires-python
|
|
39
|
+
Dynamic: summary
|
|
31
40
|
|
|
32
41
|
# Array API compatibility library
|
|
33
42
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
array_api_compat/__init__.py,sha256=ST4tFa84YyTEresLvKAdYddeBZhy7e_m3NFs9d6JCu0,990
|
|
2
|
+
array_api_compat/_internal.py,sha256=0GHLUJRbBHZLsbgRYE0OCtxAKdYuLtr1qzh70N5vBQI,1010
|
|
3
|
+
array_api_compat/common/__init__.py,sha256=HB4vvyS0GnH6JQSEgAC75oa-s2WBIiQQebpgXnW00N0,37
|
|
4
|
+
array_api_compat/common/_aliases.py,sha256=DY_bAnVpA0zcLbO4A8OvknTiVKHLuZtZ4GYqEIHxWuQ,18484
|
|
5
|
+
array_api_compat/common/_fft.py,sha256=o5097rkGCd_0Y6bonv-k6PDsIT6znNyi2xrZ2QAHF0U,4795
|
|
6
|
+
array_api_compat/common/_helpers.py,sha256=G8gsIcTxhndRacPY4HWwYq7BXJ5ywbwPQSmdCVwFEpo,27817
|
|
7
|
+
array_api_compat/common/_linalg.py,sha256=BebUx7WRkz9DAx9lrrP8d57-uN0VobwLGX0xbvI-7Wg,6142
|
|
8
|
+
array_api_compat/common/_typing.py,sha256=lNxZYl5ajs8rKNWgIj7nGDuqjZxDjAypwQAdKbLskS4,478
|
|
9
|
+
array_api_compat/cupy/__init__.py,sha256=DdnXhTZPfmyCyrejnxBcgPJLP0vHMZQl1ed1oiaaj-A,442
|
|
10
|
+
array_api_compat/cupy/_aliases.py,sha256=p6oiLqof7paweR1p8HCulBsooB0bcWiQbacw2fgI7LQ,4920
|
|
11
|
+
array_api_compat/cupy/_info.py,sha256=uV9O0LbA9rr9Y_GFFN8oI10e_kH95JX4EFQ8BU6tM-g,9809
|
|
12
|
+
array_api_compat/cupy/_typing.py,sha256=oDhrZB8R-D6wvee7tR4YkyBhTq93M0fFi3Tv-lpN_Dg,617
|
|
13
|
+
array_api_compat/cupy/fft.py,sha256=xCAC42CNAwAyVW7uCREsSoAV23R3rL2dqrT7w877zuE,842
|
|
14
|
+
array_api_compat/cupy/linalg.py,sha256=nKOM-_wcOHzHhEeV9KBzcMVNlviJK4nP1nFBUtvnjTM,1444
|
|
15
|
+
array_api_compat/dask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
array_api_compat/dask/array/__init__.py,sha256=KQy67alfyrWNoaqjXNYzkm4Y8_UctjpmgLyBwIY4z4k,242
|
|
17
|
+
array_api_compat/dask/array/_aliases.py,sha256=wfKgPfqQbIvJABI8uaDJxPW14qnmP74g_-PyclqlswQ,9883
|
|
18
|
+
array_api_compat/dask/array/_info.py,sha256=NjJDNgZ0YVewuuuzusqkHqZJ2DFU7txN_68PLN6QG3E,10414
|
|
19
|
+
array_api_compat/dask/array/fft.py,sha256=FWXfXVz9zUGKVtYJWl-xSb9BUp7UIewQ89FzGimwOOA,553
|
|
20
|
+
array_api_compat/dask/array/linalg.py,sha256=5E3wSAXmiZJ5rf69u6Pzw1Xs0lCdMpiVBnheA4lzY4E,2441
|
|
21
|
+
array_api_compat/numpy/__init__.py,sha256=xdBoXg4nHg3zwF3Bsgng7tLk78EGODdd97bUGRuBWPI,831
|
|
22
|
+
array_api_compat/numpy/_aliases.py,sha256=Dhi-mci8Vr7jK6x7JVydsRMBWdV6PE-R6mAc-WOyZs8,4706
|
|
23
|
+
array_api_compat/numpy/_info.py,sha256=LiphQgoWIHfkIgYun-0w3zXcNDoAoIMgG8qRIXlSFU8,10388
|
|
24
|
+
array_api_compat/numpy/_typing.py,sha256=OFRXfhT8-snL_4VeOjbOCd_yYIGqVS-IRrZoWNcL3v4,618
|
|
25
|
+
array_api_compat/numpy/fft.py,sha256=vlrYUcv2VV5mOOEb5R4u83nFSSDmE-nfJYM-lmq1Dao,679
|
|
26
|
+
array_api_compat/numpy/linalg.py,sha256=ne4h3Ui1esyzD9p7Ko2IueJvgpSUmfF_Z5aWbiBKJc0,3256
|
|
27
|
+
array_api_compat/torch/__init__.py,sha256=vsGeo4X4u0vAqFZ8aNzMiVOd6wd5JM-_Sz3r3Bv5HwA,591
|
|
28
|
+
array_api_compat/torch/_aliases.py,sha256=kOTGZGPiqas9jOdWRgF3NWFgK4c3_n1n0TzFciJ7E30,29860
|
|
29
|
+
array_api_compat/torch/_info.py,sha256=LIMbq4DH2Q3xL9TZac-xSu4zFWUJMmGK5h3m53lTKj4,11417
|
|
30
|
+
array_api_compat/torch/fft.py,sha256=AVHOwIxM-t9_w-FjVF79RrzeC5wYc5g97WPUp7bIHlA,1794
|
|
31
|
+
array_api_compat/torch/linalg.py,sha256=dJ0o1gCbSDtklpvgZCxx3gbHXW9q3I4u8ZLFPW24dJs,4770
|
|
32
|
+
array_api_compat-1.11.dist-info/LICENSE,sha256=T_2Xjj-hjQWNmMZncc_qftY0qvcCPPlhK4tV7umo8P4,1097
|
|
33
|
+
array_api_compat-1.11.dist-info/METADATA,sha256=yQ9XlxBaME4up9d1KfYvQWvkqBd_XBNhMGbB4656u8I,1828
|
|
34
|
+
array_api_compat-1.11.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
|
35
|
+
array_api_compat-1.11.dist-info/top_level.txt,sha256=I3ahdJi_PlO7taw_OFq_VENWBrs_68Ez_KnB05xditc,17
|
|
36
|
+
array_api_compat-1.11.dist-info/RECORD,,
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
array_api_compat/__init__.py,sha256=kANHGhPdqQI0GA5ZVQ9q0UZqJWbOm4MRPNAU3YnLx_U,992
|
|
2
|
-
array_api_compat/_internal.py,sha256=0GHLUJRbBHZLsbgRYE0OCtxAKdYuLtr1qzh70N5vBQI,1010
|
|
3
|
-
array_api_compat/common/__init__.py,sha256=HB4vvyS0GnH6JQSEgAC75oa-s2WBIiQQebpgXnW00N0,37
|
|
4
|
-
array_api_compat/common/_aliases.py,sha256=6BGUNUWr-u6bhUWDicuQWm0LF_aQr9al-OnBW7LegFE,17884
|
|
5
|
-
array_api_compat/common/_fft.py,sha256=qZvAveqXFwEQxCbTNx9l_41EpQpAwMfwS2GqWKEVwow,4520
|
|
6
|
-
array_api_compat/common/_helpers.py,sha256=xPQAvGvmb4pkSJXk5NLcEfEPugytPyl1W4o5VEy7UCQ,24624
|
|
7
|
-
array_api_compat/common/_linalg.py,sha256=BebUx7WRkz9DAx9lrrP8d57-uN0VobwLGX0xbvI-7Wg,6142
|
|
8
|
-
array_api_compat/common/_typing.py,sha256=KBJcLRAG2MeID9V38-GBipfpsFWGGrxOKkgfSQmgjXE,414
|
|
9
|
-
array_api_compat/cupy/__init__.py,sha256=3079YH9uF2HoG8E27bp_1lsIVvYsdrq8hKMk_jT3NFs,442
|
|
10
|
-
array_api_compat/cupy/_aliases.py,sha256=aCmWDlvcdhagje7QDxgF-jqTmUk6mnVIl2hOky1IpBE,4538
|
|
11
|
-
array_api_compat/cupy/_info.py,sha256=kdUS8xcIVg_0Mgg2qSzuqOrXgopaHO_G8JmGBB-4qOM,9805
|
|
12
|
-
array_api_compat/cupy/_typing.py,sha256=oDhrZB8R-D6wvee7tR4YkyBhTq93M0fFi3Tv-lpN_Dg,617
|
|
13
|
-
array_api_compat/cupy/fft.py,sha256=xCAC42CNAwAyVW7uCREsSoAV23R3rL2dqrT7w877zuE,842
|
|
14
|
-
array_api_compat/cupy/linalg.py,sha256=nKOM-_wcOHzHhEeV9KBzcMVNlviJK4nP1nFBUtvnjTM,1444
|
|
15
|
-
array_api_compat/dask/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
array_api_compat/dask/array/__init__.py,sha256=7_FttjbrGeKtPFGS_CA85WZZmbxPwkpxvsMS8KTMEFw,242
|
|
17
|
-
array_api_compat/dask/array/_aliases.py,sha256=Kn31uXAkUFOX11DzlEXnlH9uSgPGHB0GKkHAfFNCYjA,6692
|
|
18
|
-
array_api_compat/dask/array/_info.py,sha256=D8hG1uRNsF31_WX5bnulbdl75Jkd6G2DbkmhXXTplEs,10410
|
|
19
|
-
array_api_compat/dask/array/fft.py,sha256=FWXfXVz9zUGKVtYJWl-xSb9BUp7UIewQ89FzGimwOOA,553
|
|
20
|
-
array_api_compat/dask/array/linalg.py,sha256=5E3wSAXmiZJ5rf69u6Pzw1Xs0lCdMpiVBnheA4lzY4E,2441
|
|
21
|
-
array_api_compat/numpy/__init__.py,sha256=uxjYAO4xcDhTQPbrD2XmkWT5TyZsjpwc5FD-ViHxN-c,831
|
|
22
|
-
array_api_compat/numpy/_aliases.py,sha256=ZrddTjHOVUNvDM1h9p7NqXqaODVJKkKu2fTyPClCmXg,4485
|
|
23
|
-
array_api_compat/numpy/_info.py,sha256=GAD-zNvAMUSeUJfjABY6p_eYkG--KBBgz1vdQkL2-UA,10384
|
|
24
|
-
array_api_compat/numpy/_typing.py,sha256=OFRXfhT8-snL_4VeOjbOCd_yYIGqVS-IRrZoWNcL3v4,618
|
|
25
|
-
array_api_compat/numpy/fft.py,sha256=vlrYUcv2VV5mOOEb5R4u83nFSSDmE-nfJYM-lmq1Dao,679
|
|
26
|
-
array_api_compat/numpy/linalg.py,sha256=ne4h3Ui1esyzD9p7Ko2IueJvgpSUmfF_Z5aWbiBKJc0,3256
|
|
27
|
-
array_api_compat/torch/__init__.py,sha256=sk32NV12KrlR8a-UjiBdjJspUcex5j7REAGgSJoI3do,591
|
|
28
|
-
array_api_compat/torch/_aliases.py,sha256=kCIeFyzzUqNh86Byo5Ai2s1guK2-OkXg62chBCN_kgU,28559
|
|
29
|
-
array_api_compat/torch/_info.py,sha256=rnInxwjMErvcHLI4S6fzom7N43hoAqS0rysw1K8Riyw,11413
|
|
30
|
-
array_api_compat/torch/fft.py,sha256=AVHOwIxM-t9_w-FjVF79RrzeC5wYc5g97WPUp7bIHlA,1794
|
|
31
|
-
array_api_compat/torch/linalg.py,sha256=dJ0o1gCbSDtklpvgZCxx3gbHXW9q3I4u8ZLFPW24dJs,4770
|
|
32
|
-
array_api_compat-1.10.0.dist-info/LICENSE,sha256=T_2Xjj-hjQWNmMZncc_qftY0qvcCPPlhK4tV7umo8P4,1097
|
|
33
|
-
array_api_compat-1.10.0.dist-info/METADATA,sha256=fv9JiUfwg6YaExW_acxdfuNqIi8EEk9ilVf3gWqDoBc,1637
|
|
34
|
-
array_api_compat-1.10.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
35
|
-
array_api_compat-1.10.0.dist-info/top_level.txt,sha256=I3ahdJi_PlO7taw_OFq_VENWBrs_68Ez_KnB05xditc,17
|
|
36
|
-
array_api_compat-1.10.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|