Repository: yt-project/unyt

Issue:
BUG: (NEP 18) np.histogram raises TypeError for range using implicit units
* unyt version: 3.0.0
* Python version: any
* Operating System: any

### Description

This is a regression in unyt 3.0.0, discovered in testing yt's cookbook.

### What I Did

```python
import unyt as un
import numpy as np

np.histogram(
    un.unyt_array([0, 1, 2, 3], 'yr'),
    bins=2,
    range=[0, 5e8], # years
)
```

```
Traceback (most recent call last):
  File "/Users/robcleme/dev/yt-project/yt/g.py", line 4, in <module>
    np.histogram(
  File "/Users/robcleme/dev/yt-project/yt/_unyt/unyt/array.py", line 2034, in __array_function__
    return _HANDLED_FUNCTIONS[func](*args, **kwargs)
  File "/Users/robcleme/dev/yt-project/yt/_unyt/unyt/_array_functions.py", line 164, in histogram
    range = _sanitize_range(range, units=[a.units])
  File "/Users/robcleme/dev/yt-project/yt/_unyt/unyt/_array_functions.py", line 149, in _sanitize_range
    raise TypeError(
TypeError: Elements of range must both have a 'units' attribute. Got [0, 500000000.0]
```

The error message, which is 100% intentional, hints a the preferred way to call this function by making units explicit:
```python
import unyt as un
import numpy as np

np.histogram(
    un.unyt_array([0, 1, 2, 3], 'yr'),
    bins=2,
    range=un.unyt_array([0, 5e8], 'yr'),
)
```

however, this doesn't work with unyt 2.9.5.
The only way to write this code that's portable against unyt 2.9.5 and unyt 3.0.0 is to strip units, which is less than ideal. In hindsight, I think it should still be allowed to use implicit units for the range argument here. I'll try to come up with a non-invasive fix.

Discussion:

Patch:
diff --git a/unyt/_array_functions.py b/unyt/_array_functions.py
index ded81b4..e288d27 100644
--- a/unyt/_array_functions.py
+++ b/unyt/_array_functions.py
@@ -146,9 +146,16 @@ def _sanitize_range(_range, units):
         ilim = _range[2 * i : 2 * (i + 1)]
         imin, imax = ilim
         if not (hasattr(imin, "units") and hasattr(imax, "units")):
-            raise TypeError(
-                f"Elements of range must both have a 'units' attribute. Got {_range}"
-            )
+            if len(units) == 1:
+                # allow range to be pure numerical scalars
+                # for backward compatibility with unyt 2.9.5
+                # see https://github.com/yt-project/unyt/issues/465
+                imin *= units[0]
+                imax *= units[0]
+            else:
+                raise TypeError(
+                    f"Elements of range must both have a 'units' attribute. Got {_range}"
+                )
         new_range[i] = imin.to_value(units[i]), imax.to_value(units[i])
     return new_range.squeeze()
 


Test patch:
diff --git a/unyt/tests/test_array_functions.py b/unyt/tests/test_array_functions.py
index 3d9fa7f..3836bb1 100644
--- a/unyt/tests/test_array_functions.py
+++ b/unyt/tests/test_array_functions.py
@@ -475,6 +475,14 @@ def test_histogram():
     assert bins.units == arr.units
 
 
+def test_histogram_implicit_units():
+    # see https://github.com/yt-project/unyt/issues/465
+    arr = np.random.normal(size=1000) * cm
+    counts, bins = np.histogram(arr, bins=10, range=(arr.min().value, arr.max().value))
+    assert type(counts) is np.ndarray
+    assert bins.units == arr.units
+
+
 def test_histogram2d():
     x = np.random.normal(size=100) * cm
     y = np.random.normal(loc=10, size=100) * s