From 7696ea73aa3c7f16057232190da329a9f068dfdf Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Fri, 28 Jul 2023 03:39:34 -0500 Subject: [PATCH 1/5] Reuse dpctl.tensor.isnan(), dpctl.tensor.isinf(), and dpctl.tensor.isfinite() functions. --- dpnp/dpnp_algo/dpnp_algo_logic.pxi | 48 ---------- dpnp/dpnp_algo/dpnp_elementwise_common.py | 109 ++++++++++++++++++++++ dpnp/dpnp_iface_logic.py | 102 +++++++++++--------- tests/test_logic.py | 24 ++++- 4 files changed, 189 insertions(+), 94 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_algo_logic.pxi b/dpnp/dpnp_algo/dpnp_algo_logic.pxi index 0d26f1ddeb5..d5cf2aa740d 100644 --- a/dpnp/dpnp_algo/dpnp_algo_logic.pxi +++ b/dpnp/dpnp_algo/dpnp_algo_logic.pxi @@ -40,9 +40,6 @@ __all__ += [ "dpnp_allclose", "dpnp_any", "dpnp_isclose", - "dpnp_isfinite", - "dpnp_isinf", - "dpnp_isnan", ] @@ -174,48 +171,3 @@ cpdef utils.dpnp_descriptor dpnp_isclose(utils.dpnp_descriptor input1, result.get_pyobj()[i] = numpy.isclose(input1.get_pyobj()[i], input2.get_pyobj()[i], rtol, atol, equal_nan) return result - - -cpdef utils.dpnp_descriptor dpnp_isfinite(utils.dpnp_descriptor input1): - input1_obj = input1.get_array() - cdef utils.dpnp_descriptor result = utils_py.create_output_descriptor_py(input1.shape, - dpnp.bool, - None, - device=input1_obj.sycl_device, - usm_type=input1_obj.usm_type, - sycl_queue=input1_obj.sycl_queue) - - for i in range(result.size): - result.get_pyobj()[i] = numpy.isfinite(input1.get_pyobj()[i]) - - return result - - -cpdef utils.dpnp_descriptor dpnp_isinf(utils.dpnp_descriptor input1): - input1_obj = input1.get_array() - cdef utils.dpnp_descriptor result = utils_py.create_output_descriptor_py(input1.shape, - dpnp.bool, - None, - device=input1_obj.sycl_device, - usm_type=input1_obj.usm_type, - sycl_queue=input1_obj.sycl_queue) - - for i in range(result.size): - result.get_pyobj()[i] = numpy.isinf(input1.get_pyobj()[i]) - - return result - - -cpdef utils.dpnp_descriptor dpnp_isnan(utils.dpnp_descriptor input1): - input1_obj = input1.get_array() - cdef utils.dpnp_descriptor result = utils_py.create_output_descriptor_py(input1.shape, - dpnp.bool, - None, - device=input1_obj.sycl_device, - usm_type=input1_obj.usm_type, - sycl_queue=input1_obj.sycl_queue) - - for i in range(result.size): - result.get_pyobj()[i] = numpy.isnan(input1.get_pyobj()[i]) - - return result diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index e7aab3ba490..c97c79ec0ad 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -49,6 +49,9 @@ "dpnp_floor_divide", "dpnp_greater", "dpnp_greater_equal", + "dpnp_isfinite", + "dpnp_isinf", + "dpnp_isnan", "dpnp_less", "dpnp_less_equal", "dpnp_log", @@ -466,6 +469,112 @@ def dpnp_greater_equal(x1, x2, out=None, order="K"): return dpnp_array._create_from_usm_ndarray(res_usm) +_isfinite_docstring = """ +isfinite(x, out=None, order="K") + +Checks if each element of input array is a finite number. + +Args: + x (dpnp.ndarray): + Input array, expected to have numeric 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", optional): + Memory layout of the newly output array, if parameter `out` is `None`. + Default: "K". +Returns: + dpnp.ndarray: + An array which is True where `x` is not positive infinity, + negative infinity, or NaN, False otherwise. + The data type of the returned array is `bool`. +""" + + +def dpnp_isfinite(x, out=None, order="K"): + """Invokes isfinite() from dpctl.tensor implementation for isfinite() function.""" + + # dpctl.tensor only works with usm_ndarray + x1_usm = dpnp.get_usm_ndarray(x) + out_usm = None if out is None else dpnp.get_usm_ndarray(out) + + func = UnaryElementwiseFunc( + "isfinite", ti._isfinite_result_type, ti._isfinite, _isfinite_docstring + ) + res_usm = func(x1_usm, out=out_usm, order=order) + return dpnp_array._create_from_usm_ndarray(res_usm) + + +_isinf_docstring = """ +isinf(x, out=None, order="K") + +Checks if each element of input array is an infinity. + +Args: + x (dpnp.ndarray): + Input array, expected to have numeric 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", optional): + Memory layout of the newly output array, if parameter `out` is `None`. + Default: "K". +Returns: + dpnp.ndarray: + An array which is True where `x` is positive or negative infinity, + False otherwise. The data type of the returned array is `bool`. +""" + + +def dpnp_isinf(x, out=None, order="K"): + """Invokes isinf() from dpctl.tensor implementation for isinf() function.""" + + # dpctl.tensor only works with usm_ndarray + x1_usm = dpnp.get_usm_ndarray(x) + out_usm = None if out is None else dpnp.get_usm_ndarray(out) + + func = UnaryElementwiseFunc( + "isinf", ti._isinf_result_type, ti._isinf, _isinf_docstring + ) + res_usm = func(x1_usm, out=out_usm, order=order) + return dpnp_array._create_from_usm_ndarray(res_usm) + + +_isnan_docstring = """ +isnan(x, out=None, order="K") + +Checks if each element of an input array is a NaN. + +Args: + x (dpnp.ndarray): + Input array, expected to have numeric 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", optional): + Memory layout of the newly output array, if parameter `out` is `None`. + Default: "K". +Returns: + dpnp.ndarray: + An array which is True where x is NaN, False otherwise. + The data type of the returned array is `bool`. +""" + + +def dpnp_isnan(x, out=None, order="K"): + """Invokes isnan() from dpctl.tensor implementation for isnan() function.""" + + # dpctl.tensor only works with usm_ndarray + x1_usm = dpnp.get_usm_ndarray(x) + out_usm = None if out is None else dpnp.get_usm_ndarray(out) + + func = UnaryElementwiseFunc( + "isnan", ti._isnan_result_type, ti._isnan, _isnan_docstring + ) + res_usm = func(x1_usm, out=out_usm, order=order) + return dpnp_array._create_from_usm_ndarray(res_usm) + + _less_docstring_ = """ less(x1, x2, out=None, order="K") diff --git a/dpnp/dpnp_iface_logic.py b/dpnp/dpnp_iface_logic.py index fdf1dcb3532..f00b61f8ed2 100644 --- a/dpnp/dpnp_iface_logic.py +++ b/dpnp/dpnp_iface_logic.py @@ -51,6 +51,9 @@ dpnp_equal, dpnp_greater, dpnp_greater_equal, + dpnp_isfinite, + dpnp_isinf, + dpnp_isnan, dpnp_less, dpnp_less_equal, dpnp_logical_and, @@ -492,7 +495,9 @@ def isclose(x1, x2, rtol=1e-05, atol=1e-08, equal_nan=False): ) -def isfinite(x1, out=None, **kwargs): +def isfinite( + x, /, out=None, *, where=True, order="K", dtype=None, subok=True, **kwargs +): """ Test element-wise for finiteness (not infinity or not Not a Number). @@ -500,11 +505,10 @@ def isfinite(x1, out=None, **kwargs): Limitations ----------- - Input array is supported as :obj:`dpnp.ndarray`. + Parameters `x` is only supported as either :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. + Parameters `where`, `dtype` and `subok` are supported with their default values. Otherwise the function will be executed sequentially on CPU. Input array data types are limited by supported DPNP :ref:`Data types`. - Parameter ``out`` is supported only with default value ``None``. - Parameter ``where`` is supported only with default value ``True``. See Also -------- @@ -525,23 +529,27 @@ def isfinite(x1, out=None, **kwargs): -------- >>> import dpnp as np >>> x = np.array([-np.inf, 0., np.inf]) - >>> out = np.isfinite(x) - >>> [i for i in out] - [False, True, False] + >>> np.isfinite(x) + array([False, True, False]) """ - # x1_desc = dpnp.get_dpnp_descriptor(x1) - # if x1_desc and kwargs: - # if out is not None: - # pass - # else: - # return dpnp_isfinite(x1_desc).get_pyobj() - - return call_origin(numpy.isfinite, x1, out, **kwargs) + return check_nd_call_func( + numpy.isfinite, + dpnp_isfinite, + x, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, + ) -def isinf(x1, out=None, **kwargs): +def isinf( + x, /, out=None, *, where=True, order="K", dtype=None, subok=True, **kwargs +): """ Test element-wise for positive or negative infinity. @@ -549,11 +557,10 @@ def isinf(x1, out=None, **kwargs): Limitations ----------- - Input array is supported as :obj:`dpnp.ndarray`. + Parameters `x` is only supported as either :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. + Parameters `where`, `dtype` and `subok` are supported with their default values. Otherwise the function will be executed sequentially on CPU. Input array data types are limited by supported DPNP :ref:`Data types`. - Parameter ``out`` is supported only with default value ``None``. - Parameter ``where`` is supported only with default value ``True``. See Also -------- @@ -569,23 +576,27 @@ def isinf(x1, out=None, **kwargs): -------- >>> import dpnp as np >>> x = np.array([-np.inf, 0., np.inf]) - >>> out = np.isinf(x) - >>> [i for i in out] - [True, False, True] + >>> np.isinf(x) + array([ True, False, True]) """ - # x1_desc = dpnp.get_dpnp_descriptor(x1) - # if x1_desc and kwargs: - # if out is not None: - # pass - # else: - # return dpnp_isinf(x1_desc).get_pyobj() - - return call_origin(numpy.isinf, x1, out, **kwargs) + return check_nd_call_func( + numpy.isinf, + dpnp_isinf, + x, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, + ) -def isnan(x1, out=None, **kwargs): +def isnan( + x, /, out=None, *, where=True, order="K", dtype=None, subok=True, **kwargs +): """ Test element-wise for NaN and return result as a boolean array. @@ -593,11 +604,10 @@ def isnan(x1, out=None, **kwargs): Limitations ----------- - Input array is supported as :obj:`dpnp.ndarray`. + Parameters `x` is only supported as either :class:`dpnp.ndarray` or :class:`dpctl.tensor.usm_ndarray`. + Parameters `where`, `dtype` and `subok` are supported with their default values. Otherwise the function will be executed sequentially on CPU. Input array data types are limited by supported DPNP :ref:`Data types`. - Parameter ``out`` is supported only with default value ``None``. - Parameter ``where`` is supported only with default value ``True``. See Also -------- @@ -614,20 +624,22 @@ def isnan(x1, out=None, **kwargs): -------- >>> import dpnp as np >>> x = np.array([np.inf, 0., np.nan]) - >>> out = np.isnan(x) - >>> [i for i in out] - [False, False, True] + >>> np.isnan(x) + array([False, False, True]) """ - # x1_desc = dpnp.get_dpnp_descriptor(x1) - # if x1_desc and kwargs: - # if out is not None: - # pass - # else: - # return dpnp_isnan(x1_desc).get_pyobj() - - return call_origin(numpy.isnan, x1, out, **kwargs) + return check_nd_call_func( + numpy.isnan, + dpnp_isnan, + x, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, + ) def less( diff --git a/tests/test_logic.py b/tests/test_logic.py index 982c58942f9..5dd90b1a13b 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -4,7 +4,7 @@ import dpnp -from .helper import get_all_dtypes +from .helper import get_all_dtypes, get_float_dtypes @pytest.mark.parametrize("type", get_all_dtypes()) @@ -294,3 +294,25 @@ def test_comparison_no_broadcast_with_shapes(op, sh1, sh2): with pytest.raises(ValueError): getattr(dpnp, op)(x1, x2) getattr(numpy, op)(x1.asnumpy(), x2.asnumpy()) + + +@pytest.mark.parametrize( + "op", ["isfinite", "isinf", "isnan"], ids=["isfinite", "isinf", "isnan"] +) +@pytest.mark.parametrize( + "data", + [ + [dpnp.inf, -1, 0, 1, dpnp.nan], + [[dpnp.inf, dpnp.nan], [dpnp.nan, 0], [1, dpnp.inf]], + ], + ids=[ + "[dpnp.inf, -1, 0, 1, dpnp.nan]", + "[[dpnp.inf, dpnp.nan], [dpnp.nan, 0], [1, dpnp.inf]]", + ], +) +@pytest.mark.parametrize("dtype", get_float_dtypes()) +def test_finite(op, data, dtype): + x = dpnp.asarray(data, dtype=dtype) + np_res = getattr(dpnp, op)(x) + dpnp_res = getattr(numpy, op)(x.asnumpy()) + assert_equal(dpnp_res, np_res) From d9c5f68a009d0f670f4c577190b62082ac9ee723 Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Mon, 31 Jul 2023 10:06:17 -0500 Subject: [PATCH 2/5] Add more test for isnan, isinf, and isfinite functions. --- tests/test_logic.py | 4 ++-- tests/third_party/cupy/logic_tests/test_content.py | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/tests/test_logic.py b/tests/test_logic.py index 3619a44674d..33934f0c4d7 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -4,7 +4,7 @@ import dpnp -from .helper import get_all_dtypes, get_float_dtypes, has_support_aspect64 +from .helper import get_all_dtypes, get_float_complex_dtypes, has_support_aspect64 @pytest.mark.parametrize("type", get_all_dtypes()) @@ -313,7 +313,7 @@ def test_comparison_no_broadcast_with_shapes(op, sh1, sh2): "[[dpnp.inf, dpnp.nan], [dpnp.nan, 0], [1, dpnp.inf]]", ], ) -@pytest.mark.parametrize("dtype", get_float_dtypes()) +@pytest.mark.parametrize("dtype", get_float_complex_dtypes()) def test_finite(op, data, dtype): x = dpnp.asarray(data, dtype=dtype) np_res = getattr(dpnp, op)(x) diff --git a/tests/third_party/cupy/logic_tests/test_content.py b/tests/third_party/cupy/logic_tests/test_content.py index 6b2eb87a9fa..ac67a4eef4c 100644 --- a/tests/third_party/cupy/logic_tests/test_content.py +++ b/tests/third_party/cupy/logic_tests/test_content.py @@ -22,14 +22,11 @@ def check_unary_nan(self, name, xp, dtype): ) return getattr(xp, name)(a) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") def test_isfinite(self): self.check_unary_inf("isfinite") - @pytest.mark.usefixtures("allow_fall_back_on_numpy") def test_isinf(self): self.check_unary_inf("isinf") - @pytest.mark.usefixtures("allow_fall_back_on_numpy") def test_isnan(self): self.check_unary_nan("isnan") From b11ab06db57f6fa8333d319454860131660ed76f Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Mon, 31 Jul 2023 08:11:05 -0700 Subject: [PATCH 3/5] Fix pre-commit --- tests/test_logic.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_logic.py b/tests/test_logic.py index 33934f0c4d7..7be9e6e1ac8 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -4,7 +4,11 @@ import dpnp -from .helper import get_all_dtypes, get_float_complex_dtypes, has_support_aspect64 +from .helper import ( + get_all_dtypes, + get_float_complex_dtypes, + has_support_aspect64, +) @pytest.mark.parametrize("type", get_all_dtypes()) From 66113ceb159d9533f76fa664f328684e5e965bf7 Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Mon, 31 Jul 2023 09:34:36 -0700 Subject: [PATCH 4/5] Update tests/test_logic.py --- tests/test_logic.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_logic.py b/tests/test_logic.py index 7be9e6e1ac8..790b5581aa5 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -9,6 +9,10 @@ get_float_complex_dtypes, has_support_aspect64, ) + get_all_dtypes, + get_float_complex_dtypes, + has_support_aspect64, +) @pytest.mark.parametrize("type", get_all_dtypes()) From 312173e09d97884c4f37b587d64f4db151ab6c16 Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Mon, 31 Jul 2023 09:35:29 -0700 Subject: [PATCH 5/5] Update test_logic.py --- tests/test_logic.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/test_logic.py b/tests/test_logic.py index 790b5581aa5..7be9e6e1ac8 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -9,10 +9,6 @@ get_float_complex_dtypes, has_support_aspect64, ) - get_all_dtypes, - get_float_complex_dtypes, - has_support_aspect64, -) @pytest.mark.parametrize("type", get_all_dtypes())