array-api-strict 2.1__py3-none-any.whl → 2.1.1__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.
@@ -66,6 +66,8 @@ ALL_DEVICES = (CPU_DEVICE, Device("device1"), Device("device2"))
66
66
 
67
67
  _default = object()
68
68
 
69
+ _allow_array = False
70
+
69
71
  class Array:
70
72
  """
71
73
  n-d array object for the array API namespace.
@@ -145,30 +147,35 @@ class Array:
145
147
  mid = np.array2string(self._array, separator=', ', prefix=prefix, suffix=suffix)
146
148
  return prefix + mid + suffix
147
149
 
148
- # This function is not required by the spec, but we implement it here for
149
- # convenience so that np.asarray(array_api_strict.Array) will work.
150
+ # Disallow __array__, meaning calling `np.func()` on an array_api_strict
151
+ # array will give an error. If we don't explicitly disallow it, NumPy
152
+ # defaults to creating an object dtype array, which would lead to
153
+ # confusing error messages at best and surprising bugs at worst.
154
+ #
155
+ # The alternative of course is to just support __array__, which is what we
156
+ # used to do. But this isn't actually supported by the standard, so it can
157
+ # lead to code assuming np.asarray(other_array) would always work in the
158
+ # standard.
150
159
  def __array__(self, dtype: None | np.dtype[Any] = None, copy: None | bool = None) -> npt.NDArray[Any]:
151
- """
152
- Warning: this method is NOT part of the array API spec. Implementers
153
- of other libraries need not include it, and users should not assume it
154
- will be present in other implementations.
155
-
156
- """
157
- if self._device != CPU_DEVICE:
158
- raise RuntimeError(f"Can not convert array on the '{self._device}' device to a Numpy array.")
159
- # copy keyword is new in 2.0.0; for older versions don't use it
160
- # retry without that keyword.
161
- if np.__version__[0] < '2':
162
- return np.asarray(self._array, dtype=dtype)
163
- elif np.__version__.startswith('2.0.0-dev0'):
164
- # Handle dev version for which we can't know based on version
165
- # number whether or not the copy keyword is supported.
166
- try:
167
- return np.asarray(self._array, dtype=dtype, copy=copy)
168
- except TypeError:
160
+ # We have to allow this to be internally enabled as there's no other
161
+ # easy way to parse a list of Array objects in asarray().
162
+ if _allow_array:
163
+ if self._device != CPU_DEVICE:
164
+ raise RuntimeError(f"Can not convert array on the '{self._device}' device to a Numpy array.")
165
+ # copy keyword is new in 2.0.0; for older versions don't use it
166
+ # retry without that keyword.
167
+ if np.__version__[0] < '2':
169
168
  return np.asarray(self._array, dtype=dtype)
170
- else:
171
- return np.asarray(self._array, dtype=dtype, copy=copy)
169
+ elif np.__version__.startswith('2.0.0-dev0'):
170
+ # Handle dev version for which we can't know based on version
171
+ # number whether or not the copy keyword is supported.
172
+ try:
173
+ return np.asarray(self._array, dtype=dtype, copy=copy)
174
+ except TypeError:
175
+ return np.asarray(self._array, dtype=dtype)
176
+ else:
177
+ return np.asarray(self._array, dtype=dtype, copy=copy)
178
+ raise ValueError("Conversion from an array_api_strict array to a NumPy ndarray is not supported")
172
179
 
173
180
  # These are various helper functions to make the array behavior match the
174
181
  # spec in places where it either deviates from or is more strict than
@@ -274,7 +281,7 @@ class Array:
274
281
  # behavior for integers within the bounds of the integer dtype.
275
282
  # Outside of those bounds we use the default NumPy behavior (either
276
283
  # cast or raise OverflowError).
277
- return Array._new(np.array(scalar, dtype=self.dtype._np_dtype), device=CPU_DEVICE)
284
+ return Array._new(np.array(scalar, dtype=self.dtype._np_dtype), device=self.device)
278
285
 
279
286
  @staticmethod
280
287
  def _normalize_two_args(x1, x2) -> Tuple[Array, Array]:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
-
3
+ from contextlib import contextmanager
4
4
  from typing import TYPE_CHECKING, List, Optional, Tuple, Union
5
5
 
6
6
  if TYPE_CHECKING:
@@ -16,6 +16,19 @@ from ._flags import get_array_api_strict_flags
16
16
 
17
17
  import numpy as np
18
18
 
19
+ @contextmanager
20
+ def allow_array():
21
+ """
22
+ Temporarily enable Array.__array__. This is needed for np.array to parse
23
+ list of lists of Array objects.
24
+ """
25
+ from . import _array_object
26
+ original_value = _array_object._allow_array
27
+ try:
28
+ _array_object._allow_array = True
29
+ yield
30
+ finally:
31
+ _array_object._allow_array = original_value
19
32
 
20
33
  def _check_valid_dtype(dtype):
21
34
  # Note: Only spelling dtypes as the dtype objects is supported.
@@ -67,6 +80,8 @@ def asarray(
67
80
  if dtype is not None:
68
81
  _np_dtype = dtype._np_dtype
69
82
  _check_device(device)
83
+ if isinstance(obj, Array) and device is None:
84
+ device = obj.device
70
85
 
71
86
  if np.__version__[0] < '2':
72
87
  if copy is False:
@@ -97,7 +112,8 @@ def asarray(
97
112
  # Give a better error message in this case. NumPy would convert this
98
113
  # to an object array. TODO: This won't handle large integers in lists.
99
114
  raise OverflowError("Integer out of bounds for array dtypes")
100
- res = np.array(obj, dtype=_np_dtype, copy=copy)
115
+ with allow_array():
116
+ res = np.array(obj, dtype=_np_dtype, copy=copy)
101
117
  return Array._new(res, device=device)
102
118
 
103
119
 
@@ -158,6 +174,8 @@ def empty_like(
158
174
 
159
175
  _check_valid_dtype(dtype)
160
176
  _check_device(device)
177
+ if device is None:
178
+ device = x.device
161
179
 
162
180
  if dtype is not None:
163
181
  dtype = dtype._np_dtype
@@ -260,6 +278,8 @@ def full_like(
260
278
 
261
279
  _check_valid_dtype(dtype)
262
280
  _check_device(device)
281
+ if device is None:
282
+ device = x.device
263
283
 
264
284
  if dtype is not None:
265
285
  dtype = dtype._np_dtype
@@ -14,6 +14,7 @@ from ._dtypes import (
14
14
  from ._array_object import Array
15
15
  from ._flags import requires_api_version
16
16
  from ._creation_functions import asarray
17
+ from ._data_type_functions import broadcast_to, iinfo
17
18
 
18
19
  from typing import Optional, Union
19
20
 
@@ -325,14 +326,51 @@ def clip(
325
326
  if min is not None and max is not None and np.any(min > max):
326
327
  raise ValueError("min must be less than or equal to max")
327
328
 
328
- result = np.clip(x._array, min, max)
329
- # Note: NumPy applies type promotion, but the standard specifies the
330
- # return dtype should be the same as x
331
- if result.dtype != x.dtype._np_dtype:
332
- # TODO: I'm not completely sure this always gives the correct thing
333
- # for integer dtypes. See https://github.com/numpy/numpy/issues/24976
334
- result = result.astype(x.dtype._np_dtype)
335
- return Array._new(result, device=x.device)
329
+ # np.clip does type promotion but the array API clip requires that the
330
+ # output have the same dtype as x. We do this instead of just downcasting
331
+ # the result of xp.clip() to handle some corner cases better (e.g.,
332
+ # avoiding uint64 -> float64 promotion).
333
+
334
+ # Note: cases where min or max overflow (integer) or round (float) in the
335
+ # wrong direction when downcasting to x.dtype are unspecified. This code
336
+ # just does whatever NumPy does when it downcasts in the assignment, but
337
+ # other behavior could be preferred, especially for integers. For example,
338
+ # this code produces:
339
+
340
+ # >>> clip(asarray(0, dtype=int8), asarray(128, dtype=int16), None)
341
+ # -128
342
+
343
+ # but an answer of 0 might be preferred. See
344
+ # https://github.com/numpy/numpy/issues/24976 for more discussion on this issue.
345
+
346
+ # At least handle the case of Python integers correctly (see
347
+ # https://github.com/numpy/numpy/pull/26892).
348
+ if type(min) is int and min <= iinfo(x.dtype).min:
349
+ min = None
350
+ if type(max) is int and max >= iinfo(x.dtype).max:
351
+ max = None
352
+
353
+ def _isscalar(a):
354
+ return isinstance(a, (int, float, type(None)))
355
+ min_shape = () if _isscalar(min) else min.shape
356
+ max_shape = () if _isscalar(max) else max.shape
357
+
358
+ result_shape = np.broadcast_shapes(x.shape, min_shape, max_shape)
359
+
360
+ out = asarray(broadcast_to(x, result_shape), copy=True)._array
361
+ device = x.device
362
+ x = x._array
363
+
364
+ if min is not None:
365
+ a = np.broadcast_to(np.asarray(min), result_shape)
366
+ ia = (out < a) | np.isnan(a)
367
+
368
+ out[ia] = a[ia]
369
+ if max is not None:
370
+ b = np.broadcast_to(np.asarray(max), result_shape)
371
+ ib = (out > b) | np.isnan(b)
372
+ out[ib] = b[ib]
373
+ return Array._new(out, device=device)
336
374
 
337
375
  def conj(x: Array, /) -> Array:
338
376
  """
@@ -855,6 +893,8 @@ def sign(x: Array, /) -> Array:
855
893
  """
856
894
  if x.dtype not in _numeric_dtypes:
857
895
  raise TypeError("Only numeric dtypes are allowed in sign")
896
+ if x.dtype in _complex_floating_dtypes:
897
+ return x/abs(x)
858
898
  return Array._new(np.sign(x._array), device=x.device)
859
899
 
860
900
 
@@ -267,6 +267,8 @@ def pinv(x: Array, /, *, rtol: Optional[Union[float, Array]] = None) -> Array:
267
267
  # default tolerance by max(M, N).
268
268
  if rtol is None:
269
269
  rtol = max(x.shape[-2:]) * finfo(x.dtype).eps
270
+ if isinstance(rtol, Array):
271
+ rtol = rtol._array
270
272
  return Array._new(np.linalg.pinv(x._array, rcond=rtol), device=x.device)
271
273
 
272
274
  @requires_extension('linalg')
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2024-10-18T12:46:30-0600",
11
+ "date": "2024-11-07T13:18:01-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "c89d77a82e2ef7772c26f6cd10cd4b101ce3f90d",
15
- "version": "2.1"
14
+ "full-revisionid": "6e59494cbfd3ed949cf2009f778fc4cfdfc3eb04",
15
+ "version": "2.1.1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -6,7 +6,7 @@ import numpy as np
6
6
  import pytest
7
7
 
8
8
  from .. import ones, asarray, result_type, all, equal
9
- from .._array_object import Array, CPU_DEVICE
9
+ from .._array_object import Array, CPU_DEVICE, Device
10
10
  from .._dtypes import (
11
11
  _all_dtypes,
12
12
  _boolean_dtypes,
@@ -88,6 +88,14 @@ def test_validate_index():
88
88
  assert_raises(IndexError, lambda: a[0])
89
89
  assert_raises(IndexError, lambda: a[:])
90
90
 
91
+ def test_promoted_scalar_inherits_device():
92
+ device1 = Device("device1")
93
+ x = asarray([1., 2, 3], device=device1)
94
+
95
+ y = x ** 2
96
+
97
+ assert y.device == device1
98
+
91
99
  def test_operators():
92
100
  # For every operator, we test that it works for the required type
93
101
  # combinations and raises TypeError otherwise
@@ -342,23 +350,19 @@ def test_array_properties():
342
350
  assert isinstance(b.mT, Array)
343
351
  assert b.mT.shape == (3, 2)
344
352
 
345
- def test___array__():
346
- a = ones((2, 3), dtype=int16)
347
- assert np.asarray(a) is a._array
348
- b = np.asarray(a, dtype=np.float64)
349
- assert np.all(np.equal(b, np.ones((2, 3), dtype=np.float64)))
350
- assert b.dtype == np.float64
351
353
 
352
354
  def test_array_conversion():
353
355
  # Check that arrays on the CPU device can be converted to NumPy
354
- # but arrays on other devices can't
356
+ # but arrays on other devices can't. Note this is testing the logic in
357
+ # __array__, which is only used in asarray when converting lists of
358
+ # arrays.
355
359
  a = ones((2, 3))
356
- np.asarray(a)
360
+ asarray([a])
357
361
 
358
362
  for device in ("device1", "device2"):
359
363
  a = ones((2, 3), device=array_api_strict.Device(device))
360
364
  with pytest.raises(RuntimeError, match="Can not convert array"):
361
- np.asarray(a)
365
+ asarray([a])
362
366
 
363
367
  def test_allow_newaxis():
364
368
  a = ones(5)
@@ -22,8 +22,8 @@ from .._creation_functions import (
22
22
  zeros,
23
23
  zeros_like,
24
24
  )
25
- from .._dtypes import float32, float64
26
- from .._array_object import Array, CPU_DEVICE
25
+ from .._dtypes import int16, float32, float64
26
+ from .._array_object import Array, CPU_DEVICE, Device
27
27
  from .._flags import set_array_api_strict_flags
28
28
 
29
29
  def test_asarray_errors():
@@ -97,6 +97,30 @@ def test_asarray_copy():
97
97
  a[0] = 0
98
98
  assert all(b[0] == 0)
99
99
 
100
+ def test_asarray_list_of_lists():
101
+ a = asarray(1, dtype=int16)
102
+ b = asarray([1], dtype=int16)
103
+ res = asarray([a, a])
104
+ assert res.shape == (2,)
105
+ assert res.dtype == int16
106
+ assert all(res == asarray([1, 1]))
107
+
108
+ res = asarray([b, b])
109
+ assert res.shape == (2, 1)
110
+ assert res.dtype == int16
111
+ assert all(res == asarray([[1], [1]]))
112
+
113
+
114
+ def test_asarray_device_inference():
115
+ assert asarray([1, 2, 3]).device == CPU_DEVICE
116
+
117
+ x = asarray([1, 2, 3])
118
+ assert asarray(x).device == CPU_DEVICE
119
+
120
+ device1 = Device("device1")
121
+ x = asarray([1, 2, 3], device=device1)
122
+ assert asarray(x).device == device1
123
+
100
124
  def test_arange_errors():
101
125
  arange(1, device=CPU_DEVICE) # Doesn't error
102
126
  assert_raises(ValueError, lambda: arange(1, device="cpu"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: array_api_strict
3
- Version: 2.1
3
+ Version: 2.1.1
4
4
  Summary: A strict, minimal implementation of the Python array API standard.
5
5
  Home-page: https://data-apis.org/array-api-strict/
6
6
  Author: Consortium for Python Data API Standards
@@ -1,15 +1,15 @@
1
1
  array_api_strict/__init__.py,sha256=iQRjMJ4h-MoNqhxYeerCVRPORO6W6KePiQ8xIeecDac,6788
2
- array_api_strict/_array_object.py,sha256=BZhkZfWAHkrYOJvxAv_sQWffb6qpP83laEUI_FmP1y4,51353
2
+ array_api_strict/_array_object.py,sha256=ElW2-ppgMqMTmpqlleKqCg1Fq798D40-y8I17gCJpNQ,51876
3
3
  array_api_strict/_constants.py,sha256=AYayN2jf1Dp5rXZ7WPBdUhtPBo_JMCi-pD9oW5zmFkI,87
4
- array_api_strict/_creation_functions.py,sha256=pNCYP6dwoVQj-bNCTfbjxM3OpjwUuUiTRjaW12IluiU,12348
4
+ array_api_strict/_creation_functions.py,sha256=lmLDCAb0aq0naeJbCJ0NgnBRqp6eHs89jnARZhHMgJE,12963
5
5
  array_api_strict/_data_type_functions.py,sha256=sKxKRQiwoXPwQJPbiN5BhF9rbc3ZtXfjRuglB7VsIgo,7016
6
6
  array_api_strict/_dtypes.py,sha256=QDGo9rqiS_o3quv7QRFM0j6DkvOjRauQ1Ymikwlu8Ns,6213
7
- array_api_strict/_elementwise_functions.py,sha256=aYxBvyUtkvFMk6y0AkhKC2Lmago82h2c53rz4VYYKns,37196
7
+ array_api_strict/_elementwise_functions.py,sha256=g9YecX4gM4V6UCjl7b7I752EFXhRusjm-EKGZ12jDeI,38651
8
8
  array_api_strict/_fft.py,sha256=xH1o4j_amq3e1yGVChV6vms7oAfCzpzB18I-IzjNSXw,9702
9
9
  array_api_strict/_flags.py,sha256=hXe5cTFNlFEgbB4nsEazbAPFwnsFnn52wGRYRDjfCFg,13121
10
10
  array_api_strict/_indexing_functions.py,sha256=VORFyAFCZjLpL6gZ_PNVNKbQjpjhIXQDNDDHf7Mjy_U,970
11
11
  array_api_strict/_info.py,sha256=WWvUjNITc5-jLyS4KrnwTidD3baD9DjHseV254hCUY0,3567
12
- array_api_strict/_linalg.py,sha256=CGE9-2aiLraRc0bjo7giGllo-cgTF9l7bvswd_hYwKY,19787
12
+ array_api_strict/_linalg.py,sha256=sfmd5fuq2nHy_9kD2QQ23_JbbVZo7LHFgb10qjrr7nw,19846
13
13
  array_api_strict/_linear_algebra_functions.py,sha256=12XPBU27X3WZdaq9Ob2eUmL_PxMzDNF76pY4iESlPyo,3872
14
14
  array_api_strict/_manipulation_functions.py,sha256=Pa1gVRlaSC39-Zd6n4Q8c9P0anZYdyCh95Wyx3az3lc,6681
15
15
  array_api_strict/_searching_functions.py,sha256=32IiLLpa91N-jtzaxj4Nw9nQgF2ROkcNZ-XH6WQQUHY,3233
@@ -18,11 +18,11 @@ array_api_strict/_sorting_functions.py,sha256=GG7g2NYCFn2G2fFS6ZdfYOWpuuSIrdZdJW
18
18
  array_api_strict/_statistical_functions.py,sha256=dMJ-x8TQW5MD2rFYCmRH6QthA2KDKXjhkj4hcbd3LSU,5148
19
19
  array_api_strict/_typing.py,sha256=TpmU57g3EAPY9PT6hJEhzBduVrwXJUWvmoBlhy5bQuQ,1803
20
20
  array_api_strict/_utility_functions.py,sha256=khxtvqPWSDpNp65VgoJUtO4zEfTiGfZZGu_MHwyfmmI,913
21
- array_api_strict/_version.py,sha256=50WQJ8riNVKMTOMoVw9_1jBMPhrtVR5mgZHcHL1LUSM,495
21
+ array_api_strict/_version.py,sha256=XW_77odg8qbb1HCC9FVKUxmwghy9Dj7x6np6j_akALk,497
22
22
  array_api_strict/tests/__init__.py,sha256=t_2GZ3lKcsu4ec4GMKPUDYaeMUJyDquBlQAcPgj7kFE,282
23
23
  array_api_strict/tests/conftest.py,sha256=-wIDryl2MdRLdSonU1G1xWssNq2TFKiWuHquNasXYA8,468
24
- array_api_strict/tests/test_array_object.py,sha256=YUwPIzDu-TwKIZZTFCOwa8cfeMQBNYv4v8ifnLuekNA,20186
25
- array_api_strict/tests/test_creation_functions.py,sha256=5YDynTg_byrKECICSUkHOKhrb5Yh4ckWv2zlSOSTMDI,7675
24
+ array_api_strict/tests/test_array_object.py,sha256=r4IpleRQghC-l3B2IhrDadpyxlZB94LZ_X-9JB3jbQg,20250
25
+ array_api_strict/tests/test_creation_functions.py,sha256=ve0MeqZPrG2dmCG3hTVhap1YgIW-lAaSZIJ_ZoOV9cQ,8323
26
26
  array_api_strict/tests/test_data_type_functions.py,sha256=pLAtm96gpAduNVqq-914JvjAollzhBp4YojVfNScBzs,2138
27
27
  array_api_strict/tests/test_device_support.py,sha256=zpfhHxeRREoXfp0qZTlilm2ZceBc71uCKea1GMVe-ZU,855
28
28
  array_api_strict/tests/test_elementwise_functions.py,sha256=bKw3b4G-v3yEmmUW2HNXRRQ2tbEPopkrUKbvF8jvUpI,6943
@@ -34,8 +34,8 @@ array_api_strict/tests/test_set_functions.py,sha256=ikzab1j3SXxGf0OqwHrOAM13HwQe
34
34
  array_api_strict/tests/test_sorting_functions.py,sha256=1xvQBKts5KTKYc-puMITX9iR_KTbzzVB8fY-SObad_Y,899
35
35
  array_api_strict/tests/test_statistical_functions.py,sha256=JwS4DhASt5T0YUNtbFK2nSXgDrVVZx1tVpe7LU2aPK4,1365
36
36
  array_api_strict/tests/test_validation.py,sha256=JdsK4z1M0U5YoJrfWSzOpEhmlehvZHF5SG0I0k6oDy8,672
37
- array_api_strict-2.1.dist-info/LICENSE,sha256=djDm4UfHAJmycwFUNyUmrTdRkrFF9Ac8uqIlbxH7j5Y,1585
38
- array_api_strict-2.1.dist-info/METADATA,sha256=lxFEfvEZ845pOZjnQTGSSB3BfSdaSuGNJrT7bjCl050,1593
39
- array_api_strict-2.1.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
40
- array_api_strict-2.1.dist-info/top_level.txt,sha256=M4cE8VLgRHp_d9TPkbk7_I4JriELFdEJz-NKJJqAkAE,17
41
- array_api_strict-2.1.dist-info/RECORD,,
37
+ array_api_strict-2.1.1.dist-info/LICENSE,sha256=djDm4UfHAJmycwFUNyUmrTdRkrFF9Ac8uqIlbxH7j5Y,1585
38
+ array_api_strict-2.1.1.dist-info/METADATA,sha256=B-jmIvTszl9pwlOp5tY9diRXDLwWQvl7y7XxJzV52yQ,1595
39
+ array_api_strict-2.1.1.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
40
+ array_api_strict-2.1.1.dist-info/top_level.txt,sha256=M4cE8VLgRHp_d9TPkbk7_I4JriELFdEJz-NKJJqAkAE,17
41
+ array_api_strict-2.1.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.2.0)
2
+ Generator: setuptools (75.3.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5