diff --git a/CHANGELOG.md b/CHANGELOG.md index 38b27b00fdc4..7222877dbff6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ This release is compatible with NumPy 2.5. * Avoided a copy of `dpnp.einsum` result into C-order by building the product in the requested layout directly [#3069](https://github.com/IntelPython/dpnp/pull/3069) * Updated the implementation of `dpnp.putmask` by adding dedicated contiguous and strided SYCL kernels [#3014](https://github.com/IntelPython/dpnp/pull/3014) * Changed `dpnp.sort`, `dpnp.argsort`, and their `dpnp.ndarray`/`dpnp.tensor` counterparts to place `NaN` values last instead of first when sorting in descending order [#3066](https://github.com/IntelPython/dpnp/pull/3066) +* Documented and made explicit support for `copy=None` ("copy only if needed") in `dpnp.nan_to_num` [#3083](https://github.com/IntelPython/dpnp/pull/3083) ### Deprecated diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 7b7ffba9dbfa..6a08300e0850 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -3605,10 +3605,10 @@ def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None): ---------- x : {dpnp.ndarray, usm_ndarray} Input data. - copy : bool, optional + copy : {None, bool}, optional Whether to create a copy of `x` (``True``) or to replace values - in-place (``False``). The in-place operation only occurs if casting to - an array does not require a copy. + in-place (``False`` or ``None``). In-place operation requires `x` to + be writable; a ``ValueError`` is raised otherwise. Default: ``True``. nan : {scalar, array_like}, optional @@ -3635,8 +3635,8 @@ def nan_to_num(x, copy=True, nan=0.0, posinf=None, neginf=None): Returns ------- out : dpnp.ndarray - `x`, with the non-finite values replaced. If `copy` is ``False``, this - may be `x` itself. + `x`, with the non-finite values replaced. If `copy` is ``False`` or + ``None``, this may be `x` itself. See Also -------- @@ -3713,10 +3713,11 @@ def _check_nan_inf(val, val_dt): if copy: out = dpnp.empty_like(x) - else: - if not x.flags.writable: - raise ValueError("copy is required for read-only array `x`") + elif x.flags.writable: + # copy=False or None (copy-if-needed): replace values in-place out = x + else: + raise ValueError("copy is required for read-only array `x`") # handle a special case when nan and infs are all scalars if all(dpnp.isscalar(el) for el in (nan, max_f, min_f)): diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index ff74cfbfdb38..b25cb2d84ce1 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -1491,13 +1491,25 @@ def test_numpy_input_array(self): dpnp.nan_to_num(a) @pytest.mark.parametrize("xp", [dpnp, numpy]) - def test_error_readonly(self, xp): + @pytest.mark.parametrize("copy", [False, None]) + def test_error_readonly(self, xp, copy): a = xp.array([0, 1, xp.nan, xp.inf, -xp.inf]) a.flags["W"] = False with pytest.raises(ValueError, match="read-only"): - xp.nan_to_num(a, copy=False) + xp.nan_to_num(a, copy=copy) - @pytest.mark.parametrize("copy", [True, False]) + @pytest.mark.parametrize("copy", [False, None]) + @pytest.mark.parametrize("dt", get_float_complex_dtypes()) + def test_copy_in_place(self, copy, dt): + a = numpy.array([0, 1, numpy.nan, numpy.inf, -numpy.inf], dtype=dt) + ia = dpnp.array(a) + + result = dpnp.nan_to_num(ia, copy=copy) + expected = numpy.nan_to_num(a, copy=copy) + assert result is ia + assert_allclose(result, expected) + + @pytest.mark.parametrize("copy", [True, False, None]) @pytest.mark.parametrize("dt", get_all_dtypes(no_bool=True, no_none=True)) def test_strided(self, copy, dt): n = 10