Repository: yt-project/yt

Issue:
Unexpected Change in Defined Quantity after Being Used to Create Data Object
<!--To help us understand and resolve your issue, please fill out the form to
the best of your ability.-->
<!--You can feel free to delete the sections that do not apply.-->

### Bug report

**Bug summary**

YT changes my defined quantity (which is unexpected), after the quantity is used to create and extract information from a data object.

**Code for reproduction**

(The data used is the sample EAGLE data from yt data hub.)

<!-- If you need to use a data file to trigger the issue you're having, consider
using one of the datasets from the yt data hub (http://yt-project.org/data). If
your issue cannot be triggered using a public dataset, you can use the yt
curldrop (https://docs.hub.yt/services.html#curldrop) to share data
files. Please include a link to the dataset in the issue if you use the
curldrop.-->

```python
import yt
import numpy as np

eagle_fname = 'snapshot_028_z000p000/snap_028_z000p000.0.hdf5'

unit_base = {'UnitLength_in_cm'         : 3.085678e+24,
             'UnitMass_in_g'            :   1.989e+43,
             'UnitVelocity_in_cm_per_s' :    100000.0}

ds = yt.load(eagle_fname, unit_base=unit_base)

center = ds.arr(np.array([12.08809, 4.474372, 1.4133347]), 'Mpccm')
radius = ds.quan(0.05, 'Mpc')
cc = ds.sphere(center, (radius, 'Mpc'))

print('radius: ', radius)
print('Number of particle in cc: ', len(cc['PartType0', 'ParticleIDs']))
print('radius: ', radius)
```

**Actual outcome**

<!--The output produced by the above code, which may be a screenshot, console
output, etc.-->

```
radius:  0.05 Mpc
Number of particle in cc:  3699
radius:  0.0338849953984 Mpc
```

**Expected outcome**

<!--A description of the expected outcome from the code snippet-->
<!--If this used to work in an earlier version of yt, please note the
version it used to work on-->
The variable "radius" is not expected to be changed.  It is not assigned to a new value by the user.  The variable is just to use for creating and extracting information from the data object (the sphere).

**Version Information**
<!--Please specify your platform and versions of the relevant libraries you are
using:-->
  * Operating System: MacOS Sierra
  * Python Version: 2.7.12
  * yt version: 3.5.dev0
  * Other Libraries (if applicable): 

<!--Please tell us how you installed yt and python e.g., from source,
pip, conda. If you installed from conda, please specify which channel you used
if not the default-->




Discussion:
@stephaniehho just a note, in your example when you do this:

```
adius = ds.quan(0.05, 'Mpc')
cc = ds.sphere(center, (radius, 'Mpc'))
```

I'm not sure how yt should handle that. I think what you want to do is this:

```
radius = ds.quan(0.05, 'Mpc')
cc = ds.sphere(center, radius)
```

I think I'm going to make it an error for the length in a (length, unit) tuple to be a quantity, since then the units are ambiguous.

Resolving patch:
diff --git a/yt/funcs.py b/yt/funcs.py
--- a/yt/funcs.py
+++ b/yt/funcs.py
@@ -751,12 +751,8 @@ def get_yt_supp():
     # Now we think we have our supplemental repository.
     return supp_path
 
-def fix_length(length, ds=None):
-    assert ds is not None
-    if ds is not None:
-        registry = ds.unit_registry
-    else:
-        registry = None
+def fix_length(length, ds):
+    registry = ds.unit_registry
     if isinstance(length, YTArray):
         if registry is not None:
             length.units.registry = registry
@@ -765,7 +761,9 @@ def fix_length(length, ds=None):
         return YTArray(length, 'code_length', registry=registry)
     length_valid_tuple = isinstance(length, (list, tuple)) and len(length) == 2
     unit_is_string = isinstance(length[1], string_types)
-    if length_valid_tuple and unit_is_string:
+    length_is_number = (isinstance(length[0], numeric_type) and not
+                        isinstance(length[0], YTArray))
+    if length_valid_tuple and unit_is_string and length_is_number:
         return YTArray(*length, registry=registry)
     else:
         raise RuntimeError("Length %s is invalid" % str(length))
@@ -880,8 +878,12 @@ def ensure_dir(path):
 
 def validate_width_tuple(width):
     if not iterable(width) or len(width) != 2:
-        raise YTInvalidWidthError("width (%s) is not a two element tuple" % width)
-    if not isinstance(width[0], numeric_type) and isinstance(width[1], string_types):
+        raise YTInvalidWidthError(
+            "width (%s) is not a two element tuple" % width)
+    is_numeric = isinstance(width[0], numeric_type)
+    length_has_units = isinstance(width[0], YTArray)
+    unit_is_string = isinstance(width[1], string_types)
+    if not is_numeric or length_has_units and unit_is_string:
         msg = "width (%s) is invalid. " % str(width)
         msg += "Valid widths look like this: (12, 'au')"
         raise YTInvalidWidthError(msg)


Test patch:
diff --git a/yt/units/tests/test_ytarray.py b/yt/units/tests/test_ytarray.py
--- a/yt/units/tests/test_ytarray.py
+++ b/yt/units/tests/test_ytarray.py
@@ -677,6 +677,8 @@ def test_fix_length():
     new_length = fix_length(length, ds=ds)
     assert_equal(YTQuantity(10, 'cm'), new_length)
 
+    assert_raises(RuntimeError, fix_length, (length, 'code_length'), ds)
+
 def test_code_unit_combinations():
     """
     Test comparing code units coming from different datasets