diff --git a/dpnp/dpnp_array.py b/dpnp/dpnp_array.py index a5b060396ba..314491fcff5 100644 --- a/dpnp/dpnp_array.py +++ b/dpnp/dpnp_array.py @@ -628,7 +628,16 @@ def choose(input, choices, out=None, mode="raise"): return dpnp.choose(input, choices, out, mode) - # 'clip', + def clip(self, min=None, max=None, out=None, **kwargs): + """ + Clip (limit) the values in an array. + + Refer to :obj:`dpnp.clip` for full documentation. + + """ + + return dpnp.clip(self, min, max, out=out, **kwargs) + # 'compress', def conj(self): diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 53ae89d2a51..35465a046cc 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -84,6 +84,7 @@ "add", "around", "ceil", + "clip", "conj", "conjugate", "convolve", @@ -381,6 +382,78 @@ def ceil( ) +def clip(a, a_min, a_max, *, out=None, order="K", **kwargs): + """ + Clip (limit) the values in an array. + + For full documentation refer to :obj:`numpy.clip`. + + Parameters + ---------- + a : {dpnp_array, usm_ndarray} + Array containing elements to clip. + a_min, a_max : {dpnp_array, usm_ndarray, None} + Minimum and maximum value. If ``None``, clipping is not performed on the corresponding edge. + Only one of `a_min` and `a_max` may be ``None``. Both are broadcast against `a`. + out : {dpnp_array, usm_ndarray}, optional + The results will be placed in this array. It may be the input array for in-place clipping. + `out` must be of the right shape to hold the output. Its type is preserved. + order : {"C", "F", "A", "K", None}, optional + Memory layout of the newly output array, if parameter `out` is `None`. + If `order` is ``None``, the default value "K" will be used. + + Returns + ------- + out : dpnp_array + An array with the elements of `a`, but where values < `a_min` are replaced with `a_min`, + and those > `a_max` with `a_max`. + + Limitations + ----------- + Keyword argument `kwargs` is currently unsupported. + Otherwise ``NotImplementedError`` exception will be raised. + + Examples + -------- + >>> import dpnp as np + >>> a = np.arange(10) + >>> a + array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + >>> np.clip(a, 1, 8) + array([1, 1, 2, 3, 4, 5, 6, 7, 8, 8]) + >>> np.clip(a, 8, 1) + array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1]) + >>> np.clip(a, 3, 6, out=a) + array([3, 3, 3, 3, 4, 5, 6, 6, 6, 6]) + >>> a + array([3, 3, 3, 3, 4, 5, 6, 6, 6, 6]) + + >>> a = np.arange(10) + >>> a + array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) + >>> min = np.array([3, 4, 1, 1, 1, 4, 4, 4, 4, 4]) + >>> np.clip(a, min, 8) + array([3, 4, 2, 3, 4, 5, 6, 7, 8, 8]) + + """ + + if kwargs: + raise NotImplementedError(f"kwargs={kwargs} is currently not supported") + + if order is None: + order = "K" + + usm_arr = dpnp.get_usm_ndarray(a) + usm_min = None if a_min is None else dpnp.get_usm_ndarray_or_scalar(a_min) + usm_max = None if a_max is None else dpnp.get_usm_ndarray_or_scalar(a_max) + + usm_out = None if out is None else dpnp.get_usm_ndarray(out) + usm_res = dpt.clip(usm_arr, usm_min, usm_max, out=usm_out, order=order) + if out is not None and isinstance(out, dpnp_array): + return out + return dpnp_array._create_from_usm_ndarray(usm_res) + + def conjugate( x, /, diff --git a/tests/skipped_tests.tbl b/tests/skipped_tests.tbl index cca2992e656..c3d1fd8b077 100644 --- a/tests/skipped_tests.tbl +++ b/tests/skipped_tests.tbl @@ -412,8 +412,6 @@ tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_ldexp tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_nextafter_combination tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_nextafter_float -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_min_max_none -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip4 tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmax_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmin_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num @@ -435,14 +433,6 @@ tests/third_party/cupy/math_tests/test_misc.py::TestConvolveInvalid_param_1_{mod tests/third_party/cupy/math_tests/test_misc.py::TestConvolveInvalid_param_2_{mode='full'}::test_convolve_empty tests/third_party/cupy/math_tests/test_misc.py::TestConvolveInvalid_param_2_{mode='full'}::test_convolve_ndim -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip1 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip3 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_min_none -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_max_none -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip1 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip2 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip3 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip2 tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fabs tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fabs_negative tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_scalar_nan diff --git a/tests/skipped_tests_gpu.tbl b/tests/skipped_tests_gpu.tbl index b8df4b5179d..86805a4560b 100644 --- a/tests/skipped_tests_gpu.tbl +++ b/tests/skipped_tests_gpu.tbl @@ -509,8 +509,6 @@ tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_ldexp tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_nextafter_combination tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_nextafter_float -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_min_max_none -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip4 tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmax_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmin_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num @@ -532,14 +530,7 @@ tests/third_party/cupy/math_tests/test_misc.py::TestConvolveInvalid_param_1_{mod tests/third_party/cupy/math_tests/test_misc.py::TestConvolveInvalid_param_2_{mode='full'}::test_convolve_empty tests/third_party/cupy/math_tests/test_misc.py::TestConvolveInvalid_param_2_{mode='full'}::test_convolve_ndim -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip1 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip3 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_min_none -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_max_none -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip1 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip2 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip3 -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip2 + tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fabs tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fabs_negative tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num_scalar_nan diff --git a/tests/test_dparray.py b/tests/test_dparray.py index 3c57d44bf91..26446c855ac 100644 --- a/tests/test_dparray.py +++ b/tests/test_dparray.py @@ -246,3 +246,12 @@ def test_repeat(): numpy_array = numpy.arange(4).repeat(3) dpnp_array = dpnp.arange(4).repeat(3) assert_array_equal(numpy_array, dpnp_array) + + +def test_clip(): + numpy_array = numpy.arange(10) + dpnp_array = dpnp.arange(10) + result = dpnp.clip(dpnp_array, 3, 7) + expected = numpy.clip(numpy_array, 3, 7) + + assert_array_equal(expected, result) diff --git a/tests/test_mathematical.py b/tests/test_mathematical.py index 51a8de6a392..3cfbd88b4dc 100644 --- a/tests/test_mathematical.py +++ b/tests/test_mathematical.py @@ -13,6 +13,7 @@ ) import dpnp +from dpnp.dpnp_array import dpnp_array from .helper import ( assert_dtype_allclose, @@ -27,6 +28,98 @@ ) +class TestClip: + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_none=True, no_complex=True) + ) + @pytest.mark.parametrize("order", ["C", "F", "A", "K", None]) + def test_clip(self, dtype, order): + dp_a = dpnp.asarray([[1, 2, 8], [1, 6, 4], [9, 5, 1]], dtype=dtype) + np_a = dpnp.asnumpy(dp_a) + + result = dpnp.clip(dp_a, 2, 6, order=order) + expected = numpy.clip(np_a, 2, 6, order=order) + assert_allclose(expected, result) + assert expected.flags.c_contiguous == result.flags.c_contiguous + assert expected.flags.f_contiguous == result.flags.f_contiguous + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_none=True, no_complex=True) + ) + def test_clip_arrays(self, dtype): + dp_a = dpnp.asarray([1, 2, 8, 1, 6, 4, 1], dtype=dtype) + np_a = dpnp.asnumpy(dp_a) + + min_v = dpnp.asarray(2, dtype=dtype) + max_v = dpnp.asarray(6, dtype=dtype) + + result = dpnp.clip(dp_a, min_v, max_v) + expected = numpy.clip(np_a, min_v.asnumpy(), max_v.asnumpy()) + assert_allclose(expected, result) + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_none=True, no_complex=True) + ) + @pytest.mark.parametrize("in_dp", [dpnp, dpt]) + @pytest.mark.parametrize("out_dp", [dpnp, dpt]) + def test_clip_out(self, dtype, in_dp, out_dp): + np_a = numpy.array([[1, 2, 8], [1, 6, 4], [9, 5, 1]], dtype=dtype) + dp_a = in_dp.asarray(np_a) + + dp_out = out_dp.ones(dp_a.shape, dtype=dtype) + np_out = numpy.ones(np_a.shape, dtype=dtype) + + result = dpnp.clip(dp_a, 2, 6, out=dp_out) + expected = numpy.clip(np_a, 2, 6, out=np_out) + assert_allclose(expected, result) + assert_allclose(np_out, dp_out) + assert isinstance(result, dpnp_array) + + def test_input_nan(self): + np_a = numpy.array([-2.0, numpy.nan, 0.5, 3.0, 0.25, numpy.nan]) + dp_a = dpnp.array(np_a) + + result = dpnp.clip(dp_a, -1, 1) + expected = numpy.clip(np_a, -1, 1) + assert_array_equal(result, expected) + + # TODO: unmute the test once dpctl resolves the issue + @pytest.mark.skip(reason="dpctl-1489 issue") + @pytest.mark.parametrize( + "kwargs", + [ + {"min": numpy.nan}, + {"max": numpy.nan}, + {"min": numpy.nan, "max": numpy.nan}, + {"min": -2, "max": numpy.nan}, + {"min": numpy.nan, "max": 10}, + ], + ) + def test_nan_edges(self, kwargs): + np_a = numpy.arange(7) + dp_a = dpnp.asarray(np_a) + + result = dp_a.clip(**kwargs) + expected = np_a.clip(**kwargs) + assert_allclose(expected, result) + + @pytest.mark.parametrize( + "kwargs", + [ + {"casting": "same_kind"}, + {"dtype": "i8"}, + {"subok": True}, + {"where": True}, + ], + ) + def test_not_implemented_kwargs(self, kwargs): + a = dpnp.arange(8, dtype="i4") + + numpy.clip(a.asnumpy(), 1, 5, **kwargs) + with pytest.raises(NotImplementedError): + dpnp.clip(a, 1, 5, **kwargs) + + class TestDiff: @pytest.mark.parametrize("n", list(range(0, 3))) @pytest.mark.parametrize("dt", get_integer_dtypes()) diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index b1175703b01..26d047c103d 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -1372,3 +1372,14 @@ def test_solve(device): assert_sycl_queue_equal(result_queue, dpnp_x.sycl_queue) assert_sycl_queue_equal(result_queue, dpnp_y.sycl_queue) + + +@pytest.mark.parametrize( + "device", + valid_devices, + ids=[device.filter_string for device in valid_devices], +) +def test_clip(device): + x = dpnp.arange(10, device=device) + y = dpnp.clip(x, 3, 7) + assert_sycl_queue_equal(x.sycl_queue, y.sycl_queue) diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 4da04c2d675..b889bab6010 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -527,3 +527,10 @@ def test_solve(matrix, vector, usm_type_matrix, usm_type_vector): assert z.usm_type == du.get_coerced_usm_type( [usm_type_matrix, usm_type_vector] ) + + +@pytest.mark.parametrize("usm_type", list_of_usm_types, ids=list_of_usm_types) +def test_clip(usm_type): + x = dp.arange(10, usm_type=usm_type) + y = dp.clip(x, 2, 7) + assert x.usm_type == y.usm_type diff --git a/tests/third_party/cupy/math_tests/test_misc.py b/tests/third_party/cupy/math_tests/test_misc.py index c05432e3642..241457fbad9 100644 --- a/tests/third_party/cupy/math_tests/test_misc.py +++ b/tests/third_party/cupy/math_tests/test_misc.py @@ -519,7 +519,7 @@ def test_convolve_non_contiguous(self, xp, dtype, mode): return xp.convolve(a[::200], b[10::70], mode=mode) @testing.for_all_dtypes(no_float16=True) - @testing.numpy_cupy_allclose(rtol=1e-4) + @testing.numpy_cupy_allclose(rtol=5e-4) def test_convolve_large_non_contiguous(self, xp, dtype, mode): a = testing.shaped_arange((10000,), xp, dtype) b = testing.shaped_arange((100,), xp, dtype)