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.
@@ -17,6 +17,6 @@ to ensure they are not using functionality outside of the standard, but prefer
17
17
  this implementation for the default when working with NumPy arrays.
18
18
 
19
19
  """
20
- __version__ = '1.10.0'
20
+ __version__ = '1.11'
21
21
 
22
22
  from .common import * # noqa: F401, F403
@@ -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
- 'astype', 'std', 'var', 'cumulative_sum', 'clip', 'permute_dims',
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']
@@ -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(n: int, /, xp, *, d: float = 1.0, device: Optional[Device] = None) -> ndarray:
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
- return xp.fft.fftfreq(n, d=d)
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(n: int, /, xp, *, d: float = 1.0, device: Optional[Device] = None) -> ndarray:
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
- return xp.fft.rfftfreq(n, d=d)
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
- def is_numpy_array(x):
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
- def is_cupy_array(x):
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
- def is_torch_array(x):
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
- def is_ndonnx_array(x):
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
- def is_dask_array(x):
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
- def is_jax_array(x):
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
- def is_array_api_obj(x):
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
- def _compat_module_name():
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
- def is_dask_namespace(xp):
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
- def is_jax_namespace(xp):
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
- def is_pydata_sparse_namespace(xp):
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
- def is_array_api_strict_namespace(xp):
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
- def _check_api_version(api_version):
418
- if api_version in ['2021.12', '2022.12']:
419
- warnings.warn(f"The {api_version} version of the array API specification was requested but the returned namespace is actually version 2023.12")
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 2023.12 version of the array API specification is currently supported")
438
+ '2023.12', '2024.12']:
439
+ raise ValueError("Only the 2024.12 version of the array API specification is currently supported")
423
440
 
424
- def array_namespace(*xs, api_version=None, use_compat=None):
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 v2023.12).
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 jax array to determine type
634
- try:
635
- import numpy as np
636
- if isinstance(x._meta, np.ndarray):
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
- # JAX has .device() as a method, but it is being deprecated so that it
644
- # can become a property, in accordance with the standard. In order for
645
- # this function to not break when JAX makes the flip, we check for
646
- # both here.
647
- if inspect.ismethod(x.device):
648
- return x.device()
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 x.device
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
- return math.prod(x.shape)
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 True
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
  ]
@@ -5,6 +5,7 @@ __all__ = [
5
5
  "SupportsBufferProtocol",
6
6
  ]
7
7
 
8
+ from types import ModuleType
8
9
  from typing import (
9
10
  Any,
10
11
  TypeVar,
@@ -21,3 +22,5 @@ SupportsBufferProtocol = Any
21
22
 
22
23
  Array = Any
23
24
  Device = Any
25
+ DType = Any
26
+ Namespace = ModuleType
@@ -13,4 +13,4 @@ __import__(__package__ + '.fft')
13
13
 
14
14
  from ..common._helpers import * # noqa: F401,F403
15
15
 
16
- __array_api_version__ = '2023.12'
16
+ __array_api_version__ = '2024.12'
@@ -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', 'bool',
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']
@@ -101,7 +101,7 @@ class __array_namespace_info__:
101
101
  "boolean indexing": True,
102
102
  "data-dependent shapes": True,
103
103
  # 'max rank' will be part of the 2024.12 standard
104
- # "max rank": 64,
104
+ "max dimensions": 64,
105
105
  }
106
106
 
107
107
  def default_device(self):
@@ -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__ = '2023.12'
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 ...common import _aliases
4
- from ...common._helpers import _check_device
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 Device, Dtype, Array, NestedSequence, SupportsBufferProtocol
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
- astype = _aliases.astype
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
- _check_device(xp, device)
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 = get_xp(da)(_dask_arange)
74
- eye = get_xp(da)(_aliases.eye)
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: "Optional[Union[bool, np._CopyMode]]" = None,
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
- # copy=False is not yet implemented in dask
134
- raise NotImplementedError("copy=False is not yet implemented")
135
- elif copy is True:
136
- if isinstance(obj, da.Array) and dtype is None:
137
- return obj.copy()
138
- # Go through numpy, since dask copy is no-op by default
139
- obj = np.array(obj, dtype=dtype, copy=True)
140
- return da.array(obj, dtype=dtype)
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 = xp.broadcast_to(xp.asarray(min), result_shape)
233
+ min = da.broadcast_to(da.asarray(min), result_shape)
192
234
  if max is not None:
193
- max = xp.broadcast_to(xp.asarray(max), result_shape)
235
+ max = da.broadcast_to(da.asarray(max), result_shape)
194
236
 
195
237
  if min is None and max is None:
196
- return xp.positive(x)
238
+ return da.positive(x)
197
239
 
198
240
  if min is None:
199
- return astype(xp.minimum(x, max), x.dtype)
241
+ return astype(da.minimum(x, max), x.dtype)
200
242
  if max is None:
201
- return astype(xp.maximum(x, min), x.dtype)
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
- return astype(xp.minimum(xp.maximum(x, min), max), x.dtype)
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
- # exclude these from all since dask.array has no sorting functions
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__ = _common_aliases + ['__array_namespace_info__', 'asarray', 'acos',
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"]
@@ -102,7 +102,7 @@ class __array_namespace_info__:
102
102
  "boolean indexing": False,
103
103
  "data-dependent shapes": False,
104
104
  # 'max rank' will be part of the 2024.12 standard
105
- # "max rank": 64,
105
+ "max dimensions": 64,
106
106
  }
107
107
 
108
108
  def default_device(self):
@@ -27,4 +27,4 @@ try:
27
27
  except ImportError:
28
28
  pass
29
29
 
30
- __array_api_version__ = '2023.12'
30
+ __array_api_version__ = '2024.12'
@@ -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', 'bool',
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']
@@ -101,7 +101,7 @@ class __array_namespace_info__:
101
101
  "boolean indexing": True,
102
102
  "data-dependent shapes": True,
103
103
  # 'max rank' will be part of the 2024.12 standard
104
- # "max rank": 64,
104
+ "max dimensions": 64,
105
105
  }
106
106
 
107
107
  def default_device(self):
@@ -21,4 +21,4 @@ __import__(__package__ + '.fft')
21
21
 
22
22
  from ..common._helpers import * # noqa: F403
23
23
 
24
- __array_api_version__ = '2023.12'
24
+ __array_api_version__ = '2024.12'
@@ -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
- def result_type(*arrays_and_dtypes: Union[array, Dtype]) -> Dtype:
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
- def astype(x: array, dtype: Dtype, /, *, copy: bool = True) -> array:
617
- return x.to(dtype, copy=copy)
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', 'divide',
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']
@@ -86,7 +86,7 @@ class __array_namespace_info__:
86
86
  "boolean indexing": True,
87
87
  "data-dependent shapes": True,
88
88
  # 'max rank' will be part of the 2024.12 standard
89
- # "max rank": 64,
89
+ "max dimensions": 64,
90
90
  }
91
91
 
92
92
  def default_device(self):
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: array_api_compat
3
- Version: 1.10.0
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,