Repository: cupy/cupy
Pull request: Add `array_equal`
closes #3125 

Problem statement:
implementing array_equal() for CuPy
I was trying to implement array_equal() for Cupy and was thinking along these lines

```py
def array_equal(a1, a2):
    try:
        a1, a2 = cupy.asarray(a1), cupy.asarray(a2)
    except Exception:
        return False
    if a1.shape != a2.shape:
        return False
    return bool(cupy.asarray(a1 == a2).all())
```

Should I move forward with this ? I would be grateful if someone could guide me.

Hints from issue discussion:
Hello, thanks for being interested in contributing to CuPy!.

1st is that `asarray` checks are not needed, since cupy functions accepts only cupy arrays. https://docs-cupy.chainer.org/en/stable/reference/difference.html#universal-functions-only-work-with-cupy-array-or-scalar

2nd is that this function currently synchronizes the device when you do `return bool(...)`
just doing a `return (a1==a2).all()` should be fine.

The function can be added to the `cupy/logic/comparison.py` file.

For sending a PR you will need to add a docstring to the function and also add tests for the external behavior.
are you interested in opening a PR? @portfoliocreator 
Otherwise, I would be fine doing it!
@rushabh-v Yes , sure go ahead.

Commit message: fix linting

Patch to review:
diff --git a/cupy/logic/comparison.py b/cupy/logic/comparison.py
index 1f2b8e30507..d1c47c7e205 100644
--- a/cupy/logic/comparison.py
+++ b/cupy/logic/comparison.py
@@ -38,6 +38,28 @@
 )
 
 
+def array_equal(a, b):
+    """Returns True if two arrays are element-wise exactly equal.
+
+    Args:
+        a (cupy.ndarray): Input array to compare.
+        b (cupy.ndarray): Input array to compare.
+
+    Returns:
+        cupy.ndarray: A boolean 0-dim array.
+            If its value is ``True``, two arrays are element-wise equal.
+
+    .. seealso:: :func:`numpy.array_equal`
+
+    """
+    a = cupy.asanyarray(a)
+    b = cupy.asanyarray(b)
+
+    if a.shape != b.shape:
+        return cupy.array(False)
+    return bool((a == b).all())
+
+
 def allclose(a, b, rtol=1.e-5, atol=1.e-8, equal_nan=False):
     """Returns True if two arrays are element-wise equal within a tolerance.
 
diff --git a/tests/cupy_tests/logic_tests/test_comparison.py b/tests/cupy_tests/logic_tests/test_comparison.py
index 77172373625..66598694aec 100644
--- a/tests/cupy_tests/logic_tests/test_comparison.py
+++ b/tests/cupy_tests/logic_tests/test_comparison.py
@@ -74,6 +74,27 @@ def test_binary_array_pyscalar(self, xp, dtype):
         return [op(a, b) for op in self.operators]
 
 
+class TestArrayEqual(unittest.TestCase):
+
+    @testing.numpy_cupy_equal()
+    def test_array_equal_not_equal(self, xp):
+        a = xp.array([1, 2, 3, 4])
+        b = xp.array([0, 0, 1000, 1000])
+        return xp.allclose(a, b)
+
+    @testing.numpy_cupy_equal()
+    def test_array_equal_is_equal(self, xp):
+        a = xp.array([1, 2, 3, 4])
+        b = xp.array([1, 2, 3, 4])
+        return xp.allclose(a, b)
+
+    @testing.numpy_cupy_equal()
+    def test_array_equal_diff_length(self, xp):
+        a = xp.array([1, 2, 3, 4])
+        b = xp.array([1, 2, 3])
+        return xp.allclose(a, b)
+
+
 class TestAllclose(unittest.TestCase):
 
     @testing.for_all_dtypes()


Code review comments:
diff_hunk: @@ -38,6 +38,28 @@
 )
 
 
+def array_equal(a, b):
+    """Returns True if two arrays are element-wise exactly equal.
+
+    Args:
+        a (cupy.ndarray): Input array to compare.
+        b (cupy.ndarray): Input array to compare.
+
+    Returns:
+        cupy.ndarray: A boolean 0-dim array.
+            If its value is ``True``, two arrays are element-wise equal.
+
+    .. seealso:: :func:`numpy.array_equal`
+
+    """
+    a = cupy.asanyarray(a)
+    b = cupy.asanyarray(b)
+
+    if a.shape != b.shape:
+        return cupy.array(False)
+    return bool((a == b).all())
path: cupy/logic/comparison.py
text: @user1:
The return value should be 0-dim ndarray.
```suggestion
    return (a == b).all()
```

@author:
Actually `(a == b).all()` returns `array(True)` or `array(False)`. And `numpy.array_equal` returns only `bool`s. So I thought they would not be comparable. 

Should I change that?

@author:
I'll make other changes soon :)

@user2:
Yes, you need to do this to follow CuPy's convention. This incompatibility is documented here: https://docs-cupy.chainer.org/en/stable/reference/difference.html#zero-dimensional-array

@user2:
(Note that `all()` *is* a kind of reduction operation.)

@author:
Thanks for providing with the documentation. 
I think I should read more and more developer guidelines before GSoC period for better understanding.

@author:
I was a bit busier today.
Will make all the changes soon.

diff_hunk: @@ -38,6 +38,28 @@
 )
 
 
+def array_equal(a, b):
path: cupy/logic/comparison.py
text: @user1:
For compatibility to `numpy.array_equal`
```suggestion
def array_equal(a1, a2):
```

diff_hunk: @@ -74,6 +74,27 @@ def test_binary_array_pyscalar(self, xp, dtype):
         return [op(a, b) for op in self.operators]
 
 
+class TestArrayEqual(unittest.TestCase):
+
+    @testing.numpy_cupy_equal()
+    def test_array_equal_not_equal(self, xp):
+        a = xp.array([1, 2, 3, 4])
+        b = xp.array([0, 0, 1000, 1000])
+        return xp.allclose(a, b)
+
+    @testing.numpy_cupy_equal()
+    def test_array_equal_is_equal(self, xp):
+        a = xp.array([1, 2, 3, 4])
+        b = xp.array([1, 2, 3, 4])
+        return xp.allclose(a, b)
+
+    @testing.numpy_cupy_equal()
+    def test_array_equal_diff_length(self, xp):
+        a = xp.array([1, 2, 3, 4])
+        b = xp.array([1, 2, 3])
+        return xp.allclose(a, b)
+
path: tests/cupy_tests/logic_tests/test_comparison.py
text: @user1:
Please add tests check
- broadcasts are not allowed
- comparison with ndarrays of different dtypes is allowed

@user2:
Why is allclose called instead of array_equal?

@author:
Oh, I actually copy pasted the tests from `allclose` for my convenience and mistakenly left that call unchanged. I think I should've checked it..

I'll edit that soon.

diff_hunk: @@ -38,6 +38,28 @@
 )
 
 
+def array_equal(a, b):
+    """Returns True if two arrays are element-wise exactly equal.
+
+    Args:
+        a (cupy.ndarray): Input array to compare.
+        b (cupy.ndarray): Input array to compare.
+
+    Returns:
+        cupy.ndarray: A boolean 0-dim array.
+            If its value is ``True``, two arrays are element-wise equal.
+
+    .. seealso:: :func:`numpy.array_equal`
+
+    """
+    a = cupy.asanyarray(a)
+    b = cupy.asanyarray(b)
path: cupy/logic/comparison.py
text: @user1:
The inputs are assumed to be `cupy.ndarray`, so this typecast is not needed.