From 5a3e078236f9d2c9debd73201e0931be91c3117d Mon Sep 17 00:00:00 2001 From: Anton <100830759+antonwolfy@users.noreply.github.com> Date: Fri, 5 Apr 2024 15:16:03 +0200 Subject: [PATCH] Align with new array api version in dpctl (#1774) * Use utility function from dpctl.tensor._type_utils * Align sum and prod tests * Align logsumexp and reduce_hypot tests * Updated docstring of impacted functions --- dpnp/dpnp_iface_mathematical.py | 28 ++------- dpnp/dpnp_iface_nanfunctions.py | 23 ++----- dpnp/dpnp_iface_trigonometric.py | 40 +++++------- tests/test_arithmetic.py | 11 +--- tests/test_mathematical.py | 28 +++++++-- tests/test_sum.py | 5 +- .../cupy/math_tests/test_sumprod.py | 62 +++++++------------ 7 files changed, 72 insertions(+), 125 deletions(-) diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 38d160cf20a..7598f6ec675 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -39,6 +39,7 @@ import dpctl.tensor as dpt +import dpctl.tensor._type_utils as dtu import numpy from numpy.core.numeric import ( normalize_axis_index, @@ -2799,25 +2800,10 @@ def sum( If ``None``, the sum is computed over the entire array. Default: ``None``. dtype : dtype, optional - Data type of the returned array. If ``None``, the default data - type is inferred from the "kind" of the input array data type. - * If `a` has a real-valued floating-point data type, - the returned array will have the default real-valued - floating-point data type for the device where input - array `a` is allocated. - * If `a` has signed integral data type, the returned array - will have the default signed integral type for the device - where input array `a` is allocated. - * If `a` has unsigned integral data type, the returned array - will have the default unsigned integral type for the device - where input array `a` is allocated. - * If `a` has a complex-valued floating-point data type, - the returned array will have the default complex-valued - floating-pointer data type for the device where input - array `a` is allocated. - * If `a` has a boolean data type, the returned array will - have the default signed integral type for the device - where input array `a` is allocated. + Data type of the returned array. If ``None``, it defaults to the dtype + of `a`, unless `a` has an integer dtype with a precision less than that + of the default platform integer. In that case, the default platform + integer is used. If the data type (either specified or resolved) differs from the data type of `a`, the input array elements are cast to the specified data type before computing the sum. @@ -2905,8 +2891,6 @@ def sum( ) ) ): - from dpctl.tensor._reduction import _default_reduction_dtype - from dpnp.backend.extensions.sycl_ext import _sycl_ext_impl input = a @@ -2916,7 +2900,7 @@ def sum( queue = input.sycl_queue out_dtype = ( - _default_reduction_dtype(input.dtype, queue) + dtu._default_accumulation_dtype(input.dtype, queue) if dtype is None else dtype ) diff --git a/dpnp/dpnp_iface_nanfunctions.py b/dpnp/dpnp_iface_nanfunctions.py index 60bd87c8fd4..f9386be7608 100644 --- a/dpnp/dpnp_iface_nanfunctions.py +++ b/dpnp/dpnp_iface_nanfunctions.py @@ -714,25 +714,10 @@ def nansum( If ``None``, the sum is computed over the entire array. Default: ``None``. dtype : dtype, optional - Data type of the returned array. If ``None``, the default data - type is inferred from the "kind" of the input array data type. - * If `a` has a real-valued floating-point data type, - the returned array will have the default real-valued - floating-point data type for the device where input - array `a` is allocated. - * If `a` has signed integral data type, the returned array - will have the default signed integral type for the device - where input array `a` is allocated. - * If `a` has unsigned integral data type, the returned array - will have the default unsigned integral type for the device - where input array `a` is allocated. - * If `a` has a complex-valued floating-point data type, - the returned array will have the default complex-valued - floating-pointer data type for the device where input - array `a` is allocated. - * If `a` has a boolean data type, the returned array will - have the default signed integral type for the device - where input array `a` is allocated. + Data type of the returned array. If ``None``, it defaults to the dtype + of `a`, unless `a` has an integer dtype with a precision less than that + of the default platform integer. In that case, the default platform + integer is used. If the data type (either specified or resolved) differs from the data type of `a`, the input array elements are cast to the specified data type before computing the sum. diff --git a/dpnp/dpnp_iface_trigonometric.py b/dpnp/dpnp_iface_trigonometric.py index 9ce343a7f79..c29bd7218a8 100644 --- a/dpnp/dpnp_iface_trigonometric.py +++ b/dpnp/dpnp_iface_trigonometric.py @@ -1352,20 +1352,14 @@ def logsumexp(x, axis=None, out=None, dtype=None, keepdims=False): If provided, the result will be inserted into this array. It should be of the appropriate shape and dtype. dtype : data type, optional - Data type of the returned array. If ``None``, the default data - type is inferred from the "kind" of the input array data type. - * If `x` has a real-valued floating-point data type, - the returned array will have the default real-valued - floating-point data type for the device where input - array `x` is allocated. - * If `x` has a boolean or integral data type, the returned array - will have the default floating point data type for the device - where input array `x` is allocated. - * If `x` has a complex-valued floating-point data type, - an error is raised. + Data type of the returned array. If ``None``, it defaults to the dtype + of `a`, unless `a` has an integer dtype with a precision less than that + of the default platform integer. In that case, the default platform + integer is used. If the data type (either specified or resolved) differs from the data type of `x`, the input array elements are cast to the - specified data type before computing the result. Default: ``None``. + specified data type before computing the result. + Default: ``None``. keepdims : bool If ``True``, the reduced axes (dimensions) are included in the result as singleton dimensions, so that the returned array remains @@ -1498,20 +1492,14 @@ def reduce_hypot(x, axis=None, out=None, dtype=None, keepdims=False): If provided, the result will be inserted into this array. It should be of the appropriate shape and dtype. dtype : data type, optional - Data type of the returned array. If ``None``, the default data - type is inferred from the "kind" of the input array data type. - * If `x` has a real-valued floating-point data type, - the returned array will have the default real-valued - floating-point data type for the device where input - array `x` is allocated. - * If `x` has a boolean or integral data type, the returned array - will have the default floating point data type for the device - where input array `x` is allocated. - * If `x` has a complex-valued floating-point data type, - an error is raised. - If the data type (either specified or resolved) differs from the - data type of `x`, the input array elements are cast to the - specified data type before computing the result. Default: ``None``. + Data type of the returned array. If ``None``, it defaults to the dtype + of `a`, unless `a` has an integer dtype with a precision less than that + of the default platform integer. In that case, the default platform + integer is used. + If the data type (either specified or resolved) differs from the + data type of `x`, the input array elements are cast to the + specified data type before computing the result. + Default: ``None``. keepdims : bool If ``True``, the reduced axes (dimensions) are included in the result as singleton dimensions, so that the returned array remains diff --git a/tests/test_arithmetic.py b/tests/test_arithmetic.py index 6ec18a545f7..3e4e3162991 100644 --- a/tests/test_arithmetic.py +++ b/tests/test_arithmetic.py @@ -6,15 +6,6 @@ from tests.third_party.cupy import testing -# Note: numpy.sum() always upcast integers to (u)int64 and float32 to -# float64 for dtype=None. `np.sum` does that too for integers, but not for -# float32, so we need to special-case it for these tests -def _get_dtype_kwargs(xp, dtype): - if xp is numpy and dtype == numpy.float32 and has_support_aspect64(): - return {"dtype": numpy.float64} - return {} - - class TestArithmetic(unittest.TestCase): @testing.for_float_dtypes() @testing.numpy_cupy_allclose() @@ -42,7 +33,7 @@ def test_nanprod(self, xp, dtype): @testing.numpy_cupy_allclose() def test_nansum(self, xp, dtype): a = xp.array([-2.5, -1.5, xp.nan, 10.5, 1.5, xp.nan], dtype=dtype) - return xp.nansum(a, **_get_dtype_kwargs(xp, a.dtype)) + return xp.nansum(a) @testing.for_float_dtypes() @testing.numpy_cupy_allclose() diff --git a/tests/test_mathematical.py b/tests/test_mathematical.py index a73c8bd2f04..06786c984d1 100644 --- a/tests/test_mathematical.py +++ b/tests/test_mathematical.py @@ -1884,7 +1884,9 @@ class TestLogSumExp: def test_logsumexp(self, dtype, axis, keepdims): a = dpnp.ones((3, 4, 5, 6, 7), dtype=dtype) res = dpnp.logsumexp(a, axis=axis, keepdims=keepdims) - exp_dtype = dpnp.default_float_type(a.device) + exp_dtype = ( + dpnp.default_float_type(a.device) if dtype == dpnp.bool else None + ) exp = numpy.logaddexp.reduce( dpnp.asnumpy(a), axis=axis, keepdims=keepdims, dtype=exp_dtype ) @@ -1896,11 +1898,17 @@ def test_logsumexp(self, dtype, axis, keepdims): @pytest.mark.parametrize("keepdims", [True, False]) def test_logsumexp_out(self, dtype, axis, keepdims): a = dpnp.ones((3, 4, 5, 6, 7), dtype=dtype) - exp_dtype = dpnp.default_float_type(a.device) + exp_dtype = ( + dpnp.default_float_type(a.device) if dtype == dpnp.bool else None + ) exp = numpy.logaddexp.reduce( dpnp.asnumpy(a), axis=axis, keepdims=keepdims, dtype=exp_dtype ) - dpnp_out = dpnp.empty(exp.shape, dtype=exp_dtype) + + exp_dtype = exp.dtype + if exp_dtype == numpy.float64 and not has_support_aspect64(): + exp_dtype = numpy.float32 + dpnp_out = dpnp.empty_like(a, shape=exp.shape, dtype=exp_dtype) res = dpnp.logsumexp(a, axis=axis, out=dpnp_out, keepdims=keepdims) assert res is dpnp_out @@ -1926,7 +1934,9 @@ class TestReduceHypot: def test_reduce_hypot(self, dtype, axis, keepdims): a = dpnp.ones((3, 4, 5, 6, 7), dtype=dtype) res = dpnp.reduce_hypot(a, axis=axis, keepdims=keepdims) - exp_dtype = dpnp.default_float_type(a.device) + exp_dtype = ( + dpnp.default_float_type(a.device) if dtype == dpnp.bool else None + ) exp = numpy.hypot.reduce( dpnp.asnumpy(a), axis=axis, keepdims=keepdims, dtype=exp_dtype ) @@ -1938,11 +1948,17 @@ def test_reduce_hypot(self, dtype, axis, keepdims): @pytest.mark.parametrize("keepdims", [True, False]) def test_reduce_hypot_out(self, dtype, axis, keepdims): a = dpnp.ones((3, 4, 5, 6, 7), dtype=dtype) - exp_dtype = dpnp.default_float_type(a.device) + exp_dtype = ( + dpnp.default_float_type(a.device) if dtype == dpnp.bool else None + ) exp = numpy.hypot.reduce( dpnp.asnumpy(a), axis=axis, keepdims=keepdims, dtype=exp_dtype ) - dpnp_out = dpnp.empty(exp.shape, dtype=exp_dtype) + + exp_dtype = exp.dtype + if exp_dtype == numpy.float64 and not has_support_aspect64(): + exp_dtype = numpy.float32 + dpnp_out = dpnp.empty_like(a, shape=exp.shape, dtype=exp_dtype) res = dpnp.reduce_hypot(a, axis=axis, out=dpnp_out, keepdims=keepdims) assert res is dpnp_out diff --git a/tests/test_sum.py b/tests/test_sum.py index 25c294d051e..c58acb86e62 100644 --- a/tests/test_sum.py +++ b/tests/test_sum.py @@ -61,10 +61,7 @@ def test_sum_axis(): ia = dpnp.array(a) result = dpnp.sum(ia, axis=1) - if has_support_aspect64(): - expected = numpy.sum(a, axis=1, dtype=numpy.float64) - else: - expected = numpy.sum(a, axis=1) + expected = numpy.sum(a, axis=1) assert_array_equal(expected, result) diff --git a/tests/third_party/cupy/math_tests/test_sumprod.py b/tests/third_party/cupy/math_tests/test_sumprod.py index e4306788885..b0e07f1023e 100644 --- a/tests/third_party/cupy/math_tests/test_sumprod.py +++ b/tests/third_party/cupy/math_tests/test_sumprod.py @@ -6,15 +6,6 @@ from tests.third_party.cupy import testing -# Note: numpy.sum() always upcast integers to (u)int64 and float32 to -# float64 for dtype=None. `np.sum` does that too for integers, but not for -# float32, so we need to special-case it for these tests -def _get_dtype_kwargs(xp, dtype): - if xp is numpy and dtype == numpy.float32 and has_support_aspect64(): - return {"dtype": numpy.float64} - return {} - - class TestSumprod: def tearDown(self): # Free huge memory for slow test @@ -26,43 +17,43 @@ def tearDown(self): @testing.numpy_cupy_allclose() def test_sum_all(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype)) + return a.sum() @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_sum_all_keepdims(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), keepdims=True) + return a.sum(keepdims=True) @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_external_sum_all(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return xp.sum(a, **_get_dtype_kwargs(xp, dtype)) + return xp.sum(a) @testing.for_all_dtypes() @testing.numpy_cupy_allclose(rtol=1e-06) def test_sum_all2(self, xp, dtype): a = testing.shaped_arange((20, 30, 40), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype)) + return a.sum() @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_sum_all_transposed(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype).transpose(2, 0, 1) - return a.sum(**_get_dtype_kwargs(xp, dtype)) + return a.sum() @testing.for_all_dtypes() @testing.numpy_cupy_allclose(rtol=1e-06) def test_sum_all_transposed2(self, xp, dtype): a = testing.shaped_arange((20, 30, 40), xp, dtype).transpose(2, 0, 1) - return a.sum(**_get_dtype_kwargs(xp, dtype)) + return a.sum() @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_sum_axis(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=1) + return a.sum(axis=1) @testing.slow @testing.numpy_cupy_allclose() @@ -74,7 +65,7 @@ def test_sum_axis_huge(self, xp): @testing.numpy_cupy_allclose() def test_external_sum_axis(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return xp.sum(a, **_get_dtype_kwargs(xp, dtype), axis=1) + return xp.sum(a, axis=1) # float16 is omitted, since NumPy's sum on float16 arrays has more error # than CuPy's. @@ -82,49 +73,49 @@ def test_external_sum_axis(self, xp, dtype): @testing.numpy_cupy_allclose() def test_sum_axis2(self, xp, dtype): a = testing.shaped_arange((20, 30, 40), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=1) + return a.sum(axis=1) @testing.for_all_dtypes() @testing.numpy_cupy_allclose(contiguous_check=False) def test_sum_axis_transposed(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype).transpose(2, 0, 1) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=1) + return a.sum(axis=1) @testing.for_all_dtypes() @testing.numpy_cupy_allclose(contiguous_check=False) def test_sum_axis_transposed2(self, xp, dtype): a = testing.shaped_arange((20, 30, 40), xp, dtype).transpose(2, 0, 1) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=1) + return a.sum(axis=1) @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_sum_axes(self, xp, dtype): a = testing.shaped_arange((2, 3, 4, 5), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=(1, 3)) + return a.sum(axis=(1, 3)) @testing.for_all_dtypes() @testing.numpy_cupy_allclose(rtol=1e-4) def test_sum_axes2(self, xp, dtype): a = testing.shaped_arange((20, 30, 40, 50), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=(1, 3)) + return a.sum(axis=(1, 3)) @testing.for_all_dtypes() @testing.numpy_cupy_allclose(rtol=1e-6) def test_sum_axes3(self, xp, dtype): a = testing.shaped_arange((2, 3, 4, 5), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=(0, 2, 3)) + return a.sum(axis=(0, 2, 3)) @testing.for_all_dtypes() @testing.numpy_cupy_allclose(rtol=1e-6) def test_sum_axes4(self, xp, dtype): a = testing.shaped_arange((20, 30, 40, 50), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=(0, 2, 3)) + return a.sum(axis=(0, 2, 3)) @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_sum_empty_axis(self, xp, dtype): a = testing.shaped_arange((2, 3, 4, 5), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=()) + return a.sum(axis=()) @testing.for_all_dtypes_combination(names=["src_dtype", "dst_dtype"]) @testing.numpy_cupy_allclose() @@ -142,7 +133,7 @@ def test_sum_keepdims_and_dtype(self, xp, src_dtype, dst_dtype): @testing.numpy_cupy_allclose() def test_sum_keepdims_multiple_axes(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return a.sum(**_get_dtype_kwargs(xp, dtype), axis=(1, 2), keepdims=True) + return a.sum(axis=(1, 2), keepdims=True) @testing.for_all_dtypes() @testing.numpy_cupy_allclose() @@ -162,25 +153,25 @@ def test_sum_out_wrong_shape(self): @testing.numpy_cupy_allclose() def test_prod_all(self, xp, dtype): a = testing.shaped_arange((2, 3), xp, dtype) - return a.prod(**_get_dtype_kwargs(xp, dtype)) + return a.prod() @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_external_prod_all(self, xp, dtype): a = testing.shaped_arange((2, 3), xp, dtype) - return xp.prod(a, **_get_dtype_kwargs(xp, dtype)) + return xp.prod(a) @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_prod_axis(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return a.prod(axis=1, **_get_dtype_kwargs(xp, dtype)) + return a.prod(axis=1) @testing.for_all_dtypes() @testing.numpy_cupy_allclose() def test_external_prod_axis(self, xp, dtype): a = testing.shaped_arange((2, 3, 4), xp, dtype) - return xp.prod(a, axis=1, **_get_dtype_kwargs(xp, dtype)) + return xp.prod(a, axis=1) @testing.for_all_dtypes_combination(names=["src_dtype", "dst_dtype"]) @testing.numpy_cupy_allclose() @@ -228,12 +219,7 @@ def _test(self, xp, dtype): if not issubclass(dtype, xp.integer): a[:, 1] = xp.nan func = getattr(xp, self.func) - return func( - a, - **_get_dtype_kwargs(xp, dtype), - axis=self.axis, - keepdims=self.keepdims, - ) + return func(a, axis=self.axis, keepdims=self.keepdims) @testing.for_all_dtypes(no_bool=True, no_float16=True) @testing.numpy_cupy_allclose(type_check=has_support_aspect64()) @@ -299,7 +285,7 @@ def test_nansum_axes(self, xp, dtype): a = testing.shaped_arange(self.shape, xp, dtype) if not issubclass(dtype, xp.integer): a[:, 1] = xp.nan - return xp.nansum(a, **_get_dtype_kwargs(xp, dtype), axis=self.axis) + return xp.nansum(a, axis=self.axis) class TestNansumNanprodHuge: @@ -307,7 +293,7 @@ def _test(self, xp, nan_slice): a = testing.shaped_random((2048, 1, 1024), xp, "f") a[nan_slice] = xp.nan a = xp.broadcast_to(a, (2048, 256, 1024)) - return xp.nansum(a, **_get_dtype_kwargs(xp, a.dtype), axis=2) + return xp.nansum(a, axis=2) @testing.slow @testing.numpy_cupy_allclose(atol=1e-1)