From 9c649807e84532ebfd9e2782f477b38a4332074f Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 5 Jul 2024 16:02:10 +0200 Subject: [PATCH 01/13] Remove limitations from dpnp.take implementation --- dpnp/dpnp_array.py | 2 +- dpnp/dpnp_iface.py | 2 + dpnp/dpnp_iface_indexing.py | 123 ++-- .../cupy/core_tests/test_ndarray.py | 690 ++++++++++++++++++ .../cupy/indexing_tests/test_indexing.py | 6 +- 5 files changed, 776 insertions(+), 47 deletions(-) create mode 100644 tests/third_party/cupy/core_tests/test_ndarray.py diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index d9936872a89..d6cdfac6963 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -1399,7 +1399,7 @@ def swapaxes(self, axis1, axis2): return dpnp.swapaxes(self, axis1=axis1, axis2=axis2) - def take(self, indices, /, *, axis=None, out=None, mode="wrap"): + def take(self, indices, axis=None, out=None, mode="wrap"): """ Take elements from an array along an axis. diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 3402f7d23a8..c29aaa778a2 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -579,9 +579,11 @@ def get_result_array(a, out=None, casting="safe"): """ if out is None: + synchronize_array_data(a) return a if a is out: + synchronize_array_data(a) return out dpnp.check_supported_arrays_type(out) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 20a046c82c1..d9e69b11685 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -37,6 +37,8 @@ """ +import operator + import dpctl.tensor as dpt import numpy from numpy.core.numeric import normalize_axis_index @@ -1247,41 +1249,55 @@ def select(condlist, choicelist, default=0): # pylint: disable=redefined-outer-name -def take(x, indices, /, *, axis=None, out=None, mode="wrap"): +def take(a, indices, /, *, axis=None, out=None, mode="wrap"): """ Take elements from an array along an axis. + When `axis` is not ``None``, this function does the same thing as "fancy" + indexing (indexing arrays using arrays); however, it can be easier to use + if you need elements along a given axis. A call such as + ``dpnp.take(a, indices, axis=3)`` is equivalent to + ``a[:, :, :,i ndices, ...]``. + For full documentation refer to :obj:`numpy.take`. + Parameters + ---------- + a : {dpnp.ndarray, usm_ndarray}, (Ni..., M, Nk...) + The source array. + indices : {array_like, scalars}, (Nj...) + The indices of the values to extract. + Also allow scalars for `indices`. + axis : {None, int}, optional + The axis over which to select values. By default, the flattened + input array is used. + Default: ``None``. + out : {dpnp.ndarray, usm_ndarray}, optional (Ni..., Nj..., Nk...) + If provided, the result will be placed in this array. It should + be of the appropriate shape and dtype. + Default: ``None``. + mode : {"wrap", "clip"}, optional + Specifies how out-of-bounds indices will be handled. Possible values + are: + + - ``"wrap"``: clamps indices to (``-n <= i < n``), then wraps + negative indices. + - ``"clip"``: clips indices to (``0 <= i < n``). + + Default: ``"wrap"``. + Returns ------- - out : dpnp.ndarray - An array with shape x.shape[:axis] + indices.shape + x.shape[axis + 1:] - filled with elements from `x`. - - Limitations - ----------- - Parameters `x` and `indices` are supported either as :class:`dpnp.ndarray` - or :class:`dpctl.tensor.usm_ndarray`. - Parameter `indices` is supported as 1-D array of integer data type. - Parameter `out` is supported only with default value. - Parameter `mode` is supported with ``wrap``, the default, and ``clip`` - values. - Providing parameter `axis` is optional when `x` is a 1-D array. - Otherwise the function will be executed sequentially on CPU. + out : dpnp.ndarray, (Ni..., Nj..., Nk...) + The returned array has the same type as `a`. See Also -------- :obj:`dpnp.compress` : Take elements using a boolean mask. + :obj:`dpnp.ndarray.take` : Equivalent method. :obj:`dpnp.take_along_axis` : Take elements by matching the array and the index arrays. - Notes - ----- - How out-of-bounds indices will be handled. - "wrap" - clamps indices to (-n <= i < n), then wraps negative indices. - "clip" - clips indices to (0 <= i < n) - Examples -------- >>> import dpnp as np @@ -1302,29 +1318,54 @@ def take(x, indices, /, *, axis=None, out=None, mode="wrap"): >>> np.take(x, indices, mode="clip") array([4, 4, 4, 8, 8]) + If `indices` is not one dimensional, the output also has these dimensions. + + >>> np.take(x, [[0, 1], [2, 3]]) + array([[4, 3], + [5, 7]]) + """ - if dpnp.is_supported_array_type(x) and dpnp.is_supported_array_type( - indices - ): - if indices.ndim != 1 or not dpnp.issubdtype( - indices.dtype, dpnp.integer - ): - pass - elif axis is None and x.ndim > 1: - pass - elif out is not None: - pass - elif mode not in ("clip", "wrap"): - pass - else: - dpt_array = dpnp.get_usm_ndarray(x) - dpt_indices = dpnp.get_usm_ndarray(indices) - return dpnp_array._create_from_usm_ndarray( - dpt.take(dpt_array, dpt_indices, axis=axis, mode=mode) - ) + if mode not in ("wrap", "clip"): + raise ValueError(f"`mode` must be 'wrap' or 'clip', but got `{mode}`.") + + usm_a = dpnp.get_usm_ndarray(a) + if not dpnp.is_supported_array_type(indices): + usm_ind = dpt.asarray( + indices, usm_type=a.usm_type, sycl_queue=a.sycl_queue + ) + else: + usm_ind = dpnp.get_usm_ndarray(indices) + + a_ndim = a.ndim + if axis is None: + res_shape = usm_ind.shape + + if a_ndim > 1: + # dpt.take requires flattened input array + usm_a = dpt.reshape(usm_a, -1) + elif a_ndim == 0: + axis = normalize_axis_index(operator.index(axis), 1) + res_shape = usm_ind.shape + else: + axis = normalize_axis_index(operator.index(axis), a_ndim) + a_sh = a.shape + res_shape = a_sh[:axis] + usm_ind.shape + a_sh[axis + 1 :] + + if usm_ind.ndim != 1: + # dpt.take supports only 1-D array of indices + usm_ind = dpt.reshape(usm_ind, -1) + + if not dpnp.issubdtype(usm_ind.dtype, dpnp.integer): + # dpt.take supports only integer dtype for array of indices + usm_ind = dpt.astype(usm_ind, dpnp.intp, copy=False) + + usm_res = dpt.take(usm_a, usm_ind, axis=axis, mode=mode) + if res_shape is not None: + usm_res = dpt.reshape(usm_res, res_shape) - return call_origin(numpy.take, x, indices, axis, out, mode) + result = dpnp_array._create_from_usm_ndarray(usm_res) + return dpnp.get_result_array(result, out) def take_along_axis(a, indices, axis): diff --git a/tests/third_party/cupy/core_tests/test_ndarray.py b/tests/third_party/cupy/core_tests/test_ndarray.py new file mode 100644 index 00000000000..4d39767daa6 --- /dev/null +++ b/tests/third_party/cupy/core_tests/test_ndarray.py @@ -0,0 +1,690 @@ +import copy +import unittest + +import dpctl +import numpy +import pytest + +import dpnp as cupy +from tests.third_party.cupy import testing + + +def get_array_module(*args): + for arg in args: + if isinstance(arg, cupy.ndarray): + return cupy + return numpy + + +def wrap_take(array, *args, **kwargs): + if get_array_module(array) == numpy: + kwargs["mode"] = "wrap" + + return array.take(*args, **kwargs) + + +class TestNdarrayInit(unittest.TestCase): + @pytest.mark.skip("passing 'None' into shape arguments is not supported") + def test_shape_none(self): + with testing.assert_warns(DeprecationWarning): + a = cupy.ndarray(None) + assert a.shape == () + + def test_shape_int(self): + a = cupy.ndarray(3) + assert a.shape == (3,) + + def test_shape_not_integer(self): + for xp in (numpy, cupy): + with pytest.raises(TypeError): + xp.ndarray(1.0) + with pytest.raises(TypeError): + xp.ndarray((1.0,)) + + @pytest.mark.skip("passing buffer as dpnp array is not supported") + def test_shape_int_with_strides(self): + dummy = cupy.ndarray(3) + a = cupy.ndarray(3, strides=(0,), buffer=dummy) + assert a.shape == (3,) + assert a.strides == (0,) + + @pytest.mark.skip("passing buffer as dpnp array is not supported") + def test_memptr(self): + a = cupy.arange(6).astype(numpy.float32).reshape((2, 3)) + memptr = a + + b = cupy.ndarray((2, 3), numpy.float32, buffer=memptr) + testing.assert_array_equal(a, b) + + b += 1 + testing.assert_array_equal(a, b) + + @pytest.mark.skip("self-overlapping strides are not supported") + def test_memptr_with_strides(self): + buf = cupy.ndarray(20, numpy.uint8) + memptr = buf + + # self-overlapping strides + a = cupy.ndarray((2, 3), numpy.float32, buffer=memptr, strides=(8, 4)) + assert a.strides == (8, 4) + + a[:] = 1 + a[0, 2] = 4 + assert float(a[1, 0]) == 4 + + @pytest.mark.skip("no exception raised by dpctl") + def test_strides_without_memptr(self): + for xp in (numpy, cupy): + with pytest.raises(ValueError): + xp.ndarray((2, 3), numpy.float32, strides=(20, 4)) + + @pytest.mark.skip("passing buffer as dpnp array is not supported") + def test_strides_is_given_and_order_is_ignored(self): + buf = cupy.ndarray(20, numpy.uint8) + a = cupy.ndarray((2, 3), numpy.float32, buf, strides=(2, 1), order="C") + assert a.strides == (2, 1) + + @pytest.mark.skip("dpctl-1724 issue") + @testing.with_requires("numpy>=1.19") + def test_strides_is_given_but_order_is_invalid(self): + for xp in (numpy, cupy): + with pytest.raises(ValueError): + xp.ndarray((2, 3), numpy.float32, strides=(2, 1), order="!") + + def test_order(self): + shape = (2, 3, 4) + a = cupy.ndarray(shape, order="F") + a_cpu = numpy.ndarray(shape, order="F", dtype=a.dtype) + assert all( + i * a.itemsize == j for i, j in zip(a.strides, a_cpu.strides) + ) + assert a.flags.f_contiguous + assert not a.flags.c_contiguous + + @pytest.mark.skip("passing 'None' into order arguments is not supported") + def test_order_none(self): + shape = (2, 3, 4) + a = cupy.ndarray(shape, order=None) + a_cpu = numpy.ndarray(shape, order=None, dtype=a.dtype) + assert a.flags.c_contiguous == a_cpu.flags.c_contiguous + assert a.flags.f_contiguous == a_cpu.flags.f_contiguous + assert all( + i * a.itemsize == j for i, j in zip(a.strides, a_cpu.strides) + ) + + @pytest.mark.skip("__slots__ is not supported") + def test_slots(self): + # Test for #7883. + a = cupy.ndarray((2, 3)) + with pytest.raises(AttributeError): + a.custom_attr = 100 + + class UserNdarray(cupy.ndarray): + pass + + b = UserNdarray((2, 3)) + b.custom_attr = 100 + + +@testing.parameterize( + *testing.product( + { + "shape": [(), (1,), (1, 2), (1, 2, 3)], + "order": ["C", "F"], + "dtype": [ + numpy.uint8, # itemsize=1 + numpy.uint16, # itemsize=2 + ], + } + ) +) +@pytest.mark.skip("strides may vary") +class TestNdarrayInitStrides(unittest.TestCase): + # Check the strides given shape, itemsize and order. + @testing.numpy_cupy_equal() + def test_strides(self, xp): + arr = xp.ndarray(self.shape, dtype=self.dtype, order=self.order) + return (arr.strides, arr.flags.c_contiguous, arr.flags.f_contiguous) + + +class TestNdarrayInitRaise(unittest.TestCase): + def test_unsupported_type(self): + arr = numpy.ndarray((2, 3), dtype=object) + with pytest.raises(TypeError): + cupy.array(arr) + + @pytest.mark.skip("no ndim limit") + def test_excessive_ndim(self): + for xp in (numpy, cupy): + with pytest.raises(ValueError): + xp.ndarray(shape=[1 for i in range(33)], dtype=xp.int8) + + +@testing.parameterize( + *testing.product( + { + "shape": [(), (0,), (1,), (0, 0, 2), (2, 3)], + } + ) +) +@pytest.mark.skip("deepcopy() is not supported") +class TestNdarrayDeepCopy(unittest.TestCase): + def _check_deepcopy(self, arr, arr2): + assert arr.data is not arr2.data + assert arr.shape == arr2.shape + assert arr.size == arr2.size + assert arr.dtype == arr2.dtype + assert arr.strides == arr2.strides + testing.assert_array_equal(arr, arr2) + + def test_deepcopy(self): + arr = _core.ndarray(self.shape) + arr2 = copy.deepcopy(arr) + self._check_deepcopy(arr, arr2) + + @testing.multi_gpu(2) + def test_deepcopy_multi_device(self): + arr = _core.ndarray(self.shape) + with cuda.Device(1): + arr2 = copy.deepcopy(arr) + self._check_deepcopy(arr, arr2) + assert arr2.device == arr.device + + +_test_copy_multi_device_with_stream_src = r""" +extern "C" __global__ +void wait_and_write(long long *x) { + clock_t start = clock(); + clock_t now; + for (;;) { + now = clock(); + clock_t cycles = now > start ? now - start : now + (0xffffffff - start); + if (cycles >= 1000000000) { + break; + } + } + x[0] = 1; + x[1] = now; // in case the compiler optimizing away the entire loop +} +""" + + +@pytest.mark.skip() +class TestNdarrayCopy: + @testing.multi_gpu(2) + @testing.for_orders("CFA") + def test_copy_multi_device_non_contiguous(self, order): + arr = cupy.ndarray((20,))[::2] + dev1 = dpctl.SyclDevice() + arr2 = arr.copy(order, device=dev1) + assert arr2.device == dev1 + testing.assert_array_equal(arr, arr2) + + @testing.multi_gpu(2) + def test_copy_multi_device_non_contiguous_K(self): + arr = _core.ndarray((20,))[::2] + with cuda.Device(1): + with pytest.raises(NotImplementedError): + arr.copy("K") + + # See cupy/cupy#5004 + @testing.multi_gpu(2) + def test_copy_multi_device_with_stream(self): + # Kernel that takes long enough then finally writes values. + src = _test_copy_multi_device_with_stream_src + if runtime.is_hip and driver.get_build_version() >= 5_00_00000: + src = "#include \n" + src + kern = cupy.RawKernel(src, "wait_and_write") + + # Allocates a memory and launches the kernel on a device with its + # stream. + with cuda.Device(0): + # Keep this stream alive over the D2D copy below for HIP + with cuda.Stream() as s1: + a = cupy.zeros((2,), dtype=numpy.uint64) + kern((1,), (1,), a) + + # D2D copy to another device with another stream should get the + # original values of the memory before the kernel on the first device + # finally makes the write. + with cuda.Device(1): + with cuda.Stream(): + b = a.copy() + testing.assert_array_equal( + b, numpy.array([0, 0], dtype=numpy.uint64) + ) + + +@pytest.mark.skip() +class TestNdarrayShape(unittest.TestCase): + @testing.numpy_cupy_array_equal() + def test_shape_set(self, xp): + arr = xp.ndarray((2, 3)) + arr.shape = (3, 2) + return xp.array(arr.shape) + + @testing.numpy_cupy_array_equal() + def test_shape_set_infer(self, xp): + arr = xp.ndarray((2, 3)) + arr.shape = (3, -1) + return xp.array(arr.shape) + + @testing.numpy_cupy_array_equal() + def test_shape_set_int(self, xp): + arr = xp.ndarray((2, 3)) + arr.shape = 6 + return xp.array(arr.shape) + + def test_shape_need_copy(self): + # from cupy/cupy#5470 + for xp in (numpy, cupy): + arr = xp.ndarray((2, 3), order="F") + with pytest.raises(AttributeError) as e: + arr.shape = (3, 2) + assert "incompatible shape" in str(e.value).lower() + + +@pytest.mark.skip("CUDA interface is not supported") +class TestNdarrayCudaInterface(unittest.TestCase): + def test_cuda_array_interface(self): + arr = cupy.zeros(shape=(2, 3), dtype=cupy.float64) + iface = arr.__cuda_array_interface__ + assert iface["version"] == 3 + assert set(iface.keys()) == set( + [ + "shape", + "typestr", + "data", + "version", + "descr", + "stream", + "strides", + ] + ) + assert iface["shape"] == (2, 3) + assert iface["typestr"] == " Date: Fri, 5 Jul 2024 17:50:04 +0200 Subject: [PATCH 02/13] Add more test to cover specail cases and increase code coverage --- dpnp/dpnp_iface_indexing.py | 5 +- tests/test_indexing.py | 133 +++++++++++++++++++++++------------- 2 files changed, 87 insertions(+), 51 deletions(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index d9e69b11685..3213a22a662 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -1358,11 +1358,10 @@ def take(a, indices, /, *, axis=None, out=None, mode="wrap"): if not dpnp.issubdtype(usm_ind.dtype, dpnp.integer): # dpt.take supports only integer dtype for array of indices - usm_ind = dpt.astype(usm_ind, dpnp.intp, copy=False) + usm_ind = dpt.astype(usm_ind, dpnp.intp, copy=False, casting="safe") usm_res = dpt.take(usm_a, usm_ind, axis=axis, mode=mode) - if res_shape is not None: - usm_res = dpt.reshape(usm_res, res_shape) + usm_res = dpt.reshape(usm_res, res_shape) result = dpnp_array._create_from_usm_ndarray(usm_res) return dpnp.get_result_array(result, out) diff --git a/tests/test_indexing.py b/tests/test_indexing.py index 8b54bc482ce..9df1c61cdac 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -535,6 +535,91 @@ def test_broadcast(self, arr_dt, idx_dt): assert_array_equal(np_a, dp_a) +class TestTake: + @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize("ind_dt", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize( + "indices", [[-2, 2], [-5, 4]], ids=["[-2, 2]", "[-5, 4]"] + ) + @pytest.mark.parametrize("mode", ["clip", "wrap"]) + def test_1d(self, a_dt, ind_dt, indices, mode): + a = numpy.array([-2, -1, 0, 1, 2], dtype=a_dt) + ind = numpy.array(indices, dtype=ind_dt) + ia, iind = dpnp.array(a), dpnp.array(ind) + + if numpy.can_cast(ind_dt, numpy.intp, casting="safe"): + result = dpnp.take(ia, iind, mode=mode) + expected = numpy.take(a, ind, mode=mode) + assert_array_equal(result, expected) + else: + assert_raises(TypeError, ia, iind, mode=mode) + assert_raises(TypeError, a, ind, mode=mode) + + + @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize("ind_dt", get_integer_dtypes()) + @pytest.mark.parametrize( + "indices", [[-1, 0], [-3, 2]], ids=["[-1, 0]", "[-3, 2]"] + ) + @pytest.mark.parametrize("mode", ["clip", "wrap"]) + @pytest.mark.parametrize("axis", [0, 1], ids=["0", "1"]) + def test_2d(self, a_dt, ind_dt, indices, mode, axis): + a = numpy.array([[-1, 0, 1], [-2, -3, -4], [2, 3, 4]], dtype=a_dt) + ind = numpy.array(indices, dtype=ind_dt) + ia, iind = dpnp.array(a), dpnp.array(ind) + + result = ia.take(iind, axis=axis, mode=mode) + expected = a.take(ind, axis=axis, mode=mode) + assert_array_equal(result, expected) + + + @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize("indices", [[-5, 5]], ids=["[-5, 5]"]) + @pytest.mark.parametrize("mode", ["clip", "wrap"]) + def test_over_index(self, a_dt, indices, mode): + a = numpy.array([-2, -1, 0, 1, 2], dtype=a_dt) + ind = numpy.array(indices, dtype=numpy.intp) + ia, iind = dpnp.array(a), dpnp.array(ind) + + result = dpnp.take(ia, iind, mode=mode) + expected = numpy.take(a, ind, mode=mode) + assert_array_equal(result, expected) + + @pytest.mark.parametrize("xp", [numpy, dpnp]) + @pytest.mark.parametrize("indices", [[0], [1]], ids=["[0]", "[1]"]) + @pytest.mark.parametrize("mode", ["clip", "wrap"]) + def test_index_error(self, xp, indices, mode): + # take from a 0-length dimension + a = xp.empty((2, 3, 0, 4)) + assert_raises(IndexError, a.take, indices, axis=2, mode=mode) + + def test_bool_axis(self): + a = numpy.array([[[1]]]) + ia = dpnp.array(a) + + result = ia.take([0], axis=False) + expected = a.take([0], axis=0) # numpy raises an error for bool axis + assert_array_equal(result, expected) + + def test_axis_as_array(self): + a = numpy.array([[[1]]]) + ia = dpnp.array(a) + + result = ia.take([0], axis=ia) + expected = a.take([0], axis=1) # numpy raises an error for bool axis + assert_array_equal(result, expected) + + def test_mode_raise(self): + a = dpnp.array([[1, 2], [3, 4]]) + assert_raises(ValueError, a.take, [-1, 4], mode="raise") + + @pytest.mark.parametrize("xp", [numpy, dpnp]) + def test_unicode_mode(self, xp): + a = xp.arange(10) + k = b'\xc3\xa4'.decode("UTF8") + assert_raises(ValueError, a.take, 5, mode=k) + + class TestTakeAlongAxis: @pytest.mark.parametrize( "func, argfunc, kwargs", @@ -964,54 +1049,6 @@ def test_select(): assert_array_equal(expected, result) -@pytest.mark.parametrize("array_type", get_all_dtypes()) -@pytest.mark.parametrize( - "indices_type", [numpy.int32, numpy.int64], ids=["int32", "int64"] -) -@pytest.mark.parametrize( - "indices", [[-2, 2], [-5, 4]], ids=["[-2, 2]", "[-5, 4]"] -) -@pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) -def test_take_1d(indices, array_type, indices_type, mode): - a = numpy.array([-2, -1, 0, 1, 2], dtype=array_type) - ind = numpy.array(indices, dtype=indices_type) - ia = dpnp.array(a) - iind = dpnp.array(ind) - expected = numpy.take(a, ind, mode=mode) - result = dpnp.take(ia, iind, mode=mode) - assert_array_equal(expected, result) - - -@pytest.mark.parametrize("array_type", get_all_dtypes()) -@pytest.mark.parametrize( - "indices_type", [numpy.int32, numpy.int64], ids=["int32", "int64"] -) -@pytest.mark.parametrize( - "indices", [[-1, 0], [-3, 2]], ids=["[-1, 0]", "[-3, 2]"] -) -@pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) -@pytest.mark.parametrize("axis", [0, 1], ids=["0", "1"]) -def test_take_2d(indices, array_type, indices_type, axis, mode): - a = numpy.array([[-1, 0, 1], [-2, -3, -4], [2, 3, 4]], dtype=array_type) - ind = numpy.array(indices, dtype=indices_type) - ia = dpnp.array(a) - iind = dpnp.array(ind) - expected = numpy.take(a, ind, axis=axis, mode=mode) - result = dpnp.take(ia, iind, axis=axis, mode=mode) - assert_array_equal(expected, result) - - -@pytest.mark.parametrize("array_type", get_all_dtypes()) -@pytest.mark.parametrize("indices", [[-5, 5]], ids=["[-5, 5]"]) -@pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) -def test_take_over_index(indices, array_type, mode): - a = dpnp.array([-2, -1, 0, 1, 2], dtype=array_type) - ind = dpnp.array(indices, dtype=dpnp.int64) - expected = dpnp.array([-2, 2], dtype=a.dtype) - result = dpnp.take(a, ind, mode=mode) - assert_array_equal(expected, result) - - @pytest.mark.parametrize( "m", [None, 0, 1, 2, 3, 4], ids=["None", "0", "1", "2", "3", "4"] ) From a89136e92d7eb34d961e007792719886b949c6da Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 5 Jul 2024 17:54:49 +0200 Subject: [PATCH 03/13] Applied pre-commit hook --- tests/test_indexing.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/test_indexing.py b/tests/test_indexing.py index 9df1c61cdac..5293ea281b0 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -555,7 +555,6 @@ def test_1d(self, a_dt, ind_dt, indices, mode): assert_raises(TypeError, ia, iind, mode=mode) assert_raises(TypeError, a, ind, mode=mode) - @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) @pytest.mark.parametrize("ind_dt", get_integer_dtypes()) @pytest.mark.parametrize( @@ -572,7 +571,6 @@ def test_2d(self, a_dt, ind_dt, indices, mode, axis): expected = a.take(ind, axis=axis, mode=mode) assert_array_equal(result, expected) - @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) @pytest.mark.parametrize("indices", [[-5, 5]], ids=["[-5, 5]"]) @pytest.mark.parametrize("mode", ["clip", "wrap"]) @@ -598,7 +596,7 @@ def test_bool_axis(self): ia = dpnp.array(a) result = ia.take([0], axis=False) - expected = a.take([0], axis=0) # numpy raises an error for bool axis + expected = a.take([0], axis=0) # numpy raises an error for bool axis assert_array_equal(result, expected) def test_axis_as_array(self): @@ -606,7 +604,7 @@ def test_axis_as_array(self): ia = dpnp.array(a) result = ia.take([0], axis=ia) - expected = a.take([0], axis=1) # numpy raises an error for bool axis + expected = a.take([0], axis=1) # numpy raises an error for bool axis assert_array_equal(result, expected) def test_mode_raise(self): @@ -616,7 +614,7 @@ def test_mode_raise(self): @pytest.mark.parametrize("xp", [numpy, dpnp]) def test_unicode_mode(self, xp): a = xp.arange(10) - k = b'\xc3\xa4'.decode("UTF8") + k = b"\xc3\xa4".decode("UTF8") assert_raises(ValueError, a.take, 5, mode=k) From f0548185e45813112abd1bd0a206a8faf0cda216 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 5 Jul 2024 20:55:14 +0200 Subject: [PATCH 04/13] Corrected test_over_index --- tests/test_indexing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_indexing.py b/tests/test_indexing.py index 5293ea281b0..18129a84d14 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -575,12 +575,11 @@ def test_2d(self, a_dt, ind_dt, indices, mode, axis): @pytest.mark.parametrize("indices", [[-5, 5]], ids=["[-5, 5]"]) @pytest.mark.parametrize("mode", ["clip", "wrap"]) def test_over_index(self, a_dt, indices, mode): - a = numpy.array([-2, -1, 0, 1, 2], dtype=a_dt) - ind = numpy.array(indices, dtype=numpy.intp) - ia, iind = dpnp.array(a), dpnp.array(ind) + a = dpnp.array([-2, -1, 0, 1, 2], dtype=a_dt) + ind = dpnp.array(indices, dtype=numpy.intp) - result = dpnp.take(ia, iind, mode=mode) - expected = numpy.take(a, ind, mode=mode) + result = dpnp.take(a, ind, mode=mode) + expected = dpnp.array([-2, 2], dtype=a.dtype) assert_array_equal(result, expected) @pytest.mark.parametrize("xp", [numpy, dpnp]) From 7e87cef53174b298f389e5ea52d5841f44ad43a9 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 7 Jul 2024 13:19:21 +0200 Subject: [PATCH 05/13] Update docsctrings with resolving typos --- dpnp/dpnp_iface_indexing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 3213a22a662..ca62b1d28d8 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -1257,7 +1257,7 @@ def take(a, indices, /, *, axis=None, out=None, mode="wrap"): indexing (indexing arrays using arrays); however, it can be easier to use if you need elements along a given axis. A call such as ``dpnp.take(a, indices, axis=3)`` is equivalent to - ``a[:, :, :,i ndices, ...]``. + ``a[:, :, :, indices, ...]``. For full documentation refer to :obj:`numpy.take`. @@ -1281,7 +1281,7 @@ def take(a, indices, /, *, axis=None, out=None, mode="wrap"): are: - ``"wrap"``: clamps indices to (``-n <= i < n``), then wraps - negative indices. + negative indices. - ``"clip"``: clips indices to (``0 <= i < n``). Default: ``"wrap"``. From cbb718881439840cd763cba75ed93c9b0bbec6dc Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 9 Jul 2024 14:46:18 +0200 Subject: [PATCH 06/13] Use dpnp.reshape() to change shape and create dpnp array from usm_ndarray result --- dpnp/dpnp_iface_indexing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index ca62b1d28d8..c5c7f96d3a7 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -1361,9 +1361,9 @@ def take(a, indices, /, *, axis=None, out=None, mode="wrap"): usm_ind = dpt.astype(usm_ind, dpnp.intp, copy=False, casting="safe") usm_res = dpt.take(usm_a, usm_ind, axis=axis, mode=mode) - usm_res = dpt.reshape(usm_res, res_shape) - result = dpnp_array._create_from_usm_ndarray(usm_res) + # need to reshape the result if shape of indices array was changed + result = dpnp.reshape(usm_res, res_shape) return dpnp.get_result_array(result, out) From c170c1921d9a8336338f2a10db6d305a2a4c2031 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 7 Jul 2024 14:35:45 +0200 Subject: [PATCH 07/13] Remove limitations from dpnp.place implementation --- dpnp/dpnp_iface.py | 44 ++++++++++ dpnp/dpnp_iface_arraycreation.py | 4 +- dpnp/dpnp_iface_histograms.py | 8 +- dpnp/dpnp_iface_indexing.py | 86 ++++++++++++------- .../cupy/indexing_tests/test_insert.py | 3 +- 5 files changed, 106 insertions(+), 39 deletions(-) diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index c29aaa778a2..d4ec550694d 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -57,6 +57,7 @@ "array_equal", "asnumpy", "astype", + "as_usm_ndarray", "check_limitations", "check_supported_arrays_type", "convert_single_elem_array_to_scalar", @@ -247,6 +248,49 @@ def astype(x1, dtype, order="K", casting="unsafe", copy=True, device=None): return dpnp_array._create_from_usm_ndarray(array_obj) +def as_usm_ndarray(a, device=None, usm_type=None, sycl_queue=None): + """ + Return :class:`dpctl.tensor.usm_ndarray` from input object `a`. + + Parameters + ---------- + a : {array_like, scalar} + Input array or scalar. + device : {None, string, SyclDevice, SyclQueue}, optional + An array API concept of device where the result array is created if + required. + The `device` can be ``None`` (the default), an OneAPI filter selector + string, an instance of :class:`dpctl.SyclDevice` corresponding to + a non-partitioned SYCL device, an instance of :class:`dpctl.SyclQueue`, + or a `Device` object returned by + :obj:`dpnp.dpnp_array.dpnp_array.device` property. + Default: ``None``. + usm_type : {None, "device", "shared", "host"}, optional + The type of SYCL USM allocation for the result array if new array + is created. + Default: ``None``. + sycl_queue : {None, SyclQueue}, optional + A SYCL queue to use for result array allocation if required. + Default: ``None``. + + Returns + ------- + out : usm_ndarray + A dpctl USM ndarray from input array or scalar `a`. + If `a` is instance of :class:`dpnp.ndarray` + or :class:`dpctl.tensor.usm_ndarray`, no array allocation will be done + and `device`, `usm_type`, `sycl_queue` keywords will be ignored. + + """ + + if is_supported_array_type(a): + return get_usm_ndarray(a) + + return dpt.asarray( + a, device=device, usm_type=usm_type, sycl_queue=sycl_queue + ) + + def check_limitations( order=None, subok=False, like=None, initial=None, where=True ): diff --git a/dpnp/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index 6698f3f782e..5c8e4ad99f8 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -3498,9 +3498,7 @@ def vander( [125, 25, 5, 1]]), Device(level_zero:gpu:0), 'host') """ - if dpnp.is_supported_array_type(x): - x = dpnp.get_usm_ndarray(x) - usm_x = dpt.asarray( + usm_x = dpnp.as_usm_ndarray( x, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) diff --git a/dpnp/dpnp_iface_histograms.py b/dpnp/dpnp_iface_histograms.py index 24c8b6aaf78..8d6a4c7e853 100644 --- a/dpnp/dpnp_iface_histograms.py +++ b/dpnp/dpnp_iface_histograms.py @@ -164,11 +164,9 @@ def _get_bin_edges(a, bins, range, usm_type): "a and bins must be allocated on the same SYCL queue" ) - bin_edges = dpnp.get_usm_ndarray(bins) - else: - bin_edges = dpt.asarray( - bins, sycl_queue=sycl_queue, usm_type=usm_type - ) + bin_edges = dpnp.as_usm_ndarray( + bin_edges, usm_type=usm_type, sycl_queue=sycl_queue + ) if dpnp.any(bin_edges[:-1] > bin_edges[1:]): raise ValueError( diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index c5c7f96d3a7..2be7b4e7694 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -551,12 +551,9 @@ def extract(condition, a): """ usm_a = dpnp.get_usm_ndarray(a) - if not dpnp.is_supported_array_type(condition): - usm_cond = dpt.asarray( - condition, usm_type=a.usm_type, sycl_queue=a.sycl_queue - ) - else: - usm_cond = dpnp.get_usm_ndarray(condition) + usm_cond = dpnp.as_usm_ndarray( + condition, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + ) if usm_cond.size != usm_a.size: usm_a = dpt.reshape(usm_a, -1) @@ -1011,30 +1008,64 @@ def nonzero(a): ) -def place(x, mask, vals, /): +def place(a, mask, vals): """ Change elements of an array based on conditional and input values. + Similar to ``dpnp.copyto(a, vals, where=mask)``, the difference is that + :obj:`dpnp.place` uses the first N elements of `vals`, where N is + the number of ``True`` values in `mask`, while :obj:`copyto` uses + the elements where `mask` is ``True``. + + Note that :obj:`dpnp.extract` does the exact opposite of :obj:`dpnp.place`. + For full documentation refer to :obj:`numpy.place`. - Limitations - ----------- - Parameters `x`, `mask` and `vals` are supported either as - :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. - Otherwise the function will be executed sequentially on CPU. + Parameters + ---------- + a : {dpnp.ndarray, usm_ndarray} + Array to put data into. + mask : {array_like, scalar} + Boolean mask array. Must have the same size as `a`. + vals : {array_like, scalar} + Values to put into `a`. Only the first N elements are used, where N is + the number of ``True`` values in `mask`. If `vals` is smaller than N, + it will be repeated, and if elements of `a` are to be masked, this + sequence must be non-empty. + + See Also + -------- + obj:`dpnp.copyto` : Copies values from one array to another. + obj:`dpnp.put` : Replaces specified elements of an array with given values. + obj:`dpnp.take` : Take elements from an array along an axis. + obj:`dpnp.extract` : Return the elements of an array that satisfy some + condition. + + Examples + -------- + >>> import dpnp as np + >>> a = np.arange(6).reshape(2, 3) + >>> np.place(a, a > 2, [44, 55]) + >>> a + array([[ 0, 1, 2], + [44, 55, 44]]) + """ - if ( - dpnp.is_supported_array_type(x) - and dpnp.is_supported_array_type(mask) - and dpnp.is_supported_array_type(vals) - ): - dpt_array = x.get_array() if isinstance(x, dpnp_array) else x - dpt_mask = mask.get_array() if isinstance(mask, dpnp_array) else mask - dpt_vals = vals.get_array() if isinstance(vals, dpnp_array) else vals - return dpt.place(dpt_array, dpt_mask, dpt_vals) + usm_a = dpnp.get_usm_ndarray(a) + usm_mask = dpnp.as_usm_ndarray( + mask, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + ) + usm_vals = dpnp.as_usm_ndarray( + vals, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + ) + + if usm_vals.ndim != 1: + # dpt.place supports only 1-D array of values + usm_vals = dpt.reshape(usm_vals, -1) - return call_origin(numpy.place, x, mask, vals, dpnp_inplace=True) + dpt.place(usm_a, usm_mask, usm_vals) + dpnp.synchronize_array_data(usm_a) def put(a, ind, v, /, *, axis=None, mode="wrap"): @@ -1330,14 +1361,11 @@ def take(a, indices, /, *, axis=None, out=None, mode="wrap"): raise ValueError(f"`mode` must be 'wrap' or 'clip', but got `{mode}`.") usm_a = dpnp.get_usm_ndarray(a) - if not dpnp.is_supported_array_type(indices): - usm_ind = dpt.asarray( - indices, usm_type=a.usm_type, sycl_queue=a.sycl_queue - ) - else: - usm_ind = dpnp.get_usm_ndarray(indices) + usm_ind = dpnp.as_usm_ndarray( + indices, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + ) - a_ndim = a.ndim + a_ndim = usm_a.ndim if axis is None: res_shape = usm_ind.shape diff --git a/tests/third_party/cupy/indexing_tests/test_insert.py b/tests/third_party/cupy/indexing_tests/test_insert.py index 538039bd43c..1b355f8ad08 100644 --- a/tests/third_party/cupy/indexing_tests/test_insert.py +++ b/tests/third_party/cupy/indexing_tests/test_insert.py @@ -43,11 +43,10 @@ class TestPlaceRaises(unittest.TestCase): # https://github.com/numpy/numpy/pull/5821 @testing.with_requires("numpy>=1.10") @testing.for_all_dtypes() - @pytest.mark.usefixtures("allow_fall_back_on_numpy") def test_place_empty_value_error(self, dtype): for xp in (numpy, cupy): a = testing.shaped_arange(self.shape, xp, dtype) - mask = testing.shaped_arange(self.shape, xp, numpy.int_) % 2 == 0 + mask = testing.shaped_arange(self.shape, xp, int) % 2 == 0 vals = testing.shaped_random((0,), xp, dtype) with pytest.raises(ValueError): xp.place(a, mask, vals) From 2d87bfc6f2e833d18d7cf82953fd3770c8566a4b Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 7 Jul 2024 15:15:49 +0200 Subject: [PATCH 08/13] Update relating tests --- dpnp/dpnp_iface.py | 12 ++- dpnp/dpnp_iface_histograms.py | 2 +- dpnp/dpnp_iface_indexing.py | 25 ++++- tests/test_indexing.py | 174 ++++++++-------------------------- 4 files changed, 68 insertions(+), 145 deletions(-) diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index d4ec550694d..12654840685 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -248,7 +248,7 @@ def astype(x1, dtype, order="K", casting="unsafe", copy=True, device=None): return dpnp_array._create_from_usm_ndarray(array_obj) -def as_usm_ndarray(a, device=None, usm_type=None, sycl_queue=None): +def as_usm_ndarray(a, dtype=None, device=None, usm_type=None, sycl_queue=None): """ Return :class:`dpctl.tensor.usm_ndarray` from input object `a`. @@ -256,6 +256,11 @@ def as_usm_ndarray(a, device=None, usm_type=None, sycl_queue=None): ---------- a : {array_like, scalar} Input array or scalar. + dtype : {None, dtype}, optional + The desired dtype for the result array if new array is creating. If not + given, a default dtype will be used that can represent the values (by + considering Promotion Type Rule and device capabilities when necessary). + Default: ``None``. device : {None, string, SyclDevice, SyclQueue}, optional An array API concept of device where the result array is created if required. @@ -279,7 +284,8 @@ def as_usm_ndarray(a, device=None, usm_type=None, sycl_queue=None): A dpctl USM ndarray from input array or scalar `a`. If `a` is instance of :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`, no array allocation will be done - and `device`, `usm_type`, `sycl_queue` keywords will be ignored. + and `dtype`, `device`, `usm_type`, `sycl_queue` keywords + will be ignored. """ @@ -287,7 +293,7 @@ def as_usm_ndarray(a, device=None, usm_type=None, sycl_queue=None): return get_usm_ndarray(a) return dpt.asarray( - a, device=device, usm_type=usm_type, sycl_queue=sycl_queue + a, dtype=dtype, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) diff --git a/dpnp/dpnp_iface_histograms.py b/dpnp/dpnp_iface_histograms.py index 8d6a4c7e853..3b211c30ef0 100644 --- a/dpnp/dpnp_iface_histograms.py +++ b/dpnp/dpnp_iface_histograms.py @@ -165,7 +165,7 @@ def _get_bin_edges(a, bins, range, usm_type): ) bin_edges = dpnp.as_usm_ndarray( - bin_edges, usm_type=usm_type, sycl_queue=sycl_queue + bins, usm_type=usm_type, sycl_queue=sycl_queue ) if dpnp.any(bin_edges[:-1] > bin_edges[1:]): diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 2be7b4e7694..7b13e91ab85 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -552,7 +552,10 @@ def extract(condition, a): usm_a = dpnp.get_usm_ndarray(a) usm_cond = dpnp.as_usm_ndarray( - condition, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + condition, + dtype=dpnp.bool, + usm_type=usm_a.usm_type, + sycl_queue=usm_a.sycl_queue, ) if usm_cond.size != usm_a.size: @@ -1054,16 +1057,27 @@ def place(a, mask, vals): usm_a = dpnp.get_usm_ndarray(a) usm_mask = dpnp.as_usm_ndarray( - mask, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + mask, + dtype=dpnp.bool, + usm_type=usm_a.usm_type, + sycl_queue=usm_a.sycl_queue, ) usm_vals = dpnp.as_usm_ndarray( - vals, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + vals, + dtype=usm_a.dtype, + usm_type=usm_a.usm_type, + sycl_queue=usm_a.sycl_queue, ) if usm_vals.ndim != 1: # dpt.place supports only 1-D array of values usm_vals = dpt.reshape(usm_vals, -1) + if usm_vals.dtype != usm_a.dtype: + # dpt.place casts values to a.dtype with "unsafe" rule, + # while numpy.place does that with "safe" casting rule + usm_vals = dpt.astype(usm_vals, usm_a.dtype, casting="safe", copy=False) + dpt.place(usm_a, usm_mask, usm_vals) dpnp.synchronize_array_data(usm_a) @@ -1362,7 +1376,10 @@ def take(a, indices, /, *, axis=None, out=None, mode="wrap"): usm_a = dpnp.get_usm_ndarray(a) usm_ind = dpnp.as_usm_ndarray( - indices, usm_type=usm_a.usm_type, sycl_queue=usm_a.sycl_queue + indices, + dtype=dpnp.intp, + usm_type=usm_a.usm_type, + sycl_queue=usm_a.sycl_queue, ) a_ndim = usm_a.ndim diff --git a/tests/test_indexing.py b/tests/test_indexing.py index 18129a84d14..3193f3ff3f5 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -95,6 +95,15 @@ def test_diagonal_errors(self): class TestExtins: + @pytest.mark.parametrize("dt", get_all_dtypes(no_none=True)) + def test_extract(self, dt): + a = numpy.array([1, 3, 2, 1, 2, 3, 3], dtype=dt) + ia = dpnp.array(a) + + result = dpnp.extract(ia > 1, ia) + expected = numpy.extract(a > 1, a) + assert_array_equal(result, expected) + @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) @pytest.mark.parametrize("cond_dt", get_all_dtypes(no_none=True)) def test_extract_diff_dtypes(self, a_dt, cond_dt): @@ -106,15 +115,6 @@ def test_extract_diff_dtypes(self, a_dt, cond_dt): expected = numpy.extract(cond, a) assert_array_equal(result, expected) - @pytest.mark.parametrize("dt", get_all_dtypes(no_none=True)) - def test_extract(self, dt): - a = numpy.array([1, 3, 2, 1, 2, 3, 3], dtype=dt) - ia = dpnp.array(a) - - result = dpnp.extract(ia > 1, ia) - expected = numpy.extract(a > 1, a) - assert_array_equal(result, expected) - @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) def test_extract_list_cond(self, a_dt): a = numpy.array([-2, -1, 0, 1, 2, 3], dtype=a_dt) @@ -125,7 +125,6 @@ def test_extract_list_cond(self, a_dt): expected = numpy.extract(cond, a) assert_array_equal(result, expected) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") @pytest.mark.parametrize("dt", get_all_dtypes(no_none=True)) def test_place(self, dt): a = numpy.array([1, 4, 3, 2, 5, 8, 7], dtype=dt) @@ -135,7 +134,34 @@ def test_place(self, dt): numpy.place(a, [0, 1, 0, 1, 0, 1, 0], [2, 4, 6]) assert_array_equal(ia, a) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") + @pytest.mark.parametrize("a_dt", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize("mask_dt", get_all_dtypes(no_none=True)) + @pytest.mark.parametrize("vals_dt", get_all_dtypes(no_none=True)) + def test_place_diff_dtypes(self, a_dt, mask_dt, vals_dt): + a = numpy.array( + [[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]], dtype=a_dt + ) + mask = numpy.array( + [ + [[True, False], [False, True]], + [[False, True], [True, False]], + [[False, False], [True, True]], + ], + dtype=mask_dt, + ) + vals = numpy.array( + [100, 200, 300, 400, 500, 600, 800, 900], dtype=vals_dt + ) + ia, imask, ivals = dpnp.array(a), dpnp.array(mask), dpnp.array(vals) + + if numpy.can_cast(vals_dt, a_dt, casting="safe"): + dpnp.place(ia, imask, ivals) + numpy.place(a, mask, vals) + assert_array_equal(ia, a) + else: + assert_raises(TypeError, dpnp.place, ia, imask, ivals) + assert_raises(TypeError, numpy.place, a, mask, vals) + def test_place_broadcast_vals(self): a = numpy.array([1, 4, 3, 2, 5, 8, 7]) ia = dpnp.array(a) @@ -144,7 +170,6 @@ def test_place_broadcast_vals(self): numpy.place(a, [1, 0, 1, 0, 1, 0, 1], [8, 9]) assert_array_equal(ia, a) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") def test_place_empty_vals(self): a = numpy.array([1, 4, 3, 2, 5, 8, 7]) mask = numpy.zeros(7) @@ -155,7 +180,6 @@ def test_place_empty_vals(self): numpy.place(a, mask, vals) assert_array_equal(ia, a) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") @pytest.mark.parametrize("xp", [numpy, dpnp]) def test_place_insert_from_empty_vals(self, xp): a = xp.array([1, 4, 3, 2, 5, 8, 7]) @@ -165,12 +189,10 @@ def test_place_insert_from_empty_vals(self, xp): lambda: xp.place(a, [0, 0, 0, 0, 0, 1, 0], []), ) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") @pytest.mark.parametrize("xp", [numpy, dpnp]) def test_place_wrong_array_type(self, xp): assert_raises(TypeError, xp.place, [1, 2, 3], [True, False], [0, 1]) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") @pytest.mark.parametrize("dt", get_all_dtypes(no_none=True)) def test_both(self, dt): a = numpy.random.rand(10).astype(dt) @@ -779,128 +801,6 @@ def test_indices(dimension, dtype, sparse): assert_array_equal(Xnp, X) -@pytest.mark.parametrize( - "vals", [[100, 200], (100, 200)], ids=["[100, 200]", "(100, 200)"] -) -@pytest.mark.parametrize( - "mask", - [ - [[True, False], [False, True]], - [[False, True], [True, False]], - [[False, False], [True, True]], - ], - ids=[ - "[[True, False], [False, True]]", - "[[False, True], [True, False]]", - "[[False, False], [True, True]]", - ], -) -@pytest.mark.parametrize( - "arr", - [[[0, 0], [0, 0]], [[1, 2], [1, 2]], [[1, 2], [3, 4]]], - ids=["[[0, 0], [0, 0]]", "[[1, 2], [1, 2]]", "[[1, 2], [3, 4]]"], -) -def test_place1(arr, mask, vals): - a = numpy.array(arr) - ia = dpnp.array(a) - m = numpy.array(mask) - im = dpnp.array(m) - iv = dpnp.array(vals) - numpy.place(a, m, vals) - dpnp.place(ia, im, iv) - assert_array_equal(a, ia) - - -@pytest.mark.parametrize( - "vals", - [ - [100, 200], - [100, 200, 300, 400, 500, 600], - [100, 200, 300, 400, 500, 600, 800, 900], - ], - ids=[ - "[100, 200]", - "[100, 200, 300, 400, 500, 600]", - "[100, 200, 300, 400, 500, 600, 800, 900]", - ], -) -@pytest.mark.parametrize( - "mask", - [ - [ - [[True, False], [False, True]], - [[False, True], [True, False]], - [[False, False], [True, True]], - ] - ], - ids=[ - "[[[True, False], [False, True]], [[False, True], [True, False]], [[False, False], [True, True]]]" - ], -) -@pytest.mark.parametrize( - "arr", - [[[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]]], - ids=["[[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]]"], -) -def test_place2(arr, mask, vals): - a = numpy.array(arr) - ia = dpnp.array(a) - m = numpy.array(mask) - im = dpnp.array(m) - iv = dpnp.array(vals) - numpy.place(a, m, vals) - dpnp.place(ia, im, iv) - assert_array_equal(a, ia) - - -@pytest.mark.parametrize( - "vals", - [ - [100, 200], - [100, 200, 300, 400, 500, 600], - [100, 200, 300, 400, 500, 600, 800, 900], - ], - ids=[ - "[100, 200]", - "[100, 200, 300, 400, 500, 600]", - "[100, 200, 300, 400, 500, 600, 800, 900]", - ], -) -@pytest.mark.parametrize( - "mask", - [ - [ - [[[False, False], [True, True]], [[True, True], [True, True]]], - [[[False, False], [True, True]], [[False, False], [False, False]]], - ] - ], - ids=[ - "[[[[False, False], [True, True]], [[True, True], [True, True]]], [[[False, False], [True, True]], [[False, False], [False, False]]]]" - ], -) -@pytest.mark.parametrize( - "arr", - [ - [ - [[[1, 2], [3, 4]], [[1, 2], [2, 1]]], - [[[1, 3], [3, 1]], [[0, 1], [1, 3]]], - ] - ], - ids=[ - "[[[[1, 2], [3, 4]], [[1, 2], [2, 1]]], [[[1, 3], [3, 1]], [[0, 1], [1, 3]]]]" - ], -) -def test_place3(arr, mask, vals): - a = numpy.array(arr) - ia = dpnp.array(a) - m = numpy.array(mask) - im = dpnp.array(m) - iv = dpnp.array(vals) - numpy.place(a, m, vals) - dpnp.place(ia, im, iv) - assert_array_equal(a, ia) - - @pytest.mark.parametrize("vals", [[100, 200]], ids=["[100, 200]"]) @pytest.mark.parametrize( "mask", From b527f77d452e117c73195d6c066c3c4cf4e5f7cf Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 7 Jul 2024 17:57:14 +0200 Subject: [PATCH 09/13] Roll back changed in dpnp.vander --- dpnp/dpnp_iface_arraycreation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dpnp/dpnp_iface_arraycreation.py b/dpnp/dpnp_iface_arraycreation.py index 5c8e4ad99f8..6698f3f782e 100644 --- a/dpnp/dpnp_iface_arraycreation.py +++ b/dpnp/dpnp_iface_arraycreation.py @@ -3498,7 +3498,9 @@ def vander( [125, 25, 5, 1]]), Device(level_zero:gpu:0), 'host') """ - usm_x = dpnp.as_usm_ndarray( + if dpnp.is_supported_array_type(x): + x = dpnp.get_usm_ndarray(x) + usm_x = dpt.asarray( x, device=device, usm_type=usm_type, sycl_queue=sycl_queue ) From dc5e7765ce4f53f22d2eb6240e485fc30f5ce12f Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 9 Jul 2024 14:52:12 +0200 Subject: [PATCH 10/13] Remove data sync at the end of function --- dpnp/dpnp_iface_indexing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 7b13e91ab85..52c441c5cc2 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -1079,7 +1079,6 @@ def place(a, mask, vals): usm_vals = dpt.astype(usm_vals, usm_a.dtype, casting="safe", copy=False) dpt.place(usm_a, usm_mask, usm_vals) - dpnp.synchronize_array_data(usm_a) def put(a, ind, v, /, *, axis=None, mode="wrap"): From 6a7d3b3151b39e578ce0f1c2a2d2df0d3a171ae1 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 10 Jul 2024 13:22:57 +0200 Subject: [PATCH 11/13] Remove data sync from dpnp.get_result_array() --- dpnp/dpnp_iface.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 12654840685..518e81dccfc 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -629,11 +629,9 @@ def get_result_array(a, out=None, casting="safe"): """ if out is None: - synchronize_array_data(a) return a if a is out: - synchronize_array_data(a) return out dpnp.check_supported_arrays_type(out) From e337a950fdc8f580843800d1c07c784558a0c824 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 10 Jul 2024 13:29:32 +0200 Subject: [PATCH 12/13] Fix typo in docstring --- dpnp/dpnp_iface_indexing.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index ee42110cbb8..e86d1188a29 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -1038,10 +1038,10 @@ def place(a, mask, vals): See Also -------- - obj:`dpnp.copyto` : Copies values from one array to another. - obj:`dpnp.put` : Replaces specified elements of an array with given values. - obj:`dpnp.take` : Take elements from an array along an axis. - obj:`dpnp.extract` : Return the elements of an array that satisfy some + :obj:`dpnp.copyto` : Copies values from one array to another. + :obj:`dpnp.put` : Replaces specified elements of an array with given values. + :obj:`dpnp.take` : Take elements from an array along an axis. + :obj:`dpnp.extract` : Return the elements of an array that satisfy some condition. Examples From 77dd403e0903e374c08a5f3b880b35aeac4ba5bc Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Wed, 10 Jul 2024 13:56:48 +0200 Subject: [PATCH 13/13] Corrected a link to dpnp.copyto() in description --- dpnp/dpnp_iface_indexing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index e86d1188a29..ca2df4d4605 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -1017,7 +1017,7 @@ def place(a, mask, vals): Similar to ``dpnp.copyto(a, vals, where=mask)``, the difference is that :obj:`dpnp.place` uses the first N elements of `vals`, where N is - the number of ``True`` values in `mask`, while :obj:`copyto` uses + the number of ``True`` values in `mask`, while :obj:`dpnp.copyto` uses the elements where `mask` is ``True``. Note that :obj:`dpnp.extract` does the exact opposite of :obj:`dpnp.place`.