From 00f35bcf42340ba7d6aa2cb2b613d26bdbaa1574 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Mon, 21 Aug 2023 21:46:41 +0200 Subject: [PATCH 1/6] Leverage dpctl.tensor.put impl --- dpnp/backend/include/dpnp_iface_fptr.hpp | 1 - dpnp/backend/kernels/dpnp_krnl_indexing.cpp | 20 --- dpnp/dpnp_algo/dpnp_algo.pxd | 2 - dpnp/dpnp_algo/dpnp_algo_indexing.pxi | 68 --------- dpnp/dpnp_array.py | 13 +- dpnp/dpnp_iface_indexing.py | 70 ++++++++-- tests/test_indexing.py | 130 +++++++++++------- .../cupy/indexing_tests/test_insert.py | 10 +- 8 files changed, 151 insertions(+), 163 deletions(-) diff --git a/dpnp/backend/include/dpnp_iface_fptr.hpp b/dpnp/backend/include/dpnp_iface_fptr.hpp index e2c29a0f4a88..a558cc0f60e3 100644 --- a/dpnp/backend/include/dpnp_iface_fptr.hpp +++ b/dpnp/backend/include/dpnp_iface_fptr.hpp @@ -308,7 +308,6 @@ enum class DPNPFuncName : size_t DPNP_FN_PTP, /**< Used in numpy.ptp() impl */ DPNP_FN_PTP_EXT, /**< Used in numpy.ptp() impl, requires extra parameters */ DPNP_FN_PUT, /**< Used in numpy.put() impl */ - DPNP_FN_PUT_EXT, /**< Used in numpy.put() impl, requires extra parameters */ DPNP_FN_PUT_ALONG_AXIS, /**< Used in numpy.put_along_axis() impl */ DPNP_FN_PUT_ALONG_AXIS_EXT, /**< Used in numpy.put_along_axis() impl, requires extra parameters */ diff --git a/dpnp/backend/kernels/dpnp_krnl_indexing.cpp b/dpnp/backend/kernels/dpnp_krnl_indexing.cpp index b8fa2179b6f6..2cca84f9e61f 100644 --- a/dpnp/backend/kernels/dpnp_krnl_indexing.cpp +++ b/dpnp/backend/kernels/dpnp_krnl_indexing.cpp @@ -602,17 +602,6 @@ void (*dpnp_put_default_c)(void *, const size_t) = dpnp_put_c<_DataType, _IndecesType, _ValueType>; -template -DPCTLSyclEventRef (*dpnp_put_ext_c)(DPCTLSyclQueueRef, - void *, - void *, - void *, - const size_t, - const size_t, - const size_t, - const DPCTLEventVectorRef) = - dpnp_put_c<_DataType, _IndecesType, _ValueType>; - template DPCTLSyclEventRef dpnp_put_along_axis_c(DPCTLSyclQueueRef q_ref, @@ -1007,15 +996,6 @@ void func_map_init_indexing_func(func_map_t &fmap) fmap[DPNPFuncName::DPNP_FN_PUT][eft_DBL][eft_DBL] = { eft_DBL, (void *)dpnp_put_default_c}; - fmap[DPNPFuncName::DPNP_FN_PUT_EXT][eft_INT][eft_INT] = { - eft_INT, (void *)dpnp_put_ext_c}; - fmap[DPNPFuncName::DPNP_FN_PUT_EXT][eft_LNG][eft_LNG] = { - eft_LNG, (void *)dpnp_put_ext_c}; - fmap[DPNPFuncName::DPNP_FN_PUT_EXT][eft_FLT][eft_FLT] = { - eft_FLT, (void *)dpnp_put_ext_c}; - fmap[DPNPFuncName::DPNP_FN_PUT_EXT][eft_DBL][eft_DBL] = { - eft_DBL, (void *)dpnp_put_ext_c}; - fmap[DPNPFuncName::DPNP_FN_PUT_ALONG_AXIS][eft_INT][eft_INT] = { eft_INT, (void *)dpnp_put_along_axis_default_c}; fmap[DPNPFuncName::DPNP_FN_PUT_ALONG_AXIS][eft_LNG][eft_LNG] = { diff --git a/dpnp/dpnp_algo/dpnp_algo.pxd b/dpnp/dpnp_algo/dpnp_algo.pxd index 7427c6c7ce44..0f9fb17e9a9f 100644 --- a/dpnp/dpnp_algo/dpnp_algo.pxd +++ b/dpnp/dpnp_algo/dpnp_algo.pxd @@ -182,8 +182,6 @@ cdef extern from "dpnp_iface_fptr.hpp" namespace "DPNPFuncName": # need this na DPNP_FN_PROD_EXT DPNP_FN_PTP DPNP_FN_PTP_EXT - DPNP_FN_PUT - DPNP_FN_PUT_EXT DPNP_FN_QR DPNP_FN_QR_EXT DPNP_FN_RADIANS diff --git a/dpnp/dpnp_algo/dpnp_algo_indexing.pxi b/dpnp/dpnp_algo/dpnp_algo_indexing.pxi index 808961298c22..36fc7ff8eb91 100644 --- a/dpnp/dpnp_algo/dpnp_algo_indexing.pxi +++ b/dpnp/dpnp_algo/dpnp_algo_indexing.pxi @@ -41,7 +41,6 @@ __all__ += [ "dpnp_diagonal", "dpnp_fill_diagonal", "dpnp_indices", - "dpnp_put", "dpnp_put_along_axis", "dpnp_putmask", "dpnp_select", @@ -80,14 +79,6 @@ ctypedef c_dpctl.DPCTLSyclEventRef(*custom_indexing_3in_with_axis_func_ptr_t)(c_ const size_t, const size_t, const c_dpctl.DPCTLEventVectorRef) -ctypedef c_dpctl.DPCTLSyclEventRef(*custom_indexing_6in_func_ptr_t)(c_dpctl.DPCTLSyclQueueRef, - void *, - void * , - void * , - const size_t, - const size_t, - const size_t, - const c_dpctl.DPCTLEventVectorRef) cpdef utils.dpnp_descriptor dpnp_choose(utils.dpnp_descriptor x1, list choices1): @@ -292,65 +283,6 @@ cpdef object dpnp_indices(dimensions): return dpnp_result -cpdef dpnp_put(dpnp_descriptor x1, object ind, v): - ind_is_list = isinstance(ind, list) - - x1_obj = x1.get_array() - - if dpnp.isscalar(ind): - ind_size = 1 - else: - ind_size = len(ind) - cdef utils.dpnp_descriptor ind_array = utils_py.create_output_descriptor_py((ind_size,), - dpnp.int64, - None, - device=x1_obj.sycl_device, - usm_type=x1_obj.usm_type, - sycl_queue=x1_obj.sycl_queue) - if dpnp.isscalar(ind): - ind_array.get_pyobj()[0] = ind - else: - for i in range(ind_size): - ind_array.get_pyobj()[i] = ind[i] - - if dpnp.isscalar(v): - v_size = 1 - else: - v_size = len(v) - cdef utils.dpnp_descriptor v_array = utils_py.create_output_descriptor_py((v_size,), - x1.dtype, - None, - device=x1_obj.sycl_device, - usm_type=x1_obj.usm_type, - sycl_queue=x1_obj.sycl_queue) - if dpnp.isscalar(v): - v_array.get_pyobj()[0] = v - else: - for i in range(v_size): - v_array.get_pyobj()[i] = v[i] - - cdef DPNPFuncType param1_type = dpnp_dtype_to_DPNPFuncType(x1.dtype) - - cdef DPNPFuncData kernel_data = get_dpnp_function_ptr(DPNP_FN_PUT_EXT, param1_type, param1_type) - - cdef c_dpctl.SyclQueue q = x1_obj.sycl_queue - cdef c_dpctl.DPCTLSyclQueueRef q_ref = q.get_queue_ref() - - cdef custom_indexing_6in_func_ptr_t func = kernel_data.ptr - - cdef c_dpctl.DPCTLSyclEventRef event_ref = func(q_ref, - x1.get_data(), - ind_array.get_data(), - v_array.get_data(), - x1.size, - ind_array.size, - v_array.size, - NULL) # dep_events_ref - - with nogil: c_dpctl.DPCTLEvent_WaitAndThrow(event_ref) - c_dpctl.DPCTLEvent_Delete(event_ref) - - cpdef dpnp_put_along_axis(dpnp_descriptor arr, dpnp_descriptor indices, dpnp_descriptor values, int axis): cdef shape_type_c arr_shape = arr.shape cdef DPNPFuncType param1_type = dpnp_dtype_to_DPNPFuncType(arr.dtype) diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index 8b2a8c90716a..48e7db381a5e 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -904,8 +904,17 @@ def prod( return dpnp.prod(self, axis, dtype, out, keepdims, initial, where) - # 'ptp', - # 'put', + # 'ptp' + + def put(self, indices, vals, /, *, axis=None, mode="wrap"): + """ + Puts values of an array into another array along a given axis. + + For full documentation refer to :obj:`numpy.put`. + """ + + return dpnp.put(self, indices, vals, axis=axis, mode=mode) + # 'ravel', # 'real', # 'repeat', diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index e378589b863b..39cd256354a1 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -417,34 +417,74 @@ def place(x, mask, vals, /): return call_origin(numpy.place, x, mask, vals, dpnp_inplace=True) -def put(x1, ind, v, mode="raise"): +def put(x, indices, vals, /, *, axis=None, mode="wrap"): """ - Replaces specified elements of an array with given values. + Puts values of an array into another array along a given axis. For full documentation refer to :obj:`numpy.put`. Limitations ----------- - Input array is supported as :obj:`dpnp.ndarray`. - Not supported parameter mode. + 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. + Paramenet `vals` must be broadcastable to the shape of `indices` + and be of the same data type as `x` if it is as :class:`dpnp.ndarray` + or :class:`dpctl.tensor.usm_ndarray`. + Parameter `mode` is supported with ``wrap``(default) and ``clip`` mode. + Parameter `axis` is supported as integer only. + Otherwise the function will be executed sequentially on CPU. + + See Also + -------- + :obj:`dpnp.putmask` : Changes elements of an array based on conditional and input values. + :obj:`dpnp.place` : Change elements of an array based on conditional and input values. + :obj:`dpnp.put_along_axis` : Put values into the destination array by matching 1d index and data slices. + + 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 + >>> x = np.arange(5) + >>> indices = np.array([0, 1]) + >>> np.put(x, indices, [-44, -55]) + >>> x + array([-44, -55, 2, 3, 4]) """ - x1_desc = dpnp.get_dpnp_descriptor( - x1, copy_when_strides=False, copy_when_nondefault_queue=False - ) - if x1_desc: - if mode != "raise": + 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 type(ind) is not type(v): + elif mode not in ("clip", "wrap"): pass - elif ( - numpy.max(ind) >= x1_desc.size or numpy.min(ind) + x1_desc.size < 0 - ): + elif axis is not None and not isinstance(axis, int): + raise TypeError(f"`axis` must be of integer type, got {type(axis)}") + elif dpnp.is_supported_array_type(vals) and x.dtype is not vals.dtype: pass else: - return dpnp_put(x1_desc, ind, v) + if axis is None and x.ndim > 1: + x = dpnp.reshape(x, -1) + dpt_array = dpnp.get_usm_ndarray(x) + dpt_indices = dpnp.get_usm_ndarray(indices) + dpt_vals = ( + dpnp.get_usm_ndarray(vals) + if isinstance(vals, dpnp_array) + else vals + ) + return dpt.put( + dpt_array, dpt_indices, dpt_vals, axis=axis, mode=mode + ) - return call_origin(numpy.put, x1, ind, v, mode, dpnp_inplace=True) + return call_origin(numpy.put, x, indices, vals, mode, dpnp_inplace=True) def put_along_axis(x1, indices, values, axis): diff --git a/tests/test_indexing.py b/tests/test_indexing.py index 67600264356d..24c7e3810ded 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -341,74 +341,104 @@ def test_place3(arr, mask, vals): assert_array_equal(a, ia) -@pytest.mark.parametrize("v", [0, 1, 2, 3, 4], ids=["0", "1", "2", "3", "4"]) -@pytest.mark.parametrize("ind", [0, 1, 2, 3], ids=["0", "1", "2", "3"]) +@pytest.mark.parametrize("array_dtype", get_all_dtypes()) @pytest.mark.parametrize( - "array", - [ - [[0, 0], [0, 0]], - [[1, 2], [1, 2]], - [[1, 2], [3, 4]], - [[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]], - [ - [[[1, 2], [3, 4]], [[1, 2], [2, 1]]], - [[[1, 3], [3, 1]], [[0, 1], [1, 3]]], - ], - ], - ids=[ - "[[0, 0], [0, 0]]", - "[[1, 2], [1, 2]]", - "[[1, 2], [3, 4]]", - "[[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]]", - "[[[[1, 2], [3, 4]], [[1, 2], [2, 1]]], [[[1, 3], [3, 1]], [[0, 1], [1, 3]]]]", - ], + "indices_dtype", [dpnp.int32, dpnp.int64], ids=["int32", "int64"] ) -def test_put(array, ind, v): - a = numpy.array(array) +@pytest.mark.parametrize( + "indices", [[-2, 2], [-5, 4]], ids=["[-2, 2]", "[-5, 4]"] +) +@pytest.mark.parametrize( + "vals", + [0, [1, 2], (2, 2), dpnp.array([1, 2])], + ids=["0", "[1, 2]", "(2, 2)", "dpnp.array([1,2])"], +) +@pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) +def test_put_1d(indices, vals, array_dtype, indices_dtype, mode): + a = numpy.array([-2, -1, 0, 1, 2], dtype=array_dtype) ia = dpnp.array(a) - numpy.put(a, ind, v) - dpnp.put(ia, ind, v) + ind = numpy.array(indices, dtype=indices_dtype) + iind = dpnp.array(ind) + + if dpnp.is_supported_array_type(vals): + vals = dpnp.astype(vals, ia.dtype) + + numpy.put(a, ind, vals, mode=mode) + dpnp.put(ia, iind, vals, mode=mode) assert_array_equal(a, ia) +@pytest.mark.parametrize("array_dtype", get_all_dtypes()) @pytest.mark.parametrize( - "v", [[10, 20], [30, 40]], ids=["[10, 20]", "[30, 40]"] + "indices_dtype", [dpnp.int32, dpnp.int64], ids=["int32", "int64"] ) -@pytest.mark.parametrize("ind", [[0, 1], [2, 3]], ids=["[0, 1]", "[2, 3]"]) +@pytest.mark.parametrize("vals", [[10, 20]], ids=["[10, 20]"]) @pytest.mark.parametrize( - "array", + "indices", [ - [[0, 0], [0, 0]], - [[1, 2], [1, 2]], - [[1, 2], [3, 4]], - [[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]], - [ - [[[1, 2], [3, 4]], [[1, 2], [2, 1]]], - [[[1, 3], [3, 1]], [[0, 1], [1, 3]]], - ], + [0, 7], + [3, 4], + [-9, 8], ], ids=[ - "[[0, 0], [0, 0]]", - "[[1, 2], [1, 2]]", - "[[1, 2], [3, 4]]", - "[[[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]]]", - "[[[[1, 2], [3, 4]], [[1, 2], [2, 1]]], [[[1, 3], [3, 1]], [[0, 1], [1, 3]]]]", + "[0, 7]", + "[3, 4]", + "[-9, 8]", ], ) -def test_put2(array, ind, v): - a = numpy.array(array) +@pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) +def test_put_2d(array_dtype, indices_dtype, indices, vals, mode): + a = numpy.array([[-1, 0, 1], [-2, -3, -4], [2, 3, 4]], dtype=array_dtype) ia = dpnp.array(a) - numpy.put(a, ind, v) - dpnp.put(ia, ind, v) + ind = numpy.array(indices, dtype=indices_dtype) + iind = dpnp.array(ind) + numpy.put(a, ind, vals, mode=mode) + dpnp.put(ia, iind, vals, mode=mode) assert_array_equal(a, ia) -def test_put3(): - a = numpy.arange(5) - ia = dpnp.array(a) - dpnp.put(ia, [0, 2], [-44, -55]) - numpy.put(a, [0, 2], [-44, -55]) - assert_array_equal(a, ia) +@pytest.mark.parametrize( + "shape", + [ + (0,), + (3,), + (4,), + ], + ids=[ + "(0,)", + "(3,)", + "(4,)", + ], +) +@pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) +def test_put_invalid_shape(shape, mode): + a = dpnp.arange(7) + ind = dpnp.array([2]) + vals = dpnp.ones(shape, dtype=a.dtype) + # vals must be broadcastable to the shape of ind` + with pytest.raises(ValueError): + dpnp.put(a, ind, vals, mode=mode) + + +@pytest.mark.parametrize( + "axis", + [ + 1.0, + (0,), + [0, 1], + ], + ids=[ + "1.0", + "(0,)", + "[0, 1]", + ], +) +def test_put_invalid_axis(axis): + a = dpnp.arange(6).reshape(2, 3) + ind = dpnp.array([1]) + vals = [1] + with pytest.raises(TypeError): + dpnp.put(a, ind, vals, axis=axis) @pytest.mark.usefixtures("allow_fall_back_on_numpy") diff --git a/tests/third_party/cupy/indexing_tests/test_insert.py b/tests/third_party/cupy/indexing_tests/test_insert.py index f48cff027335..ef5d3afd13d7 100644 --- a/tests/third_party/cupy/indexing_tests/test_insert.py +++ b/tests/third_party/cupy/indexing_tests/test_insert.py @@ -71,7 +71,7 @@ def test_place_shape_unmatch_error(self, dtype): { "shape": [(7,), (2, 3), (4, 3, 2)], "mode": ["raise", "wrap", "clip"], - "n_vals": [0, 1, 3, 4, 5], + "n_vals": [1, 4], } ) ) @@ -83,12 +83,12 @@ class TestPut(unittest.TestCase): def test_put(self, xp, dtype): a = testing.shaped_arange(self.shape, xp, dtype) # Take care so that actual indices don't overlap. - if self.mode == "raise": - inds = xp.array([2, -1, 3, 0]) + if self.mode in ("raise", "wrap"): + inds = xp.array([2, -1, 3, -6]) else: - inds = xp.array([2, -8, 3, 7]) + inds = xp.array([2, -8, 3, 6]) vals = testing.shaped_random((self.n_vals,), xp, dtype) - xp.put(a, inds, vals, self.mode) + xp.put(a, inds, vals, mode=self.mode) return a From 82f6558ddfea4feabb4b3f057abe2e80e8b3978b Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 30 Aug 2023 15:50:48 +0200 Subject: [PATCH 2/6] Apply review remarks --- dpnp/dpnp_iface_indexing.py | 25 ++++++++++++------- .../cupy/indexing_tests/test_insert.py | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 39cd256354a1..6a3774a36376 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -417,7 +417,7 @@ def place(x, mask, vals, /): return call_origin(numpy.place, x, mask, vals, dpnp_inplace=True) -def put(x, indices, vals, /, *, axis=None, mode="wrap"): +def put(a, indices, vals, /, *, axis=None, mode="wrap"): """ Puts values of an array into another array along a given axis. @@ -425,11 +425,11 @@ def put(x, indices, vals, /, *, axis=None, mode="wrap"): Limitations ----------- - Parameters `x` and `indices` are supported either as :class:`dpnp.ndarray` + Parameters `a` 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. Paramenet `vals` must be broadcastable to the shape of `indices` - and be of the same data type as `x` if it is as :class:`dpnp.ndarray` + and has the same data type as `a` if it is as :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. Parameter `mode` is supported with ``wrap``(default) and ``clip`` mode. Parameter `axis` is supported as integer only. @@ -455,9 +455,16 @@ def put(x, indices, vals, /, *, axis=None, mode="wrap"): >>> np.put(x, indices, [-44, -55]) >>> x array([-44, -55, 2, 3, 4]) + + >>> x = np.arange(5) + >>> indices = np.array([22]) + >>> np.put(x, indices, -5, mode='clip') + >>> x + array([0, 1, 2, 3,-5]) + """ - if dpnp.is_supported_array_type(x) and dpnp.is_supported_array_type( + if dpnp.is_supported_array_type(a) and dpnp.is_supported_array_type( indices ): if indices.ndim != 1 or not dpnp.issubdtype( @@ -468,12 +475,12 @@ def put(x, indices, vals, /, *, axis=None, mode="wrap"): pass elif axis is not None and not isinstance(axis, int): raise TypeError(f"`axis` must be of integer type, got {type(axis)}") - elif dpnp.is_supported_array_type(vals) and x.dtype is not vals.dtype: + elif dpnp.is_supported_array_type(vals) and a.dtype != vals.dtype: pass else: - if axis is None and x.ndim > 1: - x = dpnp.reshape(x, -1) - dpt_array = dpnp.get_usm_ndarray(x) + if axis is None and a.ndim > 1: + a = dpnp.reshape(a, -1) + dpt_array = dpnp.get_usm_ndarray(a) dpt_indices = dpnp.get_usm_ndarray(indices) dpt_vals = ( dpnp.get_usm_ndarray(vals) @@ -484,7 +491,7 @@ def put(x, indices, vals, /, *, axis=None, mode="wrap"): dpt_array, dpt_indices, dpt_vals, axis=axis, mode=mode ) - return call_origin(numpy.put, x, indices, vals, mode, dpnp_inplace=True) + return call_origin(numpy.put, a, indices, vals, mode, dpnp_inplace=True) def put_along_axis(x1, indices, values, axis): diff --git a/tests/third_party/cupy/indexing_tests/test_insert.py b/tests/third_party/cupy/indexing_tests/test_insert.py index ef5d3afd13d7..0625d271d753 100644 --- a/tests/third_party/cupy/indexing_tests/test_insert.py +++ b/tests/third_party/cupy/indexing_tests/test_insert.py @@ -99,9 +99,9 @@ def test_put(self, xp, dtype): } ) ) -@pytest.mark.usefixtures("allow_fall_back_on_numpy") @testing.gpu class TestPutScalars(unittest.TestCase): + @pytest.mark.usefixtures("allow_fall_back_on_numpy") @testing.numpy_cupy_array_equal() def test_put_index_scalar(self, xp): dtype = cupy.float32 From 4b1c265cc8d68ce50f7b6059fbd5e9baf19c6258 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 31 Aug 2023 17:00:55 +0200 Subject: [PATCH 3/6] Update tests for dpnp.put and dpnp.take --- tests/test_indexing.py | 18 ++++++++++++++++++ tests/test_sycl_queue.py | 22 ++++++++++++++++++++++ tests/test_usm_type.py | 14 ++++++++++++++ 3 files changed, 54 insertions(+) diff --git a/tests/test_indexing.py b/tests/test_indexing.py index 24c7e3810ded..03541dc2d55d 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -356,10 +356,13 @@ def test_place3(arr, mask, vals): @pytest.mark.parametrize("mode", ["clip", "wrap"], ids=["clip", "wrap"]) def test_put_1d(indices, vals, array_dtype, indices_dtype, mode): a = numpy.array([-2, -1, 0, 1, 2], dtype=array_dtype) + b = numpy.copy(a) ia = dpnp.array(a) + ib = dpnp.array(b) ind = numpy.array(indices, dtype=indices_dtype) iind = dpnp.array(ind) + # TODO: remove when #1382(dpctl) is solved if dpnp.is_supported_array_type(vals): vals = dpnp.astype(vals, ia.dtype) @@ -367,6 +370,10 @@ def test_put_1d(indices, vals, array_dtype, indices_dtype, mode): dpnp.put(ia, iind, vals, mode=mode) assert_array_equal(a, ia) + b.put(ind, vals, mode=mode) + ib.put(iind, vals, mode=mode) + assert_array_equal(b, ib) + @pytest.mark.parametrize("array_dtype", get_all_dtypes()) @pytest.mark.parametrize( @@ -397,6 +404,17 @@ def test_put_2d(array_dtype, indices_dtype, indices, vals, mode): assert_array_equal(a, ia) +@pytest.mark.usefixtures("allow_fall_back_on_numpy") +def test_put_2d_ind(): + a = numpy.arange(5) + ia = dpnp.array(a) + ind = numpy.array([[3, 0, 2, 1]]) + iind = dpnp.array(ind) + numpy.put(a, ind, 10) + dpnp.put(ia, iind, 10) + assert_array_equal(a, ia) + + @pytest.mark.parametrize( "shape", [ diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index aaa7b3bae8e9..f44afa8a640f 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -1106,3 +1106,25 @@ def test_asarray(device_x, device_y): x = dpnp.array([1, 2, 3], device=device_x) y = dpnp.asarray([x, x, x], device=device_y) assert_sycl_queue_equal(y.sycl_queue, x.to_device(device_y).sycl_queue) + + +@pytest.mark.parametrize( + "device", + valid_devices, + ids=[device.filter_string for device in valid_devices], +) +def test_take(device): + numpy_data = numpy.arange(5) + dpnp_data = dpnp.array(numpy_data, device=device) + + ind = [0, 2, 4] + dpnp_ind = dpnp.array(ind, device=device) + + result = dpnp.take(dpnp_data, dpnp_ind) + expected = numpy.take(numpy_data, ind) + assert_allclose(expected, result) + + expected_queue = dpnp_data.get_array().sycl_queue + result_queue = result.get_array().sycl_queue + + assert_sycl_queue_equal(result_queue, expected_queue) diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 8eb8b1b779d8..fa72efcf9162 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -326,3 +326,17 @@ def test_broadcast_to(usm_type): x = dp.ones(7, usm_type=usm_type) y = dp.broadcast_to(x, (2, 7)) assert x.usm_type == y.usm_type + + +@pytest.mark.parametrize("usm_type_x", list_of_usm_types, ids=list_of_usm_types) +@pytest.mark.parametrize( + "usm_type_ind", list_of_usm_types, ids=list_of_usm_types +) +def test_take(usm_type_x, usm_type_ind): + x = dp.arange(5, usm_type=usm_type_x) + ind = dp.array([0, 2, 4], usm_type=usm_type_ind) + z = dp.take(x, ind) + + assert x.usm_type == usm_type_x + assert ind.usm_type == usm_type_ind + assert z.usm_type == du.get_coerced_usm_type([usm_type_x, usm_type_ind]) From 80ebe4d0cc3ec6dfda631a823a1d143a604ccf3d Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Wed, 6 Sep 2023 18:09:36 +0200 Subject: [PATCH 4/6] Add todo for vals type checking --- dpnp/dpnp_iface_indexing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 6a3774a36376..8ef2772a0853 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -475,6 +475,7 @@ def put(a, indices, vals, /, *, axis=None, mode="wrap"): pass elif axis is not None and not isinstance(axis, int): raise TypeError(f"`axis` must be of integer type, got {type(axis)}") + # TODO: remove when #1382(dpctl) is solved elif dpnp.is_supported_array_type(vals) and a.dtype != vals.dtype: pass else: From b39280942fa858143d0cee9f57f72a99bfdaf422 Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 7 Sep 2023 18:26:47 +0200 Subject: [PATCH 5/6] Apply review remarks --- dpnp/dpnp_iface_indexing.py | 13 ++++++------- .../third_party/cupy/indexing_tests/test_insert.py | 10 ++++++++-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/dpnp/dpnp_iface_indexing.py b/dpnp/dpnp_iface_indexing.py index 8ef2772a0853..ededb973b3b4 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -428,10 +428,10 @@ def put(a, indices, vals, /, *, axis=None, mode="wrap"): Parameters `a` 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. - Paramenet `vals` must be broadcastable to the shape of `indices` + Parameter `vals` must be broadcastable to the shape of `indices` and has the same data type as `a` if it is as :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. - Parameter `mode` is supported with ``wrap``(default) and ``clip`` mode. + Parameter `mode` is supported with ``wrap``, the default, and ``clip`` values. Parameter `axis` is supported as integer only. Otherwise the function will be executed sequentially on CPU. @@ -443,9 +443,8 @@ def put(a, indices, vals, /, *, axis=None, mode="wrap"): 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). + In contrast to :obj:`numpy.put` `wrap` mode which wraps indices around the array for cyclic operations, + :obj:`dpnp.put` `wrap` mode clamps indices to a fixed range within the array boundaries (-n <= i < n). Examples -------- @@ -460,7 +459,7 @@ def put(a, indices, vals, /, *, axis=None, mode="wrap"): >>> indices = np.array([22]) >>> np.put(x, indices, -5, mode='clip') >>> x - array([0, 1, 2, 3,-5]) + array([ 0, 1, 2, 3, -5]) """ @@ -605,7 +604,7 @@ def take(x, indices, /, *, axis=None, out=None, mode="wrap"): 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``(default) and ``clip`` mode. + 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. diff --git a/tests/third_party/cupy/indexing_tests/test_insert.py b/tests/third_party/cupy/indexing_tests/test_insert.py index 0625d271d753..1840b22d5c46 100644 --- a/tests/third_party/cupy/indexing_tests/test_insert.py +++ b/tests/third_party/cupy/indexing_tests/test_insert.py @@ -71,6 +71,7 @@ def test_place_shape_unmatch_error(self, dtype): { "shape": [(7,), (2, 3), (4, 3, 2)], "mode": ["raise", "wrap", "clip"], + # The vals shape of array must be broadcastable to the shape of indices "n_vals": [1, 4], } ) @@ -83,10 +84,15 @@ class TestPut(unittest.TestCase): def test_put(self, xp, dtype): a = testing.shaped_arange(self.shape, xp, dtype) # Take care so that actual indices don't overlap. - if self.mode in ("raise", "wrap"): + if self.mode in ("raise"): + inds = xp.array([2, -1, 3, 0]) + # `wrap` mode in dpctl.tensor.put is different from numpy.put (#1365): + # numpy`s `wrap` mode wraps indices around for cyclic operations + # while dpctl`s `wrap` mode restricts indices to stay within the array bounds (-n <= i < n). + elif self.mode in ("wrap"): inds = xp.array([2, -1, 3, -6]) else: - inds = xp.array([2, -8, 3, 6]) + inds = xp.array([2, -8, 3, 7]) vals = testing.shaped_random((self.n_vals,), xp, dtype) xp.put(a, inds, vals, mode=self.mode) return a From 5ea70e07d6cf183fea609df377e853fa3bd729ff Mon Sep 17 00:00:00 2001 From: Vladislav Perevezentsev Date: Thu, 7 Sep 2023 18:29:49 +0200 Subject: [PATCH 6/6] Update examples --- 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 ededb973b3b4..0c19127831a1 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -453,7 +453,7 @@ def put(a, indices, vals, /, *, axis=None, mode="wrap"): >>> indices = np.array([0, 1]) >>> np.put(x, indices, [-44, -55]) >>> x - array([-44, -55, 2, 3, 4]) + array([-44, -55, 2, 3, 4]) >>> x = np.arange(5) >>> indices = np.array([22])