From 4347c09a70a269647bfed03ba441d2a92450095b Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Fri, 2 Aug 2024 10:58:39 -0700 Subject: [PATCH 1/4] Implement dpnp.array_equal and dpnp.array_equiv --- dpnp/dpnp_iface.py | 19 --- dpnp/dpnp_iface_logic.py | 154 ++++++++++++++++++ tests/test_logic.py | 140 ++++++++++++++++ .../cupy/logic_tests/test_comparison.py | 3 - 4 files changed, 294 insertions(+), 22 deletions(-) diff --git a/dpnp/dpnp_iface.py b/dpnp/dpnp_iface.py index bc005c21e0ec..29913ff8a409 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 2f28b3635ecd..1af5e78971e3 100644 --- a/dpnp/dpnp_iface_logic.py +++ b/dpnp/dpnp_iface_logic.py @@ -57,6 +57,8 @@ "all", "allclose", "any", + "array_equal", + "array_equiv", "equal", "greater", "greater_equal", @@ -342,6 +344,158 @@ def any(a, /, axis=None, out=None, keepdims=False, *, where=True): return result +def array_equal(a1, a2, equal_nan=False): + """ + ``True`` if two arrays have the same shape and elements, ``False`` + otherwise. + + Parameters + ---------- + a1 : {dpnp.ndarray, usm_ndarray, scalar} + First input array, expected to have numeric data type. + Both inputs `x1` and `x2` can not be scalars at the same time. + a2 : {dpnp.ndarray, usm_ndarray, scalar} + Second input array, also expected to have numeric data type. + Both inputs `x1` and `x2` can not be scalars at the same time. + equal_nan : bool + 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 ``NaNs``. + + 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) + + >>> a = np.array([1, 2]) + >>> b = np.array([1, 2, 3]) + >>> np.array_equal(a, b) + array(False) + + >>> a = np.array([1, 2]) + >>> 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 nan. + + >>> 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 a1.shape != a2.shape: + return dpnp.array(False) + + if not equal_nan: + return (a1 == a2).all() + + if a1 is a2: + return dpnp.array(True) + + cannot_have_nan = ( + dpnp.issubdtype(a1, dpnp.bool) or dpnp.issubdtype(a1, dpnp.integer) + ) and (dpnp.issubdtype(a2, dpnp.bool) or dpnp.issubdtype(a2, dpnp.integer)) + + if cannot_have_nan: + 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) + # 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. + + Parameters + ---------- + a1 : {dpnp.ndarray, usm_ndarray, scalar} + First input array, expected to have numeric data type. + Both inputs `x1` and `x2` can not be scalars at the same time. + a2 : {dpnp.ndarray, usm_ndarray, scalar} + Second input array, also expected to have numeric data type. + 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: + + >>> a = np.array([1, 2]) + >>> 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) + + >>> a = np.array([1, 2]) + >>> b = np.array([[1, 2], [1, 3]]) + >>> np.array_equiv(a, b) + array(False) + + """ + dpnp.check_supported_arrays_type(a1, a2, scalar_type=True) + try: + dpnp.broadcast_arrays(a1, a2) + except ValueError: + return dpnp.array(False) + 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..27c672ed01a1 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -494,3 +494,143 @@ 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) + + +def _test_array_equal_parametrizations(): + """ + we pre-create arrays as we sometime want to pass the same instance + and sometime not. Passing the same instances may not mean the array are + equal, especially when containing None + """ + # those are 0-d arrays, it used to be a special case + # where (e0 == e0).all() would raise + e0 = dpnp.array(0, dtype="i4") + e1 = dpnp.array(1, dtype="f4") + # x,y, nan_equal, expected_result + yield (e0, e0.copy(), None, True) + yield (e0, e0.copy(), False, True) + yield (e0, e0.copy(), True, True) + + # + yield (e1, e1.copy(), None, True) + yield (e1, e1.copy(), False, True) + yield (e1, e1.copy(), True, True) + + # Non-nanable – those cannot hold nans + a12 = dpnp.array([1, 2]) + a12b = a12.copy() + a123 = dpnp.array([1, 2, 3]) + a13 = dpnp.array([1, 3]) + a34 = dpnp.array([3, 4]) + + yield (a12, a12b, None, True) + yield (a12, a12, None, True) + yield (a12, a123, None, False) + yield (a12, a34, None, False) + yield (a12, a13, None, False) + + # Non-float dtype - equal_nan should have no effect, + yield (a123, a123, None, True) + yield (a123, a123, False, True) + yield (a123, a123, True, True) + yield (a123, a123.copy(), None, True) + yield (a123, a123.copy(), False, True) + yield (a123, a123.copy(), True, True) + yield (a123.astype("f4"), a123.astype("f4"), None, True) + yield (a123.astype("f4"), a123.astype("f4"), False, True) + yield (a123.astype("f4"), a123.astype("f4"), True, True) + + # these can hold None + b1 = dpnp.array([1, 2, dpnp.nan]) + b2 = dpnp.array([1, dpnp.nan, 2]) + b3 = dpnp.array([1, 2, dpnp.inf]) + b4 = dpnp.array(dpnp.nan) + + # instances are the same + yield (b1, b1, None, False) + yield (b1, b1, False, False) + yield (b1, b1, True, True) + + # equal but not same instance + yield (b1, b1.copy(), None, False) + yield (b1, b1.copy(), False, False) + yield (b1, b1.copy(), True, True) + + # same once stripped of Nan + yield (b1, b2, None, False) + yield (b1, b2, False, False) + yield (b1, b2, True, False) + + # nan's not conflated with inf's + yield (b1, b3, None, False) + yield (b1, b3, False, False) + yield (b1, b3, True, False) + + # all Nan + yield (b4, b4, None, False) + yield (b4, b4, False, False) + yield (b4, b4, True, True) + yield (b4, b4.copy(), None, False) + yield (b4, b4.copy(), False, False) + yield (b4, b4.copy(), True, True) + + # Multi-dimensional array + md1 = dpnp.array([[0, 1], [dpnp.nan, 1]]) + + yield (md1, md1, None, False) + yield (md1, md1, False, False) + yield (md1, md1, True, True) + yield (md1, md1.copy(), None, False) + yield (md1, md1.copy(), False, False) + yield (md1, md1.copy(), True, True) + # both complexes are nan+nan.j but the same instance + cplx1, cplx2 = [dpnp.array([dpnp.nan + dpnp.nan * 1j])] * 2 + + # only real or img are nan. + cplx3 = dpnp.array(1 + dpnp.nan * 1j) + cplx4 = dpnp.array(dpnp.nan + 1j) + + # Complex values + yield (cplx1, cplx2, None, False) + yield (cplx1, cplx2, False, False) + yield (cplx1, cplx2, True, True) + + # Complex values, 1+nan, nan+1j + yield (cplx3, cplx4, None, False) + yield (cplx3, cplx4, False, False) + yield (cplx3, cplx4, True, True) + + +class TestArrayComparisons: + @pytest.mark.parametrize( + "bx,by,equal_nan,expected", _test_array_equal_parametrizations() + ) + def test_array_equal_equal_nan(self, bx, by, equal_nan, expected): + if equal_nan is None: + res = dpnp.array_equal(bx, by) + else: + res = dpnp.array_equal(bx, by, equal_nan=equal_nan) + assert_equal(res, dpnp.array(expected)) + + def test_array_equiv(self): + res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([1, 2])) + assert res + res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([1, 2, 3])) + assert not res + res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([3, 4])) + assert not res + res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([1, 3])) + assert not res + + res = dpnp.array_equiv(dpnp.array([1, 1]), dpnp.array([1])) + assert res + res = dpnp.array_equiv(dpnp.array([1, 1]), dpnp.array([[1], [1]])) + assert res + res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([2])) + assert not res + res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([[1], [2]])) + assert not res + res = dpnp.array_equiv( + dpnp.array([1, 2]), dpnp.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) + ) + assert not res 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() From 55a3da8eaf23297baf67a9314fb533da84030fac Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Fri, 2 Aug 2024 11:24:28 -0700 Subject: [PATCH 2/4] Added CFD --- dpnp/dpnp_iface_logic.py | 58 +++++++++++++++++++++++++++++++++++++--- tests/test_sycl_queue.py | 2 ++ tests/test_usm_type.py | 2 ++ 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/dpnp/dpnp_iface_logic.py b/dpnp/dpnp_iface_logic.py index 1af5e78971e3..6ab808e1bb4e 100644 --- a/dpnp/dpnp_iface_logic.py +++ b/dpnp/dpnp_iface_logic.py @@ -50,6 +50,7 @@ import numpy import dpnp +import dpnp.dpnp_utils as utils from dpnp.dpnp_algo.dpnp_elementwise_common import DPNPBinaryFunc, DPNPUnaryFunc from dpnp.dpnp_array import dpnp_array @@ -412,15 +413,39 @@ def array_equal(a1, a2, equal_nan=False): """ 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 = utils.get_usm_allocations([a1, a2]) if a1.shape != a2.shape: - return dpnp.array(False) + 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: - return dpnp.array(True) + return dpnp.array( + True, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc + ) cannot_have_nan = ( dpnp.issubdtype(a1, dpnp.bool) or dpnp.issubdtype(a1, dpnp.integer) @@ -433,7 +458,9 @@ def array_equal(a1, a2, equal_nan=False): a1nan, a2nan = isnan(a1), isnan(a2) # NaNs occur at different locations if not (a1nan == a2nan).all(): - return dpnp.array(False) + 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() @@ -489,10 +516,33 @@ def array_equiv(a1, a2): """ 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 = utils.get_usm_allocations([a1, a2]) + try: dpnp.broadcast_arrays(a1, a2) except ValueError: - return dpnp.array(False) + return dpnp.array( + False, usm_type=usm_type_alloc, sycl_queue=sycl_queue_alloc + ) return (a1 == a2).all() diff --git a/tests/test_sycl_queue.py b/tests/test_sycl_queue.py index 57e2e6dc4c9f..90f9759e61ca 100644 --- a/tests/test_sycl_queue.py +++ b/tests/test_sycl_queue.py @@ -746,6 +746,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 b81b9f25edda..7b9dd9eaea7e 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", From 9207515c04ce349e079a58da4efcc3138c055218 Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Tue, 6 Aug 2024 13:16:46 -0700 Subject: [PATCH 3/4] Applied review comments --- doc/reference/logic.rst | 1 + dpnp/dpnp_iface_logic.py | 83 ++++++++---------- tests/test_logic.py | 182 ++++++++++----------------------------- 3 files changed, 79 insertions(+), 187 deletions(-) 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_logic.py b/dpnp/dpnp_iface_logic.py index cc02d7059acf..00dd8dce6e58 100644 --- a/dpnp/dpnp_iface_logic.py +++ b/dpnp/dpnp_iface_logic.py @@ -50,9 +50,10 @@ import numpy import dpnp -import dpnp.dpnp_utils as utils from dpnp.dpnp_algo.dpnp_elementwise_common import DPNPBinaryFunc, DPNPUnaryFunc +from .dpnp_utils import get_usm_allocations + __all__ = [ "all", "allclose", @@ -115,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`` @@ -279,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`` @@ -345,23 +346,26 @@ 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, expected to have numeric data type. + 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, also expected to have numeric data type. + Second input array. Both inputs `x1` and `x2` can not be scalars at the same time. - equal_nan : bool + 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 ``NaNs``. + imaginary component of a given value is ``NaN``. + Default: ``False``. Returns ------- b : dpnp.ndarray - An array with a data type of `bool` + An array with a data type of `bool`. Returns ``True`` if the arrays are equal. See Also @@ -379,12 +383,10 @@ def array_equal(a1, a2, equal_nan=False): >>> np.array_equal(a, b) array(True) - >>> a = np.array([1, 2]) >>> b = np.array([1, 2, 3]) >>> np.array_equal(a, b) array(False) - >>> a = np.array([1, 2]) >>> b = np.array([1, 4]) >>> np.array_equal(a, b) array(False) @@ -397,7 +399,8 @@ def array_equal(a1, a2, equal_nan=False): array(True) When ``equal_nan`` is ``True``, complex values with nan components are - considered equal if either the real *or* the imaginary components are nan. + considered equal if either the real *or* the imaginary components are + ``NaNs``. >>> a = np.array([1 + 1j]) >>> b = a.copy() @@ -407,6 +410,7 @@ def array_equal(a1, a2, equal_nan=False): array(True) """ + dpnp.check_supported_arrays_type(a1, a2, scalar_type=True) if dpnp.isscalar(a1): usm_type_alloc = a2.usm_type @@ -427,7 +431,7 @@ def array_equal(a1, a2, equal_nan=False): sycl_queue=sycl_queue_alloc, ) else: - usm_type_alloc, sycl_queue_alloc = utils.get_usm_allocations([a1, a2]) + usm_type_alloc, sycl_queue_alloc = get_usm_allocations([a1, a2]) if a1.shape != a2.shape: return dpnp.array( @@ -438,15 +442,14 @@ def array_equal(a1, a2, equal_nan=False): 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 ) - cannot_have_nan = ( - dpnp.issubdtype(a1, dpnp.bool) or dpnp.issubdtype(a1, dpnp.integer) - ) and (dpnp.issubdtype(a2, dpnp.bool) or dpnp.issubdtype(a2, dpnp.integer)) - - if cannot_have_nan: + 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 @@ -468,19 +471,21 @@ def array_equiv(a1, a2): 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, expected to have numeric data type. + 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, also expected to have numeric data type. + 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` + An array with a data type of `bool`. ``True`` if equivalent, ``False`` otherwise. Examples @@ -496,7 +501,6 @@ def array_equiv(a1, a2): Showing the shape equivalence: - >>> a = np.array([1, 2]) >>> b = np.array([[1, 2], [1, 2]]) >>> c = np.array([[1, 2, 1, 2], [1, 2, 1, 2]]) >>> np.array_equiv(a, b) @@ -504,40 +508,21 @@ def array_equiv(a1, a2): >>> np.array_equiv(a, c) array(False) - >>> a = np.array([1, 2]) >>> 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 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 = utils.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 - ) + 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() diff --git a/tests/test_logic.py b/tests/test_logic.py index 27c672ed01a1..413e2bf3ebc5 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -496,141 +496,47 @@ def test_isclose(dtype, rtol, atol): assert_allclose(dpnp_res, np_res) -def _test_array_equal_parametrizations(): - """ - we pre-create arrays as we sometime want to pass the same instance - and sometime not. Passing the same instances may not mean the array are - equal, especially when containing None - """ - # those are 0-d arrays, it used to be a special case - # where (e0 == e0).all() would raise - e0 = dpnp.array(0, dtype="i4") - e1 = dpnp.array(1, dtype="f4") - # x,y, nan_equal, expected_result - yield (e0, e0.copy(), None, True) - yield (e0, e0.copy(), False, True) - yield (e0, e0.copy(), True, True) - - # - yield (e1, e1.copy(), None, True) - yield (e1, e1.copy(), False, True) - yield (e1, e1.copy(), True, True) - - # Non-nanable – those cannot hold nans - a12 = dpnp.array([1, 2]) - a12b = a12.copy() - a123 = dpnp.array([1, 2, 3]) - a13 = dpnp.array([1, 3]) - a34 = dpnp.array([3, 4]) - - yield (a12, a12b, None, True) - yield (a12, a12, None, True) - yield (a12, a123, None, False) - yield (a12, a34, None, False) - yield (a12, a13, None, False) - - # Non-float dtype - equal_nan should have no effect, - yield (a123, a123, None, True) - yield (a123, a123, False, True) - yield (a123, a123, True, True) - yield (a123, a123.copy(), None, True) - yield (a123, a123.copy(), False, True) - yield (a123, a123.copy(), True, True) - yield (a123.astype("f4"), a123.astype("f4"), None, True) - yield (a123.astype("f4"), a123.astype("f4"), False, True) - yield (a123.astype("f4"), a123.astype("f4"), True, True) - - # these can hold None - b1 = dpnp.array([1, 2, dpnp.nan]) - b2 = dpnp.array([1, dpnp.nan, 2]) - b3 = dpnp.array([1, 2, dpnp.inf]) - b4 = dpnp.array(dpnp.nan) - - # instances are the same - yield (b1, b1, None, False) - yield (b1, b1, False, False) - yield (b1, b1, True, True) - - # equal but not same instance - yield (b1, b1.copy(), None, False) - yield (b1, b1.copy(), False, False) - yield (b1, b1.copy(), True, True) - - # same once stripped of Nan - yield (b1, b2, None, False) - yield (b1, b2, False, False) - yield (b1, b2, True, False) - - # nan's not conflated with inf's - yield (b1, b3, None, False) - yield (b1, b3, False, False) - yield (b1, b3, True, False) - - # all Nan - yield (b4, b4, None, False) - yield (b4, b4, False, False) - yield (b4, b4, True, True) - yield (b4, b4.copy(), None, False) - yield (b4, b4.copy(), False, False) - yield (b4, b4.copy(), True, True) - - # Multi-dimensional array - md1 = dpnp.array([[0, 1], [dpnp.nan, 1]]) - - yield (md1, md1, None, False) - yield (md1, md1, False, False) - yield (md1, md1, True, True) - yield (md1, md1.copy(), None, False) - yield (md1, md1.copy(), False, False) - yield (md1, md1.copy(), True, True) - # both complexes are nan+nan.j but the same instance - cplx1, cplx2 = [dpnp.array([dpnp.nan + dpnp.nan * 1j])] * 2 - - # only real or img are nan. - cplx3 = dpnp.array(1 + dpnp.nan * 1j) - cplx4 = dpnp.array(dpnp.nan + 1j) - - # Complex values - yield (cplx1, cplx2, None, False) - yield (cplx1, cplx2, False, False) - yield (cplx1, cplx2, True, True) - - # Complex values, 1+nan, nan+1j - yield (cplx3, cplx4, None, False) - yield (cplx3, cplx4, False, False) - yield (cplx3, cplx4, True, True) - - -class TestArrayComparisons: - @pytest.mark.parametrize( - "bx,by,equal_nan,expected", _test_array_equal_parametrizations() - ) - def test_array_equal_equal_nan(self, bx, by, equal_nan, expected): - if equal_nan is None: - res = dpnp.array_equal(bx, by) - else: - res = dpnp.array_equal(bx, by, equal_nan=equal_nan) - assert_equal(res, dpnp.array(expected)) - - def test_array_equiv(self): - res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([1, 2])) - assert res - res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([1, 2, 3])) - assert not res - res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([3, 4])) - assert not res - res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([1, 3])) - assert not res - - res = dpnp.array_equiv(dpnp.array([1, 1]), dpnp.array([1])) - assert res - res = dpnp.array_equiv(dpnp.array([1, 1]), dpnp.array([[1], [1]])) - assert res - res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([2])) - assert not res - res = dpnp.array_equiv(dpnp.array([1, 2]), dpnp.array([[1], [2]])) - assert not res - res = dpnp.array_equiv( - dpnp.array([1, 2]), dpnp.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) - ) - assert not 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("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( + "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.copy()) + b = dpnp.array(a) + result = dpnp.array_equal(b, b.copy()) + assert_equal(expected, result) From 1f56a8d695f0c3d3c003d913dc894c601b6dd012 Mon Sep 17 00:00:00 2001 From: Natalia Polina Date: Wed, 7 Aug 2024 14:13:31 -0700 Subject: [PATCH 4/4] Added tests --- tests/test_logic.py | 59 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/tests/test_logic.py b/tests/test_logic.py index 413e2bf3ebc5..c52aba89aa78 100644 --- a/tests/test_logic.py +++ b/tests/test_logic.py @@ -518,6 +518,23 @@ def test_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 @@ -527,6 +544,24 @@ def test_array_equiv_scalar(a): 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", [ @@ -536,7 +571,27 @@ def test_array_equiv_scalar(a): ], ) def test_array_equal_same_arr(a): - expected = numpy.array_equal(a, a.copy()) + expected = numpy.array_equal(a, a) b = dpnp.array(a) - result = dpnp.array_equal(b, b.copy()) + 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)