Repository: yt-project/unyt (python)

Issue:
numpy DeprecationWarning when quantities with different units are compared.
* unyt version:
2.3.1
* Python version:
3.7
* Operating System:
GNU/Linux & conda env

### Description

We noticed that unyt outputs a deprecationwarning when quantities with different units are compared. 
### What I Did

```
In [3]: 1*u.mm == 1*u.V                                                                                                                                      
/opt/miniconda/envs/py3/bin/ipython:1: DeprecationWarning: elementwise comparison failed; this will raise an error in the future.
  #!/opt/miniconda/envs/py3/bin/python
```
Note that no DeprecationWarning is output when the units are rescalable:
```

In [5]: 1*u.mm == 1*u.m                                                                                                                                      
Out[5]: array(False)
```




Pull request description:
Allow == and != for data with different units. Fixes #105.
Up until now we were unconditionally raising a UnitOperationError for all
comparisons of data with different units, including `equal` and `not_equal`. Due
to details of how NumPy handles this internally (I guess it happens in C code
because I don't see any of this when stepping through in pdb) numpy swallows
that error and raises a DeprecationWarning (!?) causing confusing behavior when
comparing quantities with units that are not dimensionally equivalent.

The fix is to add special handling for equal and not_equal when comparing data
with units that have different dimensions. I've also added tests for this case.

This may have negative performance implications for some workflows however I
think we have to do this in the interest of correctness. Also comparing data
with different units like this is kinda a weird thing to do and hopefully won't
impact common workflows.

Thanks to @thisch for pointing this out.

Patch:
diff --git a/unyt/array.py b/unyt/array.py
index 6a655e3..c389812 100644
--- a/unyt/array.py
+++ b/unyt/array.py
@@ -1673,7 +1673,26 @@ class unyt_array(np.ndarray):
                             elif u1.is_dimensionless:
                                 u1 = u0
                             else:
-                                raise UnitOperationError(ufunc, u0, u1)
+                                # comparison with different units, so need to check if
+                                # this is == and != which we allow and handle in a
+                                # special way using an early return from __array_ufunc__
+                                if ufunc in (equal, not_equal):
+                                    if ufunc is equal:
+                                        func = np.zeros_like
+                                    else:
+                                        func = np.ones_like
+                                    ret = func(np.asarray(inp1), dtype=bool)
+                                    if out is not None:
+                                        out[:] = ret[:]
+                                        if isinstance(out, unyt_array):
+                                            out.units = Unit(
+                                                "", registry=self.units.registry
+                                            )
+                                    if ret.shape == ():
+                                        ret = bool(ret)
+                                    return ret
+                                else:
+                                    raise UnitOperationError(ufunc, u0, u1)
                         else:
                             raise UnitOperationError(ufunc, u0, u1)
                     conv, offset = u1.get_conversion_factor(u0, inp1.dtype)


Test patch:
diff --git a/unyt/tests/test_unyt_array.py b/unyt/tests/test_unyt_array.py
index 78b6caf..5c031cc 100644
--- a/unyt/tests/test_unyt_array.py
+++ b/unyt/tests/test_unyt_array.py
@@ -509,6 +509,7 @@ def test_comparisons():
     a1 = unyt_array([1, 2, 3], "cm")
     a2 = unyt_array([2, 1, 3], "cm")
     a3 = unyt_array([0.02, 0.01, 0.03], "m")
+    a4 = unyt_array([1, 2, 3], "g")
     dimless = np.array([2, 1, 3])
 
     ops = (np.less, np.less_equal, np.greater, np.greater_equal, np.equal, np.not_equal)
@@ -542,6 +543,26 @@ def test_comparisons():
     assert_equal(2 < a1, [False, False, True])
     assert_equal(2 < a1, np.less(2, a1))
 
+    # Check that comparisons with arrays that have different units with
+    # different dimensions work properly
+    operate_and_compare(a1, a4, np.equal, [False, False, False])
+    operate_and_compare(a1, a4, np.not_equal, [True, True, True])
+
+    # check that comparing quantities returns bools and not 0-D arrays
+    el1, el4 = a1[0], a4[0]
+    assert (el1 == el4) is False
+    assert (el1 != el4) is True
+
+    # comparisons that aren't == and !=
+    with pytest.raises(UnitOperationError):
+        np.greater(a1, a4)
+    with pytest.raises(UnitOperationError):
+        a1 > a4
+    with pytest.raises(UnitOperationError):
+        np.greater(el1, el4)
+    with pytest.raises(UnitOperationError):
+        el1 > el4
+
 
 def test_unit_conversions():
     """
@@ -1003,8 +1024,6 @@ def binary_ufunc_comparison(ufunc, a, b):
             "greater_equal",
             "less",
             "less_equal",
-            "equal",
-            "not_equal",
             "logical_and",
             "logical_or",
             "logical_xor",