diff --git a/CHANGELOG.md b/CHANGELOG.md index 1115e80b3243..b12214ce5bb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,7 @@ In addition, this release completes implementation of `dpnp.fft` module and adds * Use `dpctl::tensor::alloc_utils::sycl_free_noexcept` instead of `sycl::free` in `host_task` tasks associated with life-time management of temporary USM allocations [#2058](https://github.com/IntelPython/dpnp/pull/2058) * Improved implementation of `dpnp.kron` to avoid unnecessary copy for non-contiguous arrays [#2059](https://github.com/IntelPython/dpnp/pull/2059) * Updated the test suit for `dpnp.fft` module [#2071](https://github.com/IntelPython/dpnp/pull/2071) +* Reworked `dpnp.clip` implementation to align with Python Array API 2023.12 specification [#2048](https://github.com/IntelPython/dpnp/pull/2048) ### Fixed diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 443ad7c43da9..050f229e8a79 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -627,7 +627,7 @@ def around(x, /, decimals=0, out=None): ) -def clip(a, a_min, a_max, *, out=None, order="K", **kwargs): +def clip(a, /, min=None, max=None, *, out=None, order="K", **kwargs): """ Clip (limit) the values in an array. @@ -637,23 +637,27 @@ def clip(a, a_min, a_max, *, out=None, order="K", **kwargs): ---------- a : {dpnp.ndarray, usm_ndarray} Array containing elements to clip. - a_min, a_max : {dpnp.ndarray, usm_ndarray, None} + min, max : {dpnp.ndarray, 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`. + the corresponding edge. If both `min` and `max` are ``None``, + the elements of the returned array stay the same. + Both are broadcast against `a`. + Default : ``None``. out : {None, dpnp.ndarray, 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. + Default : ``None``. order : {"C", "F", "A", "K", None}, optional - Memory layout of the newly output array, if parameter `out` is `None`. + Memory layout of the newly output array, if parameter `out` is ``None``. If `order` is ``None``, the default value ``"K"`` will be used. + Default: ``"K"``. Returns ------- out : dpnp.ndarray - An array with the elements of `a`, but where values < `a_min` are - replaced with `a_min`, and those > `a_max` with `a_max`. + An array with the elements of `a`, but where values < `min` are + replaced with `min`, and those > `max` with `max`. Limitations ----------- @@ -687,15 +691,12 @@ def clip(a, a_min, a_max, *, out=None, order="K", **kwargs): if kwargs: raise NotImplementedError(f"kwargs={kwargs} is currently not supported") - if a_min is None and a_max is None: - raise ValueError("One of max or min must be given") - 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_min = None if min is None else dpnp.get_usm_ndarray_or_scalar(min) + usm_max = None if max is None else dpnp.get_usm_ndarray_or_scalar(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) diff --git a/tests/third_party/cupy/math_tests/test_misc.py b/tests/third_party/cupy/math_tests/test_misc.py index 086f7da17284..05254f712877 100644 --- a/tests/third_party/cupy/math_tests/test_misc.py +++ b/tests/third_party/cupy/math_tests/test_misc.py @@ -130,8 +130,17 @@ def test_clip_max_none(self, xp, dtype): def test_clip_min_max_none(self, dtype): for xp in (numpy, cupy): a = testing.shaped_arange((2, 3, 4), xp, dtype) - with pytest.raises(ValueError): - a.clip(None, None) + # According to Python Array API, clip() should return an array + # with the same elements in `a` if `min` and `max` are `None`. + # Numpy < 2.1 is not compatible with this and raises a ValueError + if ( + xp is numpy + and numpy.lib.NumpyVersion(numpy.__version__) < "2.1.0" + ): + with pytest.raises(ValueError): + a.clip(None, None) + else: + return a.clip(None, None) @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_array_equal() @@ -155,8 +164,18 @@ def test_external_clip3(self, xp, dtype): def test_external_clip4(self, dtype): for xp in (numpy, cupy): a = testing.shaped_arange((2, 3, 4), xp, dtype) - with pytest.raises(TypeError): - xp.clip(a, 3) + # Starting with numpy 2.1.0, it's possible to pass only one argument + # (min or max) as a keyword argument according to Python Array API. + # In older versions of numpy, both arguments must be positional; + # passing only one raises a TypeError. + if ( + xp is numpy + and numpy.lib.NumpyVersion(numpy.__version__) < "2.1.0" + ): + with pytest.raises(TypeError): + xp.clip(a, 3) + else: + return xp.clip(a, min=3) @testing.for_all_dtypes(no_complex=True) @testing.numpy_cupy_array_equal()