diff --git a/dpnp/backend/include/dpnp_iface_fptr.hpp b/dpnp/backend/include/dpnp_iface_fptr.hpp index 717bbd2d6a0d..dd9a9a97df37 100644 --- a/dpnp/backend/include/dpnp_iface_fptr.hpp +++ b/dpnp/backend/include/dpnp_iface_fptr.hpp @@ -280,7 +280,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 c3ba9338cee9..a512737ef31e 100644 --- a/dpnp/dpnp_algo/dpnp_algo.pxd +++ b/dpnp/dpnp_algo/dpnp_algo.pxd @@ -150,8 +150,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 a73a5a6cfaaa..24bc3a03a5e8 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -963,8 +963,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..0c19127831a1 100644 --- a/dpnp/dpnp_iface_indexing.py +++ b/dpnp/dpnp_iface_indexing.py @@ -417,34 +417,81 @@ 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(a, 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 `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. + 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``, the default, and ``clip`` values. + 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 + ----- + 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 + -------- + >>> 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]) + + >>> x = np.arange(5) + >>> indices = np.array([22]) + >>> np.put(x, indices, -5, mode='clip') + >>> x + array([ 0, 1, 2, 3, -5]) + """ - 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(a) 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)}") + # TODO: remove when #1382(dpctl) is solved + elif dpnp.is_supported_array_type(vals) and a.dtype != vals.dtype: pass else: - return dpnp_put(x1_desc, ind, v) + 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) + 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, a, indices, vals, mode, dpnp_inplace=True) def put_along_axis(x1, indices, values, axis): @@ -557,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/test_indexing.py b/tests/test_indexing.py index 67600264356d..03541dc2d55d 100644 --- a/tests/test_indexing.py +++ b/tests/test_indexing.py @@ -341,76 +341,124 @@ 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) + b = numpy.copy(a) ia = dpnp.array(a) - numpy.put(a, ind, v) - dpnp.put(ia, ind, v) + 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) + + numpy.put(a, ind, vals, mode=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( - "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(): +@pytest.mark.usefixtures("allow_fall_back_on_numpy") +def test_put_2d_ind(): a = numpy.arange(5) ia = dpnp.array(a) - dpnp.put(ia, [0, 2], [-44, -55]) - numpy.put(a, [0, 2], [-44, -55]) + 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", + [ + (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") def test_put_along_axis_val_int(): a = numpy.arange(16).reshape(4, 4) diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index dd78b23647e9..f5b0248f33c7 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -1134,3 +1134,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 17ccb55b6fd2..a935c699f771 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -356,3 +356,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]) diff --git a/tests/third_party/cupy/indexing_tests/test_insert.py b/tests/third_party/cupy/indexing_tests/test_insert.py index f48cff027335..1840b22d5c46 100644 --- a/tests/third_party/cupy/indexing_tests/test_insert.py +++ b/tests/third_party/cupy/indexing_tests/test_insert.py @@ -71,7 +71,8 @@ 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], + # The vals shape of array must be broadcastable to the shape of indices + "n_vals": [1, 4], } ) ) @@ -83,12 +84,17 @@ 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": + 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, 7]) 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 @@ -99,9 +105,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