Repository: yt-project/unyt (python)

Issue:
unyt_array.in_units() casts complex numbers to real losing imaginary part
* unyt version: 2.7.2
* Python version: 3.8.5
* Operating System: Ubuntu 18.04

### Description
Attempted to convert unit of a complex valued array.

### What I Did

```
>>> from unyt import Ω, F, Hz
>>> freq = [1j, 1j*10]*Hz
>>> arr = 1/(Ω*F*freq)
>>> arr.to("dimensionless")
.../numpy/core/_asarray.py:83:ComplexWarning: Casting complex values to real discards the imaginary part
return array(a, dtype, copy=False, order=order)
unyt_array([0., 0.], dtype=float128, units='dimensionless')
```

The problem is in array.py, `unyt_array.in_units` line 846:
```
new_dtype = np.dtype("f" + str(dsize))
```

As a workaround, I can use `unyt_array.in_base()` and then check for `dimensionless`.

Pull request:
Fix unyt_array.to() to support complex-valued arrays
Closes #171

Patch:
diff --git a/unyt/array.py b/unyt/array.py
index 281d31b..012ccaf 100644
--- a/unyt/array.py
+++ b/unyt/array.py
@@ -843,8 +843,8 @@ class unyt_array(np.ndarray):
                         RuntimeWarning,
                         stacklevel=2,
                     )
-            new_dtype = np.dtype("f" + str(dsize))
-            conversion_factor = new_dtype.type(conversion_factor)
+            new_dtypekind = "c" if self.dtype.kind == "c" else "f"
+            new_dtype = np.dtype(new_dtypekind + str(dsize))
             ret = np.asarray(self.ndview * conversion_factor, dtype=new_dtype)
             if offset:
                 np.subtract(ret, offset, ret)
@@ -982,8 +982,6 @@ class unyt_array(np.ndarray):
         else:
             to_units = self.units.get_base_equivalent(unit_system)
             conv, offset = self.units.get_conversion_factor(to_units, self.dtype)
-        new_dtype = np.dtype("f" + str(self.dtype.itemsize))
-        conv = new_dtype.type(conv)
         ret = self.v * conv
         if offset:
             ret = ret - offset
@@ -2335,7 +2333,8 @@ def loadtxt(fname, dtype="float", delimiter="\t", usecols=None, comments="#"):
             # Here we catch the first line of numbers
             col_words = line.strip().split(delimiter)
             for word in col_words:
-                float(word)
+                # test that word can be converted to a number
+                complex(word)
             num_cols = len(col_words)
             break
     f.close()
diff --git a/unyt/mpl_interface.py b/unyt/mpl_interface.py
index 5c8a685..4c76c85 100644
--- a/unyt/mpl_interface.py
+++ b/unyt/mpl_interface.py
@@ -178,11 +178,11 @@ else:
         @property
         def label_style(self):
             """str: One of the following set, ``{'()', '[]', '/'}``.
-               These choices correspond to the following unit labels:
+            These choices correspond to the following unit labels:
 
-                 * ``'()'`` -> ``'(unit)'``
-                 * ``'[]'`` -> ``'[unit]'``
-                 * ``'/'`` -> ``'q_x / unit'``
+              * ``'()'`` -> ``'(unit)'``
+              * ``'[]'`` -> ``'[unit]'``
+              * ``'/'`` -> ``'q_x / unit'``
             """
             return self._labelstyle
 


Test patch:
diff --git a/unyt/tests/test_unyt_array.py b/unyt/tests/test_unyt_array.py
index 4f69fb7..d5898ee 100644
--- a/unyt/tests/test_unyt_array.py
+++ b/unyt/tests/test_unyt_array.py
@@ -2423,3 +2423,37 @@ def test_kip():
 
 def test_ksi():
     assert_allclose_units(unyt_quantity(1, "lbf/inch**2"), unyt_quantity(0.001, "ksi"))
+
+
+def test_complexvalued():
+    freq = unyt_array([1j, 1j * 10], "Hz")
+    arr = 1 / (Unit("F") * Unit("Ω") * freq)
+    arr = arr.to("dimensionless")
+    assert arr.units.is_dimensionless
+    assert np.all(arr.v == np.asarray([-1j, -1j * 0.1]))
+    arr = unyt_array([1j, 1j * 10], "mJ")
+    arr.convert_to_base()
+    assert_allclose_units(arr, unyt_array([1j * 0.001, 1j * 0.01], "J"))
+    arr.convert_to_units("mJ")
+    assert_allclose_units(arr, unyt_array([1j, 1j * 10], "mJ"))
+    arr.convert_to_mks()
+    assert_allclose_units(arr, unyt_array([1j * 0.001, 1j * 0.01], "J"))
+    arr.convert_to_cgs()
+    assert_allclose_units(arr, unyt_array([1j * 10000, 1j * 100000], "erg"))
+    arr.convert_to_equivalent("K", "thermal")
+    assert_allclose_units(
+        arr, unyt_array([1j * 7.24297157e19, 1j * 7.24297157e20], "K")
+    )
+    arr = arr.to_equivalent("J", "thermal")
+    assert_allclose_units(arr, unyt_array([1j * 0.001, 1j * 0.01], "J"))
+    assert_allclose_units(arr.to_ndarray(), np.asarray([1j * 0.001, 1j * 0.01]))
+    assert_allclose_units(arr.to_value(), np.asarray([1j * 0.001, 1j * 0.01]))
+    assert arr.tolist() == [1j * 0.001, 1j * 0.01]
+    assert_allclose_units(arr.in_units("mJ"), unyt_array([1j, 1j * 10], "mJ"))
+    assert_allclose_units(arr.in_base(), unyt_array([1j * 0.001, 1j * 0.01], "J"))
+    assert_allclose_units(arr.in_cgs(), unyt_array([1j * 10000, 1j * 100000], "erg"))
+    assert_allclose_units(arr.in_mks(), unyt_array([1j * 0.001, 1j * 0.01], "J"))
+    file = "test_complexvalued.txt"
+    savetxt(file, arr)
+    farr = loadtxt(file, dtype=np.complex128)
+    assert_allclose_units(farr, unyt_array([1j * 0.001, 1j * 0.01], "J"))