diff --git a/CHANGELOG.md b/CHANGELOG.md index ff154638f9..ce01d083d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* `dpctl.tensor.usm_ndarray` object allows implicit conversions to NumPy array changing implementation from [gh-1964](https://github.com/IntelPython/dpctl/pull/1964) for a more user-friendly behavior [gh-2131](https://github.com/IntelPython/dpctl/pull/2131). + ### Fixed ### Maintenance diff --git a/dpctl/tensor/_copy_utils.py b/dpctl/tensor/_copy_utils.py index 3af5ebbe19..e23ba6eb6f 100644 --- a/dpctl/tensor/_copy_utils.py +++ b/dpctl/tensor/_copy_utils.py @@ -41,26 +41,7 @@ def _copy_to_numpy(ary): if not isinstance(ary, dpt.usm_ndarray): raise TypeError(f"Expected dpctl.tensor.usm_ndarray, got {type(ary)}") - if ary.size == 0: - # no data needs to be copied for zero sized array - return np.ndarray(ary.shape, dtype=ary.dtype) - nb = ary.usm_data.nbytes - q = ary.sycl_queue - hh = dpm.MemoryUSMHost(nb, queue=q) - h = np.ndarray(nb, dtype="u1", buffer=hh).view(ary.dtype) - itsz = ary.itemsize - strides_bytes = tuple(si * itsz for si in ary.strides) - offset = ary._element_offset * itsz - # ensure that content of ary.usm_data is final - q.wait() - hh.copy_from_device(ary.usm_data) - return np.ndarray( - ary.shape, - dtype=ary.dtype, - buffer=h, - strides=strides_bytes, - offset=offset, - ) + return ary.__array__() def _copy_from_numpy(np_ary, usm_type="device", sycl_queue=None): diff --git a/dpctl/tensor/_usmarray.pyx b/dpctl/tensor/_usmarray.pyx index 0e42b42faf..a418072dd9 100644 --- a/dpctl/tensor/_usmarray.pyx +++ b/dpctl/tensor/_usmarray.pyx @@ -1585,20 +1585,35 @@ cdef class usm_ndarray: return usm_ndarray_repr(self) def __array__(self, dtype=None, /, *, copy=None): - """NumPy's array protocol method to disallow implicit conversion. + if copy is False: + raise TypeError("dpctl.tensors must copy data from device") - Without this definition, `numpy.asarray(usm_ar)` converts - usm_ndarray instance into NumPy array with data type `object` - and every element being 0d usm_ndarray. + # it is assumed that copy=None requires a copy due to + # the change of a dpctl dtype to a NumPy dtype therefore + # not violating the NumPy standard for the __array__ + # method. - https://github.com/IntelPython/dpctl/pull/1384#issuecomment-1707212972 - """ - raise TypeError( - "Implicit conversion to a NumPy array is not allowed. " - "Use `dpctl.tensor.asnumpy` to copy data from this " - "`dpctl.tensor.usm_ndarray` instance to NumPy array" + if self.size == 0: + # no data needs to be copied for zero sized array + return np.ndarray(self.shape, dtype=self.dtype) + nb = self.usm_data.nbytes + q = self.sycl_queue + hh = dpmem.MemoryUSMHost(nb, queue=q) + h = np.ndarray(nb, dtype="u1", buffer=hh).view(self.dtype) + itsz = self.itemsize + strides_bytes = tuple(si * itsz for si in self.strides) + offset = self._element_offset * itsz + # ensure that content of ary.usm_data is final + q.wait() + hh.copy_from_device(self.usm_data) + ndarray = np.ndarray( + self.shape, + dtype=self.dtype, + buffer=h, + strides=strides_bytes, + offset=offset, ) - + return ndarray if dtype is None else ndarray.astype(dtype) cdef usm_ndarray _real_view(usm_ndarray ary): """ diff --git a/dpctl/tests/test_usm_ndarray_ctor.py b/dpctl/tests/test_usm_ndarray_ctor.py index df55dcfc48..25d083ca2d 100644 --- a/dpctl/tests/test_usm_ndarray_ctor.py +++ b/dpctl/tests/test_usm_ndarray_ctor.py @@ -2768,10 +2768,20 @@ def test_setitem_copy_as_contig_alignment(dt): assert dpt.all(x[1:, :] == vals) -def test_asarray_property(): - get_queue_or_skip() +@pytest.mark.parametrize("dt", _all_dtypes) +def test_asarray_property(dt): + q = get_queue_or_skip() + + dtype_ = dpt.dtype(dt) + n0, n1 = 8, 23 + + x = dpt.eye(n0, n1, dtype=dtype_, sycl_queue=q) + x_np = np.eye(n0, n1, dtype=dt) - x = dpt.ones(11, dtype="i4") + # test __array__ attribute + x_cvt = np.asarray(x) + np.testing.assert_array_equal(x_np, x_cvt) + # test that copy=False is not supported with pytest.raises(TypeError): - np.asarray(x) + np.asarray(x, copy=False)