From 70c2614371c879eb1ed975ca5ab93574f79847e5 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Sun, 25 Oct 2026 15:00:28 +0100 Subject: [PATCH] Support boolean input in `dpnp.positive` NumPy 2.6 defines `positive` and the unary `+` operator for boolean arrays, returning a boolean copy instead of raising `TypeError`. Align `dpnp.positive` by running the `abs` kernel for boolean input, which is the identity on bool, as NumPy reuses its `absolute` loop. `dpnp.tensor.positive` is unchanged. --- CHANGELOG.md | 16 ++++++++++++ dpnp/dpnp_algo/dpnp_elementwise_common.py | 12 --------- dpnp/dpnp_iface_mathematical.py | 25 +++++++++++++++---- dpnp/tests/test_mathematical.py | 23 ++++++++--------- .../cupy/core_tests/test_ndarray_unary_op.py | 2 +- 5 files changed, 47 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eeb32c244e14..ab652851c7f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.22.0] - 2027-MM-DD + +### Added + +* Added support for boolean input in `dpnp.positive` and the `+` operator of `dpnp.ndarray` [#3128](https://github.com/IntelPython/dpnp/pull/3128) + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + ## [0.21.0] - 2026-MM-DD This release achieves 100% compliance with Python Array API specification (revision [2025.12](https://data-apis.org/array-api/2025.12/)). diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index f8110d7013be..8790c479365e 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -71,7 +71,6 @@ "DPNPUnaryTwoOutputsFunc", "acceptance_fn_gcd_lcm", "acceptance_fn_negative", - "acceptance_fn_positive", "acceptance_fn_sign", "acceptance_fn_subtract", "resolve_weak_types_2nd_arg_int", @@ -1386,17 +1385,6 @@ def acceptance_fn_negative(arg_dtype, buf_dt, res_dt, sycl_dev): return True -def acceptance_fn_positive(arg_dtype, buf_dt, res_dt, sycl_dev): - # positive is not defined for boolean data type - if arg_dtype.char == "?": - raise TypeError( - "The `positive` function is not supported for inputs of data type " - "bool" - ) - else: - return True - - def acceptance_fn_sign(arg_dtype, buf_dt, res_dt, sycl_dev): # sign is not defined for boolean data type if arg_dtype.char == "?": diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 6a08300e0850..e5d7daa26a24 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -70,7 +70,6 @@ DPNPUnaryTwoOutputsFunc, acceptance_fn_gcd_lcm, acceptance_fn_negative, - acceptance_fn_positive, acceptance_fn_sign, acceptance_fn_subtract, resolve_weak_types_2nd_arg_int, @@ -267,6 +266,23 @@ def _gradient_num_diff_edges( ) +def _positive(src, dst, sycl_queue, depends=None): + """Call positive kernel, using abs one for boolean input.""" + + # dpnp.tensor.positive has no boolean loop, abs is identity on bool + if src.dtype.char == "?": + return ti._abs(src, dst, sycl_queue, depends) + return ti._positive(src, dst, sycl_queue, depends) + + +def _positive_result_type(dtype): + """Get the result data type of positive, including boolean input.""" + + if dtype.char == "?": + return ti._abs_result_type(dtype) + return ti._positive_result_type(dtype) + + def _process_ediff1d_args(arg, arg_name, ary_dtype, ary_sycl_queue, usm_type): """Process the argument for ediff1d.""" if not dpnp.is_supported_array_type(arg): @@ -3901,7 +3917,7 @@ def _check_nan_inf(val, val_dt): Parameters ---------- x : {dpnp.ndarray, usm_ndarray} - Input array, expected to have a numeric data type. + Input array, expected to have a boolean or numeric data type. out : {None, dpnp.ndarray, usm_ndarray, tuple of ndarray}, optional Output array to populate. Array must have the correct shape and the expected data type. @@ -3951,10 +3967,9 @@ def _check_nan_inf(val, val_dt): positive = DPNPUnaryFunc( "positive", - ti._positive_result_type, - ti._positive, + _positive_result_type, + _positive, _POSITIVE_DOCSTRING, - acceptance_fn=acceptance_fn_positive, ) diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index b25cb2d84ce1..31f929e26a2b 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -2376,32 +2376,29 @@ def test_negative_boolean(): @pytest.mark.parametrize( "data", [[[1.0, -1.0], [0.1, -0.1]], [-2, -1, 0, 1, 2]], ids=["2D", "1D"] ) -@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True, no_bool=True)) +@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) def test_positive(data, dtype): a = get_abs_array(data, dtype=dtype) ia = dpnp.array(a) + # numpy < 2.6 raises TypeError for boolean input + if dtype == dpnp.bool and numpy_version() < "2.6.0.dev0": + expected = a.copy() + else: + expected = numpy.positive(a) + result = dpnp.positive(ia) - expected = numpy.positive(a) - assert_allclose(result, expected) + assert_dtype_allclose(result, expected) result = +ia - expected = +a - assert_allclose(result, expected) + assert_dtype_allclose(result, expected) # out keyword if dtype is not None: iout = dpnp.empty(expected.shape, dtype=dtype) result = dpnp.positive(ia, out=iout) assert result is iout - assert_allclose(result, expected) - - -def test_positive_boolean(): - ia = dpnp.array([True, False]) - - with pytest.raises(TypeError): - dpnp.positive(ia) + assert_dtype_allclose(result, expected) @pytest.mark.parametrize("dtype", get_float_dtypes(no_float16=False)) diff --git a/dpnp/tests/third_party/cupy/core_tests/test_ndarray_unary_op.py b/dpnp/tests/third_party/cupy/core_tests/test_ndarray_unary_op.py index 651cad1454ae..23d4ddc6199e 100644 --- a/dpnp/tests/third_party/cupy/core_tests/test_ndarray_unary_op.py +++ b/dpnp/tests/third_party/cupy/core_tests/test_ndarray_unary_op.py @@ -71,7 +71,7 @@ def test_pos_array(self, xp, dtype): return +a @pytest.mark.skip( - "dpnp boolean positive, the `+` operator, is not supported" + "dpnp boolean positive, the `+` operator, emits no DeprecationWarning" ) @testing.with_requires("numpy<1.25") def test_pos_boolarray(self):