diff --git a/doc/reference/logic.rst b/doc/reference/logic.rst index 57133259c710..5cd7066b4524 100644 --- a/doc/reference/logic.rst +++ b/doc/reference/logic.rst @@ -68,6 +68,7 @@ Comparison dpnp.allclose dpnp.isclose dpnp.array_equal + dpnp.array_equiv dpnp.greater dpnp.greater_equal dpnp.less diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index 78e0342ef4c6..b2891c06adc3 100644 --- a/dpnp/dpnp_iface.py +++ b/dpnp/dpnp_iface.py @@ -56,7 +56,6 @@ __all__ = [ "are_same_logical_tensors", - "array_equal", "asnumpy", "astype", "as_usm_ndarray", @@ -173,24 +172,6 @@ def are_same_logical_tensors(ar1, ar2): ) -def array_equal(a1, a2, equal_nan=False): - """ - True if two arrays have the same shape and elements, False otherwise. - - For full documentation refer to :obj:`numpy.array_equal`. - - See Also - -------- - :obj:`dpnp.allclose` : Returns True if two arrays are element-wise equal - within a tolerance. - :obj:`dpnp.array_equiv` : Returns True if input arrays are shape consistent - and all elements equal. - - """ - - return numpy.array_equal(a1, a2, equal_nan=equal_nan) - - def asnumpy(a, order="C"): """ Returns the NumPy array with input data. diff --git a/dpnp/dpnp_iface_logic.py b/dpnp/dpnp_iface_logic.py index c6bc63f3ce7d..00dd8dce6e58 100644 --- a/dpnp/dpnp_iface_logic.py +++ b/dpnp/dpnp_iface_logic.py @@ -52,10 +52,14 @@ import dpnp from dpnp.dpnp_algo.dpnp_elementwise_common import DPNPBinaryFunc, DPNPUnaryFunc +from .dpnp_utils import get_usm_allocations + __all__ = [ "all", "allclose", "any", + "array_equal", + "array_equiv", "equal", "greater", "greater_equal", @@ -112,7 +116,7 @@ def all(a, /, axis=None, out=None, keepdims=False, *, where=True): Returns ------- out : dpnp.ndarray - An array with a data type of `bool` + An array with a data type of `bool`. containing the results of the logical AND reduction is returned unless `out` is specified. Otherwise, a reference to `out` is returned. The result has the same shape as `a` if `axis` is not ``None`` @@ -276,7 +280,7 @@ def any(a, /, axis=None, out=None, keepdims=False, *, where=True): Returns ------- out : dpnp.ndarray - An array with a data type of `bool` + An array with a data type of `bool`. containing the results of the logical OR reduction is returned unless `out` is specified. Otherwise, a reference to `out` is returned. The result has the same shape as `a` if `axis` is not ``None`` @@ -337,6 +341,191 @@ def any(a, /, axis=None, out=None, keepdims=False, *, where=True): return dpnp.get_result_array(usm_res, out) +def array_equal(a1, a2, equal_nan=False): + """ + ``True`` if two arrays have the same shape and elements, ``False`` + otherwise. + + For full documentation refer to :obj:`numpy.array_equal`. + + Parameters + ---------- + a1 : {dpnp.ndarray, usm_ndarray, scalar} + First input array. + Both inputs `x1` and `x2` can not be scalars at the same time. + a2 : {dpnp.ndarray, usm_ndarray, scalar} + Second input array. + Both inputs `x1` and `x2` can not be scalars at the same time. + equal_nan : bool, optional + Whether to compare ``NaNs`` as equal. If the dtype of `a1` and `a2` is + complex, values will be considered equal if either the real or the + imaginary component of a given value is ``NaN``. + Default: ``False``. + + Returns + ------- + b : dpnp.ndarray + An array with a data type of `bool`. + Returns ``True`` if the arrays are equal. + + See Also + -------- + :obj:`dpnp.allclose`: Returns ``True`` if two arrays are element-wise equal + within a tolerance. + :obj:`dpnp.array_equiv`: Returns ``True`` if input arrays are shape + consistent and all elements equal. + + Examples + -------- + >>> import dpnp as np + >>> a = np.array([1, 2]) + >>> b = np.array([1, 2]) + >>> np.array_equal(a, b) + array(True) + + >>> b = np.array([1, 2, 3]) + >>> np.array_equal(a, b) + array(False) + + >>> b = np.array([1, 4]) + >>> np.array_equal(a, b) + array(False) + + >>> a = np.array([1, np.nan]) + >>> np.array_equal(a, a) + array(False) + + >>> np.array_equal(a, a, equal_nan=True) + array(True) + + When ``equal_nan`` is ``True``, complex values with nan components are + considered equal if either the real *or* the imaginary components are + ``NaNs``. + + >>> a = np.array([1 + 1j]) + >>> b = a.copy() + >>> a.real = np.nan + >>> b.imag = np.nan + >>> np.array_equal(a, b, equal_nan=True) + array(True) + + """ + + dpnp.check_supported_arrays_type(a1, a2, scalar_type=True) + if dpnp.isscalar(a1): + usm_type_alloc = a2.usm_type + sycl_queue_alloc = a2.sycl_queue + a1 = dpnp.array( + a1, + dtype=dpnp.result_type(a1, a2), + usm_type=usm_type_alloc, + sycl_queue=sycl_queue_alloc, + ) + elif dpnp.isscalar(a2): + usm_type_alloc = a1.usm_type + sycl_queue_alloc = a1.sycl_queue + a2 = dpnp.array( + a2, + dtype=dpnp.result_type(a1, a2), + usm_type=usm_type_alloc, + sycl_queue=sycl_queue_alloc, + ) + else: + usm_type_alloc, sycl_queue_alloc = get_usm_allocations([a1, a2]) + + if a1.shape != a2.shape: + return dpnp.array( + False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc + ) + + if not equal_nan: + return (a1 == a2).all() + + if a1 is a2: + # NaN will compare equal so an array will compare equal to itself + return dpnp.array( + True, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc + ) + + if not ( + dpnp.issubdtype(a1, dpnp.inexact) or dpnp.issubdtype(a2, dpnp.inexact) + ): + return (a1 == a2).all() + + # Handling NaN values if equal_nan is True + a1nan, a2nan = isnan(a1), isnan(a2) + # NaNs occur at different locations + if not (a1nan == a2nan).all(): + return dpnp.array( + False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc + ) + # Shapes of a1, a2 and masks are guaranteed to be consistent by this point + return (a1[~a1nan] == a2[~a1nan]).all() + + +def array_equiv(a1, a2): + """ + Returns ``True`` if input arrays are shape consistent and all elements + equal. + + Shape consistent means they are either the same shape, or one input array + can be broadcasted to create the same shape as the other one. + + For full documentation refer to :obj:`numpy.array_equiv`. + + Parameters + ---------- + a1 : {dpnp.ndarray, usm_ndarray, scalar} + First input array. + Both inputs `x1` and `x2` can not be scalars at the same time. + a2 : {dpnp.ndarray, usm_ndarray, scalar} + Second input array. + Both inputs `x1` and `x2` can not be scalars at the same time. + + Returns + ------- + out : dpnp.ndarray + An array with a data type of `bool`. + ``True`` if equivalent, ``False`` otherwise. + + Examples + -------- + >>> import dpnp as np + >>> a = np.array([1, 2]) + >>> b = np.array([1, 2]) + >>> c = np.array([1, 3]) + >>> np.array_equiv(a, b) + array(True) + >>> np.array_equiv(a, c) + array(False) + + Showing the shape equivalence: + + >>> b = np.array([[1, 2], [1, 2]]) + >>> c = np.array([[1, 2, 1, 2], [1, 2, 1, 2]]) + >>> np.array_equiv(a, b) + array(True) + >>> np.array_equiv(a, c) + array(False) + + >>> b = np.array([[1, 2], [1, 3]]) + >>> np.array_equiv(a, b) + array(False) + + """ + + dpnp.check_supported_arrays_type(a1, a2, scalar_type=True) + if not dpnp.isscalar(a1) and not dpnp.isscalar(a2): + usm_type_alloc, sycl_queue_alloc = get_usm_allocations([a1, a2]) + try: + dpnp.broadcast_arrays(a1, a2) + except ValueError: + return dpnp.array( + False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc + ) + return (a1 == a2).all() + + _EQUAL_DOCSTRING = """ Calculates equality test results for each element `x1_i` of the input array `x1` with the respective element `x2_i` of the input array `x2`. diff --git a/tests/test_logic.py b/tests/test_logic.py index 92b766c3cf4e..c52aba89aa78 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -494,3 +494,104 @@ def test_isclose(dtype, rtol, atol): np_res = numpy.isclose(a, b, 1e-05, 1e-08) dpnp_res = dpnp.isclose(dpnp_a, dpnp_b, rtol, atol) assert_allclose(dpnp_res, np_res) + + +@pytest.mark.parametrize("a", [numpy.array([1, 2]), numpy.array([1, 1])]) +@pytest.mark.parametrize( + "b", + [ + numpy.array([1, 2]), + numpy.array([1, 2, 3]), + numpy.array([3, 4]), + numpy.array([1, 3]), + numpy.array([1]), + numpy.array([[1], [1]]), + numpy.array([2]), + numpy.array([[1], [2]]), + numpy.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + ], +) +def test_array_equiv(a, b): + result = dpnp.array_equiv(dpnp.array(a), dpnp.array(b)) + expected = numpy.array_equiv(a, b) + + assert_equal(expected, result) + + +@pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True, no_complex=True)) +def test_array_equiv_dtype(dtype): + a = numpy.array([1, 2], dtype=dtype) + b = numpy.array([1, 2], dtype=dtype) + c = numpy.array([1, 3], dtype=dtype) + + result = dpnp.array_equiv(dpnp.array(a), dpnp.array(b)) + expected = numpy.array_equiv(a, b) + + assert_equal(expected, result) + + result = dpnp.array_equiv(dpnp.array(a), dpnp.array(c)) + expected = numpy.array_equiv(a, c) + + assert_equal(expected, result) + + +@pytest.mark.parametrize("a", [numpy.array([1, 2]), numpy.array([1, 1])]) +def test_array_equiv_scalar(a): + b = 1 + result = dpnp.array_equiv(dpnp.array(a), b) + expected = numpy.array_equiv(a, b) + + assert_equal(expected, result) + + +@pytest.mark.parametrize("dtype", get_all_dtypes(no_bool=True, no_complex=True)) +@pytest.mark.parametrize("equal_nan", [True, False]) +def test_array_equal_dtype(dtype, equal_nan): + a = numpy.array([1, 2], dtype=dtype) + b = numpy.array([1, 2], dtype=dtype) + c = numpy.array([1, 3], dtype=dtype) + + result = dpnp.array_equal(dpnp.array(a), dpnp.array(b), equal_nan=equal_nan) + expected = numpy.array_equal(a, b, equal_nan=equal_nan) + + assert_equal(expected, result) + + result = dpnp.array_equal(dpnp.array(a), dpnp.array(c), equal_nan=equal_nan) + expected = numpy.array_equal(a, c, equal_nan=equal_nan) + + assert_equal(expected, result) + + +@pytest.mark.parametrize( + "a", + [ + numpy.array([1, 2]), + numpy.array([1.0, numpy.nan]), + numpy.array([1.0, numpy.inf]), + ], +) +def test_array_equal_same_arr(a): + expected = numpy.array_equal(a, a) + b = dpnp.array(a) + result = dpnp.array_equal(b, b) + assert_equal(expected, result) + + expected = numpy.array_equal(a, a, equal_nan=True) + result = dpnp.array_equal(b, b, equal_nan=True) + assert_equal(expected, result) + + +@pytest.mark.parametrize( + "a", + [ + numpy.array([1, 2]), + numpy.array([1.0, numpy.nan]), + numpy.array([1.0, numpy.inf]), + ], +) +def test_array_equal_nan(a): + a = numpy.array([1.0, numpy.nan]) + b = numpy.array([1.0, 2.0]) + result = dpnp.array_equal(dpnp.array(a), dpnp.array(b), equal_nan=True) + expected = numpy.array_equal(a, b, equal_nan=True) + assert_equal(expected, result) diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index bb776477ee14..322bfcf3a78c 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -752,6 +752,8 @@ def test_2in_1out(func, data1, data2, device): @pytest.mark.parametrize( "op", [ + "array_equal", + "array_equiv", "equal", "greater", "greater_equal", diff --git a/tests/test_usm_type.py b/tests/test_usm_type.py index 50e3c0724e47..ab1956eb4282 100644 --- a/tests/test_usm_type.py +++ b/tests/test_usm_type.py @@ -378,6 +378,8 @@ def test_coerced_usm_types_logic_op_1in(op, usm_type_x): @pytest.mark.parametrize( "op", [ + "array_equal", + "array_equiv", "equal", "greater", "greater_equal", diff --git a/tests/third_party/cupy/logic_tests/test_comparison.py b/tests/third_party/cupy/logic_tests/test_comparison.py index 668c4679f59d..571967ff6228 100644 --- a/tests/third_party/cupy/logic_tests/test_comparison.py +++ b/tests/third_party/cupy/logic_tests/test_comparison.py @@ -102,7 +102,6 @@ def test_array_equal_diff_length(self, xp, dtype): @testing.with_requires("numpy>=1.19") @testing.for_float_dtypes() @testing.numpy_cupy_equal() - @pytest.mark.skip("Not supported yet") def test_array_equal_infinite_equal_nan(self, xp, dtype): nan = float("nan") inf = float("inf") @@ -114,7 +113,6 @@ def test_array_equal_infinite_equal_nan(self, xp, dtype): @testing.with_requires("numpy>=1.19") @testing.for_complex_dtypes() @testing.numpy_cupy_equal() - @pytest.mark.skip("Not supported yet") def test_array_equal_complex_equal_nan(self, xp, dtype): a = xp.array([1 + 2j], dtype=dtype) b = a.copy() @@ -141,7 +139,6 @@ def test_array_equal_broadcast_not_allowed(self, xp): return xp.array_equal(a, b) -@pytest.mark.skip("dpnp.array_equiv() is not implemented yet") class TestArrayEquiv(unittest.TestCase): @testing.for_all_dtypes()