Repository: yt-project/unyt
Pull request #197 (2021-08-11 12:28:15)

BUG: fix unit conversion for array with dtype int8
This dtype was apparently overlooked in PR #56

```python
import unyt as u
a = u.unyt_array(1, "cm", dtype="int8")
a.to("km") 
```
fails with
```python
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-fe344d50e9a4> in <module>
      1 import unyt as u
      2 a = u.unyt_array(1, "cm", dtype="int8")
----> 3 a.in_units("km")

~/dev/python/yt-project/unyt/unyt/array.py in in_units(self, units, equivalence, **kwargs)
    845                     )
    846             new_dtypekind = "c" if self.dtype.kind == "c" else "f"
--> 847             new_dtype = np.dtype(new_dtypekind + str(dsize))
    848             ret = np.asarray(self.ndview * conversion_factor, dtype=new_dtype)
    849             if offset:

TypeError: data type 'f1' not understood
```
the root cause is that `float8` dtype doesn't exist. The fix is to force minimal `itemsize` in bytes to 2, rather than copying it directly from the input dtype, so input array with dtype `int8` gets converted to `float16`.

While I'm at it I also patched `unyt_array.convert_to_units` so that it raises a clearer error message in case the user attempts to convert a `int8` array in place (which isn't possible, I think)


Patch:
diff --git a/unyt/array.py b/unyt/array.py
index c24f22c..2c8624f 100644
--- a/unyt/array.py
+++ b/unyt/array.py
@@ -653,6 +653,12 @@ class unyt_array(np.ndarray):
                 # form, it's possible this may lose precision for very
                 # large integers
                 dsize = values.dtype.itemsize
+                if dsize == 1:
+                    raise ValueError(
+                        "Can't convert memory buffer in place. "
+                        f"Input dtype ({self.dtype}) has a smaller itemsize than the "
+                        "smallest floating point representation possible."
+                    )
                 new_dtype = "f" + str(dsize)
                 large = LARGE_INPUT.get(dsize, 0)
                 if large and np.any(np.abs(values) > large):
@@ -713,7 +719,7 @@ class unyt_array(np.ndarray):
         self.convert_to_units(
             self.units.get_base_equivalent(unit_system),
             equivalence=equivalence,
-            **kwargs
+            **kwargs,
         )
 
     def convert_to_cgs(self, equivalence=None, **kwargs):
@@ -833,7 +839,7 @@ class unyt_array(np.ndarray):
                 (conversion_factor, offset) = self.units.get_conversion_factor(
                     new_units, self.dtype
                 )
-            dsize = self.dtype.itemsize
+            dsize = max(2, self.dtype.itemsize)
             if self.dtype.kind in ("u", "i"):
                 large = LARGE_INPUT.get(dsize, 0)
                 if large and np.any(np.abs(self.d) > large):


Test patch:
diff --git a/unyt/tests/test_unyt_array.py b/unyt/tests/test_unyt_array.py
index d5898ee..2f806ba 100644
--- a/unyt/tests/test_unyt_array.py
+++ b/unyt/tests/test_unyt_array.py
@@ -22,6 +22,7 @@ import operator
 import os
 import pickle
 import pytest
+import re
 import shutil
 import tempfile
 import warnings
@@ -2230,6 +2231,29 @@ def test_round():
         round([1, 2, 3] * km)
 
 
+@pytest.mark.parametrize("itemsize", (8, 16, 32, 64))
+def test_conversion_from_int_types(itemsize):
+    a = unyt_array([1], "cm", dtype=f"int{itemsize}")
+
+    # check copy conversion
+    a.in_units("m")
+
+    # check in place conversion
+    if itemsize == 8:
+        with pytest.raises(
+            ValueError,
+            match=re.escape(
+                "Can't convert memory buffer in place. "
+                "Input dtype (int8) has a smaller itemsize than the "
+                "smallest floating point representation possible."
+            ),
+        ):
+            a.convert_to_units("m")
+    else:
+        a.convert_to_units("m")
+        assert a.dtype == f"float{itemsize}"
+
+
 def test_integer_arrays():
     from unyt import km, m, mile, ms, s