From 7367ea39068faaa666b290de360f980c15b690b1 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Wed, 13 Sep 2023 21:13:31 -0500 Subject: [PATCH 1/3] implement dpnp.logaddexp --- dpnp/dpnp_algo/dpnp_elementwise_common.py | 53 +++++++++++++++ dpnp/dpnp_iface_trigonometric.py | 65 +++++++++++++++++++ tests/skipped_tests.tbl | 3 - tests/skipped_tests_gpu.tbl | 3 - tests/test_strides.py | 1 + tests/test_sycl_queue.py | 5 ++ tests/test_usm_type.py | 5 ++ .../cupy/math_tests/test_explog.py | 11 +++- 8 files changed, 139 insertions(+), 7 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index c9bb15433afd..69f043b9a8d6 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -73,6 +73,7 @@ "dpnp_less", "dpnp_less_equal", "dpnp_log", + "dpnp_logaddexp", "dpnp_logical_and", "dpnp_logical_not", "dpnp_logical_or", @@ -1705,6 +1706,58 @@ def dpnp_log(x, out=None, order="K"): return dpnp_array._create_from_usm_ndarray(res_usm) +_logaddexp_docstring_ = """ +logaddexp(x1, x2, out=None, order="K") + +Calculates the natural logarithm of the sum of exponentiations for each element +`x1_i` of the input array `x1` with the respective element `x2_i` of the input +array `x2`. + +This function calculates `log(exp(x1) + exp(x2))` more accurately for small +values of `x`. + +Args: + x1 (dpnp.ndarray): + First input array, expected to have a real-valued floating-point + data type. + x2 (dpnp.ndarray): + Second input array, also expected to have a real-valued + floating-point data type. + out ({None, dpnp.ndarray}, optional): + Output array to populate. + Array have the correct shape and the expected data type. + order ("C","F","A","K", None, optional): + Memory layout of the newly output array, if parameter `out` is `None`. + Default: "K". +Returns: + dpnp.ndarray: + An array containing the result of element-wise result. The data type + of the returned array is determined by the Type Promotion Rules. +""" + + +logaddexp_func = BinaryElementwiseFunc( + "logaddexp", + ti._logaddexp_result_type, + ti._logaddexp, + _logaddexp_docstring_, +) + + +def dpnp_logaddexp(x1, x2, out=None, order="K"): + """Invokes logaddexp() from dpctl.tensor implementation for logaddexp() function.""" + + # dpctl.tensor only works with usm_ndarray or scalar + x1_usm_or_scalar = dpnp.get_usm_ndarray_or_scalar(x1) + x2_usm_or_scalar = dpnp.get_usm_ndarray_or_scalar(x2) + out_usm = None if out is None else dpnp.get_usm_ndarray(out) + + res_usm = logaddexp_func( + x1_usm_or_scalar, x2_usm_or_scalar, out=out_usm, order=order + ) + return dpnp_array._create_from_usm_ndarray(res_usm) + + _logical_and_docstring_ = """ logical_and(x1, x2, out=None, order='K') diff --git a/dpnp/dpnp_iface_trigonometric.py b/dpnp/dpnp_iface_trigonometric.py index a68c9718b89f..94c8d9580820 100644 --- a/dpnp/dpnp_iface_trigonometric.py +++ b/dpnp/dpnp_iface_trigonometric.py @@ -59,6 +59,7 @@ dpnp_cosh, dpnp_hypot, dpnp_log, + dpnp_logaddexp, dpnp_sin, dpnp_sinh, dpnp_sqrt, @@ -88,6 +89,7 @@ "log10", "log1p", "log2", + "logaddexp", "rad2deg", "radians", "reciprocal", @@ -1058,6 +1060,69 @@ def log2(x1): return call_origin(numpy.log2, x1) +def logaddexp( + x1, + x2, + /, + out=None, + *, + where=True, + order="K", + dtype=None, + subok=True, + **kwargs, +): + """ + Calculates ``log(exp(x1) + exp(x2))``, element-wise. + + For full documentation refer to :obj:`numpy.logaddexp`. + + Returns + ------- + out : dpnp.ndarray + Logarithm of ``exp(x1) + exp(x2)``, element-wise. + + Limitations + ----------- + Parameters `x1` and `x2` are supported as either scalar, :class:`dpnp.ndarray` + or :class:`dpctl.tensor.usm_ndarray`, but both `x1` and `x2` can not be scalars at the same time. + Parameters `where`, `dtype` and `subok` are supported with their default values. + Keyword arguments `kwargs` are currently unsupported. + Otherwise the function will be executed sequentially on CPU. + Input array data types are limited by supported DPNP :ref:`Data types`. + + See Also + -------- + :obj:`dpnp.log` : Natural logarithm, element-wise. + :obj:`dpnp.exp` : Exponential, element-wise. + + Examples + -------- + >>> import dpnp as np + >>> prob1 = np.log(np.array(1e-50)) + >>> prob2 = np.log(np.array(2.5e-50)) + >>> prob12 = np.logaddexp(prob1, prob2) + >>> prob12 + -113.87649168120691 + >>> np.exp(prob12) + 3.5000000000000057e-50 + + """ + + return check_nd_call_func( + numpy.logaddexp, + dpnp_logaddexp, + x1, + x2, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, + ) + + def reciprocal(x1, **kwargs): """ Return the reciprocal of the argument, element-wise. diff --git a/tests/skipped_tests.tbl b/tests/skipped_tests.tbl index 83b2013ed1bf..e3a7d60bb24d 100644 --- a/tests/skipped_tests.tbl +++ b/tests/skipped_tests.tbl @@ -107,8 +107,6 @@ tests/test_umath.py::test_umaths[('ldexp', 'fi')] tests/test_umath.py::test_umaths[('ldexp', 'fl')] tests/test_umath.py::test_umaths[('ldexp', 'di')] tests/test_umath.py::test_umaths[('ldexp', 'dl')] -tests/test_umath.py::test_umaths[('logaddexp', 'ff')] -tests/test_umath.py::test_umaths[('logaddexp', 'dd')] tests/test_umath.py::test_umaths[('logaddexp2', 'ff')] tests/test_umath.py::test_umaths[('logaddexp2', 'dd')] tests/test_umath.py::test_umaths[('nextafter', 'ff')] @@ -490,7 +488,6 @@ tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticModf::test_m tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticRaisesWithNumpyInput_param_3_{name='angle', nargs=1}::test_raises_with_numpy_input -tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp2 tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp2_infinities tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_copysign_float diff --git a/tests/skipped_tests_gpu.tbl b/tests/skipped_tests_gpu.tbl index f81e94b0c547..b9da380e113c 100644 --- a/tests/skipped_tests_gpu.tbl +++ b/tests/skipped_tests_gpu.tbl @@ -53,8 +53,6 @@ tests/test_umath.py::test_umaths[('ldexp', 'fi')] tests/test_umath.py::test_umaths[('ldexp', 'fl')] tests/test_umath.py::test_umaths[('ldexp', 'di')] tests/test_umath.py::test_umaths[('ldexp', 'dl')] -tests/test_umath.py::test_umaths[('logaddexp', 'ff')] -tests/test_umath.py::test_umaths[('logaddexp', 'dd')] tests/test_umath.py::test_umaths[('logaddexp2', 'ff')] tests/test_umath.py::test_umaths[('logaddexp2', 'dd')] tests/test_umath.py::test_umaths[('nextafter', 'ff')] @@ -625,7 +623,6 @@ tests/third_party/cupy/manipulation_tests/test_tiling.py::TestTile_param_5_{reps tests/third_party/cupy/math_tests/test_arithmetic.py::TestArithmeticRaisesWithNumpyInput_param_3_{name='angle', nargs=1}::test_raises_with_numpy_input -tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp2 tests/third_party/cupy/math_tests/test_explog.py::TestExplog::test_logaddexp2_infinities tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_copysign_float diff --git a/tests/test_strides.py b/tests/test_strides.py index ca0638d4e694..098ff53f1e0b 100644 --- a/tests/test_strides.py +++ b/tests/test_strides.py @@ -167,6 +167,7 @@ def test_strides_reciprocal(dtype, shape): "fmax", "fmin", "hypot", + "logaddexp", "maximum", "minimum", "multiply", diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index 56713ca638ac..48bce836e3fd 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -369,6 +369,11 @@ def test_proj(device): [[1.0, 2.0, 3.0, 4.0]], [[-1.0, -2.0, -4.0, -5.0]], ), + pytest.param( + "logaddexp", + [[-1, 2, 5, 9]], + [[4, -3, 2, -8]], + ), pytest.param( "matmul", [[1.0, 0.0], [0.0, 1.0]], [[4.0, 1.0], [1.0, 2.0]] ), diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 116fd995d796..9fc4f86e42c3 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -371,6 +371,11 @@ def test_1in_1out(func, data, usm_type): [[1.0, 2.0, 3.0, 4.0]], [[-1.0, -2.0, -4.0, -5.0]], ), + pytest.param( + "logaddexp", + [[-1, 2, 5, 9]], + [[4, -3, 2, -8]], + ), pytest.param( "maximum", [[0.0, 1.0, 2.0]], diff --git a/tests/third_party/cupy/math_tests/test_explog.py b/tests/third_party/cupy/math_tests/test_explog.py index 9d68b125a1dd..54833670dac0 100644 --- a/tests/third_party/cupy/math_tests/test_explog.py +++ b/tests/third_party/cupy/math_tests/test_explog.py @@ -3,6 +3,7 @@ import numpy import pytest +from tests.helper import has_support_aspect64 from tests.third_party.cupy import testing @@ -17,7 +18,7 @@ def check_unary(self, name, xp, dtype, no_complex=False): return getattr(xp, name)(a) @testing.for_all_dtypes() - @testing.numpy_cupy_allclose(atol=1e-5) + @testing.numpy_cupy_allclose(atol=1e-5, type_check=has_support_aspect64()) def check_binary(self, name, xp, dtype, no_complex=False): if no_complex: if numpy.dtype(dtype).kind == "c": @@ -62,3 +63,11 @@ def test_logaddexp2(self): def test_logaddexp2_infinities(self, xp, dtype, val): a = xp.full((2, 3), val, dtype=dtype) return xp.logaddexp2(a, a) + + +@pytest.mark.parametrize("val", [numpy.inf, -numpy.inf]) +@testing.for_float_dtypes() +@testing.numpy_cupy_allclose() +def test_logaddexp_infinities(xp, dtype, val): + a = xp.full((2, 3), val, dtype=dtype) + return xp.logaddexp(a, a) From 098832c3280a0b0630b87c04078ee2507812c29c Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Fri, 29 Sep 2023 16:15:11 -0500 Subject: [PATCH 2/3] address comments --- dpnp/dpnp_iface_trigonometric.py | 4 ++-- tests/test_sycl_queue.py | 2 +- tests/test_usm_type.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dpnp/dpnp_iface_trigonometric.py b/dpnp/dpnp_iface_trigonometric.py index 94c8d9580820..1abf44de0680 100644 --- a/dpnp/dpnp_iface_trigonometric.py +++ b/dpnp/dpnp_iface_trigonometric.py @@ -1103,9 +1103,9 @@ def logaddexp( >>> prob2 = np.log(np.array(2.5e-50)) >>> prob12 = np.logaddexp(prob1, prob2) >>> prob12 - -113.87649168120691 + array(-113.87649168) >>> np.exp(prob12) - 3.5000000000000057e-50 + array(3.5e-50) """ diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index 48bce836e3fd..a11880d9d444 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -373,7 +373,7 @@ def test_proj(device): "logaddexp", [[-1, 2, 5, 9]], [[4, -3, 2, -8]], - ), + ), pytest.param( "matmul", [[1.0, 0.0], [0.0, 1.0]], [[4.0, 1.0], [1.0, 2.0]] ), diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 9fc4f86e42c3..a124d7e9a1e6 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -374,8 +374,8 @@ def test_1in_1out(func, data, usm_type): pytest.param( "logaddexp", [[-1, 2, 5, 9]], - [[4, -3, 2, -8]], - ), + [[4, -3, 2, -8]], + ), pytest.param( "maximum", [[0.0, 1.0, 2.0]], From 7175cd6742b5b06a06994b179e859a5867b453fe Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Mon, 2 Oct 2023 11:55:56 -0500 Subject: [PATCH 3/3] add nan test --- tests/third_party/cupy/math_tests/test_explog.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/third_party/cupy/math_tests/test_explog.py b/tests/third_party/cupy/math_tests/test_explog.py index 54833670dac0..2c43dcf09549 100644 --- a/tests/third_party/cupy/math_tests/test_explog.py +++ b/tests/third_party/cupy/math_tests/test_explog.py @@ -1,4 +1,5 @@ import unittest +import warnings import numpy import pytest @@ -71,3 +72,13 @@ def test_logaddexp2_infinities(self, xp, dtype, val): def test_logaddexp_infinities(xp, dtype, val): a = xp.full((2, 3), val, dtype=dtype) return xp.logaddexp(a, a) + + +@testing.for_float_dtypes() +@testing.numpy_cupy_allclose() +def test_logaddexp_nan(xp, dtype): + a = xp.full((2, 3), xp.nan, dtype=dtype) + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=RuntimeWarning) + result = xp.logaddexp(a, a) + return result