Skip to content

Commit

Permalink
Update dpnp.clip() with Numpy 2.0 (#2048)
Browse files Browse the repository at this point in the history
* Update dpnp.clip to align with numpy 2.0

* Update cupy tests

* Compliance of dpnp.clip() with Array API

* Update CHANGELOG.md
  • Loading branch information
vlad-perevezentsev committed Sep 27, 2024
1 parent a8370dc commit bd6546e
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
25 changes: 13 additions & 12 deletions dpnp/dpnp_iface_mathematical.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
-----------
Expand Down Expand Up @@ -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)
Expand Down
27 changes: 23 additions & 4 deletions tests/third_party/cupy/math_tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand Down

0 comments on commit bd6546e

Please sign in to comment.