Skip to content

Commit 446e146

Browse files
committed
BUG: do not change the dtype in asarray(array_from_another_library)
Consider >>> x = np.asarray(3, dtype=np.float32) >>> y = xp.asarray(x, device=xp.Device("device1")) >>> y.dtype == xp.float32 Since `x` is an array, the dtype of `y` must match the dtype of `x`, not be the default dtype of the device of `y`! In fact, this is the behavior in 2.5, so restore it. The fix follows from the difference between 2.5 and 2.6: - in 2.5, we called `res = np.array(...)` and wrapped `res` into an xp-array - in 2.6, we additionally select adjust the dtype to match the default for the device. The last part should be gated: - if `obj` is an array_like python object, it does not have a .dtype attribute, thus we adjust res.dtype (this is new in 2.6) - if `obj` is a array from numpy or pytorch CPU (i.e. numpy conversion succeeds), `res.dtype` is a numpy analog of `obj.dtype`, so we honor that (this is what 2.6 broke, and we fix here) - if `obj` has a dtype attribute but numpy fails to convert it (so `obj` is a GPU array or a pydata sparse array etc), calling `np.array(obj)` fails with an exception, and we never reach the dtype adjustment stage anyway (and this is unchanged from 2.5). $ git diff 2.5..2.6 -- array_api_strict/_creation_functions.py ... snip ... @@ -108,6 +106,27 @@ def asarray( raise OverflowError("Integer out of bounds for array dtypes") res = np.array(obj, dtype=_np_dtype, copy=copy) + + # numpy default dtype may differ; if so, adjust the dtype + if dtype is None and device is not None: + res_dtype = DType(res.dtype) + # The dtype selected by Numpy might not be the default dtype + # on this device. We thus find the default dtype for the dtype "kind", and + # cast to the device-appropriate default. + from ._data_type_functions import isdtype + if isdtype(res_dtype, "bool"): + target_dtype = DType("bool") + elif isdtype(res_dtype, "integral"): + target_dtype = get_default_dtypes(device)["integral"] + elif isdtype(res_dtype, "real floating"): + target_dtype = get_default_dtypes(device)["real floating"] + elif isdtype(res_dtype, "complex floating"): + target_dtype = get_default_dtypes(device)["complex floating"] + else: + raise ValueError(f"{res_dtype = } not understood.") + + res = res.astype(target_dtype._np_dtype) + return Array._new(res, device=device)
1 parent 95ded08 commit 446e146

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

array_api_strict/_creation_functions.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,12 @@ def asarray(
107107

108108
res = np.array(obj, dtype=_np_dtype, copy=copy)
109109

110-
# numpy default dtype may differ; if so, adjust the dtype
111-
if dtype is None and device is not None:
110+
# numpy default dtype may differ; if so, adjust the dtype---
111+
# unless `obj` is already an array, potentially from some other library.
112+
# In the latter case, `obj.dtype` is a thing, numpy has already done the
113+
# conversion and `res.dtype` is the numpy analog of `obj.dtype`
114+
# (if numpy failed to convert `obj`, it has already raised an exception).
115+
if dtype is None and device is not None and not hasattr(obj, "dtype"):
112116
res_dtype = DType(res.dtype)
113117
# The dtype selected by Numpy might not be the default dtype
114118
# on this device. We thus find the default dtype for the dtype "kind", and

array_api_strict/tests/test_creation_functions.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,13 @@ def test_asarray_device_2():
368368
assert y.dtype == float64
369369

370370

371+
def test_asarray_device_1():
372+
# regression test for https://github.com/data-apis/array-api-strict/issues/222
373+
x = np.ones(3, dtype=np.float32)
374+
y = asarray(x, device=Device('device1'))
375+
assert y.dtype == float32
376+
377+
371378
@pytest.mark.parametrize("api_version", ['2021.12', '2022.12', '2023.12'])
372379
def from_dlpack_2023_12(api_version):
373380
if api_version != '2022.12':

0 commit comments

Comments
 (0)