Repository: yt-project/unyt

Issue:
BUG: temperature units comparison
* unyt version: 2.9.5

### Description

I would not expect Kelvin and degree Celsius to compare equal, yet this is the current result of `Unit.__eq__`

### What I Did

```python
import unyt as un
assert un.K != un.degC
```
raises `AssertionError`


Note that on the main branch (unyt 3.0, using NEP 18), this is breaking array comparison
```python
import numpy as np
import unyt as un
assert all(np.isclose([1, 2, 3] * un.K, [-272.15, -271.15, -270.15] * un.degC))
```

I think the correct solution is to include `Unit.base_offset` in the comparison routine, but I'm not 100% certain.

Discussion:
neutrinoceros: Thanks @ngoldbaum for this early review !
Two,very similar tests are currently failing, that raise a `FutureWarning`, see
https://github.com/yt-project/unyt/blob/fd98eea6f406129afecc1189601b9b2df3cdf1ae/unyt/tests/test_unyt_array.py#L2470-L2480

Am I understanding correctly that `1K + 1degC` is deprecated but `1degC + 1K` isn't ?
In any case, I missed this deprecation in #402, should it be expired for unyt 3.0 ?
ngoldbaum: > Am I understanding correctly that 1K + 1degC is deprecated but 1degC + 1K isn't

Yes, because 1 K is the same as 1 delta_degC, so treating it as a temperature delta is well-defined.

Good call on the deprecation, it’s probably worth making it raise an error. 

TBH temperature units are such a pain I kinda wish we had stuck with just supporting Kelvin but that ship sailed a long time ago.
neutrinoceros: Bugs have to come from *somewhere* though :D
I'll make a separate (hopefully easier to ship) PR to expire the deprecation
neutrinoceros: I've revamped this.
- it's now rebased on top of #410 
- tests pass
- I've taken performances considerations into account. I ended up tossing `Unit.__ne__` completely, in favour of `Unit.__eq__`, so hopefully every bit of optimisation and logic is implemented just once and reused.
neutrinoceros: There's now a new error in doc tests, where the results of adding two scalar temperatures with `dtype == int64` has `dtype == float64`, so the `repr` is a little different. This is actually consistent with how other unitful scalars have been behaving for a while, so I think the correct fix is to just change the test's expectation, but maybe I'm misguided, so I'll wait for inputs on this point.
ngoldbaum: Thanks so much for looking closer at this! Changing the doctest is fine.

Patch:
diff --git a/docs/usage.rst b/docs/usage.rst
index 3564460..cc4a097 100644
--- a/docs/usage.rst
+++ b/docs/usage.rst
@@ -363,9 +363,9 @@ Performing arithmetic with temperature quantities can be ambiguous. To clarify i
   >>> t1 = 23*degC
   >>> t2 = 1*delta_degC
   >>> print(t1 + t2)
-  24 °C
+  24.0 °C
   >>> print(t2 - t1)
-  -22 °C
+  -22.0 °C
   >>> tempco = 10.0*V/delta_degC
   >>> print(tempco*2*delta_degC)
   20.0 V
diff --git a/unyt/array.py b/unyt/array.py
index efc0f3d..34db577 100644
--- a/unyt/array.py
+++ b/unyt/array.py
@@ -1884,7 +1884,11 @@ class unyt_array(np.ndarray):
                     conv, offset = u1.get_conversion_factor(u0, inp1.dtype)
                     new_dtype = np.dtype("f" + str(inp1.dtype.itemsize))
                     conv = new_dtype.type(conv)
-                    if offset is not None:
+                    if (
+                        offset is not None
+                        and u1.base_offset != 0.0
+                        and not repr(u0).startswith("delta_")
+                    ):
                         raise InvalidUnitOperation(
                             "Quantities with units of Fahrenheit or Celsius "
                             "cannot by multiplied, divided, subtracted or "
diff --git a/unyt/unit_object.py b/unyt/unit_object.py
index 2b13e46..5b734ef 100644
--- a/unyt/unit_object.py
+++ b/unyt/unit_object.py
@@ -480,25 +480,18 @@ class Unit:
 
     def __eq__(self, u):
         """Test unit equality."""
-        if not isinstance(u, Unit):
-            return False
         return (
-            math.isclose(self.base_value, u.base_value)
-            and self.dimensions == u.dimensions
+            isinstance(u, Unit)
+            and math.isclose(self.base_value, u.base_value)
+            and math.isclose(self.base_offset, u.base_offset)
+            and (
+                # use 'is' comparison dimensions to avoid expensive sympy operation
+                self.dimensions is u.dimensions
+                # fall back to expensive sympy comparison
+                or self.dimensions == u.dimensions
+            )
         )
 
-    def __ne__(self, u):
-        """Test unit inequality."""
-        if not isinstance(u, Unit):
-            return True
-        if not math.isclose(self.base_value, u.base_value):
-            return True
-        # use 'is' comparison dimensions to avoid expensive sympy operation
-        if self.dimensions is u.dimensions:
-            return False
-        # fall back to expensive sympy comparison
-        return self.dimensions != u.dimensions
-
     def copy(self, *, deep=False):
         expr = str(self.expr)
         base_value = copy.deepcopy(self.base_value)


Test patch:
diff --git a/unyt/tests/test_units.py b/unyt/tests/test_units.py
index fd70b6d..41322e6 100644
--- a/unyt/tests/test_units.py
+++ b/unyt/tests/test_units.py
@@ -504,15 +504,25 @@ def test_base_equivalent():
     assert u.get_base_equivalent(unit_system="mks") == Unit("A")
 
 
-def test_temperature_offsets():
-    u1 = Unit("degC")
-    u2 = Unit("degF")
-
+@pytest.mark.parametrize(
+    ("u1", "u2"),
+    [
+        (Unit("degC"), Unit("degF")),
+        (Unit("degC"), Unit("K")),
+        (Unit("degF"), Unit("K")),
+    ],
+)
+def test_temperature_offsets(u1, u2):
     with pytest.raises(InvalidUnitOperation):
         operator.mul(u1, u2)
     with pytest.raises(InvalidUnitOperation):
         operator.truediv(u1, u2)
 
+    assert u1 != u2
+    assert u2 != u1
+    assert not u1 == u2
+    assert not u2 == u1
+
 
 def test_latex_repr():
     registry = UnitRegistry()