Repository: yt-project/unyt (python)

Issue:
Cannot coerce N-tuple of `unyt_array` instances with different units into a single N-dimensional `unyt_array` instance.
* unyt version: 2.8.0
* Python version: 3.7.3

### What I Did

```
In [1]: import unyt as u

In [2]: d = ([1, 2]*u.g, [1, 2]*u.kg)

In [3]: u.unyt_array(d)
---------------------------------------------------------------------------
IterableUnitCoercionError                 Traceback (most recent call last)
<ipython-input-3-8c7b43202134> in <module>
----> 1 u.unyt_array(d)

~/Documents/unyt/unyt/array.py in unyt_array.__new__(cls, input_array, units, registry, dtype, bypass_validation, input_units, name)
    551 elif _iterable(input_array) and input_array:
    552     if isinstance(input_array[0], unyt_array):
--> 553         return _coerce_iterable_units(input_array, registry)
    555 # Input array is an already formed ndarray instance
    556 # We first cast to be our class type
    558 obj = np.asarray(input_array, dtype=dtype).view(cls)

~/Documents/unyt/unyt/array.py in _coerce_iterable_units(input_object, registry)
    246 ff = getattr(input_object[0], "units", NULL_UNIT)
    247 if any([ff != getattr(_, "units", NULL_UNIT) for _ in input_object]):
--> 248     raise IterableUnitCoercionError(input_object)
    249 # This will create a copy of the data in the iterable.
    250 return unyt_array(np.array(input_object), ff, registry=registry)

<class 'str'>: (<class 'TypeError'>, TypeError('not all arguments converted during string formatting'))
```

The error is also confusing because the error we're supposed to be raising is itself raising a `TypeError` when we try to instantiate it (d'oh!).

Astropy handles this case just fine:

```
In [1]: import astropy.units as u

In [2]: d = ([1, 2]*u.g, [1, 2]*u.kg)

In [3]: u.Quantity(d)
Out[3]:
<Quantity [[1.e+00, 2.e+00],
           [1.e+03, 2.e+03]] g>
```


Pull request:
support coercing iterables with nonuniform dimensionally equivalent units to a single unyt_array
Fixes #210.

See the test I modified for a usage example.

Patch:
diff --git a/unyt/array.py b/unyt/array.py
index 67d9a13..ac7085b 100644
--- a/unyt/array.py
+++ b/unyt/array.py
@@ -257,7 +257,13 @@ def _coerce_iterable_units(input_object, registry=None):
         if any([isinstance(o, unyt_array) for o in input_object]):
             ff = getattr(input_object[0], "units", NULL_UNIT)
             if any([ff != getattr(_, "units", NULL_UNIT) for _ in input_object]):
-                raise IterableUnitCoercionError(input_object)
+                ret = []
+                for datum in input_object:
+                    try:
+                        ret.append(datum.in_units(ff.units))
+                    except UnitConversionError:
+                        raise IterableUnitCoercionError(str(input_object))
+                return unyt_array(np.array(ret), ff, registry=registry)
             # This will create a copy of the data in the iterable.
             return unyt_array(np.array(input_object), ff, registry=registry)
     return np.asarray(input_object)
diff --git a/unyt/exceptions.py b/unyt/exceptions.py
index d100163..9ab2964 100644
--- a/unyt/exceptions.py
+++ b/unyt/exceptions.py
@@ -161,15 +161,15 @@ class IterableUnitCoercionError(Exception):
     Example
     -------
 
-    >>> from unyt import km, cm, unyt_array
-    >>> data = [2*cm, 3*km]
+    >>> from unyt import g, cm, unyt_array
+    >>> data = [2*cm, 3*g]
     >>> unyt_array(data)\
   # doctest: +IGNORE_EXCEPTION_DETAIL +NORMALIZE_WHITESPACE
     Traceback (most recent call last):
     ...
     unyt.exceptions.IterableUnitCoercionError: Received a list or
     tuple of quantities with nonuniform units:
-    [unyt_quantity(2., 'cm'), unyt_quantity(3., 'km')]
+    [unyt_quantity(2., 'cm'), unyt_quantity(3., 'g')]
     """
 
     def __init__(self, quantity_list):


Test patch:
diff --git a/unyt/tests/test_unyt_array.py b/unyt/tests/test_unyt_array.py
index 128cd1e..009a6fd 100644
--- a/unyt/tests/test_unyt_array.py
+++ b/unyt/tests/test_unyt_array.py
@@ -2161,17 +2161,21 @@ def test_ones_and_zeros_like():
 
 
 def test_coerce_iterable():
-    from unyt import cm, km
+    from unyt import cm, m, g
 
     a = unyt_array([1, 2, 3], "cm")
-    b = [1 * cm, 2 * km, 3 * cm]
+    b = [1 * cm, 2 * m, 3 * cm]
+    c = [1 * g, 2 * m, 3 * cm]
 
+    assert_equal(a + b, unyt_array([2, 202, 6], "cm"))
+    assert_equal(b + a, unyt_array([2, 202, 6], "cm"))
     with pytest.raises(IterableUnitCoercionError):
-        a + b
+        a + c
     with pytest.raises(IterableUnitCoercionError):
-        b + a
+        c + a
+    assert_equal(unyt_array(b), unyt_array([1, 200, 3], "cm"))
     with pytest.raises(IterableUnitCoercionError):
-        unyt_array(b)
+        unyt_array(c)
 
 
 def test_bypass_validation():