Repository: yt-project/yt

Issue:
Metallicity is broken in the stream frontend for grid data
Originally reported by: **John ZuHone (Bitbucket: [jzuhone](https://bitbucket.org/jzuhone), GitHub: [jzuhone](https://github.com/jzuhone))**

----------------------------------------

If one sets up a stream dataset with metallicity as an input field, the values get transformed. A simple script demonstrates this:

```python
import numpy as np
import yt

Z = np.random.uniform(size=(32,32,32))
d = np.random.uniform(size=(32,32,32))

data = {"density": d, "metallicity": Z}

ds = yt.load_uniform_grid(data, (32,32,32))

dd = ds.all_data()

print(Z.max())
print(dd["metallicity"].max())
```

gives:

```pycon
0.99996801409647373
0.012949585782549334 dimensionless
```

This happens regardless if one sets the units in the dict above. I am pretty sure that somehow the derived field for `metallicity` is being used instead, because that rescales `metal_density` by the solar value, and somehow the input field is getting interpreted as `metal_density`. 

This bug got accidentally introduced in yt 3.3.2 with this PR:

https://bitbucket.org/yt_analysis/yt/pull-requests/2331/adding-metallicity-to-alias-fields-in/

I'll see if I can track it down.

----------------------------------------
- Bitbucket: https://bitbucket.org/yt_analysis/yt/issue/1344

Metallicity is broken in the stream frontend for grid data
Originally reported by: **John ZuHone (Bitbucket: [jzuhone](https://bitbucket.org/jzuhone), GitHub: [jzuhone](https://github.com/jzuhone))**

----------------------------------------

If one sets up a stream dataset with metallicity as an input field, the values get transformed. A simple script demonstrates this:

```python
import numpy as np
import yt

Z = np.random.uniform(size=(32,32,32))
d = np.random.uniform(size=(32,32,32))

data = {"density": d, "metallicity": Z}

ds = yt.load_uniform_grid(data, (32,32,32))

dd = ds.all_data()

print(Z.max())
print(dd["metallicity"].max())
```

gives:

```pycon
0.99996801409647373
0.012949585782549334 dimensionless
```

This happens regardless if one sets the units in the dict above. I am pretty sure that somehow the derived field for `metallicity` is being used instead, because that rescales `metal_density` by the solar value, and somehow the input field is getting interpreted as `metal_density`. 

This bug got accidentally introduced in yt 3.3.2 with this PR:

https://bitbucket.org/yt_analysis/yt/pull-requests/2331/adding-metallicity-to-alias-fields-in/

I'll see if I can track it down.

----------------------------------------
- Bitbucket: https://bitbucket.org/yt_analysis/yt/issue/1344



Discussion:
*Original comment by* **John ZuHone (Bitbucket: [jzuhone](https://bitbucket.org/jzuhone), GitHub: [jzuhone](https://github.com/jzuhone))**:

----------------------------------------

Actually, I am thinking too hard--the issue here is that there is a conversion in the `unit_lookup_table` for metallicity units to solar units. I'm not sure why the alias isn't handling the units correctly, but @chummels originally noted in his PR above that the field was being returned as `dimensionless` instead of `Zsun`.

*Original comment by* **John ZuHone (Bitbucket: [jzuhone](https://bitbucket.org/jzuhone), GitHub: [jzuhone](https://github.com/jzuhone))**:

----------------------------------------

Actually, I am thinking too hard--the issue here is that there is a conversion in the `unit_lookup_table` for metallicity units to solar units. I'm not sure why the alias isn't handling the units correctly, but @chummels originally noted in his PR above that the field was being returned as `dimensionless` instead of `Zsun`.


Resolving patch:
diff --git a/yt/fields/field_info_container.py b/yt/fields/field_info_container.py
--- a/yt/fields/field_info_container.py
+++ b/yt/fields/field_info_container.py
@@ -21,6 +21,7 @@
 from yt.extern.six import string_types
 from yt.funcs import mylog, only_on_root
 from yt.units.unit_object import Unit
+from yt.units.dimensions import dimensionless
 from .derived_field import \
     DerivedField, \
     NullFunc, \
@@ -86,15 +87,15 @@ def setup_fluid_index_fields(self):
                 self[(ftype, f)].units = self["index", f].units
 
     def setup_particle_fields(self, ptype, ftype='gas', num_neighbors=64 ):
-        skip_output_units = ("code_length",)
+        skip_output_units = ("code_length")
         for f, (units, aliases, dn) in sorted(self.known_particle_fields):
             units = self.ds.field_units.get((ptype, f), units)
+            output_units = units
             if (f in aliases or ptype not in self.ds.particle_types_raw) and \
                 units not in skip_output_units:
                 u = Unit(units, registry = self.ds.unit_registry)
-                output_units = str(self.ds.unit_system[u.dimensions])
-            else:
-                output_units = units
+                if u.dimensions is not dimensionless:
+                    output_units = str(self.ds.unit_system[u.dimensions])
             if (ptype, f) not in self.field_list:
                 continue
             self.add_output_field((ptype, f), sampling_type="particle",
@@ -307,7 +308,10 @@ def alias(self, alias_name, original_name, units = None):
             # as well.
             u = Unit(self[original_name].units,
                       registry = self.ds.unit_registry)
-            units = str(self.ds.unit_system[u.dimensions])
+            if u.dimensions is not dimensionless:
+                units = str(self.ds.unit_system[u.dimensions])
+            else:
+                units = self[original_name].units
         self.field_aliases[alias_name] = original_name
         self.add_field(alias_name,
             function = TranslationFunc(original_name),


Test patch:
diff --git a/yt/frontends/stream/tests/test_outputs.py b/yt/frontends/stream/tests/test_outputs.py
--- a/yt/frontends/stream/tests/test_outputs.py
+++ b/yt/frontends/stream/tests/test_outputs.py
@@ -13,12 +13,16 @@
 # The full license is in the file COPYING.txt, distributed with this software.
 #-----------------------------------------------------------------------------
 
+import numpy as np
 import os
 import shutil
 import tempfile
 import unittest
 
-from yt.testing import assert_raises
+from yt.frontends.stream.data_structures import load_uniform_grid
+from yt.testing import \
+    assert_equal, \
+    assert_raises
 from yt.convenience import load
 from yt.utilities.exceptions import YTOutputNotIdentified
 
@@ -43,3 +47,15 @@ def test_load_empty_file(self):
         assert_raises(YTOutputNotIdentified, load, "not_a_file")
         assert_raises(YTOutputNotIdentified, load, "empty_file")
         assert_raises(YTOutputNotIdentified, load, "empty_directory")
+
+def test_dimensionless_field_units():
+    Z = np.random.uniform(size=(32,32,32))
+    d = np.random.uniform(size=(32,32,32))
+
+    data = {"density": d, "metallicity": Z}
+
+    ds = load_uniform_grid(data, (32,32,32))
+
+    dd = ds.all_data()
+
+    assert_equal(Z.max(), dd["metallicity"].max())