From 48d36a450cb1d333561cc5509aa39dd94833dfee Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Wed, 26 Mar 2025 09:54:42 -0700 Subject: [PATCH 1/9] update dpnp.fix docstring --- CHANGELOG.md | 1 + dpnp/dpnp_iface_mathematical.py | 4 +-- dpnp/dpnp_iface_statistics.py | 4 +-- dpnp/tests/test_binary_ufuncs.py | 6 ++++ dpnp/tests/test_mathematical.py | 61 ++++++++++++-------------------- 5 files changed, 33 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c515151616..eea23c311e8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Allowed input array of `uint64` dtype in `dpnp.bincount` [#2361](https://github.com/IntelPython/dpnp/pull/2361) * The vector norms `ord={None, 1, 2, inf}` and the matrix norms `ord={None, 1, 2, inf, "fro", "nuc"}` now consistently return zero for empty arrays, which are arrays with at least one axis of size zero. This change affects `dpnp.linalg.norm`, `dpnp.linalg.vector_norm`, and `dpnp.linalg.matrix_norm`. Previously, dpnp would either raise errors or return zero depending on the parameters provided [#2371](https://github.com/IntelPython/dpnp/pull/2371) * Improved performance of `dpnp.nansum`, `dpnp.nanprod`, `dpnp.nancumsum`, and `dpnp.nancumprod` by reusing `dpnp.nan_to_num` function in implementation of the functions [#2339](https://github.com/IntelPython/dpnp/pull/2339) +* Updated `dpnp.fix` docstring to indicate that the output data-type will be a floating point dtype but not necessarily the default floating point dtype [#2392](https://github.com/IntelPython/dpnp/pull/2392) ### Fixed diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 1d3c7789a332..3a75c688cec4 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -1750,8 +1750,8 @@ def ediff1d(ary, to_end=None, to_begin=None): ------- out : dpnp.ndarray An array with the rounded values and with the same dimensions as the input. - The returned array will have the default floating point data type for the - device where `a` is allocated. + The returned array will have a floating point data type that input can cast + to it safely considering device capabilities. If `out` is ``None`` then a float array is returned with the rounded values. Otherwise the result is stored there and the return value `out` is a reference to that array. diff --git a/dpnp/dpnp_iface_statistics.py b/dpnp/dpnp_iface_statistics.py index 57084d86a449..4cade1d9ca1e 100644 --- a/dpnp/dpnp_iface_statistics.py +++ b/dpnp/dpnp_iface_statistics.py @@ -797,8 +797,8 @@ def cov( Default: ``None``. dtype : {None, str, dtype object}, optional Data-type of the result. By default, the return data-type will have - at least floating point type based on the capabilities of the device on - which the input arrays reside. + the default floating point data-type of the device on which the input + arrays reside. Default: ``None``. diff --git a/dpnp/tests/test_binary_ufuncs.py b/dpnp/tests/test_binary_ufuncs.py index 1bc1ddd5b967..41e72b6b957d 100644 --- a/dpnp/tests/test_binary_ufuncs.py +++ b/dpnp/tests/test_binary_ufuncs.py @@ -684,6 +684,7 @@ def test_float_nan(self, dt): expected = numpy.nextafter(numpy.nan, a) assert_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") @pytest.mark.parametrize("val", [0x7C00, 0x8000], ids=["val1", "val2"]) def test_f16_strides(self, val): a = numpy.arange(val, dtype=numpy.uint16).astype(numpy.float16) @@ -702,6 +703,7 @@ def test_f16_strides(self, val): expected = numpy.nextafter(a[1:], -hinf) assert_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") @pytest.mark.parametrize("val", [0x7C00, 0x8000], ids=["val1", "val2"]) def test_f16_array_inf(self, val): a = numpy.arange(val, dtype=numpy.uint16).astype(numpy.float16) @@ -716,6 +718,7 @@ def test_f16_array_inf(self, val): expected = numpy.nextafter(-hinf, a) assert_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") @pytest.mark.parametrize( "sign1, sign2", [ @@ -734,6 +737,7 @@ def test_f16_inf(self, sign1, sign2): expected = numpy.nextafter(hinf1, hinf2) assert_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") @pytest.mark.parametrize("val", [0x7C00, 0x8000], ids=["val1", "val2"]) def test_f16_array_nan(self, val): a = numpy.arange(val, dtype=numpy.uint16).astype(numpy.float16) @@ -748,6 +752,7 @@ def test_f16_array_nan(self, val): expected = numpy.nextafter(nan, a) assert_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") @pytest.mark.parametrize( "val1, val2", [ @@ -765,6 +770,7 @@ def test_f16_inf_nan(self, val1, val2): expected = numpy.nextafter(v1, v2) assert_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") @pytest.mark.parametrize( "val, scalar", [ diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index ecb146b3f213..e7a5850c5b2b 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -36,30 +36,6 @@ from .third_party.cupy import testing -def _get_output_data_type(dtype): - """Return a data type specified by input `dtype` and device capabilities.""" - dtype_float16 = any( - dpnp.issubdtype(dtype, t) for t in (dpnp.bool, dpnp.int8, dpnp.uint8) - ) - dtype_float32 = any( - dpnp.issubdtype(dtype, t) for t in (dpnp.int16, dpnp.uint16) - ) - if dtype_float16: - out_dtype = dpnp.float16 if has_support_aspect16() else dpnp.float32 - elif dtype_float32: - out_dtype = dpnp.float32 - elif dpnp.issubdtype(dtype, dpnp.complexfloating): - out_dtype = dpnp.complex64 - if has_support_aspect64() and dtype != dpnp.complex64: - out_dtype = dpnp.complex128 - else: - out_dtype = dpnp.float32 - if has_support_aspect64() and dtype != dpnp.float32: - out_dtype = dpnp.float64 - - return out_dtype - - @pytest.mark.parametrize("deg", [True, False]) class TestAngle: def test_angle_bool(self, deg): @@ -775,6 +751,16 @@ def test_errors(self): class TestFix: + def get_numpy_output_dtype(self, dtype): + # this is used to determine the output dtype of numpy array + # which is on cpu so no need for checking has_support_aspect64 + if has_support_aspect16() and dpnp.can_cast(dtype, dpnp.float16): + return dpnp.float16 + if dpnp.can_cast(dtype, dpnp.float32): + return dpnp.float32 + if dpnp.can_cast(dtype, dpnp.float64): + return dpnp.float64 + @pytest.mark.parametrize( "dt", get_all_dtypes(no_none=True, no_complex=True) ) @@ -794,28 +780,25 @@ def test_complex(self, xp, dt): xp.fix(a) @pytest.mark.parametrize( - "a_dt", get_all_dtypes(no_none=True, no_bool=True, no_complex=True) + "dt", get_all_dtypes(no_none=True, no_complex=True) ) - def test_out(self, a_dt): - a = get_abs_array( - [[1.0, 1.1, 1.5, 1.8], [-1.0, -1.1, -1.5, -1.8]], a_dt - ) - ia = dpnp.array(a) - - out_dt = _get_output_data_type(a.dtype) - out = numpy.zeros_like(a, dtype=out_dt) - iout = dpnp.array(out) + def test_out(self, dt): + data = [[1.0, 1.1, 1.5, 1.8], [-1.0, -1.1, -1.5, -1.8]] + a = get_abs_array(data, dtype=dt) + # numpy output has the same dtype as input + # dpnp output always has a floating point dtype + dt_out = self.get_numpy_output_dtype(a.dtype) + out = numpy.zeros_like(a, dtype=dt_out) + ia, iout = dpnp.array(a), dpnp.array(out) result = dpnp.fix(ia, out=iout) expected = numpy.fix(a, out=out) assert_array_equal(result, expected) @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") - @pytest.mark.parametrize("dt", [bool, numpy.float16]) - def test_out_float16(self, dt): - a = numpy.array( - [[1.0, 1.1], [1.5, 1.8], [-1.0, -1.1], [-1.5, -1.8]], dtype=dt - ) + def test_out_float16(self): + data = [[1.0, 1.1], [1.5, 1.8], [-1.0, -1.1], [-1.5, -1.8]] + a = numpy.array(data, dtype=numpy.float16) out = numpy.zeros_like(a, dtype=numpy.float16) ia, iout = dpnp.array(a), dpnp.array(out) From bd0cad2208dcffdf3366ab6349d6b06702bb0910 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 27 Mar 2025 06:45:29 -0700 Subject: [PATCH 2/9] align dpnp.fix with numpy 2.2 --- .../ufunc/elementwise_functions/fix.cpp | 10 +- .../kernels/elementwise_functions/fix.hpp | 7 +- dpnp/dpnp_iface_mathematical.py | 15 +- dpnp/tests/test_mathematical.py | 156 +++++++----------- 4 files changed, 81 insertions(+), 107 deletions(-) diff --git a/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp b/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp index 9fefbac85d41..0d06f7116e8d 100644 --- a/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp +++ b/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp @@ -60,7 +60,15 @@ template struct OutputType { using value_type = - typename std::disjunction, + typename std::disjunction, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, + td_ns::TypeMapResultEntry, td_ns::TypeMapResultEntry, td_ns::TypeMapResultEntry, td_ns::DefaultResultEntry>::result_type; diff --git a/dpnp/backend/kernels/elementwise_functions/fix.hpp b/dpnp/backend/kernels/elementwise_functions/fix.hpp index b32ab0d7a74e..a7c0ebb1d8ff 100644 --- a/dpnp/backend/kernels/elementwise_functions/fix.hpp +++ b/dpnp/backend/kernels/elementwise_functions/fix.hpp @@ -43,7 +43,12 @@ struct FixFunctor resT operator()(const argT &x) const { - return (x >= 0.0) ? sycl::floor(x) : sycl::ceil(x); + if constexpr (std::is_integral_v) { + return x; + } + else { + return (x >= 0.0) ? sycl::floor(x) : sycl::ceil(x); + } } }; } // namespace dpnp::kernels::fix diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 3a75c688cec4..7652c22f666f 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -1728,14 +1728,14 @@ def ediff1d(ary, to_end=None, to_begin=None): Round to nearest integer towards zero. Round an array of floats element-wise to nearest integer towards zero. -The rounded values are returned as floats. +The rounded values have the same data-type as the input. For full documentation refer to :obj:`numpy.fix`. Parameters ---------- x : {dpnp.ndarray, usm_ndarray} - An array of floats to be rounded. + Input array, expected to have a real-valued data type. out : {None, dpnp.ndarray, usm_ndarray}, optional Output array to populate. Array must have the correct shape and the expected data type. @@ -1749,12 +1749,11 @@ def ediff1d(ary, to_end=None, to_begin=None): Returns ------- out : dpnp.ndarray - An array with the rounded values and with the same dimensions as the input. - The returned array will have a floating point data type that input can cast - to it safely considering device capabilities. - If `out` is ``None`` then a float array is returned with the rounded values. - Otherwise the result is stored there and the return value `out` is - a reference to that array. + An array with the same dimensions and data-type as the input. + If second argument is not supplied then a new array is returned + with the rounded values. + If a second argument is supplied the result is stored there. + The return value `out` is then a reference to that array. Limitations ----------- diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index e7a5850c5b2b..610bcb47ffc2 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -750,75 +750,6 @@ def test_errors(self): assert_raises(ExecutionPlacementError, dpnp.ediff1d, ia, to_end=to_end) -class TestFix: - def get_numpy_output_dtype(self, dtype): - # this is used to determine the output dtype of numpy array - # which is on cpu so no need for checking has_support_aspect64 - if has_support_aspect16() and dpnp.can_cast(dtype, dpnp.float16): - return dpnp.float16 - if dpnp.can_cast(dtype, dpnp.float32): - return dpnp.float32 - if dpnp.can_cast(dtype, dpnp.float64): - return dpnp.float64 - - @pytest.mark.parametrize( - "dt", get_all_dtypes(no_none=True, no_complex=True) - ) - def test_basic(self, dt): - a = get_abs_array([[1.0, 1.1, 1.5, 1.8], [-1.0, -1.1, -1.5, -1.8]], dt) - ia = dpnp.array(a) - - result = dpnp.fix(ia) - expected = numpy.fix(a) - assert_array_equal(result, expected) - - @pytest.mark.parametrize("xp", [numpy, dpnp]) - @pytest.mark.parametrize("dt", get_complex_dtypes()) - def test_complex(self, xp, dt): - a = xp.array([1.1, -1.1], dtype=dt) - with pytest.raises((ValueError, TypeError)): - xp.fix(a) - - @pytest.mark.parametrize( - "dt", get_all_dtypes(no_none=True, no_complex=True) - ) - def test_out(self, dt): - data = [[1.0, 1.1, 1.5, 1.8], [-1.0, -1.1, -1.5, -1.8]] - a = get_abs_array(data, dtype=dt) - # numpy output has the same dtype as input - # dpnp output always has a floating point dtype - dt_out = self.get_numpy_output_dtype(a.dtype) - out = numpy.zeros_like(a, dtype=dt_out) - ia, iout = dpnp.array(a), dpnp.array(out) - - result = dpnp.fix(ia, out=iout) - expected = numpy.fix(a, out=out) - assert_array_equal(result, expected) - - @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") - def test_out_float16(self): - data = [[1.0, 1.1], [1.5, 1.8], [-1.0, -1.1], [-1.5, -1.8]] - a = numpy.array(data, dtype=numpy.float16) - out = numpy.zeros_like(a, dtype=numpy.float16) - ia, iout = dpnp.array(a), dpnp.array(out) - - result = dpnp.fix(ia, out=iout) - expected = numpy.fix(a, out=out) - assert_array_equal(result, expected) - - @pytest.mark.parametrize("xp", [numpy, dpnp]) - @pytest.mark.parametrize("dt", [bool] + get_integer_dtypes()) - def test_out_invalid_dtype(self, xp, dt): - a = xp.array([[1.5, 1.8], [-1.0, -1.1]]) - out = xp.zeros_like(a, dtype=dt) - - with pytest.raises((ValueError, TypeError)): - xp.fix(a, out=out) - - def test_scalar(self): - assert_raises(TypeError, dpnp.fix, -3.4) - - class TestGradient: @pytest.mark.parametrize("dt", get_all_dtypes(no_none=True, no_bool=True)) def test_basic(self, dt): @@ -2274,50 +2205,81 @@ def test_projection(self, dtype): assert dpnp.allclose(result, expected) -@pytest.mark.parametrize("func", ["ceil", "floor", "trunc"]) +@pytest.mark.parametrize("func", ["ceil", "floor", "trunc", "fix"]) class TestRoundingFuncs: @pytest.mark.parametrize( - "dtype", get_all_dtypes(no_none=True, no_complex=True) + "dt", get_all_dtypes(no_none=True, no_complex=True) ) - def test_out(self, func, dtype): - a = generate_random_numpy_array(10, dtype) + def test_basic(self, func, dt): + a = generate_random_numpy_array((2, 4), dt) + ia = dpnp.array(a) + + result = getattr(dpnp, func)(ia) expected = getattr(numpy, func)(a) + assert_array_equal(result, expected) - ia = dpnp.array(a) - out_dt = numpy.int8 if dtype == dpnp.bool else dtype - iout = dpnp.empty(expected.shape, dtype=out_dt) + @pytest.mark.parametrize("xp", [numpy, dpnp]) + @pytest.mark.parametrize("dt", get_complex_dtypes()) + def test_complex(self, func, xp, dt): + a = xp.array([1.1, -1.1], dtype=dt) + with pytest.raises((ValueError, TypeError)): + getattr(xp, func)(a) + + @testing.with_requires("numpy>=2.1.0") + @pytest.mark.parametrize( + "dt", get_all_dtypes(no_none=True, no_complex=True) + ) + def test_out(self, func, dt): + a = generate_random_numpy_array(10, dt) + dt_out = numpy.int8 if dt == dpnp.bool else dt + out = numpy.empty(a.shape, dtype=dt_out) + ia, iout = dpnp.array(a), dpnp.array(out) + + expected = getattr(numpy, func)(a, out=out) result = getattr(dpnp, func)(ia, out=iout) + assert result is iout + assert_array_equal(result, expected) + @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") + def test_out_float16(self, func): + a = generate_random_numpy_array((4, 2), numpy.float16) + out = numpy.zeros_like(a, dtype=numpy.float16) + ia, iout = dpnp.array(a), dpnp.array(out) + + result = getattr(dpnp, func)(ia, out=iout) + expected = getattr(numpy, func)(a, out=out) assert result is iout - # numpy.ceil, numpy.floor, numpy.trunc always return float dtype for - # NumPy < 2.1.0 while output has the dtype of input for NumPy >= 2.1.0 - # (dpnp follows the latter behavior except for boolean dtype where it - # returns int8) - if numpy_version() < "2.1.0" or dtype == dpnp.bool: - check_type = False - else: - check_type = True - assert_dtype_allclose(result, expected, check_type=check_type) + assert_array_equal(result, expected) + @pytest.mark.parametrize("xp", [numpy, dpnp]) @pytest.mark.parametrize( - "dtype", get_all_dtypes(no_complex=True, no_none=True)[:-1] + "dt_out", get_all_dtypes(no_none=True, no_complex=True)[:-1] ) - def test_invalid_dtype(self, func, dtype): - dpnp_dtype = get_all_dtypes(no_complex=True, no_none=True)[-1] - ia = dpnp.arange(10, dtype=dpnp_dtype) - iout = dpnp.empty(10, dtype=dtype) - - with pytest.raises(ValueError): - getattr(dpnp, func)(ia, out=iout) + def test_invalid_dtype(self, func, xp, dt_out): + dt_in = get_all_dtypes(no_none=True, no_complex=True)[-1] + a = xp.arange(10, dtype=dt_in) + out = xp.empty(10, dtype=dt_out) + if dt_out == numpy.float32 and dt_in == numpy.float64: + if xp == dpnp: + # NumPy allows "same_kind" casting, dpnp does not + assert_raises(ValueError, getattr(dpnp, func), a, out=out) + else: + numpy_error = numpy._core._exceptions._UFuncOutputCastingError + assert_raises( + (ValueError, numpy_error), getattr(xp, func), a, out=out + ) + @pytest.mark.parametrize("xp", [numpy, dpnp]) @pytest.mark.parametrize( "shape", [(0,), (15,), (2, 2)], ids=["(0,)", "(15,)", "(2, 2)"] ) - def test_invalid_shape(self, func, shape): - ia = dpnp.arange(10, dtype=dpnp.float32) - iout = dpnp.empty(shape, dtype=dpnp.float32) + def test_invalid_shape(self, func, xp, shape): + a = xp.arange(10, dtype=xp.float32) + out = xp.empty(shape, dtype=xp.float32) + assert_raises(ValueError, getattr(xp, func), a, out=out) - assert_raises(ValueError, getattr(dpnp, func), ia, out=iout) + def test_scalar(self, func): + assert_raises(TypeError, getattr(dpnp, func), -3.4) class TestHypot: From 5f04646d160a1e42e93f51b1c64eb7a7f350673e Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 27 Mar 2025 06:50:21 -0700 Subject: [PATCH 3/9] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eea23c311e8f..aca3bb19e806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Allowed input array of `uint64` dtype in `dpnp.bincount` [#2361](https://github.com/IntelPython/dpnp/pull/2361) * The vector norms `ord={None, 1, 2, inf}` and the matrix norms `ord={None, 1, 2, inf, "fro", "nuc"}` now consistently return zero for empty arrays, which are arrays with at least one axis of size zero. This change affects `dpnp.linalg.norm`, `dpnp.linalg.vector_norm`, and `dpnp.linalg.matrix_norm`. Previously, dpnp would either raise errors or return zero depending on the parameters provided [#2371](https://github.com/IntelPython/dpnp/pull/2371) * Improved performance of `dpnp.nansum`, `dpnp.nanprod`, `dpnp.nancumsum`, and `dpnp.nancumprod` by reusing `dpnp.nan_to_num` function in implementation of the functions [#2339](https://github.com/IntelPython/dpnp/pull/2339) -* Updated `dpnp.fix` docstring to indicate that the output data-type will be a floating point dtype but not necessarily the default floating point dtype [#2392](https://github.com/IntelPython/dpnp/pull/2392) +* Updated `dpnp.fix` to return output with the same data-type of input [#2392](https://github.com/IntelPython/dpnp/pull/2392) ### Fixed From 1c70c46f0da4be8c4e53e9109253c67672077ffb Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 27 Mar 2025 08:25:41 -0700 Subject: [PATCH 4/9] fix raised error type --- dpnp/tests/test_mathematical.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index 610bcb47ffc2..2f078d962815 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -2264,9 +2264,8 @@ def test_invalid_dtype(self, func, xp, dt_out): # NumPy allows "same_kind" casting, dpnp does not assert_raises(ValueError, getattr(dpnp, func), a, out=out) else: - numpy_error = numpy._core._exceptions._UFuncOutputCastingError assert_raises( - (ValueError, numpy_error), getattr(xp, func), a, out=out + (ValueError, TypeError), getattr(xp, func), a, out=out ) @pytest.mark.parametrize("xp", [numpy, dpnp]) From d4d09c6cef24d42453697f8444eb8b08560ff338 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad <120411540+vtavana@users.noreply.github.com> Date: Thu, 27 Mar 2025 10:28:14 -0500 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Anton <100830759+antonwolfy@users.noreply.github.com> --- dpnp/dpnp_iface_mathematical.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 7652c22f666f..a5ba9edc302c 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -1750,10 +1750,10 @@ def ediff1d(ary, to_end=None, to_begin=None): ------- out : dpnp.ndarray An array with the same dimensions and data-type as the input. - If second argument is not supplied then a new array is returned + If `out` is ``None`` then a new array is returned with the rounded values. - If a second argument is supplied the result is stored there. - The return value `out` is then a reference to that array. + Otherwise the result is stored there and the return value `out` is + a reference to that array. Limitations ----------- From b6638ebbb5aee28338e3534cf27a4587bbe43695 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 27 Mar 2025 14:55:51 -0700 Subject: [PATCH 6/9] make changes on python-side only --- .../ufunc/elementwise_functions/fix.cpp | 10 +---- .../kernels/elementwise_functions/fix.hpp | 7 +--- dpnp/dpnp_algo/dpnp_elementwise_common.py | 40 +++++++++++++++++++ dpnp/dpnp_iface_mathematical.py | 15 +++---- dpnp/tests/test_mathematical.py | 1 + 5 files changed, 49 insertions(+), 24 deletions(-) diff --git a/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp b/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp index 0d06f7116e8d..9fefbac85d41 100644 --- a/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp +++ b/dpnp/backend/extensions/ufunc/elementwise_functions/fix.cpp @@ -60,15 +60,7 @@ template struct OutputType { using value_type = - typename std::disjunction, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, - td_ns::TypeMapResultEntry, + typename std::disjunction, td_ns::TypeMapResultEntry, td_ns::TypeMapResultEntry, td_ns::DefaultResultEntry>::result_type; diff --git a/dpnp/backend/kernels/elementwise_functions/fix.hpp b/dpnp/backend/kernels/elementwise_functions/fix.hpp index a7c0ebb1d8ff..b32ab0d7a74e 100644 --- a/dpnp/backend/kernels/elementwise_functions/fix.hpp +++ b/dpnp/backend/kernels/elementwise_functions/fix.hpp @@ -43,12 +43,7 @@ struct FixFunctor resT operator()(const argT &x) const { - if constexpr (std::is_integral_v) { - return x; - } - else { - return (x >= 0.0) ? sycl::floor(x) : sycl::ceil(x); - } + return (x >= 0.0) ? sycl::floor(x) : sycl::ceil(x); } }; } // namespace dpnp::kernels::fix diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index be0e357a0ccf..f38fc8914d96 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -41,6 +41,7 @@ "DPNPI0", "DPNPAngle", "DPNPBinaryFunc", + "DPNPFix", "DPNPImag", "DPNPReal", "DPNPRound", @@ -511,6 +512,45 @@ def __call__(self, x, deg=False, out=None, order="K"): return res +class DPNPFix(DPNPUnaryFunc): + """Class that implements dpnp.real unary element-wise functions.""" + + def __init__( + self, + name, + result_type_resolver_fn, + unary_dp_impl_fn, + docs, + ): + super().__init__( + name, + result_type_resolver_fn, + unary_dp_impl_fn, + docs, + ) + + def __call__(self, x, out=None, order="K"): + if not dpnp.is_supported_array_type(x): + pass # pass to raise error in main implementation + elif dpnp.issubdtype(x.dtype, dpnp.inexact): + pass # for inexact types, pass to calculate in the backend + elif out is not None and ( + not dpnp.is_supported_array_type(out) or out.dtype != x.dtype + ): + pass # pass to raise error in main implementation + else: + # for exact types, return the input + if out is None: + return dpnp.asarray(x, copy=True) + + if isinstance(out, dpt.usm_ndarray): + out = dpnp_array._create_from_usm_ndarray(out) + out[...] = x + return out + + return super().__call__(x, out=out, order=order) + + class DPNPI0(DPNPUnaryFunc): """Class that implements dpnp.i0 unary element-wise functions.""" diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index a5ba9edc302c..8d35d62b1a96 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -63,6 +63,7 @@ DPNPI0, DPNPAngle, DPNPBinaryFunc, + DPNPFix, DPNPImag, DPNPReal, DPNPRound, @@ -1755,12 +1756,6 @@ def ediff1d(ary, to_end=None, to_begin=None): Otherwise the result is stored there and the return value `out` is a reference to that array. -Limitations ------------ -Parameters `where` and `subok` are supported with their default values. -Keyword argument `kwargs` is currently unsupported. -Otherwise ``NotImplementedError`` exception will be raised. - See Also -------- :obj:`dpnp.round` : Round to given number of decimals. @@ -1781,7 +1776,7 @@ def ediff1d(ary, to_end=None, to_begin=None): array([ 2., 2., -2., -2.]) """ -fix = DPNPUnaryFunc( +fix = DPNPFix( "fix", ufi._fix_result_type, ufi._fix, @@ -1933,8 +1928,10 @@ def ediff1d(ary, to_end=None, to_begin=None): Notes ----- -Some spreadsheet programs calculate the "floor-towards-zero", in other words floor(-2.5) == -2. -DPNP instead uses the definition of floor where floor(-2.5) == -3. +Some spreadsheet programs calculate the "floor-towards-zero", where +``floor(-2.5) == -2``. DPNP instead uses the definition of :obj:`dpnp.floor` +where ``floor(-2.5) == -3``. The "floor-towards-zero" function is called +:obj:`dpnp.fix` in DPNP. Examples -------- diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index 2f078d962815..f59ac6f5307d 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -2231,6 +2231,7 @@ def test_complex(self, func, xp, dt): ) def test_out(self, func, dt): a = generate_random_numpy_array(10, dt) + # TODO: use dt_out = dt when dpctl#2030 is fixed dt_out = numpy.int8 if dt == dpnp.bool else dt out = numpy.empty(a.shape, dtype=dt_out) ia, iout = dpnp.array(a), dpnp.array(out) From 5e1a355d53fb038636ff675d37a8fda90ee87f8c Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Fri, 28 Mar 2025 12:03:57 -0700 Subject: [PATCH 7/9] add new tests --- dpnp/dpnp_algo/dpnp_elementwise_common.py | 12 ++-- dpnp/tests/test_mathematical.py | 72 +++++++++++++++-------- 2 files changed, 55 insertions(+), 29 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index f38fc8914d96..edef86822959 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -513,7 +513,7 @@ def __call__(self, x, deg=False, out=None, order="K"): class DPNPFix(DPNPUnaryFunc): - """Class that implements dpnp.real unary element-wise functions.""" + """Class that implements dpnp.fix unary element-wise functions.""" def __init__( self, @@ -534,14 +534,16 @@ def __call__(self, x, out=None, order="K"): pass # pass to raise error in main implementation elif dpnp.issubdtype(x.dtype, dpnp.inexact): pass # for inexact types, pass to calculate in the backend - elif out is not None and ( - not dpnp.is_supported_array_type(out) or out.dtype != x.dtype - ): + elif out is not None and not dpnp.is_supported_array_type(out): pass # pass to raise error in main implementation + elif out is not None and out.dtype != x.dtype: + raise ValueError( + f"Output array of type {x.dtype} is needed, got {out.dtype}" + ) else: # for exact types, return the input if out is None: - return dpnp.asarray(x, copy=True) + return dpnp.copy(x, order=order) if isinstance(out, dpt.usm_ndarray): out = dpnp_array._create_from_usm_ndarray(out) diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index f59ac6f5307d..b5a2cec4ca50 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -2227,19 +2227,37 @@ def test_complex(self, func, xp, dt): @testing.with_requires("numpy>=2.1.0") @pytest.mark.parametrize( - "dt", get_all_dtypes(no_none=True, no_complex=True) + "dt_in", get_all_dtypes(no_none=True, no_complex=True) ) - def test_out(self, func, dt): - a = generate_random_numpy_array(10, dt) - # TODO: use dt_out = dt when dpctl#2030 is fixed - dt_out = numpy.int8 if dt == dpnp.bool else dt + @pytest.mark.parametrize( + "dt_out", get_all_dtypes(no_none=True, no_complex=True) + ) + def test_out(self, func, dt_in, dt_out): + a = generate_random_numpy_array(10, dt_in) out = numpy.empty(a.shape, dtype=dt_out) ia, iout = dpnp.array(a), dpnp.array(out) - expected = getattr(numpy, func)(a, out=out) - result = getattr(dpnp, func)(ia, out=iout) - assert result is iout - assert_array_equal(result, expected) + if dt_in != dt_out: + if numpy.can_cast(dt_in, dt_out, casting="same_kind"): + # NumPy allows "same_kind" casting, dpnp does not + if func != "fix" and dt_in == dpnp.bool and dt_out == dpnp.int8: + # TODO: get rid of w/a when dpctl#2030 is fixed + pass + else: + assert_raises(ValueError, getattr(dpnp, func), ia, out=iout) + else: + assert_raises(ValueError, getattr(dpnp, func), ia, out=iout) + assert_raises(TypeError, getattr(numpy, func), a, out=out) + else: + if func != "fix" and dt_in == dpnp.bool: + # TODO: get rid of w/a when dpctl#2030 is fixed + out = out.astype(numpy.int8) + iout = iout.astype(dpnp.int8) + + expected = getattr(numpy, func)(a, out=out) + result = getattr(dpnp, func)(ia, out=iout) + assert result is iout + assert_array_equal(result, expected) @pytest.mark.skipif(not has_support_aspect16(), reason="no fp16 support") def test_out_float16(self, func): @@ -2252,22 +2270,22 @@ def test_out_float16(self, func): assert result is iout assert_array_equal(result, expected) - @pytest.mark.parametrize("xp", [numpy, dpnp]) @pytest.mark.parametrize( - "dt_out", get_all_dtypes(no_none=True, no_complex=True)[:-1] + "dt", get_all_dtypes(no_none=True, no_complex=True) ) - def test_invalid_dtype(self, func, xp, dt_out): - dt_in = get_all_dtypes(no_none=True, no_complex=True)[-1] - a = xp.arange(10, dtype=dt_in) - out = xp.empty(10, dtype=dt_out) - if dt_out == numpy.float32 and dt_in == numpy.float64: - if xp == dpnp: - # NumPy allows "same_kind" casting, dpnp does not - assert_raises(ValueError, getattr(dpnp, func), a, out=out) - else: - assert_raises( - (ValueError, TypeError), getattr(xp, func), a, out=out - ) + def test_out_usm_ndarray(self, func, dt): + a = generate_random_numpy_array(10, dt) + out = numpy.empty(a.shape, dtype=dt) + ia, usm_out = dpnp.array(a), dpt.asarray(out) + + if func != "fix" and dt == dpnp.bool: + # TODO: get rid of w/a when dpctl#2030 is fixed + out = out.astype(numpy.int8) + usm_out = dpt.asarray(usm_out, dtype=dpnp.int8) + + expected = getattr(numpy, func)(a, out=out) + result = getattr(dpnp, func)(ia, out=usm_out) + assert_array_equal(result, expected) @pytest.mark.parametrize("xp", [numpy, dpnp]) @pytest.mark.parametrize( @@ -2278,9 +2296,15 @@ def test_invalid_shape(self, func, xp, shape): out = xp.empty(shape, dtype=xp.float32) assert_raises(ValueError, getattr(xp, func), a, out=out) - def test_scalar(self, func): + def test_error(self, func): + # scalar, unsupported input assert_raises(TypeError, getattr(dpnp, func), -3.4) + # unsupported out + a = dpnp.array([1, 2, 3]) + out = numpy.empty_like(3, dtype=a.dtype) + assert_raises(TypeError, getattr(dpnp, func), a, out=out) + class TestHypot: @pytest.mark.parametrize( From 34299054a98ec69dc744f83f78e70583e6193260 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Fri, 28 Mar 2025 12:32:47 -0700 Subject: [PATCH 8/9] add a comment --- dpnp/dpnp_algo/dpnp_elementwise_common.py | 1 + dpnp/tests/test_mathematical.py | 1 + 2 files changed, 2 insertions(+) diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index edef86822959..0e232e0a2bcc 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -537,6 +537,7 @@ def __call__(self, x, out=None, order="K"): elif out is not None and not dpnp.is_supported_array_type(out): pass # pass to raise error in main implementation elif out is not None and out.dtype != x.dtype: + # passing will raise an error but with incorrect needed dtype raise ValueError( f"Output array of type {x.dtype} is needed, got {out.dtype}" ) diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index b5a2cec4ca50..590050751235 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -2270,6 +2270,7 @@ def test_out_float16(self, func): assert result is iout assert_array_equal(result, expected) + @testing.with_requires("numpy>=2.1.0") @pytest.mark.parametrize( "dt", get_all_dtypes(no_none=True, no_complex=True) ) From 9cb65be67184eb6a5d2d4a38736987d4dfb5f442 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Fri, 28 Mar 2025 12:52:25 -0700 Subject: [PATCH 9/9] add an assert --- dpnp/tests/test_mathematical.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dpnp/tests/test_mathematical.py b/dpnp/tests/test_mathematical.py index 590050751235..b66b65dc1c44 100644 --- a/dpnp/tests/test_mathematical.py +++ b/dpnp/tests/test_mathematical.py @@ -2286,6 +2286,7 @@ def test_out_usm_ndarray(self, func, dt): expected = getattr(numpy, func)(a, out=out) result = getattr(dpnp, func)(ia, out=usm_out) + assert result.get_array() is usm_out assert_array_equal(result, expected) @pytest.mark.parametrize("xp", [numpy, dpnp])