From 8f716c477299e5786bb9c80042ae27b9dea894bb Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Tue, 5 Sep 2023 18:12:18 -0600 Subject: [PATCH 1/3] implement maximum and minimum --- dpnp/backend/include/dpnp_iface_fptr.hpp | 8 +- dpnp/dpnp_algo/dpnp_algo.pxd | 5 +- dpnp/dpnp_algo/dpnp_algo_mathematical.pxi | 8 +- dpnp/dpnp_algo/dpnp_elementwise_common.py | 94 ++++++ dpnp/dpnp_iface_mathematical.py | 380 ++++++++++++++-------- tests/skipped_tests.tbl | 2 - tests/skipped_tests_gpu.tbl | 2 - tests/test_mathematical.py | 10 +- tests/test_usm_type.py | 10 + 9 files changed, 356 insertions(+), 163 deletions(-) diff --git a/dpnp/backend/include/dpnp_iface_fptr.hpp b/dpnp/backend/include/dpnp_iface_fptr.hpp index dd9a9a97df3..b810072329b 100644 --- a/dpnp/backend/include/dpnp_iface_fptr.hpp +++ b/dpnp/backend/include/dpnp_iface_fptr.hpp @@ -242,8 +242,8 @@ enum class DPNPFuncName : size_t requires extra parameters */ DPNP_FN_MAX, /**< Used in numpy.max() impl */ DPNP_FN_MAX_EXT, /**< Used in numpy.max() impl, requires extra parameters */ - DPNP_FN_MAXIMUM, /**< Used in numpy.maximum() impl */ - DPNP_FN_MAXIMUM_EXT, /**< Used in numpy.maximum() impl , requires extra + DPNP_FN_MAXIMUM, /**< Used in numpy.fmax() impl */ + DPNP_FN_MAXIMUM_EXT, /**< Used in numpy.fmax() impl , requires extra parameters */ DPNP_FN_MEAN, /**< Used in numpy.mean() impl */ DPNP_FN_MEDIAN, /**< Used in numpy.median() impl */ @@ -251,8 +251,8 @@ enum class DPNPFuncName : size_t parameters */ DPNP_FN_MIN, /**< Used in numpy.min() impl */ DPNP_FN_MIN_EXT, /**< Used in numpy.min() impl, requires extra parameters */ - DPNP_FN_MINIMUM, /**< Used in numpy.minimum() impl */ - DPNP_FN_MINIMUM_EXT, /**< Used in numpy.minimum() impl, requires extra + DPNP_FN_MINIMUM, /**< Used in numpy.fmin() impl */ + DPNP_FN_MINIMUM_EXT, /**< Used in numpy.fmax() impl, requires extra parameters */ DPNP_FN_MODF, /**< Used in numpy.modf() impl */ DPNP_FN_MODF_EXT, /**< Used in numpy.modf() impl, requires extra parameters diff --git a/dpnp/dpnp_algo/dpnp_algo.pxd b/dpnp/dpnp_algo/dpnp_algo.pxd index a512737ef31..943fae54508 100644 --- a/dpnp/dpnp_algo/dpnp_algo.pxd +++ b/dpnp/dpnp_algo/dpnp_algo.pxd @@ -386,11 +386,10 @@ Mathematical functions """ cpdef dpnp_descriptor dpnp_hypot(dpnp_descriptor x1_obj, dpnp_descriptor x2_obj, object dtype=*, dpnp_descriptor out=*, object where=*) -cpdef dpnp_descriptor dpnp_maximum(dpnp_descriptor x1_obj, dpnp_descriptor x2_obj, object dtype=*, +cpdef dpnp_descriptor dpnp_fmax(dpnp_descriptor x1_obj, dpnp_descriptor x2_obj, object dtype=*, dpnp_descriptor out=*, object where=*) -cpdef dpnp_descriptor dpnp_minimum(dpnp_descriptor x1_obj, dpnp_descriptor x2_obj, object dtype=*, +cpdef dpnp_descriptor dpnp_fmin(dpnp_descriptor x1_obj, dpnp_descriptor x2_obj, object dtype=*, dpnp_descriptor out=*, object where=*) - """ Array manipulation routines """ diff --git a/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi b/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi index 401232ca8f2..f903df9560c 100644 --- a/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi +++ b/dpnp/dpnp_algo/dpnp_algo_mathematical.pxi @@ -47,8 +47,8 @@ __all__ += [ "dpnp_fmod", "dpnp_gradient", 'dpnp_hypot', - "dpnp_maximum", - "dpnp_minimum", + "dpnp_fmax", + "dpnp_fmin", "dpnp_modf", "dpnp_nancumprod", "dpnp_nancumsum", @@ -281,7 +281,7 @@ cpdef utils.dpnp_descriptor dpnp_hypot(utils.dpnp_descriptor x1_obj, return call_fptr_2in_1out_strides(DPNP_FN_HYPOT_EXT, x1_obj, x2_obj, dtype, out, where) -cpdef utils.dpnp_descriptor dpnp_maximum(utils.dpnp_descriptor x1_obj, +cpdef utils.dpnp_descriptor dpnp_fmax(utils.dpnp_descriptor x1_obj, utils.dpnp_descriptor x2_obj, object dtype=None, utils.dpnp_descriptor out=None, @@ -289,7 +289,7 @@ cpdef utils.dpnp_descriptor dpnp_maximum(utils.dpnp_descriptor x1_obj, return call_fptr_2in_1out_strides(DPNP_FN_MAXIMUM_EXT, x1_obj, x2_obj, dtype, out, where) -cpdef utils.dpnp_descriptor dpnp_minimum(utils.dpnp_descriptor x1_obj, +cpdef utils.dpnp_descriptor dpnp_fmin(utils.dpnp_descriptor x1_obj, utils.dpnp_descriptor x2_obj, object dtype=None, utils.dpnp_descriptor out=None, diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index 9c9276d3376..373976d6141 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -76,6 +76,8 @@ "dpnp_logical_not", "dpnp_logical_or", "dpnp_logical_xor", + "dpnp_maximum", + "dpnp_minimum", "dpnp_multiply", "dpnp_negative", "dpnp_not_equal", @@ -1812,6 +1814,98 @@ def dpnp_logical_xor(x1, x2, out=None, order="K"): return dpnp_array._create_from_usm_ndarray(res_usm) +_maximum_docstring_ = """ +maximum(x1, x2, out=None, order='K') + +Compares two input arrays `x1` and `x2` and returns +a new array containing the element-wise maxima. + +Args: + x1 (dpnp.ndarray): + First input array, expected to have numeric data type. + x2 (dpnp.ndarray): + Second input array, also 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 containing the element-wise minima. The data type of + the returned array is determined by the Type Promotion Rules. +""" + + +maximum_func = BinaryElementwiseFunc( + "maximum", + ti._maximum_result_type, + ti._maximum, + _maximum_docstring_, +) + + +def dpnp_maximum(x1, x2, out=None, order="K"): + """Invokes maximum() from dpctl.tensor implementation for maximum() 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 = maximum_func( + x1_usm_or_scalar, x2_usm_or_scalar, out=out_usm, order=order + ) + return dpnp_array._create_from_usm_ndarray(res_usm) + + +_minimum_docstring_ = """ +minimum(x1, x2, out=None, order='K') + +Compares two input arrays `x1` and `x2` and returns +a new array containing the element-wise minima. + +Args: + x1 (dpnp.ndarray): + First input array, expected to have numeric data type. + x2 (dpnp.ndarray): + Second input array, also 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 containing the element-wise maxima. The data type of + the returned array is determined by the Type Promotion Rules. +""" + + +minimum_func = BinaryElementwiseFunc( + "minimum", + ti._minimum_result_type, + ti._minimum, + _minimum_docstring_, +) + + +def dpnp_minimum(x1, x2, out=None, order="K"): + """Invokes minimum() from dpctl.tensor implementation for minimum() 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 = minimum_func( + x1_usm_or_scalar, x2_usm_or_scalar, out=out_usm, order=order + ) + return dpnp_array._create_from_usm_ndarray(res_usm) + + _multiply_docstring_ = """ multiply(x1, x2, out=None, order="K") diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 82277276824..5e7bf9bb8c5 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -57,6 +57,8 @@ dpnp_floor, dpnp_floor_divide, dpnp_imag, + dpnp_maximum, + dpnp_minimum, dpnp_multiply, dpnp_negative, dpnp_power, @@ -973,46 +975,212 @@ def floor_divide( ) -def fmax(*args, **kwargs): +def fmax(x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs): """ Element-wise maximum of array elements. For full documentation refer to :obj:`numpy.fmax`. + Returns + ------- + out : dpnp.ndarray + The maximum of `x1` and `x2`, element-wise, ignoring NaNs. + + 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 argument `kwargs` is 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.maximum` : Element-wise maximum of array elements. - :obj:`dpnp.fmin` : Element-wise minimum of array elements. + :obj:`dpnp.maximum` : Element-wise maximum of array elements, propagates NaNs.. + :obj:`dpnp.fmin` : Element-wise minimum of array elements, ignore NaNs. :obj:`dpnp.fmod` : Calculate the element-wise remainder of division. - Notes - ----- - This function works the same as :obj:`dpnp.maximum` + Examples + -------- + >>> import dpnp as np + >>> x1 = np.array([2, 3, 4]) + >>> x2 = np.array([1, 5, 2]) + >>> np.fmax(x1, x2) + array([2, 5, 4]) + + >>> x1 = np.eye(2) + >>> x2 = np.array([0.5, 2]) + >>> np.fmax(x1, x2) # broadcasting + array([[1. , 2. ], + [0.5, 2. ]]) + + >>> x1 = np.array([np.nan, 0, np.nan]) + >>> x2 = np.array([0, np.nan, np.nan]) + >>> np.fmax(x1, x2) + array([ 0., 0., nan]) """ - return dpnp.maximum(*args, **kwargs) + if kwargs: + pass + elif where is not True: + pass + elif dtype is not None: + pass + elif subok is not True: + pass + elif dpnp.isscalar(x1) and dpnp.isscalar(x2): + # at least either x1 or x2 has to be an array + pass + else: + # get USM type and queue to copy scalar from the host memory into a USM allocation + usm_type, queue = ( + get_usm_allocations([x1, x2]) + if dpnp.isscalar(x1) or dpnp.isscalar(x2) + else (None, None) + ) + + x1_desc = dpnp.get_dpnp_descriptor( + x1, + copy_when_strides=False, + copy_when_nondefault_queue=False, + alloc_usm_type=usm_type, + alloc_queue=queue, + ) + x2_desc = dpnp.get_dpnp_descriptor( + x2, + copy_when_strides=False, + copy_when_nondefault_queue=False, + alloc_usm_type=usm_type, + alloc_queue=queue, + ) + if x1_desc and x2_desc: + if out is not None: + if not dpnp.is_supported_array_type(out): + raise TypeError( + "return array must be of supported array type" + ) + out_desc = ( + dpnp.get_dpnp_descriptor( + out, copy_when_nondefault_queue=False + ) + or None + ) + else: + out_desc = None + + return dpnp_fmax( + x1_desc, x2_desc, dtype=dtype, out=out_desc, where=where + ).get_pyobj() + + return call_origin( + numpy.fmax, x1, x2, dtype=dtype, out=out, where=where, **kwargs + ) -def fmin(*args, **kwargs): +def fmin(x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs): """ Element-wise minimum of array elements. For full documentation refer to :obj:`numpy.fmin`. + Returns + ------- + out : dpnp.ndarray + The minimum of `x1` and `x2`, element-wise, ignoring NaNs. + + 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 argument `kwargs` is 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.maximum` : Element-wise maximum of array elements. - :obj:`dpnp.fmax` : Element-wise maximum of array elements. + :obj:`dpnp.minimum` : Element-wise minimum of array elements, propagates NaNs. + :obj:`dpnp.fmax` : Element-wise maximum of array elements, ignore NaNs. :obj:`dpnp.fmod` : Calculate the element-wise remainder of division. - Notes - ----- - This function works the same as :obj:`dpnp.minimum` + Examples + -------- + >>> import dpnp as np + >>> x1 = np.array([2, 3, 4]) + >>> x2 = np.array([1, 5, 2]) + >>> np.fmin(x1, x2) + array([1, 3, 2]) + + >>> x1 = np.eye(2) + >>> x2 = np.array([0.5, 2]) + >>> np.fmin(x1, x2) # broadcasting + array([[0.5, 0. ], + [0. , 1. ]] + + >>> x1 = np.array([np.nan, 0, np.nan]) + >>> x2 = np.array([0, np.nan, np.nan]) + >>> np.fmin(x1, x2) + array([ 0., 0., nan]) """ - return dpnp.minimum(*args, **kwargs) + if kwargs: + pass + elif where is not True: + pass + elif dtype is not None: + pass + elif subok is not True: + pass + elif dpnp.isscalar(x1) and dpnp.isscalar(x2): + # at least either x1 or x2 has to be an array + pass + else: + # get USM type and queue to copy scalar from the host memory into a USM allocation + usm_type, queue = ( + get_usm_allocations([x1, x2]) + if dpnp.isscalar(x1) or dpnp.isscalar(x2) + else (None, None) + ) + + x1_desc = dpnp.get_dpnp_descriptor( + x1, + copy_when_strides=False, + copy_when_nondefault_queue=False, + alloc_usm_type=usm_type, + alloc_queue=queue, + ) + x2_desc = dpnp.get_dpnp_descriptor( + x2, + copy_when_strides=False, + copy_when_nondefault_queue=False, + alloc_usm_type=usm_type, + alloc_queue=queue, + ) + if x1_desc and x2_desc: + if out is not None: + if not dpnp.is_supported_array_type(out): + raise TypeError( + "return array must be of supported array type" + ) + out_desc = ( + dpnp.get_dpnp_descriptor( + out, copy_when_nondefault_queue=False + ) + or None + ) + else: + out_desc = None + + return dpnp_fmin( + x1_desc, x2_desc, dtype=dtype, out=out_desc, where=where + ).get_pyobj() + + return call_origin( + numpy.fmin, x1, x2, dtype=dtype, out=out, where=where, **kwargs + ) def fmod(x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs): @@ -1138,8 +1306,8 @@ def gradient(x1, *varargs, **kwargs): Otherwise the function will be executed sequentially on CPU. Input array data types are limited by supported DPNP :ref:`Data types`. - Example - ------- + Examples + -------- >>> import dpnp as np >>> y = np.array([1, 2, 4, 7, 11, 16], dtype=float) >>> result = np.gradient(y) @@ -1213,7 +1381,16 @@ def imag(x): def maximum( - x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs + x1, + x2, + /, + out=None, + *, + where=True, + order="K", + dtype=None, + subok=True, + **kwargs, ): """ Element-wise maximum of array elements. @@ -1223,7 +1400,7 @@ def maximum( Returns ------- out : dpnp.ndarray - The maximum of `x1` and `x2`, element-wise. + The maximum of `x1` and `x2`, element-wise, propagating NaNs. Limitations ----------- @@ -1244,8 +1421,8 @@ def maximum( :obj:`dpnp.amix` : The minimum value of an array along a given axis, propagates NaNs. :obj:`dpnp.nanmix` : The minimum value of an array along a given axis, ignores NaNs. - Example - ------- + Examples + -------- >>> import dpnp as np >>> x1 = np.array([2, 3, 4]) >>> x2 = np.array([1, 5, 2]) @@ -1261,72 +1438,38 @@ def maximum( >>> x1 = np.array([np.nan, 0, np.nan]) >>> x2 = np.array([0, np.nan, np.nan]) >>> np.maximum(x1, x2) - array([ 0., 0., nan]) + array([ nan, nan, nan]) >>> np.maximum(np.array(np.Inf), 1) array(inf) """ - if kwargs: - pass - elif where is not True: - pass - elif dtype is not None: - pass - elif subok is not True: - pass - elif dpnp.isscalar(x1) and dpnp.isscalar(x2): - # at least either x1 or x2 has to be an array - pass - else: - # get USM type and queue to copy scalar from the host memory into a USM allocation - usm_type, queue = ( - get_usm_allocations([x1, x2]) - if dpnp.isscalar(x1) or dpnp.isscalar(x2) - else (None, None) - ) - - x1_desc = dpnp.get_dpnp_descriptor( - x1, - copy_when_strides=False, - copy_when_nondefault_queue=False, - alloc_usm_type=usm_type, - alloc_queue=queue, - ) - x2_desc = dpnp.get_dpnp_descriptor( - x2, - copy_when_strides=False, - copy_when_nondefault_queue=False, - alloc_usm_type=usm_type, - alloc_queue=queue, - ) - if x1_desc and x2_desc: - if out is not None: - if not dpnp.is_supported_array_type(out): - raise TypeError( - "return array must be of supported array type" - ) - out_desc = ( - dpnp.get_dpnp_descriptor( - out, copy_when_nondefault_queue=False - ) - or None - ) - else: - out_desc = None - - return dpnp_maximum( - x1_desc, x2_desc, dtype=dtype, out=out_desc, where=where - ).get_pyobj() - - return call_origin( - numpy.maximum, x1, x2, dtype=dtype, out=out, where=where, **kwargs + return check_nd_call_func( + numpy.maximum, + dpnp_maximum, + x1, + x2, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, ) def minimum( - x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs + x1, + x2, + /, + out=None, + *, + where=True, + order="K", + dtype=None, + subok=True, + **kwargs, ): """ Element-wise minimum of array elements. @@ -1336,7 +1479,7 @@ def minimum( Returns ------- out : dpnp.ndarray - The minimum of `x1` and `x2`, element-wise. + The minimum of `x1` and `x2`, element-wise, propagating NaNs. Limitations ----------- @@ -1357,8 +1500,8 @@ def minimum( :obj:`dpnp.amax` : The maximum value of an array along a given axis, propagates NaNs. :obj:`dpnp.nanmax` : The maximum value of an array along a given axis, ignores NaNs. - Example - ------- + Examples + -------- >>> import dpnp as np >>> x1 = np.array([2, 3, 4]) >>> x2 = np.array([1, 5, 2]) @@ -1374,67 +1517,24 @@ def minimum( >>> x1 = np.array([np.nan, 0, np.nan]) >>> x2 = np.array([0, np.nan, np.nan]) >>> np.minimum(x1, x2) - array([ 0., 0., nan]) + array([ nan, nan, nan]) >>> np.minimum(np.array(-np.Inf), 1) array(-inf) """ - if kwargs: - pass - elif where is not True: - pass - elif dtype is not None: - pass - elif subok is not True: - pass - elif dpnp.isscalar(x1) and dpnp.isscalar(x2): - # at least either x1 or x2 has to be an array - pass - else: - # get USM type and queue to copy scalar from the host memory into a USM allocation - usm_type, queue = ( - get_usm_allocations([x1, x2]) - if dpnp.isscalar(x1) or dpnp.isscalar(x2) - else (None, None) - ) - - x1_desc = dpnp.get_dpnp_descriptor( - x1, - copy_when_strides=False, - copy_when_nondefault_queue=False, - alloc_usm_type=usm_type, - alloc_queue=queue, - ) - x2_desc = dpnp.get_dpnp_descriptor( - x2, - copy_when_strides=False, - copy_when_nondefault_queue=False, - alloc_usm_type=usm_type, - alloc_queue=queue, - ) - if x1_desc and x2_desc: - if out is not None: - if not dpnp.is_supported_array_type(out): - raise TypeError( - "return array must be of supported array type" - ) - out_desc = ( - dpnp.get_dpnp_descriptor( - out, copy_when_nondefault_queue=False - ) - or None - ) - else: - out_desc = None - - return dpnp_minimum( - x1_desc, x2_desc, dtype=dtype, out=out_desc, where=where - ).get_pyobj() - - return call_origin( - numpy.minimum, x1, x2, dtype=dtype, out=out, where=where, **kwargs + return check_nd_call_func( + numpy.minimum, + dpnp_minimum, + x1, + x2, + out=out, + where=where, + order=order, + dtype=dtype, + subok=subok, + **kwargs, ) @@ -1830,8 +1930,8 @@ def power( :obj:`dpnp.fmod` : Calculate the element-wise remainder of division. - Example - ------- + Examples + -------- >>> import dpnp as dp >>> a = dp.arange(6) >>> dp.power(a, 3) @@ -2091,8 +2191,8 @@ def remainder( :obj:`dpnp.floor_divide` : Compute the largest integer smaller or equal to the division of the inputs. :obj:`dpnp.mod` : Calculate the element-wise remainder of division. - Example - ------- + Examples + -------- >>> import dpnp as np >>> np.remainder(np.array([4, 7]), np.array([2, 3])) array([0, 1]) @@ -2392,8 +2492,8 @@ def subtract( Otherwise the function will be executed sequentially on CPU. Input array data types are limited by supported DPNP :ref:`Data types`. - Example - ------- + Examples + -------- >>> import dpnp as np >>> np.subtract(dp.array([4, 3]), np.array([2, 7])) array([ 2, -4]) diff --git a/tests/skipped_tests.tbl b/tests/skipped_tests.tbl index 98cdc65136a..1169b1be3af 100644 --- a/tests/skipped_tests.tbl +++ b/tests/skipped_tests.tbl @@ -506,8 +506,6 @@ tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_nextafter tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_min_max_none tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip4 tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_absolute_negative -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_maximum_nan -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_minimum_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmax_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmin_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num diff --git a/tests/skipped_tests_gpu.tbl b/tests/skipped_tests_gpu.tbl index 3ab40ed4d04..7ca6261137e 100644 --- a/tests/skipped_tests_gpu.tbl +++ b/tests/skipped_tests_gpu.tbl @@ -641,8 +641,6 @@ tests/third_party/cupy/math_tests/test_floating.py::TestFloating::test_nextafter tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_clip_min_max_none tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_external_clip4 tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_absolute_negative -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_maximum_nan -tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_minimum_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmax_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_fmin_nan tests/third_party/cupy/math_tests/test_misc.py::TestMisc::test_nan_to_num diff --git a/tests/test_mathematical.py b/tests/test_mathematical.py index 46dbab5c30a..0feced38cad 100644 --- a/tests/test_mathematical.py +++ b/tests/test_mathematical.py @@ -194,17 +194,11 @@ def test_floor_divide(self, dtype, lhs, rhs): def test_hypot(self, dtype, lhs, rhs): self._test_mathematical("hypot", dtype, lhs, rhs) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") - @pytest.mark.parametrize( - "dtype", get_all_dtypes(no_bool=True, no_complex=True) - ) + @pytest.mark.parametrize("dtype", get_all_dtypes()) def test_maximum(self, dtype, lhs, rhs): self._test_mathematical("maximum", dtype, lhs, rhs, check_type=False) - @pytest.mark.usefixtures("allow_fall_back_on_numpy") - @pytest.mark.parametrize( - "dtype", get_all_dtypes(no_bool=True, no_complex=True) - ) + @pytest.mark.parametrize("dtype", get_all_dtypes()) def test_minimum(self, dtype, lhs, rhs): self._test_mathematical("minimum", dtype, lhs, rhs, check_type=False) diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 79125a5376b..6c279164164 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -343,6 +343,16 @@ def test_1in_1out(func, data, usm_type): [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]], [[4.0, 4.0], [4.0, 4.0], [4.0, 4.0]], ), + pytest.param( + "maximum", + [[0.0, 1.0, 2.0]], + [[3.0, 4.0, 5.0]], + ), + pytest.param( + "minimum", + [[0.0, 1.0, 2.0]], + [[3.0, 4.0, 5.0]], + ), ], ) @pytest.mark.parametrize("usm_type_x", list_of_usm_types, ids=list_of_usm_types) From 2c91a9019e1f6f2dc6a1fa9ce6900d6a6ce4bbaa Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Fri, 15 Sep 2023 12:23:39 -0500 Subject: [PATCH 2/3] address comments --- dpnp/dpnp_algo/dpnp_elementwise_common.py | 4 ++-- dpnp/dpnp_iface_mathematical.py | 12 +++++++----- tests/test_mathematical.py | 12 ++++++++++++ tests/test_strides.py | 2 ++ tests/test_sycl_queue.py | 2 ++ tests/test_usm_type.py | 10 ++++++++++ 6 files changed, 35 insertions(+), 7 deletions(-) diff --git a/dpnp/dpnp_algo/dpnp_elementwise_common.py b/dpnp/dpnp_algo/dpnp_elementwise_common.py index 373976d6141..d5ec2f663b3 100644 --- a/dpnp/dpnp_algo/dpnp_elementwise_common.py +++ b/dpnp/dpnp_algo/dpnp_elementwise_common.py @@ -1833,7 +1833,7 @@ def dpnp_logical_xor(x1, x2, out=None, order="K"): Default: "K". Returns: dpnp.ndarray: - An array containing the element-wise minima. The data type of + An array containing the element-wise maxima. The data type of the returned array is determined by the Type Promotion Rules. """ @@ -1879,7 +1879,7 @@ def dpnp_maximum(x1, x2, out=None, order="K"): Default: "K". Returns: dpnp.ndarray: - An array containing the element-wise maxima. The data type of + An array containing the element-wise minima. The data type of the returned array is determined by the Type Promotion Rules. """ diff --git a/dpnp/dpnp_iface_mathematical.py b/dpnp/dpnp_iface_mathematical.py index 5e7bf9bb8c5..b465f15040d 100644 --- a/dpnp/dpnp_iface_mathematical.py +++ b/dpnp/dpnp_iface_mathematical.py @@ -993,12 +993,13 @@ def fmax(x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs): Parameters `where`, `dtype` and `subok` are supported with their default values. Keyword argument `kwargs` is currently unsupported. Otherwise the function will be executed sequentially on CPU. - Input array data types are limited by supported DPNP :ref:`Data types`. + Input array data types are limited by real-valued data types. See Also -------- - :obj:`dpnp.maximum` : Element-wise maximum of array elements, propagates NaNs.. + :obj:`dpnp.maximum` : Element-wise maximum of array elements, propagates NaNs. :obj:`dpnp.fmin` : Element-wise minimum of array elements, ignore NaNs. + :obj:`dpnp.minimum` : Element-wise minimum of array elements, propagates NaNs. :obj:`dpnp.fmod` : Calculate the element-wise remainder of division. Examples @@ -1097,12 +1098,13 @@ def fmin(x1, x2, /, out=None, *, where=True, dtype=None, subok=True, **kwargs): Parameters `where`, `dtype` and `subok` are supported with their default values. Keyword argument `kwargs` is currently unsupported. Otherwise the function will be executed sequentially on CPU. - Input array data types are limited by supported DPNP :ref:`Data types`. + Input array data types are limited by real-valued data types. See Also -------- :obj:`dpnp.minimum` : Element-wise minimum of array elements, propagates NaNs. :obj:`dpnp.fmax` : Element-wise maximum of array elements, ignore NaNs. + :obj:`dpnp.maximum` : Element-wise maximum of array elements, propagates NaNs. :obj:`dpnp.fmod` : Calculate the element-wise remainder of division. Examples @@ -1438,7 +1440,7 @@ def maximum( >>> x1 = np.array([np.nan, 0, np.nan]) >>> x2 = np.array([0, np.nan, np.nan]) >>> np.maximum(x1, x2) - array([ nan, nan, nan]) + array([nan, nan, nan]) >>> np.maximum(np.array(np.Inf), 1) array(inf) @@ -1517,7 +1519,7 @@ def minimum( >>> x1 = np.array([np.nan, 0, np.nan]) >>> x2 = np.array([0, np.nan, np.nan]) >>> np.minimum(x1, x2) - array([ nan, nan, nan]) + array([nan, nan, nan]) >>> np.minimum(np.array(-np.Inf), 1) array(-inf) diff --git a/tests/test_mathematical.py b/tests/test_mathematical.py index 0feced38cad..5bd1fe6f988 100644 --- a/tests/test_mathematical.py +++ b/tests/test_mathematical.py @@ -159,6 +159,18 @@ def test_copysign(self, dtype, lhs, rhs): def test_divide(self, dtype, lhs, rhs): self._test_mathematical("divide", dtype, lhs, rhs) + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True) + ) + def test_fmax(self, dtype, lhs, rhs): + self._test_mathematical("fmax", dtype, lhs, rhs, check_type=False) + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True) + ) + def test_fmin(self, dtype, lhs, rhs): + self._test_mathematical("fmin", dtype, lhs, rhs, check_type=False) + @pytest.mark.usefixtures("allow_fall_back_on_numpy") @pytest.mark.parametrize( "dtype", get_all_dtypes(no_bool=True, no_complex=True) diff --git a/tests/test_strides.py b/tests/test_strides.py index 84e794cd053..5cc65ee8207 100644 --- a/tests/test_strides.py +++ b/tests/test_strides.py @@ -163,6 +163,8 @@ def test_strides_reciprocal(dtype, shape): "add", "arctan2", "divide", + "fmax", + "fmin", "hypot", "maximum", "minimum", diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index 48a562cc798..6d6c4b2abcd 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -354,6 +354,8 @@ def test_proj(device): pytest.param( "floor_divide", [1.0, 2.0, 3.0, 4.0], [2.5, 2.5, 2.5, 2.5] ), + pytest.param("fmax", [2.0, 3.0, 4.0], [1.0, 5.0, 2.0]), + pytest.param("fmin", [2.0, 3.0, 4.0], [1.0, 5.0, 2.0]), pytest.param( "fmod", [-3.0, -2.0, -1.0, 1.0, 2.0, 3.0], diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 6c279164164..99dab0a1b53 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -343,6 +343,16 @@ def test_1in_1out(func, data, usm_type): [[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]], [[4.0, 4.0], [4.0, 4.0], [4.0, 4.0]], ), + pytest.param( + "fmax", + [[0.0, 1.0, 2.0]], + [[3.0, 4.0, 5.0]], + ), + pytest.param( + "fmin", + [[0.0, 1.0, 2.0]], + [[3.0, 4.0, 5.0]], + ), pytest.param( "maximum", [[0.0, 1.0, 2.0]], From c80e404e4f7f8865d3ea5d7fc9ef4851805daf28 Mon Sep 17 00:00:00 2001 From: Vahid Tavanashad Date: Thu, 21 Sep 2023 11:30:12 -0500 Subject: [PATCH 3/3] add new tests --- tests/test_mathematical.py | 324 +++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) diff --git a/tests/test_mathematical.py b/tests/test_mathematical.py index 5bd1fe6f988..2ef1923f1a3 100644 --- a/tests/test_mathematical.py +++ b/tests/test_mathematical.py @@ -940,6 +940,330 @@ def test_invalid_out(self, out): assert_raises(TypeError, numpy.add, a.asnumpy(), 2, out) +class TestFmax: + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True, no_none=True) + ) + def test_fmax(self, dtype): + array1_data = numpy.arange(10) + array2_data = numpy.arange(5, 15) + out = numpy.empty(10, dtype=dtype) + + # DPNP + dp_array1 = dpnp.array(array1_data, dtype=dtype) + dp_array2 = dpnp.array(array2_data, dtype=dtype) + dp_out = dpnp.array(out, dtype=dtype) + result = dpnp.fmax(dp_array1, dp_array2, out=dp_out) + + # original + np_array1 = numpy.array(array1_data, dtype=dtype) + np_array2 = numpy.array(array2_data, dtype=dtype) + expected = numpy.fmax(np_array1, np_array2, out=out) + + assert_allclose(expected, result) + assert_allclose(out, dp_out) + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True, no_none=True) + ) + def test_out_dtypes(self, dtype): + size = 10 + + np_array1 = numpy.arange(size, 2 * size, dtype=dtype) + np_array2 = numpy.arange(size, dtype=dtype) + np_out = numpy.empty(size, dtype=numpy.float32) + expected = numpy.fmax(np_array1, np_array2, out=np_out) + + dp_array1 = dpnp.arange(size, 2 * size, dtype=dtype) + dp_array2 = dpnp.arange(size, dtype=dtype) + with pytest.raises(TypeError): + dpnp.fmax(dp_array1, dp_array2, out=np_out) + + dp_out = dpnp.empty(size, dtype=dpnp.float32) + result = dpnp.fmax(dp_array1, dp_array2, out=dp_out) + assert_array_equal(expected, result) + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True, no_none=True) + ) + def test_out_overlap(self, dtype): + size = 15 + # DPNP + dp_a = dpnp.arange(2 * size, dtype=dtype) + dpnp.fmax(dp_a[size::], dp_a[::2], out=dp_a[:size:]) + + # original + np_a = numpy.arange(2 * size, dtype=dtype) + numpy.fmax(np_a[size::], np_a[::2], out=np_a[:size:]) + + assert_allclose(np_a, dp_a) + + @pytest.mark.parametrize( + "shape", [(0,), (15,), (2, 2)], ids=["(0,)", "(15, )", "(2,2)"] + ) + def test_invalid_shape(self, shape): + dp_array1 = dpnp.arange(10) + dp_array2 = dpnp.arange(5, 15) + dp_out = dpnp.empty(shape) + + with pytest.raises(ValueError): + dpnp.fmax(dp_array1, dp_array2, out=dp_out) + + @pytest.mark.parametrize( + "out", + [4, (), [], (3, 7), [2, 4]], + ids=["4", "()", "[]", "(3, 7)", "[2, 4]"], + ) + def test_invalid_out(self, out): + a = dpnp.arange(10) + + assert_raises(TypeError, dpnp.fmax, a, 2, out) + assert_raises(TypeError, numpy.fmax, a.asnumpy(), 2, out) + + +class TestFmin: + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True, no_none=True) + ) + def test_fmin(self, dtype): + array1_data = numpy.arange(10) + array2_data = numpy.arange(5, 15) + out = numpy.empty(10, dtype=dtype) + + # DPNP + dp_array1 = dpnp.array(array1_data, dtype=dtype) + dp_array2 = dpnp.array(array2_data, dtype=dtype) + dp_out = dpnp.array(out, dtype=dtype) + result = dpnp.fmin(dp_array1, dp_array2, out=dp_out) + + # original + np_array1 = numpy.array(array1_data, dtype=dtype) + np_array2 = numpy.array(array2_data, dtype=dtype) + expected = numpy.fmin(np_array1, np_array2, out=out) + + assert_allclose(expected, result) + assert_allclose(out, dp_out) + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True, no_none=True) + ) + def test_out_dtypes(self, dtype): + size = 10 + + np_array1 = numpy.arange(size, 2 * size, dtype=dtype) + np_array2 = numpy.arange(size, dtype=dtype) + np_out = numpy.empty(size, dtype=numpy.float32) + expected = numpy.fmin(np_array1, np_array2, out=np_out) + + dp_array1 = dpnp.arange(size, 2 * size, dtype=dtype) + dp_array2 = dpnp.arange(size, dtype=dtype) + with pytest.raises(TypeError): + dpnp.fmin(dp_array1, dp_array2, out=np_out) + + dp_out = dpnp.empty(size, dtype=dpnp.float32) + result = dpnp.fmin(dp_array1, dp_array2, out=dp_out) + assert_array_equal(expected, result) + + @pytest.mark.parametrize( + "dtype", get_all_dtypes(no_bool=True, no_complex=True, no_none=True) + ) + def test_out_overlap(self, dtype): + size = 15 + # DPNP + dp_a = dpnp.arange(2 * size, dtype=dtype) + dpnp.fmin(dp_a[size::], dp_a[::2], out=dp_a[:size:]) + + # original + np_a = numpy.arange(2 * size, dtype=dtype) + numpy.fmin(np_a[size::], np_a[::2], out=np_a[:size:]) + + assert_allclose(np_a, dp_a) + + @pytest.mark.parametrize( + "shape", [(0,), (15,), (2, 2)], ids=["(0,)", "(15, )", "(2,2)"] + ) + def test_invalid_shape(self, shape): + dp_array1 = dpnp.arange(10) + dp_array2 = dpnp.arange(5, 15) + dp_out = dpnp.empty(shape) + + with pytest.raises(ValueError): + dpnp.fmin(dp_array1, dp_array2, out=dp_out) + + @pytest.mark.parametrize( + "out", + [4, (), [], (3, 7), [2, 4]], + ids=["4", "()", "[]", "(3, 7)", "[2, 4]"], + ) + def test_invalid_out(self, out): + a = dpnp.arange(10) + + assert_raises(TypeError, dpnp.fmin, a, 2, out) + assert_raises(TypeError, numpy.fmin, a.asnumpy(), 2, out) + + +class TestMaximum: + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + def test_maximum(self, dtype): + array1_data = numpy.arange(10) + array2_data = numpy.arange(5, 15) + out = numpy.empty(10, dtype=dtype) + + # DPNP + dp_array1 = dpnp.array(array1_data, dtype=dtype) + dp_array2 = dpnp.array(array2_data, dtype=dtype) + dp_out = dpnp.array(out, dtype=dtype) + result = dpnp.maximum(dp_array1, dp_array2, out=dp_out) + + # original + np_array1 = numpy.array(array1_data, dtype=dtype) + np_array2 = numpy.array(array2_data, dtype=dtype) + expected = numpy.maximum(np_array1, np_array2, out=out) + + assert_allclose(expected, result) + assert_allclose(out, dp_out) + + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + def test_out_dtypes(self, dtype): + size = 2 if dtype == dpnp.bool else 10 + + np_array1 = numpy.arange(size, 2 * size, dtype=dtype) + np_array2 = numpy.arange(size, dtype=dtype) + np_out = numpy.empty(size, dtype=numpy.complex64) + expected = numpy.maximum(np_array1, np_array2, out=np_out) + + dp_array1 = dpnp.arange(size, 2 * size, dtype=dtype) + dp_array2 = dpnp.arange(size, dtype=dtype) + + dp_out = dpnp.empty(size, dtype=dpnp.complex64) + if dtype != dpnp.complex64: + # dtype of out mismatches types of input arrays + with pytest.raises(TypeError): + dpnp.maximum(dp_array1, dp_array2, out=dp_out) + + # allocate new out with expected type + dp_out = dpnp.empty(size, dtype=dtype) + + result = dpnp.maximum(dp_array1, dp_array2, out=dp_out) + assert_array_equal(expected, result) + + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + def test_out_overlap(self, dtype): + size = 1 if dtype == dpnp.bool else 15 + # DPNP + dp_a = dpnp.arange(2 * size, dtype=dtype) + dpnp.maximum(dp_a[size::], dp_a[::2], out=dp_a[:size:]) + + # original + np_a = numpy.arange(2 * size, dtype=dtype) + numpy.maximum(np_a[size::], np_a[::2], out=np_a[:size:]) + + assert_allclose(np_a, dp_a) + + @pytest.mark.parametrize( + "shape", [(0,), (15,), (2, 2)], ids=["(0,)", "(15, )", "(2,2)"] + ) + def test_invalid_shape(self, shape): + dp_array1 = dpnp.arange(10) + dp_array2 = dpnp.arange(5, 15) + dp_out = dpnp.empty(shape) + + with pytest.raises(ValueError): + dpnp.maximum(dp_array1, dp_array2, out=dp_out) + + @pytest.mark.parametrize( + "out", + [4, (), [], (3, 7), [2, 4]], + ids=["4", "()", "[]", "(3, 7)", "[2, 4]"], + ) + def test_invalid_out(self, out): + a = dpnp.arange(10) + + assert_raises(TypeError, dpnp.maximum, a, 2, out) + assert_raises(TypeError, numpy.maximum, a.asnumpy(), 2, out) + + +class TestMinimum: + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + def test_minimum(self, dtype): + array1_data = numpy.arange(10) + array2_data = numpy.arange(5, 15) + out = numpy.empty(10, dtype=dtype) + + # DPNP + dp_array1 = dpnp.array(array1_data, dtype=dtype) + dp_array2 = dpnp.array(array2_data, dtype=dtype) + dp_out = dpnp.array(out, dtype=dtype) + result = dpnp.minimum(dp_array1, dp_array2, out=dp_out) + + # original + np_array1 = numpy.array(array1_data, dtype=dtype) + np_array2 = numpy.array(array2_data, dtype=dtype) + expected = numpy.minimum(np_array1, np_array2, out=out) + + assert_allclose(expected, result) + assert_allclose(out, dp_out) + + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + def test_out_dtypes(self, dtype): + size = 2 if dtype == dpnp.bool else 10 + + np_array1 = numpy.arange(size, 2 * size, dtype=dtype) + np_array2 = numpy.arange(size, dtype=dtype) + np_out = numpy.empty(size, dtype=numpy.complex64) + expected = numpy.minimum(np_array1, np_array2, out=np_out) + + dp_array1 = dpnp.arange(size, 2 * size, dtype=dtype) + dp_array2 = dpnp.arange(size, dtype=dtype) + + dp_out = dpnp.empty(size, dtype=dpnp.complex64) + if dtype != dpnp.complex64: + # dtype of out mismatches types of input arrays + with pytest.raises(TypeError): + dpnp.minimum(dp_array1, dp_array2, out=dp_out) + + # allocate new out with expected type + dp_out = dpnp.empty(size, dtype=dtype) + + result = dpnp.minimum(dp_array1, dp_array2, out=dp_out) + assert_array_equal(expected, result) + + @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) + def test_out_overlap(self, dtype): + size = 1 if dtype == dpnp.bool else 15 + # DPNP + dp_a = dpnp.arange(2 * size, dtype=dtype) + dpnp.minimum(dp_a[size::], dp_a[::2], out=dp_a[:size:]) + + # original + np_a = numpy.arange(2 * size, dtype=dtype) + numpy.minimum(np_a[size::], np_a[::2], out=np_a[:size:]) + + assert_allclose(np_a, dp_a) + + @pytest.mark.parametrize( + "shape", [(0,), (15,), (2, 2)], ids=["(0,)", "(15, )", "(2,2)"] + ) + def test_invalid_shape(self, shape): + dp_array1 = dpnp.arange(10) + dp_array2 = dpnp.arange(5, 15) + dp_out = dpnp.empty(shape) + + with pytest.raises(ValueError): + dpnp.minimum(dp_array1, dp_array2, out=dp_out) + + @pytest.mark.parametrize( + "out", + [4, (), [], (3, 7), [2, 4]], + ids=["4", "()", "[]", "(3, 7)", "[2, 4]"], + ) + def test_invalid_out(self, out): + a = dpnp.arange(10) + + assert_raises(TypeError, dpnp.minimum, a, 2, out) + assert_raises(TypeError, numpy.minimum, a.asnumpy(), 2, out) + + class TestMultiply: @pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True)) def test_multiply(self, dtype):