[yt-svn] commit/yt: 43 new changesets

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Thu Jan 19 09:10:58 PST 2017


43 new commits in yt:

https://bitbucket.org/yt_analysis/yt/commits/95bed50dcd69/
Changeset:   95bed50dcd69
Branch:      yt
User:        jzuhone
Date:        2016-12-03 13:24:27+00:00
Summary:     Moving spectral_frequency_integrator over to the fields directory and simplifying some of the logic a bit.
Affected #:  6 files

diff -r bb7f23a27456bed189cbd20b0b5cdc968a2f31e0 -r 95bed50dcd691f9b7992f0254d30ec669fb8330b yt/__init__.py
--- a/yt/__init__.py
+++ b/yt/__init__.py
@@ -119,7 +119,8 @@
     ValidateSpatial, \
     ValidateGridType, \
     add_field, \
-    derived_field
+    derived_field, \
+    add_xray_emissivity_field
 
 from yt.data_objects.api import \
     DatasetSeries, ImageArray, \

diff -r bb7f23a27456bed189cbd20b0b5cdc968a2f31e0 -r 95bed50dcd691f9b7992f0254d30ec669fb8330b yt/analysis_modules/spectral_integrator/api.py
--- a/yt/analysis_modules/spectral_integrator/api.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""
-API for spectral_integrator
-
-
-
-"""
-
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-
-from .spectral_frequency_integrator import \
-    EmissivityIntegrator, \
-    add_xray_emissivity_field
\ No newline at end of file

diff -r bb7f23a27456bed189cbd20b0b5cdc968a2f31e0 -r 95bed50dcd691f9b7992f0254d30ec669fb8330b yt/analysis_modules/spectral_integrator/spectral_frequency_integrator.py
--- a/yt/analysis_modules/spectral_integrator/spectral_frequency_integrator.py
+++ /dev/null
@@ -1,279 +0,0 @@
-"""
-Integrator classes to deal with interpolation and integration of input spectral
-bins.  Currently only supports Cloudy and APEC-style data.
-
-
-
-"""
-
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-
-from yt.utilities.on_demand_imports import _h5py as h5py
-import numpy as np
-import os
-
-from yt.funcs import \
-     download_file, \
-     mylog, \
-     only_on_root
-
-from yt.utilities.exceptions import YTFieldNotFound
-from yt.utilities.exceptions import YTException
-from yt.utilities.linear_interpolators import \
-    UnilinearFieldInterpolator, BilinearFieldInterpolator
-from yt.utilities.physical_constants import \
-    hcgs, mp
-from yt.units.yt_array import YTArray, YTQuantity
-from yt.utilities.physical_ratios import \
-    primordial_H_mass_fraction, erg_per_keV
-
-xray_data_version = 1
-
-def _get_data_file(data_file=None):
-    if data_file is None:
-        data_file = "cloudy_emissivity.h5"
-    data_url = "http://yt-project.org/data"
-    if "YT_DEST" in os.environ and \
-      os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
-        data_dir = os.path.join(os.environ["YT_DEST"], "data")
-    else:
-        data_dir = "."
-    data_path = os.path.join(data_dir, data_file)
-    if not os.path.exists(data_path):
-        mylog.info("Attempting to download supplementary data from %s to %s." % 
-                   (data_url, data_dir))
-        fn = download_file(os.path.join(data_url, data_file), data_path)
-        if fn != data_path:
-            raise RuntimeError("Failed to download supplementary data.")
-    return data_path
-
-class EnergyBoundsException(YTException):
-    def __init__(self, lower, upper):
-        self.lower = lower
-        self.upper = upper
-
-    def __str__(self):
-        return "Energy bounds are %e to %e keV." % \
-          (self.lower, self.upper)
-
-class ObsoleteDataException(YTException):
-    def __str__(self):
-        return "X-ray emissivity data is out of date.\n" + \
-               "Download the latest data from http://yt-project.org/data/cloudy_emissivity.h5 and move it to %s." % \
-          os.path.join(os.environ["YT_DEST"], "data", "cloudy_emissivity.h5")
-          
-class EmissivityIntegrator(object):
-    r"""Class for making X-ray emissivity fields with hdf5 data tables 
-    from Cloudy.
-    
-    Initialize an EmissivityIntegrator object.
-
-    Parameters
-    ----------
-    filename: string, default None
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-        
-    """
-    def __init__(self, filename=None):
-
-        default_filename = False
-        if filename is None:
-            filename = _get_data_file()
-            default_filename = True
-
-        if not os.path.exists(filename):
-            mylog.warning("File %s does not exist, will attempt to find it." % filename)
-            filename = _get_data_file(data_file=filename)
-        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
-        in_file = h5py.File(filename, "r")
-        if "info" in in_file.attrs:
-            only_on_root(mylog.info, in_file.attrs["info"])
-        if default_filename and \
-          in_file.attrs["version"] < xray_data_version:
-            raise ObsoleteDataException()
-        else:
-            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                         in_file.attrs["version"])
-
-        for field in ["emissivity_primordial", "emissivity_metals",
-                      "log_nH", "log_T", "log_E"]:
-            if field in in_file:
-                setattr(self, field, in_file[field][:])
-        in_file.close()
-
-        E_diff = np.diff(self.log_E)
-        self.E_bins = \
-                  YTArray(np.power(10, np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
-                                                      [self.log_E[-1] - 0.5 * E_diff[-1],
-                                                       self.log_E[-1] + 0.5 * E_diff[-1]]])),
-                          "keV")
-        self.dnu = (np.diff(self.E_bins)/hcgs).in_units("Hz")
-
-    def get_interpolator(self, data, e_min, e_max):
-        e_min = YTQuantity(e_min, "keV")
-        e_max = YTQuantity(e_max, "keV")
-        if (e_min - self.E_bins[0]) / e_min < -1e-3 or \
-          (e_max - self.E_bins[-1]) / e_max > 1e-3:
-            raise EnergyBoundsException(self.E_bins[0], self.E_bins[-1])
-        e_is, e_ie = np.digitize([e_min, e_max], self.E_bins)
-        e_is = np.clip(e_is - 1, 0, self.E_bins.size - 1)
-        e_ie = np.clip(e_ie, 0, self.E_bins.size - 1)
-
-        my_dnu = self.dnu[e_is: e_ie].copy()
-        # clip edge bins if the requested range is smaller
-        my_dnu[0] -= ((e_min - self.E_bins[e_is])/hcgs).in_units("Hz")
-        my_dnu[-1] -= ((self.E_bins[e_ie] - e_max)/hcgs).in_units("Hz")
-
-        interp_data = (data[..., e_is:e_ie] * my_dnu).sum(axis=-1)
-        if len(data.shape) == 2:
-            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
-                                               [self.log_T[0],  self.log_T[-1]],
-                                               "log_T", truncate=True)
-        else:
-            emiss = BilinearFieldInterpolator(np.log10(interp_data),
-                                              [self.log_nH[0], self.log_nH[-1],
-                                               self.log_T[0],  self.log_T[-1]],
-                                              ["log_nH", "log_T"], truncate=True)
-
-        return emiss
-
-def add_xray_emissivity_field(ds, e_min, e_max,
-                              filename=None,
-                              with_metals=True,
-                              constant_metallicity=None):
-    r"""Create X-ray emissivity fields for a given energy range.
-
-    Parameters
-    ----------
-    e_min: float
-        the minimum energy in keV for the energy band.
-    e_min: float
-        the maximum energy in keV for the energy band.
-    filename: string, optional
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-    with_metals: bool, optional
-        If True, use the metallicity field to add the contribution from 
-        metals.  If False, only the emission from H/He is considered.
-        Default: True.
-    constant_metallicity: float, optional
-        If specified, assume a constant metallicity for the emission 
-        from metals.  The *with_metals* keyword must be set to False 
-        to use this. It should be given in unit of solar metallicity.
-        Default: None.
-
-    This will create three fields:
-
-    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
-    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
-    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
-
-    Examples
-    --------
-
-    >>> from yt.mods import *
-    >>> from yt.analysis_modules.spectral_integrator.api import *
-    >>> ds = load(dataset)
-    >>> add_xray_emissivity_field(ds, 0.5, 2)
-    >>> p = ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
-    >>> p.save()
-
-    """
-
-    if with_metals:
-        try:
-            ds._get_field_info("metal_density")
-        except YTFieldNotFound:
-            raise RuntimeError("Your dataset does not have a \"metal_density\" field! " +
-                               "Perhaps you should specify a constant metallicity?")
-
-    my_si = EmissivityIntegrator(filename=filename)
-
-    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
-    em_Z = None
-    if with_metals or constant_metallicity is not None:
-        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
-
-    energy_erg = np.power(10, my_si.log_E) * erg_per_keV
-    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
-                                   e_min, e_max)
-    emp_Z = None
-    if with_metals or constant_metallicity is not None:
-        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
-                                       e_min, e_max)
-
-    try:
-        ds._get_field_info("H_number_density")
-    except YTFieldNotFound:
-        mylog.warning("Could not find a field for \"H_number_density\". Assuming primordial H " +
-                      "mass fraction.")
-        def _nh(field, data):
-            return primordial_H_mass_fraction*data["gas","density"]/mp
-        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
-
-    def _emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
-
-        my_emissivity = np.power(10, em_0(dd))
-        if em_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
-            my_emissivity += my_Z * np.power(10, em_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "erg*cm**3/s")
-
-    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", emiss_name), function=_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/cm**3/s")
-
-    def _luminosity_field(field, data):
-        return data[emiss_name] * data["cell_volume"]
-
-    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", lum_name), function=_luminosity_field,
-                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/s")
-
-    def _photon_emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
-
-        my_emissivity = np.power(10, emp_0(dd))
-        if emp_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
-            my_emissivity += my_Z * np.power(10, emp_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "photons*cm**3/s")
-
-    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="photons/cm**3/s")
-
-    return emiss_name, lum_name, phot_name

diff -r bb7f23a27456bed189cbd20b0b5cdc968a2f31e0 -r 95bed50dcd691f9b7992f0254d30ec669fb8330b yt/fields/api.py
--- a/yt/fields/api.py
+++ b/yt/fields/api.py
@@ -43,3 +43,5 @@
     FieldDetector
 from .field_info_container import \
     FieldInfoContainer
+from .emission_fields import \
+    add_xray_emissivity_field
\ No newline at end of file

diff -r bb7f23a27456bed189cbd20b0b5cdc968a2f31e0 -r 95bed50dcd691f9b7992f0254d30ec669fb8330b yt/fields/emission_fields.py
--- /dev/null
+++ b/yt/fields/emission_fields.py
@@ -0,0 +1,271 @@
+"""
+Integrator classes to deal with interpolation and integration of input spectral
+bins.  Currently only supports Cloudy and APEC-style data.
+
+
+
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, yt Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+from yt.utilities.on_demand_imports import _h5py as h5py
+import numpy as np
+import os
+
+from yt.funcs import \
+     download_file, \
+     mylog, \
+     only_on_root
+
+from yt.utilities.exceptions import YTFieldNotFound
+from yt.utilities.exceptions import YTException
+from yt.utilities.linear_interpolators import \
+    UnilinearFieldInterpolator, BilinearFieldInterpolator
+from yt.utilities.physical_constants import \
+    hcgs, mp
+from yt.units.yt_array import YTArray, YTQuantity
+from yt.utilities.physical_ratios import \
+    primordial_H_mass_fraction, erg_per_keV
+
+xray_data_version = 1
+
+def _get_data_file(table_type):
+    if table_type == "cloudy":
+        data_file = "cloudy_emissivity.h5"
+    else:
+        data_file = "apec_emissivity.h5"
+    data_url = "http://yt-project.org/data"
+    if "YT_DEST" in os.environ and \
+      os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
+        data_dir = os.path.join(os.environ["YT_DEST"], "data")
+    else:
+        data_dir = "."
+    data_path = os.path.join(data_dir, data_file)
+    if not os.path.exists(data_path):
+        mylog.info("Attempting to download supplementary data from %s to %s." % 
+                   (data_url, data_dir))
+        fn = download_file(os.path.join(data_url, data_file), data_path)
+        if fn != data_path:
+            raise RuntimeError("Failed to download supplementary data.")
+    return data_path
+
+class EnergyBoundsException(YTException):
+    def __init__(self, lower, upper):
+        self.lower = lower
+        self.upper = upper
+
+    def __str__(self):
+        return "Energy bounds are %e to %e keV." % \
+          (self.lower, self.upper)
+
+class ObsoleteDataException(YTException):
+    def __str__(self):
+        return "X-ray emissivity data is out of date.\n" + \
+               "Download the latest data from http://yt-project.org/data/cloudy_emissivity.h5 and move it to %s." % \
+          os.path.join(os.environ["YT_DEST"], "data", "cloudy_emissivity.h5")
+
+class EmissivityIntegrator(object):
+    r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
+    generated from Cloudy and AtomDB.
+
+    Initialize an EmissivityIntegrator object.
+
+    Parameters
+    ----------
+    filename: string, default None
+        Path to data file containing emissivity values.  If None,
+        a file called "cloudy_emissivity.h5" is used, for photoionized
+        plasmas. A second option, for collisionally ionized plasmas, is
+        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
+        These files contain emissivity tables for primordial elements and
+        for metals at solar metallicity for the energy range 0.1 to 100 keV.
+        Default: None.
+
+    """
+    def __init__(self, table_type):
+
+        filename = _get_data_file(table_type)
+        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
+        in_file = h5py.File(filename, "r")
+        if "info" in in_file.attrs:
+            only_on_root(mylog.info, in_file.attrs["info"])
+        if in_file.attrs["version"] < xray_data_version:
+            raise ObsoleteDataException()
+        else:
+            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
+                         in_file.attrs["version"])
+
+        for field in ["emissivity_primordial", "emissivity_metals",
+                      "log_nH", "log_T", "log_E"]:
+            if field in in_file:
+                setattr(self, field, in_file[field][:])
+        in_file.close()
+
+        E_diff = np.diff(self.log_E)
+        self.E_bins = \
+                  YTArray(np.power(10, np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
+                                                      [self.log_E[-1] - 0.5 * E_diff[-1],
+                                                       self.log_E[-1] + 0.5 * E_diff[-1]]])),
+                          "keV")
+        self.dnu = (np.diff(self.E_bins)/hcgs).in_units("Hz")
+
+    def get_interpolator(self, data, e_min, e_max):
+        e_min = YTQuantity(e_min, "keV")
+        e_max = YTQuantity(e_max, "keV")
+        if (e_min - self.E_bins[0]) / e_min < -1e-3 or \
+          (e_max - self.E_bins[-1]) / e_max > 1e-3:
+            raise EnergyBoundsException(self.E_bins[0], self.E_bins[-1])
+        e_is, e_ie = np.digitize([e_min, e_max], self.E_bins)
+        e_is = np.clip(e_is - 1, 0, self.E_bins.size - 1)
+        e_ie = np.clip(e_ie, 0, self.E_bins.size - 1)
+
+        my_dnu = self.dnu[e_is: e_ie].copy()
+        # clip edge bins if the requested range is smaller
+        my_dnu[0] -= ((e_min - self.E_bins[e_is])/hcgs).in_units("Hz")
+        my_dnu[-1] -= ((self.E_bins[e_ie] - e_max)/hcgs).in_units("Hz")
+
+        interp_data = (data[..., e_is:e_ie] * my_dnu).sum(axis=-1)
+        if len(data.shape) == 2:
+            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
+                                               [self.log_T[0],  self.log_T[-1]],
+                                               "log_T", truncate=True)
+        else:
+            emiss = BilinearFieldInterpolator(np.log10(interp_data),
+                                              [self.log_nH[0], self.log_nH[-1],
+                                               self.log_T[0],  self.log_T[-1]],
+                                              ["log_nH", "log_T"], truncate=True)
+
+        return emiss
+
+def add_xray_emissivity_field(ds, e_min, e_max, table_type="cloudy", 
+                              with_metals=True, constant_metallicity=None):
+    r"""Create X-ray emissivity fields for a given energy range.
+
+    Parameters
+    ----------
+    e_min : float
+        The minimum energy in keV for the energy band.
+    e_min : float
+        The maximum energy in keV for the energy band.
+    table_type : string, optional
+        The type of emissivity table to be used when creating the fields. 
+        
+    filename: string, optional
+        Path to data file containing emissivity values.  If None,
+        a file called "cloudy_emissivity.h5" is used, for photoionized
+        plasmas. A second option, for collisionally ionized plasmas, is
+        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
+        These files contain emissivity tables for primordial elements and
+        for metals at solar metallicity for the energy range 0.1 to 100 keV.
+        Default: None.
+    with_metals : bool, optional
+        If True, use the metallicity field to add the contribution from 
+        metals.  If False, only the emission from H/He is considered.
+        Default: True.
+    constant_metallicity : float, optional
+        If specified, assume a constant metallicity for the emission 
+        from metals.  The *with_metals* keyword must be set to False 
+        to use this. It should be given in unit of solar metallicity.
+        Default: None.
+
+    This will create three fields:
+
+    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
+    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
+    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
+
+    Examples
+    --------
+
+    >>> import yt
+    >>> ds = yt.load(dataset)
+    >>> yt.add_xray_emissivity_field(ds, 0.5, 2)
+    >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
+    >>> p.save()
+    """
+    if with_metals:
+        try:
+            ds._get_field_info("metal_density")
+        except YTFieldNotFound:
+            raise RuntimeError("Your dataset does not have a \"metal_density\" field! " +
+                               "Perhaps you should specify a constant metallicity?")
+
+    my_si = EmissivityIntegrator(table_type)
+
+    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
+    em_Z = None
+    if with_metals or constant_metallicity is not None:
+        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
+
+    energy_erg = np.power(10, my_si.log_E) * erg_per_keV
+    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
+                                   e_min, e_max)
+    emp_Z = None
+    if with_metals or constant_metallicity is not None:
+        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
+                                       e_min, e_max)
+
+    try:
+        ds._get_field_info("H_number_density")
+    except YTFieldNotFound:
+        mylog.warning("Could not find a field for \"H_number_density\". Assuming primordial H " +
+                      "mass fraction.")
+        def _nh(field, data):
+            return primordial_H_mass_fraction*data["gas","density"]/mp
+        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
+
+    def _emissivity_field(field, data):
+        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
+              "log_T"   : np.log10(data["gas","temperature"])}
+
+        my_emissivity = np.power(10, em_0(dd))
+        if em_Z is not None:
+            if with_metals:
+                my_Z = data["gas","metallicity"]
+            elif constant_metallicity is not None:
+                my_Z = constant_metallicity
+            my_emissivity += my_Z * np.power(10, em_Z(dd))
+
+        return data["gas","H_number_density"]**2 * \
+            YTArray(my_emissivity, "erg*cm**3/s")
+
+    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", emiss_name), function=_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 units="erg/cm**3/s")
+
+    def _luminosity_field(field, data):
+        return data[emiss_name] * data["cell_volume"]
+
+    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", lum_name), function=_luminosity_field,
+                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
+                 units="erg/s")
+
+    def _photon_emissivity_field(field, data):
+        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
+              "log_T"   : np.log10(data["gas","temperature"])}
+
+        my_emissivity = np.power(10, emp_0(dd))
+        if emp_Z is not None:
+            if with_metals:
+                my_Z = data["gas","metallicity"]
+            elif constant_metallicity is not None:
+                my_Z = constant_metallicity
+            my_emissivity += my_Z * np.power(10, emp_Z(dd))
+
+        return data["gas","H_number_density"]**2 * \
+            YTArray(my_emissivity, "photons*cm**3/s")
+
+    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 units="photons/cm**3/s")
+
+    return emiss_name, lum_name, phot_name


https://bitbucket.org/yt_analysis/yt/commits/97dea27a301f/
Changeset:   97dea27a301f
Branch:      yt
User:        jzuhone
Date:        2016-12-03 13:42:35+00:00
Summary:     Further cleaning up this code and simplifying the logic a bit. We now only use the yt-bundled files and download the latest version if necessary.
Affected #:  1 file

diff -r 95bed50dcd691f9b7992f0254d30ec669fb8330b -r 97dea27a301f7d923c0645bd7b31d8f53cda8bc7 yt/fields/emission_fields.py
--- a/yt/fields/emission_fields.py
+++ b/yt/fields/emission_fields.py
@@ -35,7 +35,7 @@
 
 xray_data_version = 1
 
-def _get_data_file(table_type):
+def _get_data_file(table_type, download=False):
     if table_type == "cloudy":
         data_file = "cloudy_emissivity.h5"
     else:
@@ -47,7 +47,7 @@
     else:
         data_dir = "."
     data_path = os.path.join(data_dir, data_file)
-    if not os.path.exists(data_path):
+    if not os.path.exists(data_path) or download:
         mylog.info("Attempting to download supplementary data from %s to %s." % 
                    (data_url, data_dir))
         fn = download_file(os.path.join(data_url, data_file), data_path)
@@ -64,12 +64,6 @@
         return "Energy bounds are %e to %e keV." % \
           (self.lower, self.upper)
 
-class ObsoleteDataException(YTException):
-    def __str__(self):
-        return "X-ray emissivity data is out of date.\n" + \
-               "Download the latest data from http://yt-project.org/data/cloudy_emissivity.h5 and move it to %s." % \
-          os.path.join(os.environ["YT_DEST"], "data", "cloudy_emissivity.h5")
-
 class EmissivityIntegrator(object):
     r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
     generated from Cloudy and AtomDB.
@@ -78,15 +72,14 @@
 
     Parameters
     ----------
-    filename: string, default None
-        Path to data file containing emissivity values.  If None,
+    table_type: string
+        The type of data to use when computing the emissivity values. If "cloudy",
         a file called "cloudy_emissivity.h5" is used, for photoionized
         plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-
+        in the file "apec_emissivity.h5".These files contain emissivity tables 
+        for primordial elements and for metals at solar metallicity for the 
+        energy range 0.1 to 100 keV. If the files are not available or out of date
+        they will be downloaded.
     """
     def __init__(self, table_type):
 
@@ -96,7 +89,12 @@
         if "info" in in_file.attrs:
             only_on_root(mylog.info, in_file.attrs["info"])
         if in_file.attrs["version"] < xray_data_version:
-            raise ObsoleteDataException()
+            msg = "X-ray emissivity data is out of date.\n" + \
+                  "Downloading the latest data from http://yt-project.org/data."
+            only_on_root(mylog.warning, msg)
+            filename = _get_data_file(table_type, download=True)
+            in_file.close()
+            in_file = h5py.File(filename, "r")
         else:
             only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
                          in_file.attrs["version"])
@@ -108,11 +106,10 @@
         in_file.close()
 
         E_diff = np.diff(self.log_E)
-        self.E_bins = \
-                  YTArray(np.power(10, np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
-                                                      [self.log_E[-1] - 0.5 * E_diff[-1],
-                                                       self.log_E[-1] + 0.5 * E_diff[-1]]])),
-                          "keV")
+        E_bins = np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
+                                [self.log_E[-1] - 0.5 * E_diff[-1],
+                                 self.log_E[-1] + 0.5 * E_diff[-1]]])
+        self.E_bins = YTArray(np.power(10, E_bins), "keV")
         self.dnu = (np.diff(self.E_bins)/hcgs).in_units("Hz")
 
     def get_interpolator(self, data, e_min, e_max):
@@ -155,15 +152,7 @@
         The maximum energy in keV for the energy band.
     table_type : string, optional
         The type of emissivity table to be used when creating the fields. 
-        
-    filename: string, optional
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
+        Options are "cloudy" or "atomdb". Default: "cloudy"
     with_metals : bool, optional
         If True, use the metallicity field to add the contribution from 
         metals.  If False, only the emission from H/He is considered.
@@ -184,7 +173,7 @@
     --------
 
     >>> import yt
-    >>> ds = yt.load(dataset)
+    >>> ds = yt.load("sloshing_nomag2_hdf5_plt_cnt_0100")
     >>> yt.add_xray_emissivity_field(ds, 0.5, 2)
     >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
     >>> p.save()
@@ -214,8 +203,8 @@
     try:
         ds._get_field_info("H_number_density")
     except YTFieldNotFound:
-        mylog.warning("Could not find a field for \"H_number_density\". Assuming primordial H " +
-                      "mass fraction.")
+        mylog.warning("Could not find a field for \"H_number_density\". "
+                      "Assuming primordial H mass fraction.")
         def _nh(field, data):
             return primordial_H_mass_fraction*data["gas","density"]/mp
         ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")


https://bitbucket.org/yt_analysis/yt/commits/dd3f4fc78f11/
Changeset:   dd3f4fc78f11
Branch:      yt
User:        jzuhone
Date:        2016-12-03 16:20:01+00:00
Summary:     Setup file versioning with mercurial version instead. Allow someone to use their own file.
Affected #:  1 file

diff -r 97dea27a301f7d923c0645bd7b31d8f53cda8bc7 -r dd3f4fc78f11502dc7bfa0b022b8dbb5608c3d1f yt/fields/emission_fields.py
--- a/yt/fields/emission_fields.py
+++ b/yt/fields/emission_fields.py
@@ -19,7 +19,6 @@
 import os
 
 from yt.funcs import \
-     download_file, \
      mylog, \
      only_on_root
 
@@ -33,26 +32,22 @@
 from yt.utilities.physical_ratios import \
     primordial_H_mass_fraction, erg_per_keV
 
-xray_data_version = 1
-
-def _get_data_file(table_type, download=False):
+def _get_data_file(table_type, data_dir=None):
     if table_type == "cloudy":
         data_file = "cloudy_emissivity.h5"
-    else:
+    elif table_type == "apec":
         data_file = "apec_emissivity.h5"
-    data_url = "http://yt-project.org/data"
-    if "YT_DEST" in os.environ and \
-      os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
-        data_dir = os.path.join(os.environ["YT_DEST"], "data")
-    else:
-        data_dir = "."
+    if data_dir is None:
+        if "YT_DEST" in os.environ and \
+            os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
+            # Try in default path
+            data_dir = os.path.join(os.environ["YT_DEST"], "data")
+        else:
+            # Try in current working directory
+            data_dir = "."
     data_path = os.path.join(data_dir, data_file)
-    if not os.path.exists(data_path) or download:
-        mylog.info("Attempting to download supplementary data from %s to %s." % 
-                   (data_url, data_dir))
-        fn = download_file(os.path.join(data_url, data_file), data_path)
-        if fn != data_path:
-            raise RuntimeError("Failed to download supplementary data.")
+    if not os.path.exists(data_path):
+        raise IOError("Failed to find emissivity data file %s!" % data_file)
     return data_path
 
 class EnergyBoundsException(YTException):
@@ -81,23 +76,15 @@
         energy range 0.1 to 100 keV. If the files are not available or out of date
         they will be downloaded.
     """
-    def __init__(self, table_type):
+    def __init__(self, table_type, data_dir=None):
 
-        filename = _get_data_file(table_type)
+        filename = _get_data_file(table_type, data_dir=data_dir)
         only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
         in_file = h5py.File(filename, "r")
         if "info" in in_file.attrs:
             only_on_root(mylog.info, in_file.attrs["info"])
-        if in_file.attrs["version"] < xray_data_version:
-            msg = "X-ray emissivity data is out of date.\n" + \
-                  "Downloading the latest data from http://yt-project.org/data."
-            only_on_root(mylog.warning, msg)
-            filename = _get_data_file(table_type, download=True)
-            in_file.close()
-            in_file = h5py.File(filename, "r")
-        else:
-            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                         in_file.attrs["version"])
+        only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
+                     in_file.attrs["version"])
 
         for field in ["emissivity_primordial", "emissivity_metals",
                       "log_nH", "log_T", "log_E"]:


https://bitbucket.org/yt_analysis/yt/commits/9a4d7a8caed2/
Changeset:   9a4d7a8caed2
Branch:      yt
User:        jzuhone
Date:        2016-12-04 04:33:55+00:00
Summary:     Linear energy binning. Rework the metallicity into a single parameter.
Affected #:  1 file

diff -r dd3f4fc78f11502dc7bfa0b022b8dbb5608c3d1f -r 9a4d7a8caed233b558a9643afd33625e4abe7637 yt/fields/emission_fields.py
--- a/yt/fields/emission_fields.py
+++ b/yt/fields/emission_fields.py
@@ -18,25 +18,21 @@
 import numpy as np
 import os
 
+from yt.fields.derived_field import DerivedField
 from yt.funcs import \
      mylog, \
      only_on_root
-
 from yt.utilities.exceptions import YTFieldNotFound
 from yt.utilities.exceptions import YTException
 from yt.utilities.linear_interpolators import \
     UnilinearFieldInterpolator, BilinearFieldInterpolator
-from yt.utilities.physical_constants import \
-    hcgs, mp
+from yt.utilities.physical_constants import mp
 from yt.units.yt_array import YTArray, YTQuantity
 from yt.utilities.physical_ratios import \
     primordial_H_mass_fraction, erg_per_keV
 
 def _get_data_file(table_type, data_dir=None):
-    if table_type == "cloudy":
-        data_file = "cloudy_emissivity.h5"
-    elif table_type == "apec":
-        data_file = "apec_emissivity.h5"
+    data_file = "%s_emissivity.h5" % table_type
     if data_dir is None:
         if "YT_DEST" in os.environ and \
             os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
@@ -61,7 +57,7 @@
 
 class EmissivityIntegrator(object):
     r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
-    generated from Cloudy and AtomDB.
+    generated from Cloudy and AtomDB/APEC.
 
     Initialize an EmissivityIntegrator object.
 
@@ -70,11 +66,13 @@
     table_type: string
         The type of data to use when computing the emissivity values. If "cloudy",
         a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5".These files contain emissivity tables 
+        plasmas. If, "apec", a file called "apec_emissivity.h5" is used for 
+        collisionally ionized plasmas. These files contain emissivity tables 
         for primordial elements and for metals at solar metallicity for the 
-        energy range 0.1 to 100 keV. If the files are not available or out of date
-        they will be downloaded.
+        energy range 0.1 to 100 keV.
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the 
     """
     def __init__(self, table_type, data_dir=None):
 
@@ -87,34 +85,30 @@
                      in_file.attrs["version"])
 
         for field in ["emissivity_primordial", "emissivity_metals",
-                      "log_nH", "log_T", "log_E"]:
+                      "log_nH", "log_T"]:
             if field in in_file:
                 setattr(self, field, in_file[field][:])
+        self.ebin = YTArray(in_file["ebin"], "keV")
         in_file.close()
-
-        E_diff = np.diff(self.log_E)
-        E_bins = np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
-                                [self.log_E[-1] - 0.5 * E_diff[-1],
-                                 self.log_E[-1] + 0.5 * E_diff[-1]]])
-        self.E_bins = YTArray(np.power(10, E_bins), "keV")
-        self.dnu = (np.diff(self.E_bins)/hcgs).in_units("Hz")
+        self.dE = np.diff(self.ebin)
+        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1])
 
     def get_interpolator(self, data, e_min, e_max):
         e_min = YTQuantity(e_min, "keV")
         e_max = YTQuantity(e_max, "keV")
-        if (e_min - self.E_bins[0]) / e_min < -1e-3 or \
-          (e_max - self.E_bins[-1]) / e_max > 1e-3:
-            raise EnergyBoundsException(self.E_bins[0], self.E_bins[-1])
-        e_is, e_ie = np.digitize([e_min, e_max], self.E_bins)
-        e_is = np.clip(e_is - 1, 0, self.E_bins.size - 1)
-        e_ie = np.clip(e_ie, 0, self.E_bins.size - 1)
+        if (e_min - self.ebin[0]) / e_min < -1e-3 or \
+          (e_max - self.ebin[-1]) / e_max > 1e-3:
+            raise EnergyBoundsException(self.ebin[0], self.ebin[-1])
+        e_is, e_ie = np.digitize([e_min, e_max], self.ebin)
+        e_is = np.clip(e_is - 1, 0, self.ebin.size - 1)
+        e_ie = np.clip(e_ie, 0, self.ebin.size - 1)
 
-        my_dnu = self.dnu[e_is: e_ie].copy()
+        my_dE = self.dE[e_is: e_ie].copy()
         # clip edge bins if the requested range is smaller
-        my_dnu[0] -= ((e_min - self.E_bins[e_is])/hcgs).in_units("Hz")
-        my_dnu[-1] -= ((self.E_bins[e_ie] - e_max)/hcgs).in_units("Hz")
+        my_dE[0] -= e_min - self.ebin[e_is]
+        my_dE[-1] -= self.ebin[e_ie] - e_max
 
-        interp_data = (data[..., e_is:e_ie] * my_dnu).sum(axis=-1)
+        interp_data = (data[..., e_is:e_ie]*my_dE).sum(axis=-1)
         if len(data.shape) == 2:
             emiss = UnilinearFieldInterpolator(np.log10(interp_data),
                                                [self.log_T[0],  self.log_T[-1]],
@@ -127,8 +121,9 @@
 
         return emiss
 
-def add_xray_emissivity_field(ds, e_min, e_max, table_type="cloudy", 
-                              with_metals=True, constant_metallicity=None):
+def add_xray_emissivity_field(ds, e_min, e_max, 
+                              metallicity=("gas", "metallicity"), 
+                              table_type="cloudy"):
     r"""Create X-ray emissivity fields for a given energy range.
 
     Parameters
@@ -137,18 +132,14 @@
         The minimum energy in keV for the energy band.
     e_min : float
         The maximum energy in keV for the energy band.
+    metallicity : field or float, optional
+        Either the name of a metallicity field or a single floating-point
+        number specifying a spatially constant metallicity. Must be in
+        solar units. If set to None, no metals will be assumed. Default: 
+        ("gas", "metallicity")
     table_type : string, optional
         The type of emissivity table to be used when creating the fields. 
-        Options are "cloudy" or "atomdb". Default: "cloudy"
-    with_metals : bool, optional
-        If True, use the metallicity field to add the contribution from 
-        metals.  If False, only the emission from H/He is considered.
-        Default: True.
-    constant_metallicity : float, optional
-        If specified, assume a constant metallicity for the emission 
-        from metals.  The *with_metals* keyword must be set to False 
-        to use this. It should be given in unit of solar metallicity.
-        Default: None.
+        Options are "cloudy" or "apec". Default: "cloudy"
 
     This will create three fields:
 
@@ -165,25 +156,23 @@
     >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
     >>> p.save()
     """
-    if with_metals:
+    if not isinstance(metallicity, float) and metallicity is not None:
         try:
-            ds._get_field_info("metal_density")
+            metallicity = ds._get_field_info(metallicity)
         except YTFieldNotFound:
-            raise RuntimeError("Your dataset does not have a \"metal_density\" field! " +
+            raise RuntimeError("Your dataset does not have a %s field! " % metallicity +
                                "Perhaps you should specify a constant metallicity?")
 
     my_si = EmissivityIntegrator(table_type)
 
     em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
-    em_Z = None
-    if with_metals or constant_metallicity is not None:
+    if metallicity is not None:
         em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
 
-    energy_erg = np.power(10, my_si.log_E) * erg_per_keV
+    energy_erg = my_si.emid.v * erg_per_keV
     emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
                                    e_min, e_max)
-    emp_Z = None
-    if with_metals or constant_metallicity is not None:
+    if metallicity is not None:
         emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
                                        e_min, e_max)
 
@@ -193,19 +182,19 @@
         mylog.warning("Could not find a field for \"H_number_density\". "
                       "Assuming primordial H mass fraction.")
         def _nh(field, data):
-            return primordial_H_mass_fraction*data["gas","density"]/mp
+            return primordial_H_mass_fraction*data["gas", "density"]/mp
         ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
 
     def _emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
+        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
 
         my_emissivity = np.power(10, em_0(dd))
-        if em_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity]
+            else:
+                my_Z = metallicity
             my_emissivity += my_Z * np.power(10, em_Z(dd))
 
         return data["gas","H_number_density"]**2 * \
@@ -225,18 +214,18 @@
                  units="erg/s")
 
     def _photon_emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
+        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
 
         my_emissivity = np.power(10, emp_0(dd))
-        if emp_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity]
+            else:
+                my_Z = metallicity
             my_emissivity += my_Z * np.power(10, emp_Z(dd))
 
-        return data["gas","H_number_density"]**2 * \
+        return data["gas", "H_number_density"]**2 * \
             YTArray(my_emissivity, "photons*cm**3/s")
 
     phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)


https://bitbucket.org/yt_analysis/yt/commits/7dfd13658c7c/
Changeset:   7dfd13658c7c
Branch:      yt
User:        jzuhone
Date:        2016-12-04 15:47:45+00:00
Summary:     Re-arrange imports
Affected #:  1 file

diff -r 9a4d7a8caed233b558a9643afd33625e4abe7637 -r 7dfd13658c7c4ead6b8545c0b4a0f5866cad4b92 yt/fields/emission_fields.py
--- a/yt/fields/emission_fields.py
+++ b/yt/fields/emission_fields.py
@@ -19,9 +19,7 @@
 import os
 
 from yt.fields.derived_field import DerivedField
-from yt.funcs import \
-     mylog, \
-     only_on_root
+from yt.funcs import mylog, only_on_root
 from yt.utilities.exceptions import YTFieldNotFound
 from yt.utilities.exceptions import YTException
 from yt.utilities.linear_interpolators import \


https://bitbucket.org/yt_analysis/yt/commits/c0084c8bbce8/
Changeset:   c0084c8bbce8
Branch:      yt
User:        jzuhone
Date:        2016-12-05 15:30:23+00:00
Summary:     Restore trying to download the file
Affected #:  1 file

diff -r 7dfd13658c7c4ead6b8545c0b4a0f5866cad4b92 -r c0084c8bbce8f8ede0c819aebf3673164710a9e2 yt/fields/emission_fields.py
--- a/yt/fields/emission_fields.py
+++ b/yt/fields/emission_fields.py
@@ -19,7 +19,7 @@
 import os
 
 from yt.fields.derived_field import DerivedField
-from yt.funcs import mylog, only_on_root
+from yt.funcs import mylog, only_on_root, download_file
 from yt.utilities.exceptions import YTFieldNotFound
 from yt.utilities.exceptions import YTException
 from yt.utilities.linear_interpolators import \
@@ -31,6 +31,7 @@
 
 def _get_data_file(table_type, data_dir=None):
     data_file = "%s_emissivity.h5" % table_type
+    data_url = "http://yt-project.org/data"
     if data_dir is None:
         if "YT_DEST" in os.environ and \
             os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
@@ -41,7 +42,12 @@
             data_dir = "."
     data_path = os.path.join(data_dir, data_file)
     if not os.path.exists(data_path):
-        raise IOError("Failed to find emissivity data file %s!" % data_file)
+        mylog.info("Attempting to download supplementary data from %s to %s." %
+                   (data_url, data_dir))
+        fn = download_file(os.path.join(data_url, data_file), data_path)
+        if fn != data_path:
+            mylog.error("Failed to download supplementary data.")
+            raise IOError("Failed to find emissivity data file %s!" % data_file)
     return data_path
 
 class EnergyBoundsException(YTException):


https://bitbucket.org/yt_analysis/yt/commits/b5c9ae4d59d4/
Changeset:   b5c9ae4d59d4
Branch:      yt
User:        jzuhone
Date:        2016-12-06 22:04:10+00:00
Summary:     Make the naming scheme more specific
Affected #:  3 files

diff -r c0084c8bbce8f8ede0c819aebf3673164710a9e2 -r b5c9ae4d59d412b7f1be48ad17b46a9bbfdf1be1 yt/fields/api.py
--- a/yt/fields/api.py
+++ b/yt/fields/api.py
@@ -43,5 +43,5 @@
     FieldDetector
 from .field_info_container import \
     FieldInfoContainer
-from .emission_fields import \
+from .xray_emission_fields import \
     add_xray_emissivity_field
\ No newline at end of file

diff -r c0084c8bbce8f8ede0c819aebf3673164710a9e2 -r b5c9ae4d59d412b7f1be48ad17b46a9bbfdf1be1 yt/fields/emission_fields.py
--- a/yt/fields/emission_fields.py
+++ /dev/null
@@ -1,240 +0,0 @@
-"""
-Integrator classes to deal with interpolation and integration of input spectral
-bins.  Currently only supports Cloudy and APEC-style data.
-
-
-
-"""
-
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-
-from yt.utilities.on_demand_imports import _h5py as h5py
-import numpy as np
-import os
-
-from yt.fields.derived_field import DerivedField
-from yt.funcs import mylog, only_on_root, download_file
-from yt.utilities.exceptions import YTFieldNotFound
-from yt.utilities.exceptions import YTException
-from yt.utilities.linear_interpolators import \
-    UnilinearFieldInterpolator, BilinearFieldInterpolator
-from yt.utilities.physical_constants import mp
-from yt.units.yt_array import YTArray, YTQuantity
-from yt.utilities.physical_ratios import \
-    primordial_H_mass_fraction, erg_per_keV
-
-def _get_data_file(table_type, data_dir=None):
-    data_file = "%s_emissivity.h5" % table_type
-    data_url = "http://yt-project.org/data"
-    if data_dir is None:
-        if "YT_DEST" in os.environ and \
-            os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
-            # Try in default path
-            data_dir = os.path.join(os.environ["YT_DEST"], "data")
-        else:
-            # Try in current working directory
-            data_dir = "."
-    data_path = os.path.join(data_dir, data_file)
-    if not os.path.exists(data_path):
-        mylog.info("Attempting to download supplementary data from %s to %s." %
-                   (data_url, data_dir))
-        fn = download_file(os.path.join(data_url, data_file), data_path)
-        if fn != data_path:
-            mylog.error("Failed to download supplementary data.")
-            raise IOError("Failed to find emissivity data file %s!" % data_file)
-    return data_path
-
-class EnergyBoundsException(YTException):
-    def __init__(self, lower, upper):
-        self.lower = lower
-        self.upper = upper
-
-    def __str__(self):
-        return "Energy bounds are %e to %e keV." % \
-          (self.lower, self.upper)
-
-class EmissivityIntegrator(object):
-    r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
-    generated from Cloudy and AtomDB/APEC.
-
-    Initialize an EmissivityIntegrator object.
-
-    Parameters
-    ----------
-    table_type: string
-        The type of data to use when computing the emissivity values. If "cloudy",
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. If, "apec", a file called "apec_emissivity.h5" is used for 
-        collisionally ionized plasmas. These files contain emissivity tables 
-        for primordial elements and for metals at solar metallicity for the 
-        energy range 0.1 to 100 keV.
-    data_dir : string, optional
-        The location to look for the data table in. If not supplied, the file
-        will be looked for in the 
-    """
-    def __init__(self, table_type, data_dir=None):
-
-        filename = _get_data_file(table_type, data_dir=data_dir)
-        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
-        in_file = h5py.File(filename, "r")
-        if "info" in in_file.attrs:
-            only_on_root(mylog.info, in_file.attrs["info"])
-        only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                     in_file.attrs["version"])
-
-        for field in ["emissivity_primordial", "emissivity_metals",
-                      "log_nH", "log_T"]:
-            if field in in_file:
-                setattr(self, field, in_file[field][:])
-        self.ebin = YTArray(in_file["ebin"], "keV")
-        in_file.close()
-        self.dE = np.diff(self.ebin)
-        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1])
-
-    def get_interpolator(self, data, e_min, e_max):
-        e_min = YTQuantity(e_min, "keV")
-        e_max = YTQuantity(e_max, "keV")
-        if (e_min - self.ebin[0]) / e_min < -1e-3 or \
-          (e_max - self.ebin[-1]) / e_max > 1e-3:
-            raise EnergyBoundsException(self.ebin[0], self.ebin[-1])
-        e_is, e_ie = np.digitize([e_min, e_max], self.ebin)
-        e_is = np.clip(e_is - 1, 0, self.ebin.size - 1)
-        e_ie = np.clip(e_ie, 0, self.ebin.size - 1)
-
-        my_dE = self.dE[e_is: e_ie].copy()
-        # clip edge bins if the requested range is smaller
-        my_dE[0] -= e_min - self.ebin[e_is]
-        my_dE[-1] -= self.ebin[e_ie] - e_max
-
-        interp_data = (data[..., e_is:e_ie]*my_dE).sum(axis=-1)
-        if len(data.shape) == 2:
-            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
-                                               [self.log_T[0],  self.log_T[-1]],
-                                               "log_T", truncate=True)
-        else:
-            emiss = BilinearFieldInterpolator(np.log10(interp_data),
-                                              [self.log_nH[0], self.log_nH[-1],
-                                               self.log_T[0],  self.log_T[-1]],
-                                              ["log_nH", "log_T"], truncate=True)
-
-        return emiss
-
-def add_xray_emissivity_field(ds, e_min, e_max, 
-                              metallicity=("gas", "metallicity"), 
-                              table_type="cloudy"):
-    r"""Create X-ray emissivity fields for a given energy range.
-
-    Parameters
-    ----------
-    e_min : float
-        The minimum energy in keV for the energy band.
-    e_min : float
-        The maximum energy in keV for the energy band.
-    metallicity : field or float, optional
-        Either the name of a metallicity field or a single floating-point
-        number specifying a spatially constant metallicity. Must be in
-        solar units. If set to None, no metals will be assumed. Default: 
-        ("gas", "metallicity")
-    table_type : string, optional
-        The type of emissivity table to be used when creating the fields. 
-        Options are "cloudy" or "apec". Default: "cloudy"
-
-    This will create three fields:
-
-    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
-    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
-    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
-
-    Examples
-    --------
-
-    >>> import yt
-    >>> ds = yt.load("sloshing_nomag2_hdf5_plt_cnt_0100")
-    >>> yt.add_xray_emissivity_field(ds, 0.5, 2)
-    >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
-    >>> p.save()
-    """
-    if not isinstance(metallicity, float) and metallicity is not None:
-        try:
-            metallicity = ds._get_field_info(metallicity)
-        except YTFieldNotFound:
-            raise RuntimeError("Your dataset does not have a %s field! " % metallicity +
-                               "Perhaps you should specify a constant metallicity?")
-
-    my_si = EmissivityIntegrator(table_type)
-
-    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
-    if metallicity is not None:
-        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
-
-    energy_erg = my_si.emid.v * erg_per_keV
-    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
-                                   e_min, e_max)
-    if metallicity is not None:
-        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
-                                       e_min, e_max)
-
-    try:
-        ds._get_field_info("H_number_density")
-    except YTFieldNotFound:
-        mylog.warning("Could not find a field for \"H_number_density\". "
-                      "Assuming primordial H mass fraction.")
-        def _nh(field, data):
-            return primordial_H_mass_fraction*data["gas", "density"]/mp
-        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
-
-    def _emissivity_field(field, data):
-        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
-              "log_T": np.log10(data["gas", "temperature"])}
-
-        my_emissivity = np.power(10, em_0(dd))
-        if metallicity is not None:
-            if isinstance(metallicity, DerivedField):
-                my_Z = data[metallicity]
-            else:
-                my_Z = metallicity
-            my_emissivity += my_Z * np.power(10, em_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "erg*cm**3/s")
-
-    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", emiss_name), function=_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/cm**3/s")
-
-    def _luminosity_field(field, data):
-        return data[emiss_name] * data["cell_volume"]
-
-    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", lum_name), function=_luminosity_field,
-                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/s")
-
-    def _photon_emissivity_field(field, data):
-        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
-              "log_T": np.log10(data["gas", "temperature"])}
-
-        my_emissivity = np.power(10, emp_0(dd))
-        if metallicity is not None:
-            if isinstance(metallicity, DerivedField):
-                my_Z = data[metallicity]
-            else:
-                my_Z = metallicity
-            my_emissivity += my_Z * np.power(10, emp_Z(dd))
-
-        return data["gas", "H_number_density"]**2 * \
-            YTArray(my_emissivity, "photons*cm**3/s")
-
-    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="photons/cm**3/s")
-
-    return emiss_name, lum_name, phot_name

diff -r c0084c8bbce8f8ede0c819aebf3673164710a9e2 -r b5c9ae4d59d412b7f1be48ad17b46a9bbfdf1be1 yt/fields/xray_emission_fields.py
--- /dev/null
+++ b/yt/fields/xray_emission_fields.py
@@ -0,0 +1,240 @@
+"""
+Integrator classes to deal with interpolation and integration of input spectral
+bins.  Currently only supports Cloudy and APEC-style data.
+
+
+
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, yt Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+from yt.utilities.on_demand_imports import _h5py as h5py
+import numpy as np
+import os
+
+from yt.fields.derived_field import DerivedField
+from yt.funcs import mylog, only_on_root, download_file
+from yt.utilities.exceptions import YTFieldNotFound
+from yt.utilities.exceptions import YTException
+from yt.utilities.linear_interpolators import \
+    UnilinearFieldInterpolator, BilinearFieldInterpolator
+from yt.utilities.physical_constants import mp
+from yt.units.yt_array import YTArray, YTQuantity
+from yt.utilities.physical_ratios import \
+    primordial_H_mass_fraction, erg_per_keV
+
+def _get_data_file(table_type, data_dir=None):
+    data_file = "%s_emissivity.h5" % table_type
+    data_url = "http://yt-project.org/data"
+    if data_dir is None:
+        if "YT_DEST" in os.environ and \
+            os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
+            # Try in default path
+            data_dir = os.path.join(os.environ["YT_DEST"], "data")
+        else:
+            # Try in current working directory
+            data_dir = "."
+    data_path = os.path.join(data_dir, data_file)
+    if not os.path.exists(data_path):
+        mylog.info("Attempting to download supplementary data from %s to %s." %
+                   (data_url, data_dir))
+        fn = download_file(os.path.join(data_url, data_file), data_path)
+        if fn != data_path:
+            mylog.error("Failed to download supplementary data.")
+            raise IOError("Failed to find emissivity data file %s!" % data_file)
+    return data_path
+
+class EnergyBoundsException(YTException):
+    def __init__(self, lower, upper):
+        self.lower = lower
+        self.upper = upper
+
+    def __str__(self):
+        return "Energy bounds are %e to %e keV." % \
+          (self.lower, self.upper)
+
+class XrayEmissivityIntegrator(object):
+    r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
+    generated from Cloudy and AtomDB/APEC.
+
+    Initialize an XrayEmissivityIntegrator object.
+
+    Parameters
+    ----------
+    table_type: string
+        The type of data to use when computing the emissivity values. If "cloudy",
+        a file called "cloudy_emissivity.h5" is used, for photoionized
+        plasmas. If, "apec", a file called "apec_emissivity.h5" is used for 
+        collisionally ionized plasmas. These files contain emissivity tables 
+        for primordial elements and for metals at solar metallicity for the 
+        energy range 0.1 to 100 keV.
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the 
+    """
+    def __init__(self, table_type, data_dir=None):
+
+        filename = _get_data_file(table_type, data_dir=data_dir)
+        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
+        in_file = h5py.File(filename, "r")
+        if "info" in in_file.attrs:
+            only_on_root(mylog.info, in_file.attrs["info"])
+        only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
+                     in_file.attrs["version"])
+
+        for field in ["emissivity_primordial", "emissivity_metals",
+                      "log_nH", "log_T"]:
+            if field in in_file:
+                setattr(self, field, in_file[field][:])
+        self.ebin = YTArray(in_file["ebin"], "keV")
+        in_file.close()
+        self.dE = np.diff(self.ebin)
+        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1])
+
+    def get_interpolator(self, data, e_min, e_max):
+        e_min = YTQuantity(e_min, "keV")
+        e_max = YTQuantity(e_max, "keV")
+        if (e_min - self.ebin[0]) / e_min < -1e-3 or \
+          (e_max - self.ebin[-1]) / e_max > 1e-3:
+            raise EnergyBoundsException(self.ebin[0], self.ebin[-1])
+        e_is, e_ie = np.digitize([e_min, e_max], self.ebin)
+        e_is = np.clip(e_is - 1, 0, self.ebin.size - 1)
+        e_ie = np.clip(e_ie, 0, self.ebin.size - 1)
+
+        my_dE = self.dE[e_is: e_ie].copy()
+        # clip edge bins if the requested range is smaller
+        my_dE[0] -= e_min - self.ebin[e_is]
+        my_dE[-1] -= self.ebin[e_ie] - e_max
+
+        interp_data = (data[..., e_is:e_ie]*my_dE).sum(axis=-1)
+        if len(data.shape) == 2:
+            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
+                                               [self.log_T[0],  self.log_T[-1]],
+                                               "log_T", truncate=True)
+        else:
+            emiss = BilinearFieldInterpolator(np.log10(interp_data),
+                                              [self.log_nH[0], self.log_nH[-1],
+                                               self.log_T[0],  self.log_T[-1]],
+                                              ["log_nH", "log_T"], truncate=True)
+
+        return emiss
+
+def add_xray_emissivity_field(ds, e_min, e_max, 
+                              metallicity=("gas", "metallicity"), 
+                              table_type="cloudy"):
+    r"""Create X-ray emissivity fields for a given energy range.
+
+    Parameters
+    ----------
+    e_min : float
+        The minimum energy in keV for the energy band.
+    e_min : float
+        The maximum energy in keV for the energy band.
+    metallicity : field or float, optional
+        Either the name of a metallicity field or a single floating-point
+        number specifying a spatially constant metallicity. Must be in
+        solar units. If set to None, no metals will be assumed. Default: 
+        ("gas", "metallicity")
+    table_type : string, optional
+        The type of emissivity table to be used when creating the fields. 
+        Options are "cloudy" or "apec". Default: "cloudy"
+
+    This will create three fields:
+
+    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
+    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
+    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
+
+    Examples
+    --------
+
+    >>> import yt
+    >>> ds = yt.load("sloshing_nomag2_hdf5_plt_cnt_0100")
+    >>> yt.add_xray_emissivity_field(ds, 0.5, 2)
+    >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
+    >>> p.save()
+    """
+    if not isinstance(metallicity, float) and metallicity is not None:
+        try:
+            metallicity = ds._get_field_info(metallicity)
+        except YTFieldNotFound:
+            raise RuntimeError("Your dataset does not have a %s field! " % metallicity +
+                               "Perhaps you should specify a constant metallicity?")
+
+    my_si = XrayEmissivityIntegrator(table_type)
+
+    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
+    if metallicity is not None:
+        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
+
+    energy_erg = my_si.emid.v * erg_per_keV
+    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
+                                   e_min, e_max)
+    if metallicity is not None:
+        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
+                                       e_min, e_max)
+
+    try:
+        ds._get_field_info("H_number_density")
+    except YTFieldNotFound:
+        mylog.warning("Could not find a field for \"H_number_density\". "
+                      "Assuming primordial H mass fraction.")
+        def _nh(field, data):
+            return primordial_H_mass_fraction*data["gas", "density"]/mp
+        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
+
+    def _emissivity_field(field, data):
+        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
+
+        my_emissivity = np.power(10, em_0(dd))
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity]
+            else:
+                my_Z = metallicity
+            my_emissivity += my_Z * np.power(10, em_Z(dd))
+
+        return data["gas","H_number_density"]**2 * \
+            YTArray(my_emissivity, "erg*cm**3/s")
+
+    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", emiss_name), function=_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 units="erg/cm**3/s")
+
+    def _luminosity_field(field, data):
+        return data[emiss_name] * data["cell_volume"]
+
+    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", lum_name), function=_luminosity_field,
+                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
+                 units="erg/s")
+
+    def _photon_emissivity_field(field, data):
+        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
+
+        my_emissivity = np.power(10, emp_0(dd))
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity]
+            else:
+                my_Z = metallicity
+            my_emissivity += my_Z * np.power(10, emp_Z(dd))
+
+        return data["gas", "H_number_density"]**2 * \
+            YTArray(my_emissivity, "photons*cm**3/s")
+
+    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 units="photons/cm**3/s")
+
+    return emiss_name, lum_name, phot_name


https://bitbucket.org/yt_analysis/yt/commits/32db25efb0f9/
Changeset:   32db25efb0f9
Branch:      yt
User:        jzuhone
Date:        2016-12-06 22:51:01+00:00
Summary:     Add intensity fields with redshift and cosmology
Affected #:  1 file

diff -r b5c9ae4d59d412b7f1be48ad17b46a9bbfdf1be1 -r 32db25efb0f920e330d1bf91e30cf8488cb956a2 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -26,6 +26,7 @@
     UnilinearFieldInterpolator, BilinearFieldInterpolator
 from yt.utilities.physical_constants import mp
 from yt.units.yt_array import YTArray, YTQuantity
+from yt.utilities.cosmology import Cosmology
 from yt.utilities.physical_ratios import \
     primordial_H_mass_fraction, erg_per_keV
 
@@ -76,9 +77,12 @@
         energy range 0.1 to 100 keV.
     data_dir : string, optional
         The location to look for the data table in. If not supplied, the file
-        will be looked for in the 
+        will be looked for in the location of the YT_DEST environment variable
+        or in the current working directory.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
     """
-    def __init__(self, table_type, data_dir=None):
+    def __init__(self, table_type, data_dir=None, redshift=0.0):
 
         filename = _get_data_file(table_type, data_dir=data_dir)
         only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
@@ -96,10 +100,11 @@
         in_file.close()
         self.dE = np.diff(self.ebin)
         self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1])
+        self.redshift = redshift
 
     def get_interpolator(self, data, e_min, e_max):
-        e_min = YTQuantity(e_min, "keV")
-        e_max = YTQuantity(e_max, "keV")
+        e_min = YTQuantity(e_min, "keV")*(1.0+self.redshift)
+        e_max = YTQuantity(e_max, "keV")*(1.0+self.redshift)
         if (e_min - self.ebin[0]) / e_min < -1e-3 or \
           (e_max - self.ebin[-1]) / e_max > 1e-3:
             raise EnergyBoundsException(self.ebin[0], self.ebin[-1])
@@ -127,7 +132,8 @@
 
 def add_xray_emissivity_field(ds, e_min, e_max, 
                               metallicity=("gas", "metallicity"), 
-                              table_type="cloudy"):
+                              table_type="cloudy", data_dir=None, redshift=0.0,
+                              cosmology=None):
     r"""Create X-ray emissivity fields for a given energy range.
 
     Parameters
@@ -144,6 +150,12 @@
     table_type : string, optional
         The type of emissivity table to be used when creating the fields. 
         Options are "cloudy" or "apec". Default: "cloudy"
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the location of the YT_DEST environment variable
+        or in the current working directory.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
 
     This will create three fields:
 
@@ -167,7 +179,7 @@
             raise RuntimeError("Your dataset does not have a %s field! " % metallicity +
                                "Perhaps you should specify a constant metallicity?")
 
-    my_si = XrayEmissivityIntegrator(table_type)
+    my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)
 
     em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
     if metallicity is not None:
@@ -189,6 +201,15 @@
             return primordial_H_mass_fraction*data["gas", "density"]/mp
         ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
 
+    if cosmology is None:
+        if hasattr(ds, "cosmology"):
+            cosmology = ds.cosmology
+        else:
+            cosmology = Cosmology()
+    D_L = cosmology.luminosity_distance(0.0, redshift)
+    angular_scale = cosmology.angular_scale(0.0, redshift)
+    dist_fac = 1.0/(4.0*np.pi*D_L*D_L*angular_scale*angular_scale)
+
     def _emissivity_field(field, data):
         dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
               "log_T": np.log10(data["gas", "temperature"])}
@@ -237,4 +258,26 @@
                  display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
                  units="photons/cm**3/s")
 
-    return emiss_name, lum_name, phot_name
+    fields = [emiss_name, lum_name, phot_name]
+
+    if redshift > 0.0:
+
+        ei_name = "xray_intensity_%s_%s_keV" % (e_min, e_max)
+        def _intensity_field(field, data):
+            I = dist_fac*data[emiss_name]
+            return I.in_units("erg/cm**2/s/arcsec**2")
+        ds.add_field(("gas", ei_name), function=_intensity_field,
+                     display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
+                     units="erg/cm**2/s/arcsec**2")
+
+        i_name = "xray_photon_intensity_%s_%s_keV" % (e_min, e_max)
+        def _photon_intensity_field(field, data):
+            I = (1.0+redshift)*dist_fac*data[phot_name]
+            return I.in_units("photons/cm**2/s/arcsec**2")
+        ds.add_field(("gas", i_name), function=_intensity_field,
+                     display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
+                     units="photons/cm**2/s/arcsec**2")
+
+        fields += [ei_name, i_name]
+
+    return fields


https://bitbucket.org/yt_analysis/yt/commits/1a6d0c8cf921/
Changeset:   1a6d0c8cf921
Branch:      yt
User:        jzuhone
Date:        2016-12-06 22:54:02+00:00
Summary:     This should go down in here instead
Affected #:  1 file

diff -r 32db25efb0f920e330d1bf91e30cf8488cb956a2 -r 1a6d0c8cf921e76677ce1cbe2d38a2f7eb86bd77 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -201,15 +201,6 @@
             return primordial_H_mass_fraction*data["gas", "density"]/mp
         ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
 
-    if cosmology is None:
-        if hasattr(ds, "cosmology"):
-            cosmology = ds.cosmology
-        else:
-            cosmology = Cosmology()
-    D_L = cosmology.luminosity_distance(0.0, redshift)
-    angular_scale = cosmology.angular_scale(0.0, redshift)
-    dist_fac = 1.0/(4.0*np.pi*D_L*D_L*angular_scale*angular_scale)
-
     def _emissivity_field(field, data):
         dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
               "log_T": np.log10(data["gas", "temperature"])}
@@ -262,6 +253,16 @@
 
     if redshift > 0.0:
 
+        if cosmology is None:
+            if hasattr(ds, "cosmology"):
+                cosmology = ds.cosmology
+            else:
+                cosmology = Cosmology()
+
+        D_L = cosmology.luminosity_distance(0.0, redshift)
+        angular_scale = cosmology.angular_scale(0.0, redshift)
+        dist_fac = 1.0/(4.0*np.pi*D_L*D_L*angular_scale*angular_scale)
+
         ei_name = "xray_intensity_%s_%s_keV" % (e_min, e_max)
         def _intensity_field(field, data):
             I = dist_fac*data[emiss_name]


https://bitbucket.org/yt_analysis/yt/commits/aff7c3982415/
Changeset:   aff7c3982415
Branch:      yt
User:        jzuhone
Date:        2016-12-06 23:42:12+00:00
Summary:     No-op
Affected #:  0 files



https://bitbucket.org/yt_analysis/yt/commits/f78f95940b37/
Changeset:   f78f95940b37
Branch:      yt
User:        jzuhone
Date:        2016-12-07 00:01:43+00:00
Summary:     Trying to make this code easier to read
Affected #:  1 file

diff -r aff7c398241529be9a371812186f5a7ebc1c3cb9 -r f78f95940b37a69d283b18ed3a0ea6b4d5521c6c yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -75,14 +75,14 @@
         collisionally ionized plasmas. These files contain emissivity tables 
         for primordial elements and for metals at solar metallicity for the 
         energy range 0.1 to 100 keV.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
     data_dir : string, optional
         The location to look for the data table in. If not supplied, the file
         will be looked for in the location of the YT_DEST environment variable
         or in the current working directory.
-    redshift : float, optional
-        The cosmological redshift of the source of the field. Default: 0.0.
     """
-    def __init__(self, table_type, data_dir=None, redshift=0.0):
+    def __init__(self, table_type, redshift=0.0, data_dir=None, use_metals=True):
 
         filename = _get_data_file(table_type, data_dir=data_dir)
         only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
@@ -92,17 +92,22 @@
         only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
                      in_file.attrs["version"])
 
-        for field in ["emissivity_primordial", "emissivity_metals",
-                      "log_nH", "log_T"]:
-            if field in in_file:
-                setattr(self, field, in_file[field][:])
+        self.log_T = in_file["log_T"][:]
+        self.emissivity_primordial = in_file["emissivity_primordial"][:]
+        if "log_nH" in in_file:
+            self.log_nH = in_file["log_nH"][:]
+        if use_metals:
+            self.emissivity_metals = in_file["emissivity_metals"][:]
         self.ebin = YTArray(in_file["ebin"], "keV")
         in_file.close()
         self.dE = np.diff(self.ebin)
-        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1])
+        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1]).to("erg")
         self.redshift = redshift
 
-    def get_interpolator(self, data, e_min, e_max):
+    def get_interpolator(self, data_type, e_min, e_max, energy=True):
+        data = getattr(self, "emissivity_%s" % data_type)
+        if not energy:
+            data = data[..., :] / self.emid.v
         e_min = YTQuantity(e_min, "keV")*(1.0+self.redshift)
         e_max = YTQuantity(e_max, "keV")*(1.0+self.redshift)
         if (e_min - self.ebin[0]) / e_min < -1e-3 or \
@@ -118,7 +123,7 @@
         my_dE[-1] -= self.ebin[e_ie] - e_max
 
         interp_data = (data[..., e_is:e_ie]*my_dE).sum(axis=-1)
-        if len(data.shape) == 2:
+        if data.ndim == 2:
             emiss = UnilinearFieldInterpolator(np.log10(interp_data),
                                                [self.log_T[0],  self.log_T[-1]],
                                                "log_T", truncate=True)
@@ -130,9 +135,9 @@
 
         return emiss
 
-def add_xray_emissivity_field(ds, e_min, e_max, 
+def add_xray_emissivity_field(ds, e_min, e_max, redshift=0.0,
                               metallicity=("gas", "metallicity"), 
-                              table_type="cloudy", data_dir=None, redshift=0.0,
+                              table_type="cloudy", data_dir=None,
                               cosmology=None):
     r"""Create X-ray emissivity fields for a given energy range.
 
@@ -142,6 +147,8 @@
         The minimum energy in keV for the energy band.
     e_min : float
         The maximum energy in keV for the energy band.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
     metallicity : field or float, optional
         Either the name of a metallicity field or a single floating-point
         number specifying a spatially constant metallicity. Must be in
@@ -154,8 +161,6 @@
         The location to look for the data table in. If not supplied, the file
         will be looked for in the location of the YT_DEST environment variable
         or in the current working directory.
-    redshift : float, optional
-        The cosmological redshift of the source of the field. Default: 0.0.
 
     This will create three fields:
 
@@ -181,16 +186,11 @@
 
     my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)
 
-    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
+    em_0 = my_si.get_interpolator("primordial", e_min, e_max)
+    emp_0 = my_si.get_interpolator("primordial", e_min, e_max, energy=False)
     if metallicity is not None:
-        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
-
-    energy_erg = my_si.emid.v * erg_per_keV
-    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
-                                   e_min, e_max)
-    if metallicity is not None:
-        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
-                                       e_min, e_max)
+        em_Z = my_si.get_interpolator("metals", e_min, e_max)
+        emp_Z = my_si.get_interpolator("metals", e_min, e_max, energy=False)
 
     try:
         ds._get_field_info("H_number_density")


https://bitbucket.org/yt_analysis/yt/commits/f29809016945/
Changeset:   f29809016945
Branch:      yt
User:        jzuhone
Date:        2016-12-07 13:52:21+00:00
Summary:     Adding logger info for all the fields we may add
Affected #:  1 file

diff -r f78f95940b37a69d283b18ed3a0ea6b4d5521c6c -r f298090169459470cc80151880a14f771c3530c1 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -98,7 +98,7 @@
             self.log_nH = in_file["log_nH"][:]
         if use_metals:
             self.emissivity_metals = in_file["emissivity_metals"][:]
-        self.ebin = YTArray(in_file["ebin"], "keV")
+        self.ebin = YTArray(in_file["E"], "keV")
         in_file.close()
         self.dE = np.diff(self.ebin)
         self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1]).to("erg")
@@ -281,4 +281,6 @@
 
         fields += [ei_name, i_name]
 
+    [mylog.info("Adding %s field." % field) for field in fields]
+
     return fields


https://bitbucket.org/yt_analysis/yt/commits/33d800b08a5d/
Changeset:   33d800b08a5d
Branch:      yt
User:        jzuhone
Date:        2016-12-07 14:13:13+00:00
Summary:     Make sure the metallicity is handled properly
Affected #:  1 file

diff -r f298090169459470cc80151880a14f771c3530c1 -r 33d800b08a5d6088d54545c442eabc8931a9f095 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -179,9 +179,9 @@
     """
     if not isinstance(metallicity, float) and metallicity is not None:
         try:
-            metallicity = ds._get_field_info(metallicity)
+            metallicity = ds._get_field_info(*metallicity)
         except YTFieldNotFound:
-            raise RuntimeError("Your dataset does not have a %s field! " % metallicity +
+            raise RuntimeError("Your dataset does not have a {} field! ".format(metallicity) +
                                "Perhaps you should specify a constant metallicity?")
 
     my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)


https://bitbucket.org/yt_analysis/yt/commits/da2480fde49a/
Changeset:   da2480fde49a
Branch:      yt
User:        jzuhone
Date:        2016-12-07 15:05:02+00:00
Summary:     Put quotes around this to isolate it from the period at the end
Affected #:  1 file

diff -r 33d800b08a5d6088d54545c442eabc8931a9f095 -r da2480fde49ac955cca1376bbfd40fe16dbb584b yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -43,7 +43,7 @@
             data_dir = "."
     data_path = os.path.join(data_dir, data_file)
     if not os.path.exists(data_path):
-        mylog.info("Attempting to download supplementary data from %s to %s." %
+        mylog.info("Attempting to download supplementary data from %s to '%s'." %
                    (data_url, data_dir))
         fn = download_file(os.path.join(data_url, data_file), data_path)
         if fn != data_path:


https://bitbucket.org/yt_analysis/yt/commits/271155187ef9/
Changeset:   271155187ef9
Branch:      yt
User:        jzuhone
Date:        2016-12-07 15:08:02+00:00
Summary:     Decode this line
Affected #:  1 file

diff -r da2480fde49ac955cca1376bbfd40fe16dbb584b -r 271155187ef974df3587d19d4b41949b7c9721bd yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -88,7 +88,7 @@
         only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
         in_file = h5py.File(filename, "r")
         if "info" in in_file.attrs:
-            only_on_root(mylog.info, in_file.attrs["info"])
+            only_on_root(mylog.info, in_file.attrs["info"].decode('utf8'))
         only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
                      in_file.attrs["version"])
 


https://bitbucket.org/yt_analysis/yt/commits/67e9f3fdbd81/
Changeset:   67e9f3fdbd81
Branch:      yt
User:        jzuhone
Date:        2016-12-07 15:48:18+00:00
Summary:     Restore the version checking
Affected #:  1 file

diff -r 271155187ef974df3587d19d4b41949b7c9721bd -r 67e9f3fdbd81b659a0498a59b460a5d6acf8eaba yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -28,11 +28,15 @@
 from yt.units.yt_array import YTArray, YTQuantity
 from yt.utilities.cosmology import Cosmology
 from yt.utilities.physical_ratios import \
-    primordial_H_mass_fraction, erg_per_keV
+    primordial_H_mass_fraction
+
+data_version = {"cloudy": 2,
+                "apec": 2}
+
+data_url = "http://yt-project.org/data"
 
 def _get_data_file(table_type, data_dir=None):
     data_file = "%s_emissivity.h5" % table_type
-    data_url = "http://yt-project.org/data"
     if data_dir is None:
         if "YT_DEST" in os.environ and \
             os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
@@ -60,6 +64,14 @@
         return "Energy bounds are %e to %e keV." % \
           (self.lower, self.upper)
 
+class ObsoleteDataException(YTException):
+    def __init__(self, table_type):
+        self.msg = "X-ray emissivity data is out of date.\n"
+        self.msg += "Download the latest data from %s/%s_emissivity.h5." % (data_url, table_type)
+
+    def __str__(self):
+        return self.msg
+
 class XrayEmissivityIntegrator(object):
     r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
     generated from Cloudy and AtomDB/APEC.
@@ -89,8 +101,11 @@
         in_file = h5py.File(filename, "r")
         if "info" in in_file.attrs:
             only_on_root(mylog.info, in_file.attrs["info"].decode('utf8'))
-        only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                     in_file.attrs["version"])
+        if in_file.attrs["version"] != data_version[table_type]:
+            raise ObsoleteDataException(table_type)
+        else:
+            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
+                         in_file.attrs["version"])
 
         self.log_T = in_file["log_T"][:]
         self.emissivity_primordial = in_file["emissivity_primordial"][:]


https://bitbucket.org/yt_analysis/yt/commits/7d56da87ccea/
Changeset:   7d56da87ccea
Branch:      yt
User:        jzuhone
Date:        2016-12-07 16:29:24+00:00
Summary:     Add answer testing for x-ray fields
Affected #:  1 file

diff -r 67e9f3fdbd81b659a0498a59b460a5d6acf8eaba -r 7d56da87ccea7c5b107835ca367a4b9966484465 yt/fields/tests/test_xray_fields.py
--- /dev/null
+++ b/yt/fields/tests/test_xray_fields.py
@@ -0,0 +1,31 @@
+from yt.fields.xray_emission_fields import \
+    add_xray_emissivity_field
+from yt.utilities.answer_testing.framework import \
+    requires_ds, \
+    small_patch_amr, \
+    data_dir_load
+
+def setup():
+    from yt.config import ytcfg
+    ytcfg["yt","__withintesting"] = "True"
+
+sloshing = "GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300"
+ at requires_ds(sloshing, big_data=True)
+def test_sloshing_cloudy():
+    ds = data_dir_load(sloshing)
+    fields = add_xray_emissivity_field(ds, 0.5, 7.0, redshift=0.05,
+                                       table_type="cloudy", metallicity=0.3)
+    for test in small_patch_amr(ds, fields):
+        test_sloshing_cloudy.__name__ = test.description
+        yield test
+
+ecp = "enzo_cosmology_plus/DD0046/DD0046"
+ at requires_ds(ecp, big_data=True)
+def test_ecp_apec():
+    ds = data_dir_load(sloshing)
+    fields = add_xray_emissivity_field(ds, 0.5, 7.0, redshift=0.05,
+                                       table_type="apec",
+                                       metallicity=("gas", "metallicity"))
+    for test in small_patch_amr(ds, fields):
+        test_ecp_apec.__name__ = test.description
+        yield test


https://bitbucket.org/yt_analysis/yt/commits/4703ee80fc4a/
Changeset:   4703ee80fc4a
Branch:      yt
User:        jzuhone
Date:        2016-12-07 16:35:16+00:00
Summary:     Put a placeholder for the old analysis module here
Affected #:  2 files

diff -r 7d56da87ccea7c5b107835ca367a4b9966484465 -r 4703ee80fc4a0e763f4249aae665bfc1b9663552 yt/analysis_modules/spectral_integrator/api.py
--- /dev/null
+++ b/yt/analysis_modules/spectral_integrator/api.py
@@ -0,0 +1,8 @@
+from yt.funcs import issue_deprecation_warning
+
+issue_deprecation_warning("The spectral_integrator module is deprecated. "
+                          "'add_xray_emissivity_field' can now be imported "
+                          "from the yt module.")
+
+from yt.fields.xray_emission_fields import \
+    add_xray_emissivity_field
\ No newline at end of file


https://bitbucket.org/yt_analysis/yt/commits/9f7297fa69b7/
Changeset:   9f7297fa69b7
Branch:      yt
User:        jzuhone
Date:        2016-12-07 16:42:42+00:00
Summary:     Cut down on noise by adding a sampling type
Affected #:  1 file

diff -r 4703ee80fc4a0e763f4249aae665bfc1b9663552 -r 9f7297fa69b76a4fe152c96f70daa027004713f6 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -214,7 +214,8 @@
                       "Assuming primordial H mass fraction.")
         def _nh(field, data):
             return primordial_H_mass_fraction*data["gas", "density"]/mp
-        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
+        ds.add_field(("gas", "H_number_density"), function=_nh, 
+                     sampling_type="cell", units="cm**-3")
 
     def _emissivity_field(field, data):
         dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
@@ -234,7 +235,7 @@
     emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
     ds.add_field(("gas", emiss_name), function=_emissivity_field,
                  display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/cm**3/s")
+                 sampling_type="cell", units="erg/cm**3/s")
 
     def _luminosity_field(field, data):
         return data[emiss_name] * data["cell_volume"]
@@ -242,7 +243,7 @@
     lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
     ds.add_field(("gas", lum_name), function=_luminosity_field,
                  display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/s")
+                 sampling_type="cell", units="erg/s")
 
     def _photon_emissivity_field(field, data):
         dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
@@ -262,7 +263,7 @@
     phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
     ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
                  display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="photons/cm**3/s")
+                 sampling_type="cell", units="photons/cm**3/s")
 
     fields = [emiss_name, lum_name, phot_name]
 
@@ -284,7 +285,7 @@
             return I.in_units("erg/cm**2/s/arcsec**2")
         ds.add_field(("gas", ei_name), function=_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
-                     units="erg/cm**2/s/arcsec**2")
+                     sampling_type="cell", units="erg/cm**2/s/arcsec**2")
 
         i_name = "xray_photon_intensity_%s_%s_keV" % (e_min, e_max)
         def _photon_intensity_field(field, data):
@@ -292,7 +293,7 @@
             return I.in_units("photons/cm**2/s/arcsec**2")
         ds.add_field(("gas", i_name), function=_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
-                     units="photons/cm**2/s/arcsec**2")
+                     sampling_type="cell", units="photons/cm**2/s/arcsec**2")
 
         fields += [ei_name, i_name]
 


https://bitbucket.org/yt_analysis/yt/commits/b0f77dd50469/
Changeset:   b0f77dd50469
Branch:      yt
User:        jzuhone
Date:        2016-12-07 16:44:12+00:00
Summary:     A bit more info here
Affected #:  1 file

diff -r 9f7297fa69b76a4fe152c96f70daa027004713f6 -r b0f77dd504695bef6cd9d484fe7a857122e48647 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -104,8 +104,8 @@
         if in_file.attrs["version"] != data_version[table_type]:
             raise ObsoleteDataException(table_type)
         else:
-            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                         in_file.attrs["version"])
+            only_on_root(mylog.info, "X-ray emissivity data version: '%s'/%s." % \
+                         (table_type, in_file.attrs["version"]))
 
         self.log_T = in_file["log_T"][:]
         self.emissivity_primordial = in_file["emissivity_primordial"][:]


https://bitbucket.org/yt_analysis/yt/commits/9761cb9747a6/
Changeset:   9761cb9747a6
Branch:      yt
User:        jzuhone
Date:        2016-12-07 18:47:49+00:00
Summary:     Make this easier to read
Affected #:  1 file

diff -r b0f77dd504695bef6cd9d484fe7a857122e48647 -r 9761cb9747a616cb27a484e5bfd47d08b5b3bf21 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -104,7 +104,7 @@
         if in_file.attrs["version"] != data_version[table_type]:
             raise ObsoleteDataException(table_type)
         else:
-            only_on_root(mylog.info, "X-ray emissivity data version: '%s'/%s." % \
+            only_on_root(mylog.info, "X-ray '%s' emissivity data version: %s." % \
                          (table_type, in_file.attrs["version"]))
 
         self.log_T = in_file["log_T"][:]


https://bitbucket.org/yt_analysis/yt/commits/4c4c425a2537/
Changeset:   4c4c425a2537
Branch:      yt
User:        jzuhone
Date:        2016-12-08 20:18:42+00:00
Summary:     Don’t auto-download data. Data files now have versions in the name.
Affected #:  1 file

diff -r 9761cb9747a616cb27a484e5bfd47d08b5b3bf21 -r 4c4c425a253797f0dbf59fcaf608092f6f204a1b yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -19,7 +19,7 @@
 import os
 
 from yt.fields.derived_field import DerivedField
-from yt.funcs import mylog, only_on_root, download_file
+from yt.funcs import mylog, only_on_root
 from yt.utilities.exceptions import YTFieldNotFound
 from yt.utilities.exceptions import YTException
 from yt.utilities.linear_interpolators import \
@@ -36,7 +36,7 @@
 data_url = "http://yt-project.org/data"
 
 def _get_data_file(table_type, data_dir=None):
-    data_file = "%s_emissivity.h5" % table_type
+    data_file = "%s_v%d_emissivity.h5" % (table_type, data_version[table_type])
     if data_dir is None:
         if "YT_DEST" in os.environ and \
             os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
@@ -47,12 +47,8 @@
             data_dir = "."
     data_path = os.path.join(data_dir, data_file)
     if not os.path.exists(data_path):
-        mylog.info("Attempting to download supplementary data from %s to '%s'." %
-                   (data_url, data_dir))
-        fn = download_file(os.path.join(data_url, data_file), data_path)
-        if fn != data_path:
-            mylog.error("Failed to download supplementary data.")
-            raise IOError("Failed to find emissivity data file %s!" % data_file)
+        mylog.error("Failed to find emissivity data file %s!" % data_file)
+        raise IOError("Failed to find emissivity data file %s!" % data_file)
     return data_path
 
 class EnergyBoundsException(YTException):
@@ -66,8 +62,9 @@
 
 class ObsoleteDataException(YTException):
     def __init__(self, table_type):
+        data_file = "%s_v%d_emissivity.h5" % (table_type, data_version[table_type])
         self.msg = "X-ray emissivity data is out of date.\n"
-        self.msg += "Download the latest data from %s/%s_emissivity.h5." % (data_url, table_type)
+        self.msg += "Download the latest data from %s/%s." % (data_url, data_file)
 
     def __str__(self):
         return self.msg


https://bitbucket.org/yt_analysis/yt/commits/7c2c39427bed/
Changeset:   7c2c39427bed
Branch:      yt
User:        jzuhone
Date:        2016-12-08 20:36:55+00:00
Summary:     Move the version number to the end of the filename
Affected #:  1 file

diff -r 4c4c425a253797f0dbf59fcaf608092f6f204a1b -r 7c2c39427bedec15cd93561e3e29c022f751cd48 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -36,7 +36,7 @@
 data_url = "http://yt-project.org/data"
 
 def _get_data_file(table_type, data_dir=None):
-    data_file = "%s_v%d_emissivity.h5" % (table_type, data_version[table_type])
+    data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
     if data_dir is None:
         if "YT_DEST" in os.environ and \
             os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
@@ -62,7 +62,7 @@
 
 class ObsoleteDataException(YTException):
     def __init__(self, table_type):
-        data_file = "%s_v%d_emissivity.h5" % (table_type, data_version[table_type])
+        data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
         self.msg = "X-ray emissivity data is out of date.\n"
         self.msg += "Download the latest data from %s/%s." % (data_url, data_file)
 


https://bitbucket.org/yt_analysis/yt/commits/cc691b668e31/
Changeset:   cc691b668e31
Branch:      yt
User:        jzuhone
Date:        2016-12-11 17:13:28+00:00
Summary:     Look for data file in supp_data_dir
Affected #:  1 file

diff -r 7c2c39427bedec15cd93561e3e29c022f751cd48 -r cc691b668e3154a0b056e4f76fc83c317344b636 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -18,6 +18,7 @@
 import numpy as np
 import os
 
+from yt.config import ytcfg
 from yt.fields.derived_field import DerivedField
 from yt.funcs import mylog, only_on_root
 from yt.utilities.exceptions import YTFieldNotFound
@@ -38,17 +39,14 @@
 def _get_data_file(table_type, data_dir=None):
     data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
     if data_dir is None:
-        if "YT_DEST" in os.environ and \
-            os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
-            # Try in default path
-            data_dir = os.path.join(os.environ["YT_DEST"], "data")
-        else:
-            # Try in current working directory
-            data_dir = "."
+        supp_data_dir = ytcfg.get("yt", "supp_data_dir")
+        data_dir = supp_data_dir if os.path.exists(supp_data_dir) else "."
     data_path = os.path.join(data_dir, data_file)
     if not os.path.exists(data_path):
-        mylog.error("Failed to find emissivity data file %s!" % data_file)
-        raise IOError("Failed to find emissivity data file %s!" % data_file)
+        msg = "Failed to find emissivity data file %s! " % data_file + \
+            "Please download from http://yt-project.org/data!"
+        mylog.error(msg)
+        raise IOError(msg)
     return data_path
 
 class EnergyBoundsException(YTException):


https://bitbucket.org/yt_analysis/yt/commits/da677a77af75/
Changeset:   da677a77af75
Branch:      yt
User:        jzuhone
Date:        2017-01-06 19:28:39+00:00
Summary:     Merge
Affected #:  94 files

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 .hgignore
--- a/.hgignore
+++ b/.hgignore
@@ -27,6 +27,7 @@
 yt/utilities/kdtree/forthonf2c.h
 yt/utilities/libconfig_wrapper.c
 yt/utilities/spatial/ckdtree.c
+yt/utilities/lib/allocation_container.c
 yt/utilities/lib/alt_ray_tracers.c
 yt/utilities/lib/amr_kdtools.c
 yt/utilities/lib/basic_octree.c

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/install_script.sh
--- a/doc/install_script.sh
+++ b/doc/install_script.sh
@@ -1505,7 +1505,8 @@
     else
         echo "Building yt from source"
         YT_DIR="${DEST_DIR}/src/yt-hg"
-        log_cmd ${DEST_DIR}/bin/hg clone -r ${BRANCH} https://bitbucket.org/yt_analysis/yt ${YT_DIR}
+        log_cmd ${DEST_DIR}/bin/hg clone https://bitbucket.org/yt_analysis/yt ${YT_DIR}
+        log_cmd ${DEST_DIR}/bin/hg -R ${YT_DIR} up -C ${BRANCH}
         if [ $INST_EMBREE -eq 1 ]
         then
             echo $DEST_DIR > ${YT_DIR}/embree.cfg

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/analyzing/fields.rst
--- a/doc/source/analyzing/fields.rst
+++ b/doc/source/analyzing/fields.rst
@@ -64,6 +64,24 @@
 You can use this to easily explore available fields, particularly through
 tab-completion in Jupyter/IPython.
 
+It's also possible to iterate over the list of fields associated with each
+field type. For example, to print all of the ``'gas'`` fields, one might do:
+
+.. code-block:: python
+
+   for field in ds.fields.gas:
+       print(field)
+
+You can also check if a given field is associated with a field type using
+standard python syntax:
+
+.. code-block:: python
+
+   # these examples evaluate to True for a dataset that has ('gas', 'density')
+   'density' in ds.fields.gas
+   ('gas', 'density') in ds.fields.gas
+   ds.fields.gas.density in ds.fields.gas
+
 For a more programmatic method of accessing fields, you can utilize the
 ``ds.field_list``, ``ds.derived_field_list`` and some accessor methods to gain
 information about fields.  The full list of fields available for a dataset can

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/analyzing/objects.rst
--- a/doc/source/analyzing/objects.rst
+++ b/doc/source/analyzing/objects.rst
@@ -64,6 +64,18 @@
        print("(%f,  %f,  %f)    %f" %
              (sp["x"][i], sp["y"][i], sp["z"][i], sp["temperature"][i]))
 
+Data objects can also be cloned; for instance:
+
+.. code-block:: python
+
+   import yt
+   ds = yt.load("RedshiftOutput0005")
+   sp = ds.sphere([0.5, 0.5, 0.5], (1, 'kpc'))
+   sp_copy = sp.clone()
+
+This can be useful for when manually chunking data or exploring different field
+parameters.
+
 .. _quickly-selecting-data:
 
 Slicing Syntax for Selecting Data
@@ -618,7 +630,7 @@
 ---------------------------------------
 
 A special type of data object is the *boolean* data object, which works with
-three-dimensional data selection.  It is built by relating already existing
+data selection objects of any dimension.  It is built by relating already existing
 data objects with the bitwise operators for AND, OR and XOR, as well as the
 subtraction operator.  These are created by using the operators ``&`` for an
 intersection ("AND"), ``|`` for a union ("OR"), ``^`` for an exclusive or

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/installing.rst
--- a/doc/source/installing.rst
+++ b/doc/source/installing.rst
@@ -95,22 +95,17 @@
 Running the Install Script
 ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-To get the installation script for the ``stable`` branch of the code,
-download it using the following command:
+You can download the installation script with the following command:
 
 .. code-block:: bash
 
-  $ wget http://bitbucket.org/yt_analysis/yt/raw/stable/doc/install_script.sh
+  $ wget http://bitbucket.org/yt_analysis/yt/raw/yt/doc/install_script.sh
 
 If you do not have ``wget``, the following should also work:
 
 .. code-block:: bash
 
-  $ curl -OL http://bitbucket.org/yt_analysis/yt/raw/stable/doc/install_script.sh
-
-If you wish to install a different version of yt (see :ref:`branches-of-yt`),
-replace ``stable`` with the appropriate branch name (e.g. ``yt``, ``yt-2.x``) in
-the path above to get the correct install script.
+  $ curl -OL http://bitbucket.org/yt_analysis/yt/raw/yt/doc/install_script.sh
 
 By default, the bash install script will create a python environment based on
 the `miniconda python distrubtion <http://conda.pydata.org/miniconda.html>`_,

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/reference/command-line.rst
--- a/doc/source/reference/command-line.rst
+++ b/doc/source/reference/command-line.rst
@@ -276,6 +276,34 @@
    $ yt hub start
    $ yt hub start /user/xarthisius/Public
 
+download
+~~~~~~~~
+
+This subcommand downloads a file from http://yt-project.org/data. Using ``yt download``, 
+one can download a file to:
+
+* ``"test_data_dir"``: Save the file to the location specified in 
+  the ``"test_data_dir"`` configuration entry for test data.
+* ``"supp_data_dir"``: Save the file to the location specified in 
+  the ``"supp_data_dir"`` configuration entry for supplemental data.
+* Any valid path to a location on disk, e.g. ``/home/jzuhone/data``.
+
+Examples:
+
+.. code-block:: bash
+
+   $ yt download apec_emissivity_v2.h5 supp_data_dir
+
+.. code-block:: bash
+
+   $ yt download GasSloshing.tar.gz test_data_dir
+
+.. code-block:: bash 
+
+   $ yt download ZeldovichPancake.tar.gz /Users/jzuhone/workspace
+
+If the configuration values ``"test_data_dir"`` or ``"supp_data_dir"`` have not
+been set by the user, an error will be thrown. 
 
 Config helper
 ~~~~~~~~~~~~~

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/reference/configuration.rst
--- a/doc/source/reference/configuration.rst
+++ b/doc/source/reference/configuration.rst
@@ -109,6 +109,8 @@
   to stdout rather than stderr
 * ``skip_dataset_cache`` (default: ``'False'``): If true, automatic caching of datasets
   is turned off.
+* ``supp_data_dir`` (default: ``'/does/not/exist'``): The default path certain
+  submodules of yt look in for supplemental data files.
 
 .. _plugin-file:
 

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/visualizing/FITSImageData.ipynb
--- a/doc/source/visualizing/FITSImageData.ipynb
+++ b/doc/source/visualizing/FITSImageData.ipynb
@@ -15,8 +15,7 @@
    },
    "outputs": [],
    "source": [
-    "import yt\n",
-    "from yt.utilities.fits_image import FITSImageData, FITSProjection"
+    "import yt"
    ]
   },
   {
@@ -27,9 +26,9 @@
    },
    "outputs": [],
    "source": [
-    "ds = yt.load(\"MHDSloshing/virgo_low_res.0054.vtk\", parameters={\"length_unit\":(1.0,\"Mpc\"),\n",
-    "                                                               \"mass_unit\":(1.0e14,\"Msun\"),\n",
-    "                                                               \"time_unit\":(1.0,\"Myr\")})"
+    "ds = yt.load(\"MHDSloshing/virgo_low_res.0054.vtk\", units_override={\"length_unit\":(1.0,\"Mpc\"),\n",
+    "                                                                   \"mass_unit\":(1.0e14,\"Msun\"),\n",
+    "                                                                   \"time_unit\":(1.0,\"Myr\")})"
    ]
   },
   {
@@ -73,7 +72,7 @@
    },
    "outputs": [],
    "source": [
-    "prj_fits = FITSProjection(ds, \"z\", [\"temperature\"], weight_field=\"density\")"
+    "prj_fits = yt.FITSProjection(ds, \"z\", [\"temperature\"], weight_field=\"density\")"
    ]
   },
   {
@@ -236,7 +235,7 @@
    "source": [
     "slc3 = ds.slice(0, 0.0)\n",
     "frb = slc3.to_frb((500.,\"kpc\"), 800)\n",
-    "fid_frb = FITSImageData(frb, fields=[\"density\",\"temperature\"], units=\"pc\")"
+    "fid_frb = yt.FITSImageData(frb, fields=[\"density\",\"temperature\"], units=\"pc\")"
    ]
   },
   {
@@ -255,7 +254,7 @@
    "outputs": [],
    "source": [
     "cvg = ds.covering_grid(ds.index.max_level, [-0.5,-0.5,-0.5], [64, 64, 64], fields=[\"density\",\"temperature\"])\n",
-    "fid_cvg = FITSImageData(cvg, fields=[\"density\",\"temperature\"], units=\"Mpc\")"
+    "fid_cvg = yt.FITSImageData(cvg, fields=[\"density\",\"temperature\"], units=\"Mpc\")"
    ]
   },
   {
@@ -280,7 +279,7 @@
    },
    "outputs": [],
    "source": [
-    "fid = FITSImageData.from_file(\"sloshing.fits\")\n",
+    "fid = yt.FITSImageData.from_file(\"sloshing.fits\")\n",
     "fid.info()"
    ]
   },
@@ -299,8 +298,8 @@
    },
    "outputs": [],
    "source": [
-    "prj_fits2 = FITSProjection(ds, \"z\", [\"density\"])\n",
-    "prj_fits3 = FITSImageData.from_images([prj_fits, prj_fits2])\n",
+    "prj_fits2 = yt.FITSProjection(ds, \"z\", [\"density\"])\n",
+    "prj_fits3 = yt.FITSImageData.from_images([prj_fits, prj_fits2])\n",
     "prj_fits3.info()"
    ]
   },
@@ -348,7 +347,27 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "So far, the FITS images we have shown have linear spatial coordinates. One may want to take a projection of an object and make a crude mock observation out of it, with celestial coordinates. For this, we can use the `create_sky_wcs` method. Specify a center (RA, Dec) coordinate in degrees, as well as a linear scale in terms of angle per distance:"
+    "So far, the FITS images we have shown have linear spatial coordinates. We can see this by looking at the header for one of the fields, and examining the `CTYPE1` and `CTYPE2` keywords:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "prj_fits[\"temperature\"].header"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The `WCSNAME` keyword is set to `\"yt\"` by default. \n",
+    "\n",
+    "However, one may want to take a projection of an object and make a crude mock observation out of it, with celestial coordinates. For this, we can use the `create_sky_wcs` method. Specify a center (RA, Dec) coordinate in degrees, as well as a linear scale in terms of angle per distance:"
    ]
   },
   {
@@ -368,7 +387,7 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "By the default, a tangent RA/Dec projection is used, but one could also use another projection using the `ctype` keyword. We can now look at the header and see it has the appropriate WCS:"
+    "By default, a tangent RA/Dec projection is used, but one could also use another projection using the `ctype` keyword. We can now look at the header and see it has the appropriate WCS:"
    ]
   },
   {
@@ -386,6 +405,49 @@
    "cell_type": "markdown",
    "metadata": {},
    "source": [
+    "and now the `WCSNAME` has been set to `\"celestial\"`. If you don't want to override the default WCS but to add another one, then you can make the call to `create_sky_wcs` and set `replace_old_wcs=False`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "prj_fits3.create_sky_wcs(sky_center, sky_scale, ctype=[\"RA---TAN\",\"DEC--TAN\"], replace_old_wcs=False)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "We now can see that there are two WCSes in the header, with the celestial WCS keywords having the \"A\" designation:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "prj_fits3[\"temperature\"].header"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Any further WCSes that are added will have \"B\", \"C\", etc."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
     "Finally, we can add header keywords to a single field or for all fields in the FITS image using `update_header`:"
    ]
   },
@@ -415,22 +477,11 @@
   }
  ],
  "metadata": {
+  "anaconda-cloud": {},
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python [default]",
    "language": "python",
    "name": "python3"
-  },
-  "language_info": {
-   "codemirror_mode": {
-    "name": "ipython",
-    "version": 3
-   },
-   "file_extension": ".py",
-   "mimetype": "text/x-python",
-   "name": "python",
-   "nbconvert_exporter": "python",
-   "pygments_lexer": "ipython3",
-   "version": "3.5.1"
   }
  },
  "nbformat": 4,

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/visualizing/colormaps/index.rst
--- a/doc/source/visualizing/colormaps/index.rst
+++ b/doc/source/visualizing/colormaps/index.rst
@@ -50,6 +50,25 @@
 colorblind/printer/grayscale-friendly plots. For more information, visit
 `http://colorbrewer2.org <http://colorbrewer2.org>`_.
 
+.. _cmocean-cmaps:
+
+Colormaps from cmocean
+~~~~~~~~~~~~~~~~~~~~~~
+
+In addition to ``palettable``, yt will also import colormaps defined in the
+`cmocean <http://matplotlib.org/cmocean>`_ package. These colormaps are
+`perceptually uniform <http://bids.github.io/colormap/>`_ and were originally
+designed for oceanography applications, but can be used for any kind of plots.
+
+Since ``cmocean`` is not installed as a dependency of yt by default, it must be
+installed separately to access the ``cmocean`` colormaps with yt. The easiest
+way to install ``cmocean`` is via ``pip``: ``pip install cmocean``.  To access
+the colormaps in yt, simply specify the name of the ``cmocean`` colormap in any
+context where you would specify a colormap. One caveat is the ``cmocean``
+colormap ``algae``. Since yt already defines a colormap named ``algae``, the
+``cmocean`` version of ``algae`` must be specified with the name
+``algae_cmocean``.
+
 .. _custom-colormaps:
 
 Making and Viewing Custom Colormaps

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 doc/source/visualizing/plots.rst
--- a/doc/source/visualizing/plots.rst
+++ b/doc/source/visualizing/plots.rst
@@ -670,6 +670,21 @@
    slc.set_log('x-velocity', True, linthresh=1.e1)
    slc.save()
 
+The :meth:`~yt.visualization.plot_container.ImagePlotContainer.set_background_color`
+function accepts a field name and a color (optional). If color is given, the function
+will set the plot's background color to that. If not, it will set it to the bottom
+value of the color map.
+
+.. python-script::
+
+   import yt
+   ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
+   slc = yt.SlicePlot(ds, 'z', 'density', width=(1.5, 'Mpc'))
+   slc.set_background_color('density')
+   slc.save('bottom_colormap_background')
+   slc.set_background_color('density', color='black')
+   slc.save('black_background')
+
 Lastly, the :meth:`~yt.visualization.plot_window.AxisAlignedSlicePlot.set_zlim`
 function makes it possible to set a custom colormap range.
 

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 setup.cfg
--- a/setup.cfg
+++ b/setup.cfg
@@ -13,6 +13,6 @@
 #      unused import errors
 #      autogenerated __config__.py files
 #      vendored libraries
-exclude = doc,benchmarks,*/api.py,*/__init__.py,*/__config__.py,yt/visualization/_mpl_imports.py,yt/utilities/lodgeit.py,yt/utilities/lru_cache.py,yt/utilities/poster/*,yt/extern/*,yt/mods.py
+exclude = doc,benchmarks,*/api.py,*/__init__.py,*/__config__.py,yt/visualization/_mpl_imports.py,yt/utilities/lodgeit.py,yt/utilities/lru_cache.py,yt/utilities/poster/*,yt/extern/*,yt/mods.py,yt/utilities/fits_image.py
 max-line-length=999
 ignore = E111,E121,E122,E123,E124,E125,E126,E127,E128,E129,E131,E201,E202,E211,E221,E222,E227,E228,E241,E301,E203,E225,E226,E231,E251,E261,E262,E265,E266,E302,E303,E402,E502,E701,E703,E731,W291,W292,W293,W391,W503
\ No newline at end of file

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 setup.py
--- a/setup.py
+++ b/setup.py
@@ -180,7 +180,7 @@
     "particle_mesh_operations", "depth_first_octree", "fortran_reader",
     "interpolators", "misc_utilities", "basic_octree", "image_utilities",
     "points_in_volume", "quad_tree", "ray_integrators", "mesh_utilities",
-    "amr_kdtools", "lenses", "distance_queue"
+    "amr_kdtools", "lenses", "distance_queue", "allocation_container"
 ]
 for ext_name in lib_exts:
     cython_extensions.append(
@@ -298,6 +298,15 @@
                 fobj.write("hg_version = '%s'\n" % changeset)
         _build_py.run(self)
 
+    def get_outputs(self):
+        # http://bitbucket.org/yt_analysis/yt/issues/1296
+        outputs = _build_py.get_outputs(self)
+        outputs.append(
+            os.path.join(self.build_lib, 'yt', '__hg_version__.py')
+        )
+        return outputs
+
+
 class build_ext(_build_ext):
     # subclass setuptools extension builder to avoid importing cython and numpy
     # at top level in setup.py. See http://stackoverflow.com/a/21621689/1382869
@@ -308,7 +317,12 @@
         _build_ext.finalize_options(self)
         # Prevent numpy from thinking it is still in its setup process
         # see http://stackoverflow.com/a/21621493/1382869
-        __builtins__.__NUMPY_SETUP__ = False
+        if isinstance(__builtins__, dict):
+            # sometimes this is a dict so we need to check for that
+            # https://docs.python.org/3/library/builtins.html
+            __builtins__["__NUMPY_SETUP__"] = False
+        else:
+            __builtins__.__NUMPY_SETUP__ = False
         import numpy
         self.include_dirs.append(numpy.get_include())
 
@@ -325,9 +339,7 @@
 setup(
     name="yt",
     version=VERSION,
-    description="An analysis and visualization toolkit for Astrophysical "
-                + "simulations, focusing on Adaptive Mesh Refinement data "
-                  "from Enzo, Orion, FLASH, and others.",
+    description="An analysis and visualization toolkit for volumetric data",
     classifiers=["Development Status :: 5 - Production/Stable",
                  "Environment :: Console",
                  "Intended Audience :: Science/Research",

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 setupext.py
--- a/setupext.py
+++ b/setupext.py
@@ -45,7 +45,7 @@
         
         if exit_code != 0:
             print("Compilation of OpenMP test code failed with the error: ")
-            print(err)
+            print(err.decode('utf8'))
             print("Disabling OpenMP support. ")
 
         # Clean up

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 tests/nose_runner.py
--- a/tests/nose_runner.py
+++ b/tests/nose_runner.py
@@ -6,6 +6,8 @@
 from yt.extern.six import StringIO
 from yt.config import ytcfg
 from yt.utilities.answer_testing.framework import AnswerTesting
+import numpy
+numpy.set_printoptions(threshold=5, edgeitems=1, precision=4)
 
 class NoseWorker(multiprocessing.Process):
 
@@ -67,7 +69,7 @@
                       if DROP_TAG not in line])
     tests = yaml.load(data)
 
-    base_argv = ['--local-dir=%s' % answers_dir, '-v',
+    base_argv = ['--local-dir=%s' % answers_dir,
                  '--with-answer-testing', '--answer-big-data', '--local']
     args = []
 

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -1,45 +1,45 @@
 answer_tests:
-  local_artio_000:
+  local_artio_001:
     - yt/frontends/artio/tests/test_outputs.py
 
-  local_athena_001:
+  local_athena_002:
     - yt/frontends/athena
 
-  local_chombo_001:
+  local_chombo_002:
     - yt/frontends/chombo/tests/test_outputs.py
 
-  local_enzo_002:
+  local_enzo_003:
     - yt/frontends/enzo
 
-  local_fits_000:
+  local_fits_001:
     - yt/frontends/fits/tests/test_outputs.py
 
-  local_flash_003:
+  local_flash_004:
     - yt/frontends/flash/tests/test_outputs.py
 
-  local_gadget_000:
+  local_gadget_001:
     - yt/frontends/gadget/tests/test_outputs.py
 
-  local_gamer_001:
+  local_gamer_002:
     - yt/frontends/gamer/tests/test_outputs.py
 
-  local_gdf_000:
+  local_gdf_001:
     - yt/frontends/gdf/tests/test_outputs.py
 
-  local_gizmo_001:
+  local_gizmo_002:
     - yt/frontends/gizmo/tests/test_outputs.py
 
-  local_halos_000:
+  local_halos_001:
     - yt/analysis_modules/halo_analysis/tests/test_halo_finders.py  # [py2]
     - yt/analysis_modules/halo_finding/tests/test_rockstar.py  # [py2]
     - yt/frontends/owls_subfind/tests/test_outputs.py
     - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g5
     - yt/frontends/gadget_fof/tests/test_outputs.py:test_fields_g42
 
-  local_owls_000:
+  local_owls_001:
     - yt/frontends/owls/tests/test_outputs.py
 
-  local_pw_011:
+  local_pw_012:
     - yt/visualization/tests/test_plotwindow.py:test_attributes
     - yt/visualization/tests/test_plotwindow.py:test_attributes_wt
     - yt/visualization/tests/test_profile_plots.py:test_phase_plot_attributes
@@ -47,10 +47,10 @@
     - yt/visualization/tests/test_particle_plot.py:test_particle_projection_filter
     - yt/visualization/tests/test_particle_plot.py:test_particle_phase_answers
 
-  local_tipsy_001:
+  local_tipsy_002:
     - yt/frontends/tipsy/tests/test_outputs.py
 
-  local_varia_006:
+  local_varia_007:
     - yt/analysis_modules/radmc3d_export
     - yt/frontends/moab/tests/test_c5.py
     - yt/analysis_modules/photon_simulator/tests/test_spectra.py
@@ -59,13 +59,13 @@
     - yt/visualization/volume_rendering/tests/test_mesh_render.py
     - yt/visualization/tests/test_mesh_slices.py:test_tri2
 
-  local_orion_000:
+  local_orion_001:
     - yt/frontends/boxlib/tests/test_orion.py
 
-  local_ramses_000:
+  local_ramses_001:
     - yt/frontends/ramses/tests/test_outputs.py
 
-  local_ytdata_001:
+  local_ytdata_002:
     - yt/frontends/ytdata
 
   local_absorption_spectrum_005:
@@ -81,8 +81,6 @@
 
 other_tests:
   unittests:
-     - '-v'
      - '--exclude=test_mesh_slices'  # disable randomly failing test
   cookbook:
-     - '-v'
      - 'doc/source/cookbook/tests/test_cookbook.py'

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/__init__.py
--- a/yt/__init__.py
+++ b/yt/__init__.py
@@ -157,7 +157,9 @@
     ProjectionPlot, OffAxisProjectionPlot, \
     show_colormaps, add_cmap, make_colormap, \
     ProfilePlot, PhasePlot, ParticlePhasePlot, \
-    ParticleProjectionPlot, ParticleImageBuffer, ParticlePlot
+    ParticleProjectionPlot, ParticleImageBuffer, ParticlePlot, \
+    FITSImageData, FITSSlice, FITSProjection, FITSOffAxisSlice, \
+    FITSOffAxisProjection
 
 from yt.visualization.volume_rendering.api import \
     volume_render, create_scene, ColorTransferFunction, TransferFunction, \

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/absorption_spectrum/absorption_spectrum.py
--- a/yt/analysis_modules/absorption_spectrum/absorption_spectrum.py
+++ b/yt/analysis_modules/absorption_spectrum/absorption_spectrum.py
@@ -495,8 +495,8 @@
                 window_width_in_bins = 2
 
                 while True:
-                    left_index = (center_index[i] - window_width_in_bins/2)
-                    right_index = (center_index[i] + window_width_in_bins/2)
+                    left_index = (center_index[i] - window_width_in_bins//2)
+                    right_index = (center_index[i] + window_width_in_bins//2)
                     n_vbins = (right_index - left_index) * n_vbins_per_bin[i]
 
                     # the array of virtual bins in lambda space

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/cosmological_observation/light_ray/light_ray.py
--- a/yt/analysis_modules/cosmological_observation/light_ray/light_ray.py
+++ b/yt/analysis_modules/cosmological_observation/light_ray/light_ray.py
@@ -673,6 +673,25 @@
                 ds["hubble_constant"] = \
                   ds["hubble_constant"].to("100*km/(Mpc*s)").d
         extra_attrs = {"data_type": "yt_light_ray"}
+
+        # save the light ray solution
+        if len(self.light_ray_solution) > 0:
+            # Convert everything to base unit system now to avoid
+            # problems with different units for each ds.
+            for s in self.light_ray_solution:
+                for f in s:
+                    if isinstance(s[f], YTArray):
+                        s[f].convert_to_base()
+            for key in self.light_ray_solution[0]:
+                if key in ["next", "previous", "index"]:
+                    continue
+                lrsa = [sol[key] for sol in self.light_ray_solution]
+                if isinstance(lrsa[-1], YTArray):
+                    to_arr = YTArray
+                else:
+                    to_arr = np.array
+                extra_attrs["light_ray_solution_%s" % key] = to_arr(lrsa)
+
         field_types = dict([(field, "grid") for field in data.keys()])
 
         # Only return LightRay elements with non-zero density

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/cosmological_observation/light_ray/tests/test_light_ray.py
--- a/yt/analysis_modules/cosmological_observation/light_ray/tests/test_light_ray.py
+++ b/yt/analysis_modules/cosmological_observation/light_ray/tests/test_light_ray.py
@@ -12,7 +12,10 @@
 
 import numpy as np
 
+from yt.convenience import \
+    load
 from yt.testing import \
+    assert_array_equal, \
     requires_file
 from yt.analysis_modules.cosmological_observation.api import LightRay
 import os
@@ -23,6 +26,19 @@
 COSMO_PLUS = "enzo_cosmology_plus/AMRCosmology.enzo"
 COSMO_PLUS_SINGLE = "enzo_cosmology_plus/RD0009/RD0009"
 
+def compare_light_ray_solutions(lr1, lr2):
+    assert len(lr1.light_ray_solution) == len(lr2.light_ray_solution)
+    if len(lr1.light_ray_solution) == 0:
+        return
+    for s1, s2 in zip(lr1.light_ray_solution, lr2.light_ray_solution):
+        for field in s1:
+            if field in ["next", "previous"]:
+                continue
+            if isinstance(s1[field], np.ndarray):
+                assert_array_equal(s1[field], s2[field])
+            else:
+                assert s1[field] == s2[field]
+
 @requires_file(COSMO_PLUS)
 def test_light_ray_cosmo():
     """
@@ -39,6 +55,9 @@
                       fields=['temperature', 'density', 'H_number_density'],
                       data_filename='lightray.h5')
 
+    ds = load('lightray.h5')
+    compare_light_ray_solutions(lr, ds)
+
     # clean up
     os.chdir(curdir)
     shutil.rmtree(tmpdir)
@@ -62,6 +81,9 @@
                       fields=['temperature', 'density', 'H_number_density'],
                       data_filename='lightray.h5')
 
+    ds = load('lightray.h5')
+    compare_light_ray_solutions(lr, ds)
+
     # clean up
     os.chdir(curdir)
     shutil.rmtree(tmpdir)
@@ -82,6 +104,9 @@
                       fields=['temperature', 'density', 'H_number_density'],
                       data_filename='lightray.h5')
 
+    ds = load('lightray.h5')
+    compare_light_ray_solutions(lr, ds)
+
     # clean up
     os.chdir(curdir)
     shutil.rmtree(tmpdir)
@@ -105,6 +130,9 @@
                       fields=['temperature', 'density', 'H_number_density'],
                       data_filename='lightray.h5')
 
+    ds = load('lightray.h5')
+    compare_light_ray_solutions(lr, ds)
+
     # clean up
     os.chdir(curdir)
     shutil.rmtree(tmpdir)
@@ -130,6 +158,9 @@
                       fields=['temperature', 'density', 'H_number_density'],
                       data_filename='lightray.h5')
 
+    ds = load('lightray.h5')
+    compare_light_ray_solutions(lr, ds)
+
     # clean up
     os.chdir(curdir)
     shutil.rmtree(tmpdir)

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/particle_trajectories/particle_trajectories.py
--- a/yt/analysis_modules/particle_trajectories/particle_trajectories.py
+++ b/yt/analysis_modules/particle_trajectories/particle_trajectories.py
@@ -99,7 +99,7 @@
         pbar = get_pbar("Constructing trajectory information", len(self.data_series))
         for i, (sto, ds) in enumerate(self.data_series.piter(storage=my_storage)):
             dd = ds.all_data()
-            newtags = dd[idx_field].ndarray_view().astype("int64")
+            newtags = dd[idx_field].d.astype("int64")
             mask = np.in1d(newtags, indices, assume_unique=True)
             sort = np.argsort(newtags[mask])
             array_indices = np.where(np.in1d(indices, newtags, assume_unique=True))[0]
@@ -197,7 +197,6 @@
 
         Examples
         ________
-        >>> from yt.mods import *
         >>> trajs = ParticleTrajectories(my_fns, indices)
         >>> trajs.add_fields(["particle_mass", "particle_gpot"])
         """
@@ -247,15 +246,15 @@
                 dd = ds.all_data()
                 for field in new_particle_fields:
                     # This is easy... just get the particle fields
-                    pfield[field] = dd[fds[field]].ndarray_view()[mask][sort]
+                    pfield[field] = dd[fds[field]].d[mask][sort]
 
             if grid_fields:
                 # This is hard... must loop over grids
                 for field in grid_fields:
-                    pfield[field] = np.zeros((self.num_indices))
-                x = self["particle_position_x"][:,step].ndarray_view()
-                y = self["particle_position_y"][:,step].ndarray_view()
-                z = self["particle_position_z"][:,step].ndarray_view()
+                    pfield[field] = np.zeros(self.num_indices)
+                x = self["particle_position_x"][:,step].d
+                y = self["particle_position_y"][:,step].d
+                z = self["particle_position_z"][:,step].d
                 particle_grids, particle_grid_inds = ds.index._find_points(x,y,z)
 
                 # This will fail for non-grid index objects
@@ -375,10 +374,10 @@
         >>> trajs.write_out_h5("orbit_trajectories")                
         """
         fid = h5py.File(filename, "w")
-        fields = [field for field in sorted(self.field_data.keys())]
         fid.create_dataset("particle_indices", dtype=np.int64,
                            data=self.indices)
-        fid.create_dataset("particle_time", data=self.times)
+        fid.close()
+        self.times.write_hdf5(filename, dataset_name="particle_times")
+        fields = [field for field in sorted(self.field_data.keys())]
         for field in fields:
-            fid.create_dataset("%s" % field, data=self[field])
-        fid.close()
+            self[field].write_hdf5(filename, dataset_name="%s" % field)

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/photon_simulator/api.py
--- a/yt/analysis_modules/photon_simulator/api.py
+++ b/yt/analysis_modules/photon_simulator/api.py
@@ -10,12 +10,10 @@
 # The full license is in the file COPYING.txt, distributed with this software.
 #-----------------------------------------------------------------------------
 
-from numpy import VisibleDeprecationWarning
+from yt.funcs import issue_deprecation_warning
 
-import warnings
-warnings.warn("The photon_simulator module is deprecated. "
-              "Please use pyXSIM (http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim) instead.",
-              VisibleDeprecationWarning, stacklevel=2)
+issue_deprecation_warning("The photon_simulator module is deprecated. Please use pyXSIM "
+                          "(http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim) instead.")
 
 from .photon_models import \
      PhotonModel, \

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/photon_simulator/photon_simulator.py
--- a/yt/analysis_modules/photon_simulator/photon_simulator.py
+++ b/yt/analysis_modules/photon_simulator/photon_simulator.py
@@ -31,7 +31,7 @@
 from yt.utilities.physical_constants import clight
 from yt.utilities.cosmology import Cosmology
 from yt.utilities.orientation import Orientation
-from yt.utilities.fits_image import assert_same_wcs
+from yt.visualization.fits_image import assert_same_wcs
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     communication_system, parallel_root_only, get_mpi_type, \
     parallel_capable

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/ppv_cube/ppv_cube.py
--- a/yt/analysis_modules/ppv_cube/ppv_cube.py
+++ b/yt/analysis_modules/ppv_cube/ppv_cube.py
@@ -13,7 +13,7 @@
 import numpy as np
 from yt.utilities.on_demand_imports import _astropy
 from yt.utilities.orientation import Orientation
-from yt.utilities.fits_image import FITSImageData, sanitize_fits_unit
+from yt.visualization.fits_image import FITSImageData, sanitize_fits_unit
 from yt.visualization.volume_rendering.off_axis_projection import off_axis_projection
 from yt.funcs import get_pbar
 from yt.utilities.physical_constants import clight, mh

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/analysis_modules/sunyaev_zeldovich/projection.py
--- a/yt/analysis_modules/sunyaev_zeldovich/projection.py
+++ b/yt/analysis_modules/sunyaev_zeldovich/projection.py
@@ -375,7 +375,7 @@
         >>> sky_center = (30., 45., "deg")
         >>> szprj.write_fits("SZbullet.fits", sky_center=sky_center, sky_scale=sky_scale)
         """
-        from yt.utilities.fits_image import FITSImageData
+        from yt.visualization.fits_image import FITSImageData
 
         dx = self.dx.in_units("kpc")
         dy = dx

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/config.py
--- a/yt/config.py
+++ b/yt/config.py
@@ -67,6 +67,7 @@
     ignore_invalid_unit_operation_errors = 'False',
     chunk_size = '1000',
     xray_data_dir = '/does/not/exist',
+    supp_data_dir = '/does/not/exist',
     default_colormap = 'arbre',
     ray_tracing_engine = 'embree',
     )

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/construction_data_containers.py
--- a/yt/data_objects/construction_data_containers.py
+++ b/yt/data_objects/construction_data_containers.py
@@ -56,6 +56,7 @@
 from yt.utilities.parallel_tools.parallel_analysis_interface import \
     parallel_objects, parallel_root_only, communication_system
 from yt.units.unit_object import Unit
+from yt.units.yt_array import uconcatenate
 import yt.geometry.particle_deposit as particle_deposit
 from yt.utilities.grid_data_format.writer import write_to_gdf
 from yt.fields.field_exceptions import \
@@ -411,9 +412,11 @@
                 path_length_unit = self.ds.field_info[path_element_name].units
                 path_length_unit = Unit(path_length_unit,
                                         registry=self.ds.unit_registry)
-                # Only convert to CGS for path elements that aren't angles
+                # Only convert to appropriate unit system for path
+                # elements that aren't angles
                 if not path_length_unit.is_dimensionless:
-                    path_length_unit = path_length_unit.get_cgs_equivalent()
+                    path_length_unit = path_length_unit.get_base_equivalent(
+                        unit_system=self.ds.unit_system)
             if self.weight_field is None:
                 self._projected_units[field] = field_unit*path_length_unit
             else:
@@ -1118,9 +1121,11 @@
                 verts.append(my_verts)
         verts = np.concatenate(verts).transpose()
         verts = self.comm.par_combine_object(verts, op='cat', datatype='array')
-        self.vertices = verts
+        # verts is an ndarray here and will always be in code units, so we
+        # expose it in the public API as a YTArray
+        self.vertices = self.ds.arr(verts, 'code_length')
         if fields is not None:
-            samples = np.concatenate(samples)
+            samples = uconcatenate(samples)
             samples = self.comm.par_combine_object(samples, op='cat',
                                 datatype='array')
             if sample_type == "face":
@@ -1222,17 +1227,24 @@
             ff = np.ones_like(vc_data[self.surface_field], dtype="float64")
         else:
             ff = vc_data[fluxing_field]
-
-        return march_cubes_grid_flux(
-            self.field_value, vc_data[self.surface_field], vc_data[field_x],
-            vc_data[field_y], vc_data[field_z], ff, mask, grid.LeftEdge,
-            grid.dds)
+        surf_vals = vc_data[self.surface_field]
+        field_x_vals = vc_data[field_x]
+        field_y_vals = vc_data[field_y]
+        field_z_vals = vc_data[field_z]
+        ret = march_cubes_grid_flux(
+            self.field_value, surf_vals, field_x_vals, field_y_vals, field_z_vals,
+            ff, mask, grid.LeftEdge, grid.dds)
+        # assumes all the fluxing fields have the same units
+        ret_units = field_x_vals.units * ff.units * grid.dds.units**2
+        ret = self.ds.arr(ret, ret_units)
+        ret.convert_to_units(self.ds.unit_system[ret_units.dimensions])
+        return ret
 
     @property
     def triangles(self):
         if self.vertices is None:
             self.get_data()
-        vv = np.empty((self.vertices.shape[1]/3, 3, 3), dtype="float64")
+        vv = np.empty((self.vertices.shape[1]//3, 3, 3), dtype="float64")
         for i in range(3):
             for j in range(3):
                 vv[:,i,j] = self.vertices[j,i::3]
@@ -1314,7 +1326,8 @@
         >>> def _Emissivity(field, data):
         ...     return (data['density']*data['density'] *
         ...             np.sqrt(data['temperature']))
-        >>> ds.add_field("emissivity", function=_Emissivity, units=r"g*K/cm**6")
+        >>> ds.add_field("emissivity", function=_Emissivity,
+        ...              sampling_type='cell', units=r"g**2*sqrt(K)/cm**6")
         >>> for i, r in enumerate(rhos):
         ...     surf = ds.surface(sp,'density',r)
         ...     surf.export_obj("my_galaxy", transparency=trans[i],
@@ -1429,22 +1442,22 @@
         vtype = [("x","float"),("y","float"), ("z","float")]
         if plot_index == 0:
             fobj.write("# yt OBJ file\n")
-            fobj.write("# www.yt-project.com\n")
+            fobj.write("# www.yt-project.org\n")
             fobj.write("mtllib " + filename + '.mtl\n\n')  # use this material file for the faces
             fmtl.write("# yt MLT file\n")
-            fmtl.write("# www.yt-project.com\n\n")
+            fmtl.write("# www.yt-project.org\n\n")
         #(0) formulate vertices
         nv = self.vertices.shape[1] # number of groups of vertices
-        f = np.empty(nv/self.vertices.shape[0], dtype=ftype) # store sets of face colors
+        f = np.empty(nv//self.vertices.shape[0], dtype=ftype) # store sets of face colors
         v = np.empty(nv, dtype=vtype) # stores vertices
         if color_field is not None:
             cs = self[color_field]
         else:
-            cs = np.empty(self.vertices.shape[1]/self.vertices.shape[0])
+            cs = np.empty(self.vertices.shape[1]//self.vertices.shape[0])
         if emit_field is not None:
             em = self[emit_field]
         else:
-            em = np.empty(self.vertices.shape[1]/self.vertices.shape[0])
+            em = np.empty(self.vertices.shape[1]//self.vertices.shape[0])
         self._color_samples_obj(cs, em, color_log, emit_log, color_map, f,
                                 color_field_max, color_field_min,  color_field,
                                 emit_field_max, emit_field_min, emit_field) # map color values to color scheme
@@ -1748,7 +1761,7 @@
             arr = np.empty(cs.shape[0], dtype=np.dtype(fs))
             self._color_samples(cs, color_log, color_map, arr)
         else:
-            arr = np.empty(nv/3, np.dtype(fs[:-3]))
+            arr = np.empty(nv//3, np.dtype(fs[:-3]))
         for i, ax in enumerate("xyz"):
             # Do the bounds first since we cast to f32
             tmp = self.vertices[i,:]
@@ -1761,7 +1774,7 @@
         v.tofile(f)
         arr["ni"][:] = 3
         vi = np.arange(nv, dtype="<i")
-        vi.shape = (nv/3, 3)
+        vi.shape = (nv//3, 3)
         arr["v1"][:] = vi[:,0]
         arr["v2"][:] = vi[:,1]
         arr["v3"][:] = vi[:,2]

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/data_containers.py
--- a/yt/data_objects/data_containers.py
+++ b/yt/data_objects/data_containers.py
@@ -1040,6 +1040,35 @@
                      [self.field_parameters])
         return (_reconstruct_object, args)
 
+    def clone(self):
+        r"""Clone a data object.
+
+        This will make a duplicate of a data object; note that the
+        `field_parameters` may not necessarily be deeply-copied.  If you modify
+        the field parameters in-place, it may or may not be shared between the
+        objects, depending on the type of object that that particular field
+        parameter is.
+
+        Notes
+        -----
+        One use case for this is to have multiple identical data objects that
+        are being chunked over in different orders.
+
+        Examples
+        --------
+
+        >>> ds = yt.load("IsolatedGalaxy/galaxy0030/galaxy0030")
+        >>> sp = ds.sphere("c", 0.1)
+        >>> sp_clone = sp.clone()
+        >>> sp["density"]
+        >>> print sp.field_data.keys()
+        [("gas", "density")]
+        >>> print sp_clone.field_data.keys()
+        []
+        """
+        args = self.__reduce__()
+        return args[0](self.ds, *args[1][1:])[1]
+
     def __repr__(self):
         # We'll do this the slow way to be clear what's going on
         s = "%s (%s): " % (self.__class__.__name__, self.ds)
@@ -1189,7 +1218,16 @@
         # This is an iterator that will yield the necessary chunks.
         self.get_data() # Ensure we have built ourselves
         if fields is None: fields = []
-        for chunk in self.index._chunk(self, chunking_style, **kwargs):
+        # chunk_ind can be supplied in the keyword arguments.  If it's a
+        # scalar, that'll be the only chunk that gets returned; if it's a list,
+        # those are the ones that will be.
+        chunk_ind = kwargs.pop("chunk_ind", None)
+        if chunk_ind is not None:
+            chunk_ind = ensure_list(chunk_ind)
+        for ci, chunk in enumerate(self.index._chunk(self, chunking_style,
+                                   **kwargs)):
+            if chunk_ind is not None and ci not in chunk_ind:
+                continue
             with self._chunked_read(chunk):
                 self.get_data(fields)
                 # NOTE: we yield before releasing the context
@@ -1352,6 +1390,43 @@
                         if f not in fields_to_generate:
                             fields_to_generate.append(f)
 
+    def __or__(self, other):
+        if not isinstance(other, YTSelectionContainer):
+            raise YTBooleanObjectError(other)
+        if self.ds is not other.ds:
+            raise YTBooleanObjectsWrongDataset()
+        # Should maybe do something with field parameters here
+        return YTBooleanContainer("OR", self, other, ds = self.ds)
+
+    def __invert__(self):
+        # ~obj
+        asel = yt.geometry.selection_routines.AlwaysSelector(self.ds)
+        return YTBooleanContainer("NOT", self, asel, ds = self.ds)
+
+    def __xor__(self, other):
+        if not isinstance(other, YTSelectionContainer):
+            raise YTBooleanObjectError(other)
+        if self.ds is not other.ds:
+            raise YTBooleanObjectsWrongDataset()
+        return YTBooleanContainer("XOR", self, other, ds = self.ds)
+
+    def __and__(self, other):
+        if not isinstance(other, YTSelectionContainer):
+            raise YTBooleanObjectError(other)
+        if self.ds is not other.ds:
+            raise YTBooleanObjectsWrongDataset()
+        return YTBooleanContainer("AND", self, other, ds = self.ds)
+
+    def __add__(self, other):
+        return self.__or__(other)
+
+    def __sub__(self, other):
+        if not isinstance(other, YTSelectionContainer):
+            raise YTBooleanObjectError(other)
+        if self.ds is not other.ds:
+            raise YTBooleanObjectsWrongDataset()
+        return YTBooleanContainer("NEG", self, other, ds = self.ds)
+
     @contextmanager
     def _field_lock(self):
         self._locked = True
@@ -1875,42 +1950,6 @@
         """
         return self.quantities.total_quantity(("index", "cell_volume"))
 
-    def __or__(self, other):
-        if not isinstance(other, YTSelectionContainer3D):
-            raise YTBooleanObjectError(other)
-        if self.ds is not other.ds:
-            raise YTBooleanObjectsWrongDataset()
-        # Should maybe do something with field parameters here
-        return YTBooleanContainer("OR", self, other, ds = self.ds)
-
-    def __invert__(self):
-        # ~obj
-        asel = yt.geometry.selection_routines.AlwaysSelector(self.ds)
-        return YTBooleanContainer("NOT", self, asel, ds = self.ds)
-
-    def __xor__(self, other):
-        if not isinstance(other, YTSelectionContainer3D):
-            raise YTBooleanObjectError(other)
-        if self.ds is not other.ds:
-            raise YTBooleanObjectsWrongDataset()
-        return YTBooleanContainer("XOR", self, other, ds = self.ds)
-
-    def __and__(self, other):
-        if not isinstance(other, YTSelectionContainer3D):
-            raise YTBooleanObjectError(other)
-        if self.ds is not other.ds:
-            raise YTBooleanObjectsWrongDataset()
-        return YTBooleanContainer("AND", self, other, ds = self.ds)
-
-    def __add__(self, other):
-        return self.__or__(other)
-
-    def __sub__(self, other):
-        if not isinstance(other, YTSelectionContainer3D):
-            raise YTBooleanObjectError(other)
-        if self.ds is not other.ds:
-            raise YTBooleanObjectsWrongDataset()
-        return YTBooleanContainer("NEG", self, other, ds = self.ds)
 
 class YTBooleanContainer(YTSelectionContainer3D):
     """
@@ -1926,9 +1965,9 @@
     ----------
     op : string
         Can be AND, OR, XOR, NOT or NEG.
-    dobj1 : YTSelectionContainer3D
+    dobj1 : YTSelectionContainer
         The first selection object
-    dobj2 : YTSelectionContainer3D
+    dobj2 : YTSelectionContainer
         The second object
 
     Examples
@@ -1978,6 +2017,9 @@
     return narg
 
 def _get_ds_by_hash(hash):
+    from yt.data_objects.static_output import Dataset
+    if isinstance(hash, Dataset):
+        return hash
     from yt.data_objects.static_output import _cached_datasets
     for ds in _cached_datasets.values():
         if ds._hash() == hash: return ds

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/grid_patch.py
--- a/yt/data_objects/grid_patch.py
+++ b/yt/data_objects/grid_patch.py
@@ -268,8 +268,9 @@
         fields = list(set(fields))
         new_fields = {}
         for field in fields:
-            new_fields[field] = np.zeros(self.ActiveDimensions + 1, dtype='float64')
-
+            finfo = self.ds._get_field_info(field)
+            new_fields[field] = self.ds.arr(
+                np.zeros(self.ActiveDimensions + 1), finfo.units)
         if no_ghost:
             for field in fields:
                 # Ensure we have the native endianness in this array.  Avoid making

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/static_output.py
--- a/yt/data_objects/static_output.py
+++ b/yt/data_objects/static_output.py
@@ -106,8 +106,32 @@
             return self.__getattribute__(attr)
         return fnc
 
+    _field_types = None
+    @property
+    def field_types(self):
+        if self._field_types is None:
+            self._field_types = set(t for t, n in self.ds.field_info)
+        return self._field_types
+
     def __dir__(self):
-        return list(set(t for t, n in self.ds.field_info))
+        return list(self.field_types)
+
+    def __iter__(self):
+        for ft in self.field_types:
+            fnc = FieldNameContainer(self.ds, ft)
+            if len(dir(fnc)) == 0:
+                yield self.__getattribute__(ft)
+            else:
+                yield fnc
+
+    def __contains__(self, obj):
+        ob = None
+        if isinstance(obj, FieldNameContainer):
+            ob = obj.field_type
+        elif isinstance(obj, string_types):
+            ob = obj
+
+        return ob in self.field_types
 
 class FieldNameContainer(object):
     def __init__(self, ds, field_type):
@@ -125,6 +149,26 @@
         return [n for t, n in self.ds.field_info
                 if t == self.field_type]
 
+    def __iter__(self):
+        for t, n in self.ds.field_info:
+            if t == self.field_type:
+                yield self.ds.field_info[t, n]
+
+    def __contains__(self, obj):
+        if isinstance(obj, DerivedField):
+            if self.field_type == obj.name[0] and obj.name in self.ds.field_info:
+                # e.g. from a completely different dataset
+                if self.ds.field_info[obj.name] is not obj:
+                    return False
+                return True
+        elif isinstance(obj, tuple):
+            if self.field_type == obj[0] and obj in self.ds.field_info:
+                return True
+        elif isinstance(obj, string_types):
+            if (self.field_type, obj) in self.ds.field_info:
+                return True
+        return False
+
 class IndexProxy(object):
     # This is a simple proxy for Index objects.  It enables backwards
     # compatibility so that operations like .h.sphere, .h.print_stats and
@@ -183,7 +227,6 @@
     particle_types_raw = ("io",)
     geometry = "cartesian"
     coordinates = None
-    max_level = 99
     storage_filename = None
     particle_unions = None
     known_filters = None
@@ -1175,13 +1218,15 @@
         self.add_field(
             ("deposit", field_name),
             function=_deposit_field,
+            sampling_type="cell",
             units=units,
             take_log=take_log,
             validators=[ValidateSpatial()])
         return ("deposit", field_name)
 
-    def add_smoothed_particle_field(self, smooth_field, method="volume_weighted",
-                                    nneighbors=64, kernel_name="cubic"):
+    def add_smoothed_particle_field(self, smooth_field,
+                                    method="volume_weighted", nneighbors=64,
+                                    kernel_name="cubic"):
         """Add a new smoothed particle field
 
         Creates a new smoothed field based on the particle *smooth_field*.
@@ -1198,7 +1243,7 @@
            for now.
         nneighbors : int, default 64
             The number of neighbors to examine during the process.
-        kernel_name : string, default 'cubic'
+        kernel_name : string, default `cubic`
             This is the name of the smoothing kernel to use. Current supported
             kernel names include `cubic`, `quartic`, `quintic`, `wendland2`,
             `wendland4`, and `wendland6`.
@@ -1208,7 +1253,10 @@
 
         The field name tuple for the newly created field.
         """
+        # The magical step
         self.index
+
+        # Parse arguments
         if isinstance(smooth_field, tuple):
             ptype, smooth_field = smooth_field[0], smooth_field[1]
         else:
@@ -1217,6 +1265,7 @@
         if method != "volume_weighted":
             raise NotImplementedError("method must be 'volume_weighted'")
 
+        # Prepare field names and registry to be used later
         coord_name = "particle_position"
         mass_name = "particle_mass"
         smoothing_length_name = "smoothing_length"
@@ -1226,6 +1275,7 @@
         density_name = "density"
         registry = self.field_info
 
+        # Do the actual work
         return add_volume_weighted_smoothed_field(ptype, coord_name, mass_name,
                    smoothing_length_name, density_name, smooth_field, registry,
                    nneighbors=nneighbors, kernel_name=kernel_name)[0]
@@ -1274,6 +1324,18 @@
         self.field_dependencies.update(deps)
         return grad_fields
 
+    _max_level = None
+    @property
+    def max_level(self):
+        if self._max_level is None:
+            self._max_level = self.index.max_level
+
+        return self._max_level
+
+    @max_level.setter
+    def max_level(self, value):
+        self._max_level = value
+
 def _reconstruct_ds(*args, **kwargs):
     datasets = ParameterFileStore()
     ds = datasets.get_ds_hash(*args)
@@ -1298,3 +1360,17 @@
 
     def __lt__(self, other):
         return self.filename < other.filename
+
+
+class ParticleDataset(Dataset):
+    _unit_base = None
+    filter_bbox = False
+
+    def __init__(self, filename, dataset_type=None, file_style=None,
+                 units_override=None, unit_system="cgs",
+                 n_ref=64, over_refine_factor=1):
+        self.n_ref = n_ref
+        self.over_refine_factor = over_refine_factor
+        super(ParticleDataset, self).__init__(
+            filename, dataset_type=dataset_type, file_style=file_style,
+            units_override=units_override, unit_system=unit_system)

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/tests/test_boolean_regions.py
--- a/yt/data_objects/tests/test_boolean_regions.py
+++ b/yt/data_objects/tests/test_boolean_regions.py
@@ -427,3 +427,380 @@
     b6.sort()
     assert_array_equal(b6, np.setxor1d(np.setxor1d(rei, spi), cyli))
 
+def test_boolean_ray_region_no_overlap():
+    r"""Test to make sure that boolean objects (ray, region, no overlap)
+    behave the way we expect.
+
+    Test non-overlapping ray and region. This also checks that the original 
+    objects don't change as part of constructing the booleans.
+    """
+    ds = fake_amr_ds()
+    re = ds.box([0.25]*3, [0.75]*3)
+    ra = ds.ray([0.1]*3, [0.1, 0.1, 0.9])
+    # Store the original indices
+    i1 = re["index","morton_index"]
+    i1.sort()
+    i2 = ra["index","morton_index"]
+    i2.sort()
+    ii = np.concatenate((i1, i2))
+    ii.sort()
+    # Make some booleans
+    bo1 = re & ra
+    bo2 = re - ra
+    bo3 = re | ra
+    bo4 = ds.union([re, ra])
+    bo5 = ds.intersection([re, ra])
+    # This makes sure the original containers didn't change.
+    new_i1 = re["index","morton_index"]
+    new_i1.sort()
+    new_i2 = ra["index","morton_index"]
+    new_i2.sort()
+    assert_array_equal(new_i1, i1)
+    assert_array_equal(new_i2, i2)
+    # Now make sure the indices also behave as we expect.
+    empty = np.array([])
+    assert_array_equal(bo1["index","morton_index"], empty)
+    assert_array_equal(bo5["index","morton_index"], empty)
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    assert_array_equal(b2, i1 )
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b3, ii)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, b4)
+    bo6 = re ^ ra
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_ray_region_overlap():
+    r"""Test to make sure that boolean objects (ray, region, overlap)
+    behave the way we expect.
+
+    Test overlapping ray and region. This also checks that the original 
+    objects don't change as part of constructing the booleans.
+    """
+    ds = fake_amr_ds()
+    re = ds.box([0.25]*3, [0.75]*3)
+    ra = ds.ray([0]*3, [1]*3)
+    # Get indices of both.
+    i1 = re["index","morton_index"]
+    i2 = ra["index","morton_index"]
+    # Make some booleans
+    bo1 = re & ra
+    bo2 = re - ra
+    bo3 = re | ra
+    bo4 = ds.union([re, ra])
+    bo5 = ds.intersection([re, ra])
+    # Now make sure the indices also behave as we expect.
+    short_line = np.intersect1d(i1, i2)
+    cube_minus_line = np.setdiff1d(i1, i2)
+    both = np.union1d(i1, i2)
+    b1 = bo1["index","morton_index"]
+    b1.sort()
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b1, short_line)
+    assert_array_equal(b2, cube_minus_line)
+    assert_array_equal(b3, both)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, b4)
+    assert_array_equal(b1, b5)
+    bo6 = re ^ ra
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_rays_no_overlap():
+    r"""Test to make sure that boolean objects (rays, no overlap)
+    behave the way we expect.
+
+    Test non-overlapping rays.
+    """
+    ds = fake_amr_ds()
+    ra1 = ds.ray([0, 0, 0], [0, 0, 1])
+    ra2 = ds.ray([1, 0, 0], [1, 0, 1])
+    # Store the original indices
+    i1 = ra1["index","morton_index"]
+    i1.sort()
+    i2 = ra2["index","morton_index"]
+    i2.sort()
+    ii = np.concatenate((i1, i2))
+    ii.sort()
+    # Make some booleans
+    bo1 = ra1 & ra2
+    bo2 = ra1 - ra2
+    bo3 = ra1 | ra2
+    bo4 = ds.union([ra1, ra2])
+    bo5 = ds.intersection([ra1, ra2])
+    # This makes sure the original containers didn't change.
+    new_i1 = ra1["index","morton_index"]
+    new_i1.sort()
+    new_i2 = ra2["index","morton_index"]
+    new_i2.sort()
+    assert_array_equal(new_i1, i1)
+    assert_array_equal(new_i2, i2)
+    # Now make sure the indices also behave as we expect.
+    empty = np.array([])
+    assert_array_equal(bo1["index","morton_index"], empty)
+    assert_array_equal(bo5["index","morton_index"], empty)
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    assert_array_equal(b2, i1 )
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b3, ii)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, b4)
+    bo6 = ra1 ^ ra2
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_rays_overlap():
+    r"""Test to make sure that boolean objects (rays, overlap)
+    behave the way we expect.
+
+    Test non-overlapping rays.
+    """
+    ds = fake_amr_ds()
+    ra1 = ds.ray([0]*3, [1]*3)
+    ra2 = ds.ray([0]*3, [0.5]*3)
+    # Get indices of both.
+    i1 = ra1["index","morton_index"]
+    i1.sort()
+    i2 = ra2["index","morton_index"]
+    i2.sort()
+    ii = np.concatenate((i1, i2))
+    ii.sort()
+    # Make some booleans
+    bo1 = ra1 & ra2
+    bo2 = ra1 - ra2
+    bo3 = ra1 | ra2
+    bo4 = ds.union([ra1, ra2])
+    bo5 = ds.intersection([ra1, ra2])
+    # Now make sure the indices also behave as we expect.
+    short_line = np.intersect1d(i1, i2)
+    short_line_b = np.setdiff1d(i1, i2)
+    full_line = np.union1d(i1, i2)
+    b1 = bo1["index","morton_index"]
+    b1.sort()
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b1, short_line)
+    assert_array_equal(b2, short_line_b)
+    assert_array_equal(b3, full_line)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, i1)
+    assert_array_equal(b3, b4)
+    assert_array_equal(b1, b5)
+    bo6 = ra1 ^ ra2
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_slices_no_overlap():
+    r"""Test to make sure that boolean objects (slices, no overlap)
+    behave the way we expect.
+
+    Test non-overlapping slices. This also checks that the original regions
+    don't change as part of constructing the booleans.
+    """
+    ds = fake_amr_ds()
+    sl1 = ds.r[:,:,0.25]
+    sl2 = ds.r[:,:,0.75]
+    # Store the original indices
+    i1 = sl1["index","morton_index"]
+    i1.sort()
+    i2 = sl2["index","morton_index"]
+    i2.sort()
+    ii = np.concatenate((i1, i2))
+    ii.sort()
+    # Make some booleans
+    bo1 = sl1 & sl2
+    bo2 = sl1 - sl2
+    bo3 = sl1 | sl2
+    bo4 = ds.union([sl1, sl2])
+    bo5 = ds.intersection([sl1, sl2])
+    # This makes sure the original containers didn't change.
+    new_i1 = sl1["index","morton_index"]
+    new_i1.sort()
+    new_i2 = sl2["index","morton_index"]
+    new_i2.sort()
+    assert_array_equal(new_i1, i1)
+    assert_array_equal(new_i2, i2)
+    # Now make sure the indices also behave as we expect.
+    empty = np.array([])
+    assert_array_equal(bo1["index","morton_index"], empty)
+    assert_array_equal(bo5["index","morton_index"], empty)
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    assert_array_equal(b2, i1 )
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b3, ii)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, b4)
+    bo6 = sl1 ^ sl2
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_slices_overlap():
+    r"""Test to make sure that boolean objects (slices, overlap)
+    behave the way we expect.
+
+    Test overlapping slices.
+    """
+    ds = fake_amr_ds()
+    sl1 = ds.r[:,:,0.25]
+    sl2 = ds.r[:,0.75,:]
+    # Get indices of both.
+    i1 = sl1["index","morton_index"]
+    i2 = sl2["index","morton_index"]
+    # Make some booleans
+    bo1 = sl1 & sl2
+    bo2 = sl1 - sl2
+    bo3 = sl1 | sl2
+    bo4 = ds.union([sl1, sl2])
+    bo5 = ds.intersection([sl1, sl2])
+    # Now make sure the indices also behave as we expect.
+    line = np.intersect1d(i1, i2)
+    orig = np.setdiff1d(i1, i2)
+    both = np.union1d(i1, i2)
+    b1 = bo1["index","morton_index"]
+    b1.sort()
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b1, line)
+    assert_array_equal(b2, orig)
+    assert_array_equal(b3, both)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, b4)
+    assert_array_equal(b1, b5)
+    bo6 = sl1 ^ sl2
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_ray_slice_no_overlap():
+    r"""Test to make sure that boolean objects (ray, slice, no overlap)
+    behave the way we expect.
+
+    Test non-overlapping ray and slice. This also checks that the original 
+    regions don't change as part of constructing the booleans.
+    """
+    ds = fake_amr_ds()
+    sl = ds.r[:,:,0.25]
+    ra = ds.ray([0]*3, [0, 1, 0])
+    # Store the original indices
+    i1 = sl["index","morton_index"]
+    i1.sort()
+    i2 = ra["index","morton_index"]
+    i2.sort()
+    ii = np.concatenate((i1, i2))
+    ii.sort()
+    # Make some booleans
+    bo1 = sl & ra
+    bo2 = sl - ra
+    bo3 = sl | ra
+    bo4 = ds.union([sl, ra])
+    bo5 = ds.intersection([sl, ra])
+    # This makes sure the original containers didn't change.
+    new_i1 = sl["index","morton_index"]
+    new_i1.sort()
+    new_i2 = ra["index","morton_index"]
+    new_i2.sort()
+    assert_array_equal(new_i1, i1)
+    assert_array_equal(new_i2, i2)
+    # Now make sure the indices also behave as we expect.
+    empty = np.array([])
+    assert_array_equal(bo1["index","morton_index"], empty)
+    assert_array_equal(bo5["index","morton_index"], empty)
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    assert_array_equal(b2, i1 )
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b3, ii)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, b4)
+    bo6 = sl ^ ra
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))
+
+def test_boolean_ray_slice_overlap():
+    r"""Test to make sure that boolean objects (rays and slices, overlap)
+    behave the way we expect.
+
+    Test overlapping rays and slices.
+    """
+    ds = fake_amr_ds()
+    sl = ds.r[:,:,0.25]
+    ra = ds.ray([0, 0, 0.25], [0, 1, 0.25])
+    # Get indices of both.
+    i1 = sl["index","morton_index"]
+    i1.sort()
+    i2 = ra["index","morton_index"]
+    i1.sort()
+    ii = np.concatenate((i1, i2))
+    ii.sort()
+    # Make some booleans
+    bo1 = sl & ra
+    bo2 = sl - ra
+    bo3 = sl | ra
+    bo4 = ds.union([sl, ra])
+    bo5 = ds.intersection([sl, ra])
+    # Now make sure the indices also behave as we expect.
+    line = np.intersect1d(i1, i2)
+    sheet_minus_line = np.setdiff1d(i1, i2)
+    sheet = np.union1d(i1, i2)
+    b1 = bo1["index","morton_index"]
+    b1.sort()
+    b2 = bo2["index","morton_index"]
+    b2.sort()
+    b3 = bo3["index","morton_index"]
+    b3.sort()
+    assert_array_equal(b1, line)
+    assert_array_equal(b2, sheet_minus_line)
+    assert_array_equal(b3, sheet)
+    b4 = bo4["index","morton_index"]
+    b4.sort()
+    b5 = bo5["index","morton_index"]
+    b5.sort()
+    assert_array_equal(b3, i1)
+    assert_array_equal(b3, b4)
+    assert_array_equal(b1, b5)
+    bo6 = sl ^ ra
+    b6 = bo6["index", "morton_index"]
+    b6.sort()
+    assert_array_equal(b6, np.setxor1d(i1, i2))

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/tests/test_clone.py
--- /dev/null
+++ b/yt/data_objects/tests/test_clone.py
@@ -0,0 +1,24 @@
+from yt.testing import \
+    fake_random_ds, \
+    assert_equal, \
+    assert_array_equal
+
+def test_clone_sphere():
+    # Now we test that we can get different radial velocities based on field
+    # parameters.
+
+    # Get the first sphere
+    ds = fake_random_ds(16, fields = ("density",
+      "velocity_x", "velocity_y", "velocity_z"))
+    sp0 = ds.sphere(ds.domain_center, 0.25)
+
+    assert_equal(list(sp0.keys()), [])
+
+    sp1 = sp0.clone()
+    sp0["density"]
+    assert_equal(list(sp0.keys()), (("gas","density"),))
+    assert_equal(list(sp1.keys()), [])
+
+    sp1["density"]
+
+    assert_array_equal(sp0["density"], sp1["density"])

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/data_objects/tests/test_fluxes.py
--- a/yt/data_objects/tests/test_fluxes.py
+++ b/yt/data_objects/tests/test_fluxes.py
@@ -1,7 +1,19 @@
+import os
+import shutil
+import tempfile
+
+import numpy as np
+
+from unittest import TestCase
+
 from yt.testing import \
     fake_random_ds, \
     assert_almost_equal, \
-    assert_equal
+    assert_equal, \
+    requires_file
+
+from yt.convenience import \
+    load
 
 def setup():
     from yt.config import ytcfg
@@ -11,9 +23,10 @@
     ds = fake_random_ds(64, nprocs = 4)
     dd = ds.all_data()
     surf = ds.surface(dd, "x", 0.51)
-    yield assert_equal, surf["x"], 0.51
+    assert_equal(surf["x"], 0.51)
     flux = surf.calculate_flux("ones", "zeros", "zeros", "ones")
-    yield assert_almost_equal, flux, 1.0, 12
+    assert_almost_equal(flux.value, 1.0, 12)
+    assert_equal(str(flux.units), 'cm**2')
 
 def test_sampling():
     ds = fake_random_ds(64, nprocs = 4)
@@ -21,4 +34,70 @@
     for i, ax in enumerate('xyz'):
         surf = ds.surface(dd, ax, 0.51)
         surf.get_data(ax, "vertex")
-        yield assert_equal, surf.vertex_samples[ax], surf.vertices[i,:]
+        assert_equal(surf.vertex_samples[ax], surf.vertices[i,:])
+        assert_equal(str(surf.vertices.units), 'code_length')
+        dens = surf['density']
+        vert_shape = surf.vertices.shape
+        assert_equal(dens.shape[0], vert_shape[1]//vert_shape[0])
+        assert_equal(str(dens.units), 'g/cm**3')
+
+ISOGAL = 'IsolatedGalaxy/galaxy0030/galaxy0030'
+
+class ExporterTests(TestCase):
+
+    def setUp(self):
+        self.curdir = os.getcwd()
+        self.tmpdir = tempfile.mkdtemp()
+        os.chdir(self.tmpdir)
+
+    def tearDown(self):
+        os.chdir(self.curdir)
+        shutil.rmtree(self.tmpdir)
+
+    def test_export_ply(self):
+        ds = fake_random_ds(64, nprocs = 4)
+        dd = ds.all_data()
+        surf = ds.surface(dd, 'x', 0.51)
+        surf.export_ply('my_ply.ply', bounds=[(0, 1), (0, 1), (0, 1)])
+        assert os.path.exists('my_ply.ply')
+
+    @requires_file(ISOGAL)
+    def test_export_obj(self):
+        ds = load(ISOGAL)
+        sp = ds.sphere("max", (10, "kpc"))
+        trans = 1.0
+        distf = 3.1e18*1e3 # distances into kpc
+        surf = ds.surface(sp, "density", 5e-27)
+        surf.export_obj("my_galaxy", transparency=trans, dist_fac = distf)
+        assert os.path.exists('my_galaxy.obj')
+        assert os.path.exists('my_galaxy.mtl')
+
+        mi, ma = sp.quantities.extrema('temperature')
+        rhos = [1e-24, 1e-25]
+        trans = [0.5, 1.0]
+        for i, r in enumerate(rhos):
+            surf = ds.surface(sp,'density',r)
+            surf.export_obj("my_galaxy_color".format(i),
+                            transparency=trans[i],
+                            color_field='temperature', dist_fac = distf,
+                            plot_index = i, color_field_max = ma,
+                            color_field_min = mi)
+
+        assert os.path.exists('my_galaxy_color.obj')
+        assert os.path.exists('my_galaxy_color.mtl')
+
+        def _Emissivity(field, data):
+            return (data['density']*data['density'] *
+                    np.sqrt(data['temperature']))
+        ds.add_field("emissivity", sampling_type='cell', function=_Emissivity,
+                     units=r"g**2*sqrt(K)/cm**6")
+        for i, r in enumerate(rhos):
+            surf = ds.surface(sp,'density',r)
+            surf.export_obj("my_galaxy_emis".format(i),
+                            transparency=trans[i],
+                            color_field='temperature',
+                            emit_field='emissivity',
+                            dist_fac = distf, plot_index = i)
+
+        assert os.path.exists('my_galaxy_emis.obj')
+        assert os.path.exists('my_galaxy_emis.mtl')

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/fields/tests/test_field_name_container.py
--- a/yt/fields/tests/test_field_name_container.py
+++ b/yt/fields/tests/test_field_name_container.py
@@ -1,13 +1,28 @@
-from yt.utilities.answer_testing.framework import \
-    requires_ds, \
-    data_dir_load
+from yt import \
+    load
+from yt.testing import \
+    requires_file
+
+def do_field_type(ft):
+    for field_name in dir(ft):
+        f = getattr(ft, field_name)
+        assert ((ft.field_type, field_name) == f.name)
+    for field in ft:
+        f = getattr(ft, field.name[1])
+        assert (f == field)
+        assert (f in ft)
+        assert (f.name in ft)
+        assert (f.name[1] in ft)
+
 
 enzotiny = "enzo_tiny_cosmology/DD0046/DD0046"
- at requires_ds(enzotiny)
-def test_simulated_halo_mass_function():
-    ds = data_dir_load(enzotiny)
+ at requires_file(enzotiny)
+def test_field_name_container():
+    ds = load(enzotiny)
     for field_type in dir(ds.fields):
+        assert (field_type in ds.fields)
         ft = getattr(ds.fields, field_type)
-        for field_name in dir(ft):
-            f = getattr(ft, field_name)
-            assert ((field_type, field_name) == f.name)
+        do_field_type(ft)
+    for field_type in ds.fields:
+        assert (field_type in ds.fields)
+        do_field_type(field_type)

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/frontends/artio/_artio_caller.pyx
--- a/yt/frontends/artio/_artio_caller.pyx
+++ b/yt/frontends/artio/_artio_caller.pyx
@@ -7,7 +7,7 @@
     SelectorObject, AlwaysSelector, OctreeSubsetSelector
 from yt.utilities.lib.fp_utils cimport imax
 from yt.geometry.oct_container cimport \
-    SparseOctreeContainer
+    SparseOctreeContainer, OctObjectPool
 from yt.geometry.oct_visitors cimport Oct
 from yt.geometry.particle_deposit cimport \
     ParticleDepositOperation
@@ -923,7 +923,7 @@
         super(ARTIOOctreeContainer, self).__init__(dims, DLE, DRE)
         self.artio_handle = range_handler.artio_handle
         self.level_offset = 1
-        self.domains = NULL
+        self.domains = OctObjectPool()
         self.root_nodes = NULL
 
     @cython.boundscheck(False)
@@ -949,7 +949,7 @@
 
         # We only allow one root oct.
         self.append_domain(oct_count)
-        self.domains[self.num_domains - 1].con_id = sfc
+        self.domains.containers[self.num_domains - 1].con_id = sfc
 
         oct_ind = -1
         ipos = 0
@@ -1009,7 +1009,7 @@
         source_arrays = []
         ipos = -1
         for i in range(self.num_domains):
-            ipos = imax(ipos, self.domains[i].n)
+            ipos = imax(ipos, self.domains.containers[i].n)
         for i in range(nf):
             field_ind[i] = field_indices[i]
             # Note that we subtract one, because we're not using the root mesh.
@@ -1029,13 +1029,13 @@
         #     double-loop to calculate domain_counts
         # The cons should be in order
         cdef np.int64_t sfc_start, sfc_end
-        sfc_start = self.domains[0].con_id
-        sfc_end = self.domains[self.num_domains - 1].con_id
+        sfc_start = self.domains.containers[0].con_id
+        sfc_end = self.domains.containers[self.num_domains - 1].con_id
         status = artio_grid_cache_sfc_range(handle, sfc_start, sfc_end)
         check_artio_status(status)
         cdef np.int64_t offset = 0
         for si in range(self.num_domains):
-            sfc = self.domains[si].con_id
+            sfc = self.domains.containers[si].con_id
             status = artio_grid_read_root_cell_begin( handle, sfc,
                     dpos, NULL, &num_oct_levels, num_octs_per_level)
             check_artio_status(status)

diff -r cc691b668e3154a0b056e4f76fc83c317344b636 -r da677a77af75c80dadd16f33c865aad224459002 yt/frontends/fits/misc.py
--- a/yt/frontends/fits/misc.py
+++ b/yt/frontends/fits/misc.py
@@ -17,7 +17,6 @@
 from yt.funcs import mylog
 from yt.utilities.on_demand_imports import _astropy
 from yt.units.yt_array import YTQuantity, YTArray
-from yt.utilities.fits_image import FITSImageData
 if PY3:
     from io import BytesIO as IO
 else:
@@ -109,6 +108,7 @@
     ...                            nan_mask=0.0)
     """
     from spectral_cube import SpectralCube
+    from yt.visualization.fits_image import FITSImageData
     from yt.frontends.fits.api import FITSDataset
     cube = SpectralCube.read(filename)
     if not isinstance(slab_width, YTQuantity):
@@ -171,14 +171,18 @@
     nx = ds.domain_dimensions[ds.lon_axis]
     ny = ds.domain_dimensions[ds.lat_axis]
     mask = filter.mask((ny,nx)).transpose()
+    if ds.events_data:
+        prefix = "event_"
+    else:
+        prefix = ""
     def _reg_field(field, data):
-        i = data["xyz"[ds.lon_axis]].ndarray_view().astype("int")-1
-        j = data["xyz"[ds.lat_axis]].ndarray_view().astype("int")-1
+        i = data[prefix+"xyz"[ds.lon_axis]].d.astype("int")-1
+        j = data[prefix+"xyz"[ds.lat_axis]].d.astype("int")-1
         new_mask = mask[i,j]
-        ret = data["zeros"].copy()
+        ret = np.zeros(data[prefix+"x"].shape)
         ret[new_mask] = 1.
         return ret
-    ds.add_field(("gas",reg_name), sampling_type="cell",  function=_reg_field)
+    ds.add_field(("gas", reg_name), sampling_type="cell",  function=_reg_field)
     if obj is None:
         obj = ds.all_data()
     if field_parameters is not None:

This diff is so big that we needed to truncate the remainder.

https://bitbucket.org/yt_analysis/yt/commits/28c7fd7ac5cc/
Changeset:   28c7fd7ac5cc
Branch:      yt
User:        jzuhone
Date:        2017-01-06 22:42:23+00:00
Summary:     Fixing bugs
Affected #:  1 file

diff -r da677a77af75c80dadd16f33c865aad224459002 -r 28c7fd7ac5ccb95f725043ceb790ff1f09d4c490 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -271,7 +271,7 @@
                 cosmology = Cosmology()
 
         D_L = cosmology.luminosity_distance(0.0, redshift)
-        angular_scale = cosmology.angular_scale(0.0, redshift)
+        angular_scale = 1.0/cosmology.angular_scale(0.0, redshift)
         dist_fac = 1.0/(4.0*np.pi*D_L*D_L*angular_scale*angular_scale)
 
         ei_name = "xray_intensity_%s_%s_keV" % (e_min, e_max)
@@ -280,7 +280,7 @@
             return I.in_units("erg/cm**2/s/arcsec**2")
         ds.add_field(("gas", ei_name), function=_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
-                     sampling_type="cell", units="erg/cm**2/s/arcsec**2")
+                     sampling_type="cell", units="erg/cm**3/s/arcsec**2")
 
         i_name = "xray_photon_intensity_%s_%s_keV" % (e_min, e_max)
         def _photon_intensity_field(field, data):
@@ -288,7 +288,7 @@
             return I.in_units("photons/cm**2/s/arcsec**2")
         ds.add_field(("gas", i_name), function=_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
-                     sampling_type="cell", units="photons/cm**2/s/arcsec**2")
+                     sampling_type="cell", units="photons/cm**3/s/arcsec**2")
 
         fields += [ei_name, i_name]
 


https://bitbucket.org/yt_analysis/yt/commits/3e1e4931140b/
Changeset:   3e1e4931140b
Branch:      yt
User:        jzuhone
Date:        2017-01-06 22:44:29+00:00
Summary:     Wrong units here too
Affected #:  1 file

diff -r 28c7fd7ac5ccb95f725043ceb790ff1f09d4c490 -r 3e1e4931140b1a935869b68822d193bba91c0d2f yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -277,7 +277,7 @@
         ei_name = "xray_intensity_%s_%s_keV" % (e_min, e_max)
         def _intensity_field(field, data):
             I = dist_fac*data[emiss_name]
-            return I.in_units("erg/cm**2/s/arcsec**2")
+            return I.in_units("erg/cm**3/s/arcsec**2")
         ds.add_field(("gas", ei_name), function=_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
                      sampling_type="cell", units="erg/cm**3/s/arcsec**2")
@@ -285,7 +285,7 @@
         i_name = "xray_photon_intensity_%s_%s_keV" % (e_min, e_max)
         def _photon_intensity_field(field, data):
             I = (1.0+redshift)*dist_fac*data[phot_name]
-            return I.in_units("photons/cm**2/s/arcsec**2")
+            return I.in_units("photons/cm**3/s/arcsec**2")
         ds.add_field(("gas", i_name), function=_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
                      sampling_type="cell", units="photons/cm**3/s/arcsec**2")


https://bitbucket.org/yt_analysis/yt/commits/d76ddabe01bc/
Changeset:   d76ddabe01bc
Branch:      yt
User:        jzuhone
Date:        2017-01-06 22:46:40+00:00
Summary:     Wrong function
Affected #:  1 file

diff -r 3e1e4931140b1a935869b68822d193bba91c0d2f -r d76ddabe01bc4c669e3b55ff219fd89418328ed8 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -286,7 +286,7 @@
         def _photon_intensity_field(field, data):
             I = (1.0+redshift)*dist_fac*data[phot_name]
             return I.in_units("photons/cm**3/s/arcsec**2")
-        ds.add_field(("gas", i_name), function=_intensity_field,
+        ds.add_field(("gas", i_name), function=_photon_intensity_field,
                      display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
                      sampling_type="cell", units="photons/cm**3/s/arcsec**2")
 


https://bitbucket.org/yt_analysis/yt/commits/65475cdc212e/
Changeset:   65475cdc212e
Branch:      yt
User:        jzuhone
Date:        2017-01-06 23:05:54+00:00
Summary:     Adding deprecations for old parameters and docstrings
Affected #:  1 file

diff -r d76ddabe01bc4c669e3b55ff219fd89418328ed8 -r 65475cdc212ed970341e72e150b7e606e056e8c5 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -20,7 +20,7 @@
 
 from yt.config import ytcfg
 from yt.fields.derived_field import DerivedField
-from yt.funcs import mylog, only_on_root
+from yt.funcs import mylog, only_on_root, issue_deprecation_warning
 from yt.utilities.exceptions import YTFieldNotFound
 from yt.utilities.exceptions import YTException
 from yt.utilities.linear_interpolators import \
@@ -148,7 +148,8 @@
 def add_xray_emissivity_field(ds, e_min, e_max, redshift=0.0,
                               metallicity=("gas", "metallicity"), 
                               table_type="cloudy", data_dir=None,
-                              cosmology=None):
+                              cosmology=None, with_metals=True,
+                              constant_metallicity=None):
     r"""Create X-ray emissivity fields for a given energy range.
 
     Parameters
@@ -171,6 +172,17 @@
         The location to look for the data table in. If not supplied, the file
         will be looked for in the location of the YT_DEST environment variable
         or in the current working directory.
+    cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional
+        If set and redshift > 0.0, this cosmology will be used when computing the
+        cosmological dependence of the emission fields. If not set, yt's default
+        LCDM cosmology will be used.
+    with_metals : boolean, optional
+        True if the dataset's metallicity field should be used, False if a constant
+        metallicity should be used. NOTE: This parameter is deprecated; simply set
+        the ``metallicity`` parameter to the correct field name instead.
+    constant_metallicity : float, optional
+        Set this to a floating-point value to use a constant metallicity. NOTE: This
+        parameter is deprecated, set ``metallicity`` to a floating-point value instead.
 
     This will create three fields:
 
@@ -187,6 +199,14 @@
     >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
     >>> p.save()
     """
+    if constant_metallicity is not None:
+        issue_deprecation_warning("The \"constant_metallicity\" parameter is deprecated. Set "
+                                  "the \"metallicity\" parameter to a constant float value instead.")
+        metallicity = constant_metallicity
+    if not with_metals and not isinstance(metallicity, float):
+        issue_deprecation_warning("The \"with_metals\" parameter is deprecated. You have set "
+                                  "it to False but did not specify a constant metallicity. "
+                                  "Proceeding to use the %s field for the metallicity." % metallicity)
     if not isinstance(metallicity, float) and metallicity is not None:
         try:
             metallicity = ds._get_field_info(*metallicity)


https://bitbucket.org/yt_analysis/yt/commits/fbf3f09b94ba/
Changeset:   fbf3f09b94ba
Branch:      yt
User:        jzuhone
Date:        2017-01-09 18:41:10+00:00
Summary:     Merge
Affected #:  7 files

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 doc/source/analyzing/units/2)_Fields_and_unit_conversion.ipynb
--- a/doc/source/analyzing/units/2)_Fields_and_unit_conversion.ipynb
+++ b/doc/source/analyzing/units/2)_Fields_and_unit_conversion.ipynb
@@ -557,6 +557,100 @@
     "print (temp, type(temp))\n",
     "print (ptemp, type(ptemp))"
    ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Defining New Units"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "yt also provides a way to define your own units. Suppose you wanted to define a new unit for \"miles per hour\", the familiar \"mph\", which is not already in yt. One can do this by calling `yt.define_unit()`:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": true
+   },
+   "outputs": [],
+   "source": [
+    "yt.define_unit(\"mph\", (1.0, \"mile/hr\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Once this unit is defined, it can be used in the same way as any other unit:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "from yt.units import clight\n",
+    "print (clight.to('mph'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "If you want to define a new unit which is prefixable (like SI units), you can set `prefixable=True` when defining the unit:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "from yt import YTQuantity\n",
+    "yt.define_unit(\"L\", (1000.0, \"cm**3\"), prefixable=True)\n",
+    "print (YTQuantity(1.0, \"mL\").to(\"cm**3\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "`yt.define_unit()` defines new units for all yt operations. However, new units can be defined for particular datasets only as well using `ds.define_unit()`, which has the same signature:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "ds.define_unit(\"M_star\", (2.0e13, \"Msun\"))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "dd = ds.all_data()\n",
+    "print(dd.quantities.total_mass().to(\"M_star\"))"
+   ]
   }
  ],
  "metadata": {
@@ -580,4 +674,4 @@
  },
  "nbformat": 4,
  "nbformat_minor": 0
-}
\ No newline at end of file
+}

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 doc/source/reference/api/api.rst
--- a/doc/source/reference/api/api.rst
+++ b/doc/source/reference/api/api.rst
@@ -155,12 +155,12 @@
 
    yt.data_objects.static_output.Dataset.arr
    yt.data_objects.static_output.Dataset.quan
+   ~yt.units.unit_object.define_unit
    ~yt.units.unit_object.Unit
    ~yt.units.unit_registry.UnitRegistry
    ~yt.units.yt_array.YTArray
    ~yt.units.yt_array.YTQuantity
 
-
 Frontends
 ---------
 

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 yt/__init__.py
--- a/yt/__init__.py
+++ b/yt/__init__.py
@@ -99,6 +99,7 @@
 
 import yt.utilities.physical_constants as physical_constants
 import yt.units as units
+from yt.units.unit_object import define_unit
 from yt.units.yt_array import \
     YTArray, \
     YTQuantity, \

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 yt/data_objects/static_output.py
--- a/yt/data_objects/static_output.py
+++ b/yt/data_objects/static_output.py
@@ -45,7 +45,8 @@
     NoParameterShelf, \
     output_type_registry
 from yt.units.dimensions import current_mks
-from yt.units.unit_object import Unit, unit_system_registry
+from yt.units.unit_object import Unit, unit_system_registry, \
+    _define_unit
 from yt.units.unit_registry import UnitRegistry
 from yt.fields.derived_field import \
     ValidateSpatial
@@ -1336,6 +1337,34 @@
     def max_level(self, value):
         self._max_level = value
 
+    def define_unit(self, symbol, value, tex_repr=None, offset=None, prefixable=False):
+        """
+        Define a new unit and add it to the dataset's unit registry.
+
+        Parameters
+        ----------
+        symbol : string
+            The symbol for the new unit.
+        value : (value, unit) tuple or YTQuantity
+            The definition of the new unit in terms of some other units. For example,
+            one would define a new "mph" unit with (1.0, "mile/hr") 
+        tex_repr : string, optional
+            The LaTeX representation of the new unit. If one is not supplied, it will
+            be generated automatically based on the symbol string.
+        offset : float, optional
+            The default offset for the unit. If not set, an offset of 0 is assumed.
+        prefixable : boolean, optional
+            Whether or not the new unit can use SI prefixes. Default: False
+
+        Examples
+        --------
+        >>> ds.define_unit("mph", (1.0, "mile/hr"))
+        >>> two_weeks = YTQuantity(14.0, "days")
+        >>> ds.define_unit("fortnight", two_weeks)
+        """
+        _define_unit(self.unit_registry, symbol, value, tex_repr=tex_repr, 
+                     offset=offset, prefixable=prefixable)
+
 def _reconstruct_ds(*args, **kwargs):
     datasets = ParameterFileStore()
     ds = datasets.get_ds_hash(*args)

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 yt/units/tests/test_define_unit.py
--- /dev/null
+++ b/yt/units/tests/test_define_unit.py
@@ -0,0 +1,25 @@
+from yt.units.unit_object import define_unit
+from yt.units.yt_array import YTQuantity
+from yt.convenience import load
+from yt.testing import requires_file
+
+def test_define_unit():
+    define_unit("mph", (1.0, "mile/hr"))
+    a = YTQuantity(2.0, "mph")
+    b = YTQuantity(1.0, "mile")
+    c = YTQuantity(1.0, "hr")
+    assert a == 2.0*b/c
+    d = YTQuantity(1000.0, "cm**3")
+    define_unit("L", d, prefixable=True)
+    e = YTQuantity(1.0, "mL")
+    f = YTQuantity(1.0, "cm**3")
+    assert e == f
+
+gslr = "GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300"
+ at requires_file(gslr)
+def test_define_unit_dataset():
+    ds = load(gslr)
+    ds.define_unit("fortnight", (14.0, "day"))
+    a = ds.quan(1.0, "fortnight")
+    b = ds.quan(3600.0*24.0*14.0, "code_time")
+    assert a == b

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 yt/units/unit_lookup_table.py
--- a/yt/units/unit_lookup_table.py
+++ b/yt/units/unit_lookup_table.py
@@ -187,7 +187,7 @@
     "u": r"\mu",
     }
 
-prefixable_units = (
+prefixable_units = [
     "m",
     "pc",
     "mcm",
@@ -217,7 +217,7 @@
     "ohm",
     "statohm",
     "Sv",
-)
+]
 
 default_base_units = {
     dimensions.mass: 'g',

diff -r 65475cdc212ed970341e72e150b7e606e056e8c5 -r fbf3f09b94ba366215f83058153787197396c581 yt/units/unit_object.py
--- a/yt/units/unit_object.py
+++ b/yt/units/unit_object.py
@@ -694,3 +694,46 @@
             power_string = ""
         units.append("(%s)%s" % (unit_string, power_string))
     return " * ".join(units)
+
+def _define_unit(registry, symbol, value, tex_repr=None, offset=None, prefixable=False):
+    from yt.units.yt_array import YTQuantity, iterable
+    if symbol in registry:
+        raise RuntimeError("The symbol \"%s\" is already in the unit registry!" % symbol)
+    if not isinstance(value, YTQuantity):
+        if iterable(value) and len(value) == 2:
+            value = YTQuantity(value[0], value[1])
+        else:
+            raise RuntimeError("\"value\" needs to be a (value, unit) tuple!")
+    base_value = float(value.in_base(unit_system='cgs-ampere'))
+    dimensions = value.units.dimensions
+    registry.add(symbol, base_value, dimensions, tex_repr=tex_repr, offset=offset)
+    if prefixable:
+        prefixable_units.append(symbol)
+
+def define_unit(symbol, value, tex_repr=None, offset=None, prefixable=False):
+    """
+    Define a new unit and add it to the default unit registry.
+
+    Parameters
+    ----------
+    symbol : string
+        The symbol for the new unit.
+    value : (value, unit) tuple or YTQuantity
+        The definition of the new unit in terms of some other units. For example,
+        one would define a new "mph" unit with (1.0, "mile/hr") 
+    tex_repr : string, optional
+        The LaTeX representation of the new unit. If one is not supplied, it will
+        be generated automatically based on the symbol string.
+    offset : float, optional
+        The default offset for the unit. If not set, an offset of 0 is assumed.
+    prefixable : boolean, optional
+        Whether or not the new unit can use SI prefixes. Default: False
+
+    Examples
+    --------
+    >>> yt.define_unit("mph", (1.0, "mile/hr"))
+    >>> two_weeks = YTQuantity(14.0, "days")
+    >>> yt.define_unit("fortnight", two_weeks)
+    """
+    _define_unit(default_unit_registry, symbol, value, tex_repr=tex_repr, 
+                 offset=offset, prefixable=prefixable)
\ No newline at end of file


https://bitbucket.org/yt_analysis/yt/commits/d5dc4dc2355e/
Changeset:   d5dc4dc2355e
Branch:      yt
User:        jzuhone
Date:        2017-01-10 19:11:23+00:00
Summary:     Add in a deprecation warning for photon_simulator to the docs
Affected #:  1 file

diff -r fbf3f09b94ba366215f83058153787197396c581 -r d5dc4dc2355e7d3c042970d8fe752faeff900b6e doc/source/analyzing/analysis_modules/photon_simulator.rst
--- a/doc/source/analyzing/analysis_modules/photon_simulator.rst
+++ b/doc/source/analyzing/analysis_modules/photon_simulator.rst
@@ -3,6 +3,14 @@
 Constructing Mock X-ray Observations
 ------------------------------------
 
+.. warning:: 
+
+  The ``photon_simulator`` analysis module has been deprecated; it is
+  no longer being updated, and it will be removed in a future version
+  of yt. Users are encouraged to download and use the
+  `pyXSIM <http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim>`_ package 
+  instead. 
+
 .. note::
 
   If you just want to create derived fields for X-ray emission,


https://bitbucket.org/yt_analysis/yt/commits/a1fbda159f1d/
Changeset:   a1fbda159f1d
Branch:      yt
User:        jzuhone
Date:        2017-01-10 20:06:12+00:00
Summary:     Handling backwards-compatibility in a way that clutters up the function signature less. Use the field tuples instead of the DerivedField instance for the metallicity
Affected #:  1 file

diff -r d5dc4dc2355e7d3c042970d8fe752faeff900b6e -r a1fbda159f1d6bab368f96f0de719aec5b837ebe yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -148,8 +148,7 @@
 def add_xray_emissivity_field(ds, e_min, e_max, redshift=0.0,
                               metallicity=("gas", "metallicity"), 
                               table_type="cloudy", data_dir=None,
-                              cosmology=None, with_metals=True,
-                              constant_metallicity=None):
+                              cosmology=None, **kwargs):
     r"""Create X-ray emissivity fields for a given energy range.
 
     Parameters
@@ -176,13 +175,6 @@
         If set and redshift > 0.0, this cosmology will be used when computing the
         cosmological dependence of the emission fields. If not set, yt's default
         LCDM cosmology will be used.
-    with_metals : boolean, optional
-        True if the dataset's metallicity field should be used, False if a constant
-        metallicity should be used. NOTE: This parameter is deprecated; simply set
-        the ``metallicity`` parameter to the correct field name instead.
-    constant_metallicity : float, optional
-        Set this to a floating-point value to use a constant metallicity. NOTE: This
-        parameter is deprecated, set ``metallicity`` to a floating-point value instead.
 
     This will create three fields:
 
@@ -199,20 +191,25 @@
     >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
     >>> p.save()
     """
-    if constant_metallicity is not None:
+    # The next several if constructs are for backwards-compatibility
+    if "constant_metallicity" in kwargs:
         issue_deprecation_warning("The \"constant_metallicity\" parameter is deprecated. Set "
                                   "the \"metallicity\" parameter to a constant float value instead.")
-        metallicity = constant_metallicity
-    if not with_metals and not isinstance(metallicity, float):
-        issue_deprecation_warning("The \"with_metals\" parameter is deprecated. You have set "
-                                  "it to False but did not specify a constant metallicity. "
-                                  "Proceeding to use the %s field for the metallicity." % metallicity)
+        metallicity = kwargs["constant_metallicity"]
+    if "with_metals" in kwargs:
+        issue_deprecation_warning("The \"with_metals\" parameter is deprecated. Use the "
+                                  "\"metallicity\" parameter to choose a constant or "
+                                  "spatially varying metallicity.")
+        if kwargs["with_metals"] and isinstance(metallicity, float):
+            raise RuntimeError("\"with_metals=True\", but you specified a constant metallicity!")
+        if not kwargs["with_metals"] and not isinstance(metallicity, float):
+            raise RuntimeError("\"with_metals=False\", but you didn't specify a constant metallicity!")
     if not isinstance(metallicity, float) and metallicity is not None:
         try:
             metallicity = ds._get_field_info(*metallicity)
         except YTFieldNotFound:
             raise RuntimeError("Your dataset does not have a {} field! ".format(metallicity) +
-                               "Perhaps you should specify a constant metallicity?")
+                               "Perhaps you should specify a constant metallicity instead?")
 
     my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)
 
@@ -239,7 +236,7 @@
         my_emissivity = np.power(10, em_0(dd))
         if metallicity is not None:
             if isinstance(metallicity, DerivedField):
-                my_Z = data[metallicity]
+                my_Z = data[metallicity.name]
             else:
                 my_Z = metallicity
             my_emissivity += my_Z * np.power(10, em_Z(dd))
@@ -267,7 +264,7 @@
         my_emissivity = np.power(10, emp_0(dd))
         if metallicity is not None:
             if isinstance(metallicity, DerivedField):
-                my_Z = data[metallicity]
+                my_Z = data[metallicity.name]
             else:
                 my_Z = metallicity
             my_emissivity += my_Z * np.power(10, emp_Z(dd))


https://bitbucket.org/yt_analysis/yt/commits/3c7f252c9846/
Changeset:   3c7f252c9846
Branch:      yt
User:        jzuhone
Date:        2017-01-10 20:21:57+00:00
Summary:     X-ray emissivity fields now have updated docs in the form of a notebook
Affected #:  3 files

diff -r a1fbda159f1d6bab368f96f0de719aec5b837ebe -r 3c7f252c9846f4ceb606dd2d98ba2f9983ad4c2d doc/source/analyzing/XrayEmissionFields.ipynb
--- /dev/null
+++ b/doc/source/analyzing/XrayEmissionFields.ipynb
@@ -0,0 +1,217 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note: If you came here trying to figure out how to create simulated X-ray photons and observations,\n",
+    "  you should go [here](analysis_modules/photon_simulator.html) instead."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This functionality provides the ability to create metallicity-dependent X-ray luminosity, emissivity, and photon emissivity fields for a given photon energy range.  This works by interpolating from emission tables created from the photoionization code [Cloudy](http://nublado.org/) or the collisional ionization database [AtomDB](http://www.atomdb.org). These can be downloaded from http://yt-project.org/data from the command line like so:\n",
+    "\n",
+    "`# Put the data in a directory you specify`  \n",
+    "`yt download cloudy_emissivity_v2.h5 /path/to/data`\n",
+    "\n",
+    "`# Put the data in the location set by \"supp_data_dir\"`  \n",
+    "`yt download apec_emissivity_v2.h5 supp_data_dir`\n",
+    "\n",
+    "The data path can be a directory on disk, or it can be \"supp_data_dir\", which will download the data to the directory specified by the `\"supp_data_dir\"` yt configuration entry. It is easiest to put these files in the directory from which you will be running yt or `\"supp_data_dir\"`, but see the note below about putting them in alternate locations."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Emission fields can be made for any energy interval between 0.1 keV and 100 keV, and will always be created for luminosity $(\\rm{erg~s^{-1}})$, emissivity $\\rm{(erg~s^{-1}~cm^{-3})}$, and photon emissivity $\\rm{(photons~s^{-1}~cm^{-3})}$.  The only required arguments are the\n",
+    "dataset object, and the minimum and maximum energies of the energy band. However, typically one needs to decide what will be used for the metallicity. This can either be a floating-point value representing a spatially constant metallicity, or a prescription for a metallicity field, e.g. `(\"gas\", \"metallicity\")`. For this first example, where the dataset has no metallicity field, we'll just assume $Z = 0.3~Z_\\odot$ everywhere:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "import yt\n",
+    "\n",
+    "ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n",
+    "\n",
+    "xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, metallicity=0.3)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note: If you place the HDF5 emissivity tables in a location other than the current working directory or the location \n",
+    "  specified by the \"supp_data_dir\" configuration value, you will need to specify it in the call to \n",
+    "  `add_xray_emissivity_field`:  \n",
+    "  `xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, data_dir=\"/path/to/data\", metallicity=0.3)`"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Having made the fields, one can see which fields were made:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "print (xray_fields)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The luminosity field is useful for summing up in regions like this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "sp = ds.sphere(\"c\", (2.0, \"Mpc\"))\n",
+    "print (sp.quantities.total_quantity(\"xray_luminosity_0.5_7.0_keV\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Whereas the emissivity fields may be useful in derived fields or for plotting:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "slc = yt.SlicePlot(ds, 'z', ['xray_emissivity_0.5_7.0_keV','xray_photon_emissivity_0.5_7.0_keV'],\n",
+    "                   width=(0.75, \"Mpc\"))\n",
+    "slc.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The emissivity and the luminosity fields take the values one would see in the frame of the source. However, if one wishes to make projections of the X-ray emission from a cosmologically distant object, the energy band will be redshifted. For this case, one can supply a `redshift` parameter and a `Cosmology` object (either from the dataset or one made on your own) to compute X-ray intensity fields along with the emissivity and luminosity fields.\n",
+    "\n",
+    "This example shows how to do that, Where we also use a spatially dependent metallicity field and the APEC tables instead of the Cloudy tables we used previously:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "ds2 = yt.load(\"D9p_500/10MpcBox_HartGal_csf_a0.500.d\")\n",
+    "\n",
+    "# In this case, use the redshift and cosmology from the dataset, \n",
+    "# but in theory you could put in something different\n",
+    "xray_fields2 = yt.add_xray_emissivity_field(ds2, 0.5, 2.0, redshift=ds2.current_redshift, cosmology=ds2.cosmology,\n",
+    "                                            metallicity=(\"gas\", \"metallicity\"), table_type='apec')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now, one can see that two new fields have been added, corresponding to X-ray intensity / surface brightness when projected:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "print (xray_fields2)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Note also that the energy range now corresponds to the *observer* frame, whereas in the source frame the energy range is between `emin*(1+redshift)` and `emax*(1+redshift)`. Let's zoom in on a galaxy and make a projection of the intensity fields:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "prj = yt.ProjectionPlot(ds2, \"x\", [\"xray_intensity_0.5_2.0_keV\", \"xray_photon_intensity_0.5_2.0_keV\"],\n",
+    "                        center=\"max\", width=(40, \"kpc\"))\n",
+    "prj.set_zlim(\"xray_intensity_0.5_2.0_keV\", 1.0e-32, 5.0e-24)\n",
+    "prj.set_zlim(\"xray_photon_intensity_0.5_2.0_keV\", 1.0e-24, 5.0e-16)\n",
+    "prj.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Warning: The X-ray fields depend on the number density of hydrogen atoms, given by the yt field\n",
+    "  `H_number_density`. If this field is not defined (either in the dataset or by the user),\n",
+    "  the primordial hydrogen mass fraction (X = 0.76) will be used to construct it."
+   ]
+  }
+ ],
+ "metadata": {
+  "anaconda-cloud": {},
+  "kernelspec": {
+   "display_name": "Python [default]",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.5.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

diff -r a1fbda159f1d6bab368f96f0de719aec5b837ebe -r 3c7f252c9846f4ceb606dd2d98ba2f9983ad4c2d doc/source/analyzing/analysis_modules/xray_emission_fields.rst
--- a/doc/source/analyzing/analysis_modules/xray_emission_fields.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-.. _xray_emission_fields:
-
-X-ray Emission Fields
-=====================
-.. sectionauthor:: Britton Smith <brittonsmith at gmail.com>, John ZuHone <jzuhone at gmail.com>
-
-.. note::
-
-  If you came here trying to figure out how to create simulated X-ray photons and observations,
-  you should go `here <photon_simulator.html>`_ instead.
-
-This functionality provides the ability to create metallicity-dependent
-X-ray luminosity, emissivity, and photon emissivity fields for a given
-photon energy range.  This works by interpolating from emission tables
-created from the photoionization code `Cloudy <http://nublado.org/>`_ or
-the collisional ionization database `AtomDB <http://www.atomdb.org>`_. If
-you installed yt with the install script, these data files should be located in
-the *data* directory inside the installation directory, or can be downloaded
-from `<http://yt-project.org/data>`_. Emission fields can be made for any
-interval between 0.1 keV and 100 keV.
-
-Adding Emission Fields
-----------------------
-
-Fields will be created for luminosity :math:`{\rm (erg~s^{-1})}`, emissivity :math:`{\rm (erg~s^{-1}~cm^{-3})}`,
-and photon emissivity :math:`{\rm (photons~s^{-1}~cm^{-3})}`.  The only required arguments are the
-dataset object, and the minimum and maximum energies of the energy band.
-
-.. code-block:: python
-
-  import yt
-  from yt.analysis_modules.spectral_integrator.api import \
-       add_xray_emissivity_field
-
-  xray_fields = add_xray_emissivity_field(ds, 0.5, 7.0)
-
-Additional keyword arguments are:
-
- * **filename** (*string*): Path to data file containing emissivity values. If None,
-   a file called "cloudy_emissivity.h5" is used, for photoionized plasmas. A second
-   option, for collisionally ionized plasmas, is in the file "apec_emissivity.h5",
-   available at http://yt-project.org/data. These files contain emissivity tables
-   for primordial elements and for metals at solar metallicity for the energy range
-   0.1 to 100 keV. Default: None.
-
- * **with_metals** (*bool*): If True, use the metallicity field to add the
-   contribution from metals.  If False, only the emission from H/He is
-   considered.  Default: True.
-
- * **constant_metallicity** (*float*): If specified, assume a constant
-   metallicity for the emission from metals.  The *with_metals* keyword
-   must be set to False to use this. It should be given in unit of solar metallicity.
-   Default: None.
-
-The resulting fields can be used like all normal fields. The function will return the names of
-the created fields in a Python list.
-
-.. code-block:: python
-
-  import yt
-  from yt.analysis_modules.spectral_integrator.api import \
-       add_xray_emissivity_field
-
-  xray_fields = add_xray_emissivity_field(ds, 0.5, 7.0, filename="apec_emissivity.h5")
-
-  ds = yt.load("enzo_tiny_cosmology/DD0046/DD0046")
-  plot = yt.SlicePlot(ds, 'x', 'xray_luminosity_0.5_7.0_keV')
-  plot.save()
-  plot = yt.ProjectionPlot(ds, 'x', 'xray_emissivity_0.5_7.0_keV')
-  plot.save()
-  plot = yt.ProjectionPlot(ds, 'x', 'xray_photon_emissivity_0.5_7.0_keV')
-  plot.save()
-
-.. warning::
-
-  The X-ray fields depend on the number density of hydrogen atoms, in the yt field
-  ``H_number_density``. If this field is not defined (either in the dataset or by the user),
-  the primordial hydrogen mass fraction (X = 0.76) will be used to construct it.

diff -r a1fbda159f1d6bab368f96f0de719aec5b837ebe -r 3c7f252c9846f4ceb606dd2d98ba2f9983ad4c2d doc/source/analyzing/xray_emission_fields.rst
--- /dev/null
+++ b/doc/source/analyzing/xray_emission_fields.rst
@@ -0,0 +1,3 @@
+.. _xray-emission-fields:
+
+.. notebook:: XrayEmissionFields.ipynb


https://bitbucket.org/yt_analysis/yt/commits/f0d36e4b394b/
Changeset:   f0d36e4b394b
Branch:      yt
User:        jzuhone
Date:        2017-01-10 20:31:23+00:00
Summary:     Use a different dataset for apec test, which actually has a metallicity field. We’ll need to bump the test number
Affected #:  2 files

diff -r 3c7f252c9846f4ceb606dd2d98ba2f9983ad4c2d -r f0d36e4b394b8f5ca51d3471b32fe99596ce3d53 tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -50,7 +50,7 @@
   local_tipsy_002:
     - yt/frontends/tipsy/tests/test_outputs.py
 
-  local_varia_007:
+  local_varia_008:
     - yt/analysis_modules/radmc3d_export
     - yt/frontends/moab/tests/test_c5.py
     - yt/analysis_modules/photon_simulator/tests/test_spectra.py
@@ -58,6 +58,7 @@
     - yt/visualization/volume_rendering/tests/test_vr_orientation.py
     - yt/visualization/volume_rendering/tests/test_mesh_render.py
     - yt/visualization/tests/test_mesh_slices.py:test_tri2
+    - yt/fields/tests/test_xray_fields.py
 
   local_orion_001:
     - yt/frontends/boxlib/tests/test_orion.py

diff -r 3c7f252c9846f4ceb606dd2d98ba2f9983ad4c2d -r f0d36e4b394b8f5ca51d3471b32fe99596ce3d53 yt/fields/tests/test_xray_fields.py
--- a/yt/fields/tests/test_xray_fields.py
+++ b/yt/fields/tests/test_xray_fields.py
@@ -13,19 +13,20 @@
 @requires_ds(sloshing, big_data=True)
 def test_sloshing_cloudy():
     ds = data_dir_load(sloshing)
-    fields = add_xray_emissivity_field(ds, 0.5, 7.0, redshift=0.05,
-                                       table_type="cloudy", metallicity=0.3)
+    fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type="cloudy", 
+                                       metallicity=0.3)
     for test in small_patch_amr(ds, fields):
         test_sloshing_cloudy.__name__ = test.description
         yield test
 
-ecp = "enzo_cosmology_plus/DD0046/DD0046"
- at requires_ds(ecp, big_data=True)
-def test_ecp_apec():
+d9p = "D9p_500/10MpcBox_HartGal_csf_a0.500.d"
+ at requires_ds(d9p, big_data=True)
+def test_d9p_apec():
     ds = data_dir_load(sloshing)
-    fields = add_xray_emissivity_field(ds, 0.5, 7.0, redshift=0.05,
+    fields = add_xray_emissivity_field(ds, 0.5, 2.0, redshift=ds.current_redshift,
                                        table_type="apec",
+                                       cosmology=ds.cosmology,
                                        metallicity=("gas", "metallicity"))
     for test in small_patch_amr(ds, fields):
-        test_ecp_apec.__name__ = test.description
+        test_d9p_apec.__name__ = test.description
         yield test


https://bitbucket.org/yt_analysis/yt/commits/eef2f3841e36/
Changeset:   eef2f3841e36
Branch:      yt
User:        jzuhone
Date:        2017-01-10 20:37:34+00:00
Summary:     Swap table types
Affected #:  2 files

diff -r f0d36e4b394b8f5ca51d3471b32fe99596ce3d53 -r eef2f3841e362180a0f734e36da08d713682e613 doc/source/analyzing/XrayEmissionFields.ipynb
--- a/doc/source/analyzing/XrayEmissionFields.ipynb
+++ b/doc/source/analyzing/XrayEmissionFields.ipynb
@@ -44,7 +44,7 @@
     "\n",
     "ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n",
     "\n",
-    "xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, metallicity=0.3)"
+    "xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, table_type='apec', metallicity=0.3)"
    ]
   },
   {
@@ -54,7 +54,7 @@
     "> Note: If you place the HDF5 emissivity tables in a location other than the current working directory or the location \n",
     "  specified by the \"supp_data_dir\" configuration value, you will need to specify it in the call to \n",
     "  `add_xray_emissivity_field`:  \n",
-    "  `xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, data_dir=\"/path/to/data\", metallicity=0.3)`"
+    "  `xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, data_dir=\"/path/to/data\", table_type='apec', metallicity=0.3)`"
    ]
   },
   {
@@ -121,7 +121,7 @@
    "source": [
     "The emissivity and the luminosity fields take the values one would see in the frame of the source. However, if one wishes to make projections of the X-ray emission from a cosmologically distant object, the energy band will be redshifted. For this case, one can supply a `redshift` parameter and a `Cosmology` object (either from the dataset or one made on your own) to compute X-ray intensity fields along with the emissivity and luminosity fields.\n",
     "\n",
-    "This example shows how to do that, Where we also use a spatially dependent metallicity field and the APEC tables instead of the Cloudy tables we used previously:"
+    "This example shows how to do that, Where we also use a spatially dependent metallicity field and the Cloudy tables instead of the APEC tables we used previously:"
    ]
   },
   {
@@ -138,7 +138,7 @@
     "# In this case, use the redshift and cosmology from the dataset, \n",
     "# but in theory you could put in something different\n",
     "xray_fields2 = yt.add_xray_emissivity_field(ds2, 0.5, 2.0, redshift=ds2.current_redshift, cosmology=ds2.cosmology,\n",
-    "                                            metallicity=(\"gas\", \"metallicity\"), table_type='apec')"
+    "                                            metallicity=(\"gas\", \"metallicity\"), table_type='cloudy')"
    ]
   },
   {

diff -r f0d36e4b394b8f5ca51d3471b32fe99596ce3d53 -r eef2f3841e362180a0f734e36da08d713682e613 yt/fields/tests/test_xray_fields.py
--- a/yt/fields/tests/test_xray_fields.py
+++ b/yt/fields/tests/test_xray_fields.py
@@ -11,22 +11,21 @@
 
 sloshing = "GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300"
 @requires_ds(sloshing, big_data=True)
-def test_sloshing_cloudy():
+def test_sloshing_apec():
     ds = data_dir_load(sloshing)
-    fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type="cloudy", 
+    fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type="apec", 
                                        metallicity=0.3)
     for test in small_patch_amr(ds, fields):
-        test_sloshing_cloudy.__name__ = test.description
+        test_sloshing_apec.__name__ = test.description
         yield test
 
 d9p = "D9p_500/10MpcBox_HartGal_csf_a0.500.d"
 @requires_ds(d9p, big_data=True)
-def test_d9p_apec():
-    ds = data_dir_load(sloshing)
+def test_d9p_cloudy():
+    ds = data_dir_load(d9p)
     fields = add_xray_emissivity_field(ds, 0.5, 2.0, redshift=ds.current_redshift,
-                                       table_type="apec",
-                                       cosmology=ds.cosmology,
+                                       table_type="cloudy", cosmology=ds.cosmology,
                                        metallicity=("gas", "metallicity"))
     for test in small_patch_amr(ds, fields):
-        test_d9p_apec.__name__ = test.description
+        test_d9p_cloudy.__name__ = test.description
         yield test


https://bitbucket.org/yt_analysis/yt/commits/411342c26f95/
Changeset:   411342c26f95
Branch:      yt
User:        jzuhone
Date:        2017-01-10 21:09:26+00:00
Summary:     We don’t need all of the tests that small_patch_amr wants
Affected #:  1 file

diff -r eef2f3841e362180a0f734e36da08d713682e613 -r 411342c26f9561b80ac8d14a1e83b923c4487ab5 yt/fields/tests/test_xray_fields.py
--- a/yt/fields/tests/test_xray_fields.py
+++ b/yt/fields/tests/test_xray_fields.py
@@ -1,21 +1,30 @@
 from yt.fields.xray_emission_fields import \
     add_xray_emissivity_field
 from yt.utilities.answer_testing.framework import \
-    requires_ds, \
-    small_patch_amr, \
-    data_dir_load
+    requires_ds, can_run_ds, data_dir_load, \
+    ProjectionValuesTest, FieldValuesTest
 
 def setup():
     from yt.config import ytcfg
     ytcfg["yt","__withintesting"] = "True"
 
+def check_xray_fields(ds_fn, fields):
+    if not can_run_ds(ds_fn): return
+    dso = [ None, ("sphere", ("m", (0.1, 'unitary')))]
+    for field in fields:
+        for axis in [0, 1, 2]:
+            for dobj_name in dso:
+                yield ProjectionValuesTest(ds_fn, axis, field, 
+                                           None, dobj_name)
+                yield FieldValuesTest(ds_fn, field, dobj_name)
+
 sloshing = "GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300"
 @requires_ds(sloshing, big_data=True)
 def test_sloshing_apec():
     ds = data_dir_load(sloshing)
     fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type="apec", 
                                        metallicity=0.3)
-    for test in small_patch_amr(ds, fields):
+    for test in check_xray_fields(ds, fields):
         test_sloshing_apec.__name__ = test.description
         yield test
 
@@ -26,6 +35,6 @@
     fields = add_xray_emissivity_field(ds, 0.5, 2.0, redshift=ds.current_redshift,
                                        table_type="cloudy", cosmology=ds.cosmology,
                                        metallicity=("gas", "metallicity"))
-    for test in small_patch_amr(ds, fields):
+    for test in check_xray_fields(ds, fields):
         test_d9p_cloudy.__name__ = test.description
         yield test


https://bitbucket.org/yt_analysis/yt/commits/e2bde6600ea2/
Changeset:   e2bde6600ea2
Branch:      yt
User:        jzuhone
Date:        2017-01-11 05:02:40+00:00
Summary:     Add docstring entry
Affected #:  1 file

diff -r 411342c26f9561b80ac8d14a1e83b923c4487ab5 -r e2bde6600ea296b6dbe6fbedf3ee74d344bbb3a9 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -88,6 +88,9 @@
         The location to look for the data table in. If not supplied, the file
         will be looked for in the location of the YT_DEST environment variable
         or in the current working directory.
+    use_metals : boolean, optional
+        If set to True, the emissivity will include contributions from metals.
+        Default: True
     """
     def __init__(self, table_type, redshift=0.0, data_dir=None, use_metals=True):
 


https://bitbucket.org/yt_analysis/yt/commits/c6a1bad200cc/
Changeset:   c6a1bad200cc
Branch:      yt
User:        jzuhone
Date:        2017-01-15 01:01:20+00:00
Summary:     This should be nuclei density
Affected #:  1 file

diff -r e2bde6600ea296b6dbe6fbedf3ee74d344bbb3a9 -r c6a1bad200cc0ccdc9c5bb8594cc0317a8ff933b yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -222,18 +222,8 @@
         em_Z = my_si.get_interpolator("metals", e_min, e_max)
         emp_Z = my_si.get_interpolator("metals", e_min, e_max, energy=False)
 
-    try:
-        ds._get_field_info("H_number_density")
-    except YTFieldNotFound:
-        mylog.warning("Could not find a field for \"H_number_density\". "
-                      "Assuming primordial H mass fraction.")
-        def _nh(field, data):
-            return primordial_H_mass_fraction*data["gas", "density"]/mp
-        ds.add_field(("gas", "H_number_density"), function=_nh, 
-                     sampling_type="cell", units="cm**-3")
-
     def _emissivity_field(field, data):
-        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
+        dd = {"log_nH": np.log10(data["gas", "H_nuclei_density"]),
               "log_T": np.log10(data["gas", "temperature"])}
 
         my_emissivity = np.power(10, em_0(dd))
@@ -244,7 +234,7 @@
                 my_Z = metallicity
             my_emissivity += my_Z * np.power(10, em_Z(dd))
 
-        return data["gas","H_number_density"]**2 * \
+        return data["gas","H_nuclei_density"]**2 * \
             YTArray(my_emissivity, "erg*cm**3/s")
 
     emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
@@ -261,7 +251,7 @@
                  sampling_type="cell", units="erg/s")
 
     def _photon_emissivity_field(field, data):
-        dd = {"log_nH": np.log10(data["gas", "H_number_density"]),
+        dd = {"log_nH": np.log10(data["gas", "H_nuclei_density"]),
               "log_T": np.log10(data["gas", "temperature"])}
 
         my_emissivity = np.power(10, emp_0(dd))
@@ -272,7 +262,7 @@
                 my_Z = metallicity
             my_emissivity += my_Z * np.power(10, emp_Z(dd))
 
-        return data["gas", "H_number_density"]**2 * \
+        return data["gas", "H_nuclei_density"]**2 * \
             YTArray(my_emissivity, "photons*cm**3/s")
 
     phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)


https://bitbucket.org/yt_analysis/yt/commits/63ab1d0ac44b/
Changeset:   63ab1d0ac44b
Branch:      yt
User:        jzuhone
Date:        2017-01-16 02:12:54+00:00
Summary:     Removing unneeded stuff
Affected #:  1 file

diff -r c6a1bad200cc0ccdc9c5bb8594cc0317a8ff933b -r 63ab1d0ac44b19e15fb0febc4f911e1bdd5b14b1 yt/fields/xray_emission_fields.py
--- a/yt/fields/xray_emission_fields.py
+++ b/yt/fields/xray_emission_fields.py
@@ -25,11 +25,8 @@
 from yt.utilities.exceptions import YTException
 from yt.utilities.linear_interpolators import \
     UnilinearFieldInterpolator, BilinearFieldInterpolator
-from yt.utilities.physical_constants import mp
 from yt.units.yt_array import YTArray, YTQuantity
 from yt.utilities.cosmology import Cosmology
-from yt.utilities.physical_ratios import \
-    primordial_H_mass_fraction
 
 data_version = {"cloudy": 2,
                 "apec": 2}


https://bitbucket.org/yt_analysis/yt/commits/0e88f26ab3c1/
Changeset:   0e88f26ab3c1
Branch:      yt
User:        jzuhone
Date:        2017-01-16 16:47:25+00:00
Summary:     Correct information about how n_H is computed now
Affected #:  1 file

diff -r 63ab1d0ac44b19e15fb0febc4f911e1bdd5b14b1 -r 0e88f26ab3c1b96e56fbcdc20a3e404f2b2970d7 doc/source/analyzing/XrayEmissionFields.ipynb
--- a/doc/source/analyzing/XrayEmissionFields.ipynb
+++ b/doc/source/analyzing/XrayEmissionFields.ipynb
@@ -187,8 +187,11 @@
    "metadata": {},
    "source": [
     "> Warning: The X-ray fields depend on the number density of hydrogen atoms, given by the yt field\n",
-    "  `H_number_density`. If this field is not defined (either in the dataset or by the user),\n",
-    "  the primordial hydrogen mass fraction (X = 0.76) will be used to construct it."
+    "  `H_nuclei_density`. In the case of the APEC model, this assumes that all of the hydrogen in your\n",
+    "  dataset is ionized, whereas in the Cloudy model the ionization level is taken into account. If \n",
+    "  this field is not defined (either in the dataset or by the user), it will be constructed using\n",
+    "  abundance information from your dataset. Finally, if your dataset contains no abundance information,\n",
+    "  a primordial hydrogen mass fraction (X = 0.76) will be assumed."
    ]
   }
  ],


https://bitbucket.org/yt_analysis/yt/commits/0bf3a2e0c0f1/
Changeset:   0bf3a2e0c0f1
Branch:      yt
User:        jzuhone
Date:        2017-01-17 16:15:55+00:00
Summary:     Merge
Affected #:  11 files

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 doc/source/analyzing/XrayEmissionFields.ipynb
--- /dev/null
+++ b/doc/source/analyzing/XrayEmissionFields.ipynb
@@ -0,0 +1,220 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note: If you came here trying to figure out how to create simulated X-ray photons and observations,\n",
+    "  you should go [here](analysis_modules/photon_simulator.html) instead."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This functionality provides the ability to create metallicity-dependent X-ray luminosity, emissivity, and photon emissivity fields for a given photon energy range.  This works by interpolating from emission tables created from the photoionization code [Cloudy](http://nublado.org/) or the collisional ionization database [AtomDB](http://www.atomdb.org). These can be downloaded from http://yt-project.org/data from the command line like so:\n",
+    "\n",
+    "`# Put the data in a directory you specify`  \n",
+    "`yt download cloudy_emissivity_v2.h5 /path/to/data`\n",
+    "\n",
+    "`# Put the data in the location set by \"supp_data_dir\"`  \n",
+    "`yt download apec_emissivity_v2.h5 supp_data_dir`\n",
+    "\n",
+    "The data path can be a directory on disk, or it can be \"supp_data_dir\", which will download the data to the directory specified by the `\"supp_data_dir\"` yt configuration entry. It is easiest to put these files in the directory from which you will be running yt or `\"supp_data_dir\"`, but see the note below about putting them in alternate locations."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Emission fields can be made for any energy interval between 0.1 keV and 100 keV, and will always be created for luminosity $(\\rm{erg~s^{-1}})$, emissivity $\\rm{(erg~s^{-1}~cm^{-3})}$, and photon emissivity $\\rm{(photons~s^{-1}~cm^{-3})}$.  The only required arguments are the\n",
+    "dataset object, and the minimum and maximum energies of the energy band. However, typically one needs to decide what will be used for the metallicity. This can either be a floating-point value representing a spatially constant metallicity, or a prescription for a metallicity field, e.g. `(\"gas\", \"metallicity\")`. For this first example, where the dataset has no metallicity field, we'll just assume $Z = 0.3~Z_\\odot$ everywhere:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "import yt\n",
+    "\n",
+    "ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n",
+    "\n",
+    "xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, table_type='apec', metallicity=0.3)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note: If you place the HDF5 emissivity tables in a location other than the current working directory or the location \n",
+    "  specified by the \"supp_data_dir\" configuration value, you will need to specify it in the call to \n",
+    "  `add_xray_emissivity_field`:  \n",
+    "  `xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, data_dir=\"/path/to/data\", table_type='apec', metallicity=0.3)`"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Having made the fields, one can see which fields were made:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "print (xray_fields)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The luminosity field is useful for summing up in regions like this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "sp = ds.sphere(\"c\", (2.0, \"Mpc\"))\n",
+    "print (sp.quantities.total_quantity(\"xray_luminosity_0.5_7.0_keV\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Whereas the emissivity fields may be useful in derived fields or for plotting:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "slc = yt.SlicePlot(ds, 'z', ['xray_emissivity_0.5_7.0_keV','xray_photon_emissivity_0.5_7.0_keV'],\n",
+    "                   width=(0.75, \"Mpc\"))\n",
+    "slc.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The emissivity and the luminosity fields take the values one would see in the frame of the source. However, if one wishes to make projections of the X-ray emission from a cosmologically distant object, the energy band will be redshifted. For this case, one can supply a `redshift` parameter and a `Cosmology` object (either from the dataset or one made on your own) to compute X-ray intensity fields along with the emissivity and luminosity fields.\n",
+    "\n",
+    "This example shows how to do that, Where we also use a spatially dependent metallicity field and the Cloudy tables instead of the APEC tables we used previously:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "ds2 = yt.load(\"D9p_500/10MpcBox_HartGal_csf_a0.500.d\")\n",
+    "\n",
+    "# In this case, use the redshift and cosmology from the dataset, \n",
+    "# but in theory you could put in something different\n",
+    "xray_fields2 = yt.add_xray_emissivity_field(ds2, 0.5, 2.0, redshift=ds2.current_redshift, cosmology=ds2.cosmology,\n",
+    "                                            metallicity=(\"gas\", \"metallicity\"), table_type='cloudy')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now, one can see that two new fields have been added, corresponding to X-ray intensity / surface brightness when projected:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "print (xray_fields2)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Note also that the energy range now corresponds to the *observer* frame, whereas in the source frame the energy range is between `emin*(1+redshift)` and `emax*(1+redshift)`. Let's zoom in on a galaxy and make a projection of the intensity fields:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "prj = yt.ProjectionPlot(ds2, \"x\", [\"xray_intensity_0.5_2.0_keV\", \"xray_photon_intensity_0.5_2.0_keV\"],\n",
+    "                        center=\"max\", width=(40, \"kpc\"))\n",
+    "prj.set_zlim(\"xray_intensity_0.5_2.0_keV\", 1.0e-32, 5.0e-24)\n",
+    "prj.set_zlim(\"xray_photon_intensity_0.5_2.0_keV\", 1.0e-24, 5.0e-16)\n",
+    "prj.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Warning: The X-ray fields depend on the number density of hydrogen atoms, given by the yt field\n",
+    "  `H_nuclei_density`. In the case of the APEC model, this assumes that all of the hydrogen in your\n",
+    "  dataset is ionized, whereas in the Cloudy model the ionization level is taken into account. If \n",
+    "  this field is not defined (either in the dataset or by the user), it will be constructed using\n",
+    "  abundance information from your dataset. Finally, if your dataset contains no abundance information,\n",
+    "  a primordial hydrogen mass fraction (X = 0.76) will be assumed."
+   ]
+  }
+ ],
+ "metadata": {
+  "anaconda-cloud": {},
+  "kernelspec": {
+   "display_name": "Python [default]",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.5.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 doc/source/analyzing/analysis_modules/photon_simulator.rst
--- a/doc/source/analyzing/analysis_modules/photon_simulator.rst
+++ b/doc/source/analyzing/analysis_modules/photon_simulator.rst
@@ -3,6 +3,14 @@
 Constructing Mock X-ray Observations
 ------------------------------------
 
+.. warning:: 
+
+  The ``photon_simulator`` analysis module has been deprecated; it is
+  no longer being updated, and it will be removed in a future version
+  of yt. Users are encouraged to download and use the
+  `pyXSIM <http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim>`_ package 
+  instead. 
+
 .. note::
 
   If you just want to create derived fields for X-ray emission,

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 doc/source/analyzing/analysis_modules/xray_emission_fields.rst
--- a/doc/source/analyzing/analysis_modules/xray_emission_fields.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-.. _xray_emission_fields:
-
-X-ray Emission Fields
-=====================
-.. sectionauthor:: Britton Smith <brittonsmith at gmail.com>, John ZuHone <jzuhone at gmail.com>
-
-.. note::
-
-  If you came here trying to figure out how to create simulated X-ray photons and observations,
-  you should go `here <photon_simulator.html>`_ instead.
-
-This functionality provides the ability to create metallicity-dependent
-X-ray luminosity, emissivity, and photon emissivity fields for a given
-photon energy range.  This works by interpolating from emission tables
-created from the photoionization code `Cloudy <http://nublado.org/>`_ or
-the collisional ionization database `AtomDB <http://www.atomdb.org>`_. If
-you installed yt with the install script, these data files should be located in
-the *data* directory inside the installation directory, or can be downloaded
-from `<http://yt-project.org/data>`_. Emission fields can be made for any
-interval between 0.1 keV and 100 keV.
-
-Adding Emission Fields
-----------------------
-
-Fields will be created for luminosity :math:`{\rm (erg~s^{-1})}`, emissivity :math:`{\rm (erg~s^{-1}~cm^{-3})}`,
-and photon emissivity :math:`{\rm (photons~s^{-1}~cm^{-3})}`.  The only required arguments are the
-dataset object, and the minimum and maximum energies of the energy band.
-
-.. code-block:: python
-
-  import yt
-  from yt.analysis_modules.spectral_integrator.api import \
-       add_xray_emissivity_field
-
-  xray_fields = add_xray_emissivity_field(ds, 0.5, 7.0)
-
-Additional keyword arguments are:
-
- * **filename** (*string*): Path to data file containing emissivity values. If None,
-   a file called "cloudy_emissivity.h5" is used, for photoionized plasmas. A second
-   option, for collisionally ionized plasmas, is in the file "apec_emissivity.h5",
-   available at http://yt-project.org/data. These files contain emissivity tables
-   for primordial elements and for metals at solar metallicity for the energy range
-   0.1 to 100 keV. Default: None.
-
- * **with_metals** (*bool*): If True, use the metallicity field to add the
-   contribution from metals.  If False, only the emission from H/He is
-   considered.  Default: True.
-
- * **constant_metallicity** (*float*): If specified, assume a constant
-   metallicity for the emission from metals.  The *with_metals* keyword
-   must be set to False to use this. It should be given in unit of solar metallicity.
-   Default: None.
-
-The resulting fields can be used like all normal fields. The function will return the names of
-the created fields in a Python list.
-
-.. code-block:: python
-
-  import yt
-  from yt.analysis_modules.spectral_integrator.api import \
-       add_xray_emissivity_field
-
-  xray_fields = add_xray_emissivity_field(ds, 0.5, 7.0, filename="apec_emissivity.h5")
-
-  ds = yt.load("enzo_tiny_cosmology/DD0046/DD0046")
-  plot = yt.SlicePlot(ds, 'x', 'xray_luminosity_0.5_7.0_keV')
-  plot.save()
-  plot = yt.ProjectionPlot(ds, 'x', 'xray_emissivity_0.5_7.0_keV')
-  plot.save()
-  plot = yt.ProjectionPlot(ds, 'x', 'xray_photon_emissivity_0.5_7.0_keV')
-  plot.save()
-
-.. warning::
-
-  The X-ray fields depend on the number density of hydrogen atoms, in the yt field
-  ``H_number_density``. If this field is not defined (either in the dataset or by the user),
-  the primordial hydrogen mass fraction (X = 0.76) will be used to construct it.

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 doc/source/analyzing/xray_emission_fields.rst
--- /dev/null
+++ b/doc/source/analyzing/xray_emission_fields.rst
@@ -0,0 +1,3 @@
+.. _xray-emission-fields:
+
+.. notebook:: XrayEmissionFields.ipynb

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -53,7 +53,7 @@
   local_tipsy_002:
     - yt/frontends/tipsy/tests/test_outputs.py
 
-  local_varia_007:
+  local_varia_008:
     - yt/analysis_modules/radmc3d_export
     - yt/frontends/moab/tests/test_c5.py
     - yt/analysis_modules/photon_simulator/tests/test_spectra.py
@@ -61,6 +61,7 @@
     - yt/visualization/volume_rendering/tests/test_vr_orientation.py
     - yt/visualization/volume_rendering/tests/test_mesh_render.py
     - yt/visualization/tests/test_mesh_slices.py:test_tri2
+    - yt/fields/tests/test_xray_fields.py
 
   local_orion_001:
     - yt/frontends/boxlib/tests/test_orion.py

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 yt/__init__.py
--- a/yt/__init__.py
+++ b/yt/__init__.py
@@ -125,7 +125,8 @@
     ValidateSpatial, \
     ValidateGridType, \
     add_field, \
-    derived_field
+    derived_field, \
+    add_xray_emissivity_field
 
 from yt.data_objects.api import \
     DatasetSeries, ImageArray, \

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 yt/analysis_modules/spectral_integrator/api.py
--- a/yt/analysis_modules/spectral_integrator/api.py
+++ b/yt/analysis_modules/spectral_integrator/api.py
@@ -1,18 +1,8 @@
-"""
-API for spectral_integrator
-
-
-
-"""
+from yt.funcs import issue_deprecation_warning
 
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
+issue_deprecation_warning("The spectral_integrator module is deprecated. "
+                          "'add_xray_emissivity_field' can now be imported "
+                          "from the yt module.")
 
-from .spectral_frequency_integrator import \
-    EmissivityIntegrator, \
+from yt.fields.xray_emission_fields import \
     add_xray_emissivity_field
\ No newline at end of file

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 yt/analysis_modules/spectral_integrator/spectral_frequency_integrator.py
--- a/yt/analysis_modules/spectral_integrator/spectral_frequency_integrator.py
+++ /dev/null
@@ -1,279 +0,0 @@
-"""
-Integrator classes to deal with interpolation and integration of input spectral
-bins.  Currently only supports Cloudy and APEC-style data.
-
-
-
-"""
-
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-
-from yt.utilities.on_demand_imports import _h5py as h5py
-import numpy as np
-import os
-
-from yt.funcs import \
-     download_file, \
-     mylog, \
-     only_on_root
-
-from yt.utilities.exceptions import YTFieldNotFound
-from yt.utilities.exceptions import YTException
-from yt.utilities.linear_interpolators import \
-    UnilinearFieldInterpolator, BilinearFieldInterpolator
-from yt.utilities.physical_constants import \
-    hcgs, mp
-from yt.units.yt_array import YTArray, YTQuantity
-from yt.utilities.physical_ratios import \
-    primordial_H_mass_fraction, erg_per_keV
-
-xray_data_version = 1
-
-def _get_data_file(data_file=None):
-    if data_file is None:
-        data_file = "cloudy_emissivity.h5"
-    data_url = "http://yt-project.org/data"
-    if "YT_DEST" in os.environ and \
-      os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
-        data_dir = os.path.join(os.environ["YT_DEST"], "data")
-    else:
-        data_dir = "."
-    data_path = os.path.join(data_dir, data_file)
-    if not os.path.exists(data_path):
-        mylog.info("Attempting to download supplementary data from %s to %s." % 
-                   (data_url, data_dir))
-        fn = download_file(os.path.join(data_url, data_file), data_path)
-        if fn != data_path:
-            raise RuntimeError("Failed to download supplementary data.")
-    return data_path
-
-class EnergyBoundsException(YTException):
-    def __init__(self, lower, upper):
-        self.lower = lower
-        self.upper = upper
-
-    def __str__(self):
-        return "Energy bounds are %e to %e keV." % \
-          (self.lower, self.upper)
-
-class ObsoleteDataException(YTException):
-    def __str__(self):
-        return "X-ray emissivity data is out of date.\n" + \
-               "Download the latest data from http://yt-project.org/data/cloudy_emissivity.h5 and move it to %s." % \
-          os.path.join(os.environ["YT_DEST"], "data", "cloudy_emissivity.h5")
-          
-class EmissivityIntegrator(object):
-    r"""Class for making X-ray emissivity fields with hdf5 data tables 
-    from Cloudy.
-    
-    Initialize an EmissivityIntegrator object.
-
-    Parameters
-    ----------
-    filename: string, default None
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-        
-    """
-    def __init__(self, filename=None):
-
-        default_filename = False
-        if filename is None:
-            filename = _get_data_file()
-            default_filename = True
-
-        if not os.path.exists(filename):
-            mylog.warning("File %s does not exist, will attempt to find it." % filename)
-            filename = _get_data_file(data_file=filename)
-        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
-        in_file = h5py.File(filename, "r")
-        if "info" in in_file.attrs:
-            only_on_root(mylog.info, in_file.attrs["info"])
-        if default_filename and \
-          in_file.attrs["version"] < xray_data_version:
-            raise ObsoleteDataException()
-        else:
-            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                         in_file.attrs["version"])
-
-        for field in ["emissivity_primordial", "emissivity_metals",
-                      "log_nH", "log_T", "log_E"]:
-            if field in in_file:
-                setattr(self, field, in_file[field][:])
-        in_file.close()
-
-        E_diff = np.diff(self.log_E)
-        self.E_bins = \
-                  YTArray(np.power(10, np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
-                                                      [self.log_E[-1] - 0.5 * E_diff[-1],
-                                                       self.log_E[-1] + 0.5 * E_diff[-1]]])),
-                          "keV")
-        self.dnu = (np.diff(self.E_bins)/hcgs).in_units("Hz")
-
-    def get_interpolator(self, data, e_min, e_max):
-        e_min = YTQuantity(e_min, "keV")
-        e_max = YTQuantity(e_max, "keV")
-        if (e_min - self.E_bins[0]) / e_min < -1e-3 or \
-          (e_max - self.E_bins[-1]) / e_max > 1e-3:
-            raise EnergyBoundsException(self.E_bins[0], self.E_bins[-1])
-        e_is, e_ie = np.digitize([e_min, e_max], self.E_bins)
-        e_is = np.clip(e_is - 1, 0, self.E_bins.size - 1)
-        e_ie = np.clip(e_ie, 0, self.E_bins.size - 1)
-
-        my_dnu = self.dnu[e_is: e_ie].copy()
-        # clip edge bins if the requested range is smaller
-        my_dnu[0] -= ((e_min - self.E_bins[e_is])/hcgs).in_units("Hz")
-        my_dnu[-1] -= ((self.E_bins[e_ie] - e_max)/hcgs).in_units("Hz")
-
-        interp_data = (data[..., e_is:e_ie] * my_dnu).sum(axis=-1)
-        if len(data.shape) == 2:
-            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
-                                               [self.log_T[0],  self.log_T[-1]],
-                                               "log_T", truncate=True)
-        else:
-            emiss = BilinearFieldInterpolator(np.log10(interp_data),
-                                              [self.log_nH[0], self.log_nH[-1],
-                                               self.log_T[0],  self.log_T[-1]],
-                                              ["log_nH", "log_T"], truncate=True)
-
-        return emiss
-
-def add_xray_emissivity_field(ds, e_min, e_max,
-                              filename=None,
-                              with_metals=True,
-                              constant_metallicity=None):
-    r"""Create X-ray emissivity fields for a given energy range.
-
-    Parameters
-    ----------
-    e_min: float
-        the minimum energy in keV for the energy band.
-    e_min: float
-        the maximum energy in keV for the energy band.
-    filename: string, optional
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-    with_metals: bool, optional
-        If True, use the metallicity field to add the contribution from 
-        metals.  If False, only the emission from H/He is considered.
-        Default: True.
-    constant_metallicity: float, optional
-        If specified, assume a constant metallicity for the emission 
-        from metals.  The *with_metals* keyword must be set to False 
-        to use this. It should be given in unit of solar metallicity.
-        Default: None.
-
-    This will create three fields:
-
-    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
-    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
-    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
-
-    Examples
-    --------
-
-    >>> from yt.mods import *
-    >>> from yt.analysis_modules.spectral_integrator.api import *
-    >>> ds = load(dataset)
-    >>> add_xray_emissivity_field(ds, 0.5, 2)
-    >>> p = ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
-    >>> p.save()
-
-    """
-
-    if with_metals:
-        try:
-            ds._get_field_info("metal_density")
-        except YTFieldNotFound:
-            raise RuntimeError("Your dataset does not have a \"metal_density\" field! " +
-                               "Perhaps you should specify a constant metallicity?")
-
-    my_si = EmissivityIntegrator(filename=filename)
-
-    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
-    em_Z = None
-    if with_metals or constant_metallicity is not None:
-        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
-
-    energy_erg = np.power(10, my_si.log_E) * erg_per_keV
-    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
-                                   e_min, e_max)
-    emp_Z = None
-    if with_metals or constant_metallicity is not None:
-        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
-                                       e_min, e_max)
-
-    try:
-        ds._get_field_info("H_number_density")
-    except YTFieldNotFound:
-        mylog.warning("Could not find a field for \"H_number_density\". Assuming primordial H " +
-                      "mass fraction.")
-        def _nh(field, data):
-            return primordial_H_mass_fraction*data["gas","density"]/mp
-        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
-
-    def _emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
-
-        my_emissivity = np.power(10, em_0(dd))
-        if em_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
-            my_emissivity += my_Z * np.power(10, em_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "erg*cm**3/s")
-
-    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", emiss_name), function=_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/cm**3/s")
-
-    def _luminosity_field(field, data):
-        return data[emiss_name] * data["cell_volume"]
-
-    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", lum_name), function=_luminosity_field,
-                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/s")
-
-    def _photon_emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
-
-        my_emissivity = np.power(10, emp_0(dd))
-        if emp_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
-            my_emissivity += my_Z * np.power(10, emp_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "photons*cm**3/s")
-
-    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="photons/cm**3/s")
-
-    return emiss_name, lum_name, phot_name

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 yt/fields/api.py
--- a/yt/fields/api.py
+++ b/yt/fields/api.py
@@ -43,3 +43,5 @@
     FieldDetector
 from .field_info_container import \
     FieldInfoContainer
+from .xray_emission_fields import \
+    add_xray_emissivity_field
\ No newline at end of file

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 yt/fields/tests/test_xray_fields.py
--- /dev/null
+++ b/yt/fields/tests/test_xray_fields.py
@@ -0,0 +1,40 @@
+from yt.fields.xray_emission_fields import \
+    add_xray_emissivity_field
+from yt.utilities.answer_testing.framework import \
+    requires_ds, can_run_ds, data_dir_load, \
+    ProjectionValuesTest, FieldValuesTest
+
+def setup():
+    from yt.config import ytcfg
+    ytcfg["yt","__withintesting"] = "True"
+
+def check_xray_fields(ds_fn, fields):
+    if not can_run_ds(ds_fn): return
+    dso = [ None, ("sphere", ("m", (0.1, 'unitary')))]
+    for field in fields:
+        for axis in [0, 1, 2]:
+            for dobj_name in dso:
+                yield ProjectionValuesTest(ds_fn, axis, field, 
+                                           None, dobj_name)
+                yield FieldValuesTest(ds_fn, field, dobj_name)
+
+sloshing = "GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300"
+ at requires_ds(sloshing, big_data=True)
+def test_sloshing_apec():
+    ds = data_dir_load(sloshing)
+    fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type="apec", 
+                                       metallicity=0.3)
+    for test in check_xray_fields(ds, fields):
+        test_sloshing_apec.__name__ = test.description
+        yield test
+
+d9p = "D9p_500/10MpcBox_HartGal_csf_a0.500.d"
+ at requires_ds(d9p, big_data=True)
+def test_d9p_cloudy():
+    ds = data_dir_load(d9p)
+    fields = add_xray_emissivity_field(ds, 0.5, 2.0, redshift=ds.current_redshift,
+                                       table_type="cloudy", cosmology=ds.cosmology,
+                                       metallicity=("gas", "metallicity"))
+    for test in check_xray_fields(ds, fields):
+        test_d9p_cloudy.__name__ = test.description
+        yield test

diff -r e03273b808d64c8e09a1914149bcea38e16e4831 -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 yt/fields/xray_emission_fields.py
--- /dev/null
+++ b/yt/fields/xray_emission_fields.py
@@ -0,0 +1,304 @@
+"""
+Integrator classes to deal with interpolation and integration of input spectral
+bins.  Currently only supports Cloudy and APEC-style data.
+
+
+
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, yt Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+from yt.utilities.on_demand_imports import _h5py as h5py
+import numpy as np
+import os
+
+from yt.config import ytcfg
+from yt.fields.derived_field import DerivedField
+from yt.funcs import mylog, only_on_root, issue_deprecation_warning
+from yt.utilities.exceptions import YTFieldNotFound
+from yt.utilities.exceptions import YTException
+from yt.utilities.linear_interpolators import \
+    UnilinearFieldInterpolator, BilinearFieldInterpolator
+from yt.units.yt_array import YTArray, YTQuantity
+from yt.utilities.cosmology import Cosmology
+
+data_version = {"cloudy": 2,
+                "apec": 2}
+
+data_url = "http://yt-project.org/data"
+
+def _get_data_file(table_type, data_dir=None):
+    data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
+    if data_dir is None:
+        supp_data_dir = ytcfg.get("yt", "supp_data_dir")
+        data_dir = supp_data_dir if os.path.exists(supp_data_dir) else "."
+    data_path = os.path.join(data_dir, data_file)
+    if not os.path.exists(data_path):
+        msg = "Failed to find emissivity data file %s! " % data_file + \
+            "Please download from http://yt-project.org/data!"
+        mylog.error(msg)
+        raise IOError(msg)
+    return data_path
+
+class EnergyBoundsException(YTException):
+    def __init__(self, lower, upper):
+        self.lower = lower
+        self.upper = upper
+
+    def __str__(self):
+        return "Energy bounds are %e to %e keV." % \
+          (self.lower, self.upper)
+
+class ObsoleteDataException(YTException):
+    def __init__(self, table_type):
+        data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
+        self.msg = "X-ray emissivity data is out of date.\n"
+        self.msg += "Download the latest data from %s/%s." % (data_url, data_file)
+
+    def __str__(self):
+        return self.msg
+
+class XrayEmissivityIntegrator(object):
+    r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
+    generated from Cloudy and AtomDB/APEC.
+
+    Initialize an XrayEmissivityIntegrator object.
+
+    Parameters
+    ----------
+    table_type: string
+        The type of data to use when computing the emissivity values. If "cloudy",
+        a file called "cloudy_emissivity.h5" is used, for photoionized
+        plasmas. If, "apec", a file called "apec_emissivity.h5" is used for 
+        collisionally ionized plasmas. These files contain emissivity tables 
+        for primordial elements and for metals at solar metallicity for the 
+        energy range 0.1 to 100 keV.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the location of the YT_DEST environment variable
+        or in the current working directory.
+    use_metals : boolean, optional
+        If set to True, the emissivity will include contributions from metals.
+        Default: True
+    """
+    def __init__(self, table_type, redshift=0.0, data_dir=None, use_metals=True):
+
+        filename = _get_data_file(table_type, data_dir=data_dir)
+        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
+        in_file = h5py.File(filename, "r")
+        if "info" in in_file.attrs:
+            only_on_root(mylog.info, in_file.attrs["info"].decode('utf8'))
+        if in_file.attrs["version"] != data_version[table_type]:
+            raise ObsoleteDataException(table_type)
+        else:
+            only_on_root(mylog.info, "X-ray '%s' emissivity data version: %s." % \
+                         (table_type, in_file.attrs["version"]))
+
+        self.log_T = in_file["log_T"][:]
+        self.emissivity_primordial = in_file["emissivity_primordial"][:]
+        if "log_nH" in in_file:
+            self.log_nH = in_file["log_nH"][:]
+        if use_metals:
+            self.emissivity_metals = in_file["emissivity_metals"][:]
+        self.ebin = YTArray(in_file["E"], "keV")
+        in_file.close()
+        self.dE = np.diff(self.ebin)
+        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1]).to("erg")
+        self.redshift = redshift
+
+    def get_interpolator(self, data_type, e_min, e_max, energy=True):
+        data = getattr(self, "emissivity_%s" % data_type)
+        if not energy:
+            data = data[..., :] / self.emid.v
+        e_min = YTQuantity(e_min, "keV")*(1.0+self.redshift)
+        e_max = YTQuantity(e_max, "keV")*(1.0+self.redshift)
+        if (e_min - self.ebin[0]) / e_min < -1e-3 or \
+          (e_max - self.ebin[-1]) / e_max > 1e-3:
+            raise EnergyBoundsException(self.ebin[0], self.ebin[-1])
+        e_is, e_ie = np.digitize([e_min, e_max], self.ebin)
+        e_is = np.clip(e_is - 1, 0, self.ebin.size - 1)
+        e_ie = np.clip(e_ie, 0, self.ebin.size - 1)
+
+        my_dE = self.dE[e_is: e_ie].copy()
+        # clip edge bins if the requested range is smaller
+        my_dE[0] -= e_min - self.ebin[e_is]
+        my_dE[-1] -= self.ebin[e_ie] - e_max
+
+        interp_data = (data[..., e_is:e_ie]*my_dE).sum(axis=-1)
+        if data.ndim == 2:
+            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
+                                               [self.log_T[0],  self.log_T[-1]],
+                                               "log_T", truncate=True)
+        else:
+            emiss = BilinearFieldInterpolator(np.log10(interp_data),
+                                              [self.log_nH[0], self.log_nH[-1],
+                                               self.log_T[0],  self.log_T[-1]],
+                                              ["log_nH", "log_T"], truncate=True)
+
+        return emiss
+
+def add_xray_emissivity_field(ds, e_min, e_max, redshift=0.0,
+                              metallicity=("gas", "metallicity"), 
+                              table_type="cloudy", data_dir=None,
+                              cosmology=None, **kwargs):
+    r"""Create X-ray emissivity fields for a given energy range.
+
+    Parameters
+    ----------
+    e_min : float
+        The minimum energy in keV for the energy band.
+    e_min : float
+        The maximum energy in keV for the energy band.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
+    metallicity : field or float, optional
+        Either the name of a metallicity field or a single floating-point
+        number specifying a spatially constant metallicity. Must be in
+        solar units. If set to None, no metals will be assumed. Default: 
+        ("gas", "metallicity")
+    table_type : string, optional
+        The type of emissivity table to be used when creating the fields. 
+        Options are "cloudy" or "apec". Default: "cloudy"
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the location of the YT_DEST environment variable
+        or in the current working directory.
+    cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional
+        If set and redshift > 0.0, this cosmology will be used when computing the
+        cosmological dependence of the emission fields. If not set, yt's default
+        LCDM cosmology will be used.
+
+    This will create three fields:
+
+    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
+    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
+    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
+
+    Examples
+    --------
+
+    >>> import yt
+    >>> ds = yt.load("sloshing_nomag2_hdf5_plt_cnt_0100")
+    >>> yt.add_xray_emissivity_field(ds, 0.5, 2)
+    >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
+    >>> p.save()
+    """
+    # The next several if constructs are for backwards-compatibility
+    if "constant_metallicity" in kwargs:
+        issue_deprecation_warning("The \"constant_metallicity\" parameter is deprecated. Set "
+                                  "the \"metallicity\" parameter to a constant float value instead.")
+        metallicity = kwargs["constant_metallicity"]
+    if "with_metals" in kwargs:
+        issue_deprecation_warning("The \"with_metals\" parameter is deprecated. Use the "
+                                  "\"metallicity\" parameter to choose a constant or "
+                                  "spatially varying metallicity.")
+        if kwargs["with_metals"] and isinstance(metallicity, float):
+            raise RuntimeError("\"with_metals=True\", but you specified a constant metallicity!")
+        if not kwargs["with_metals"] and not isinstance(metallicity, float):
+            raise RuntimeError("\"with_metals=False\", but you didn't specify a constant metallicity!")
+    if not isinstance(metallicity, float) and metallicity is not None:
+        try:
+            metallicity = ds._get_field_info(*metallicity)
+        except YTFieldNotFound:
+            raise RuntimeError("Your dataset does not have a {} field! ".format(metallicity) +
+                               "Perhaps you should specify a constant metallicity instead?")
+
+    my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)
+
+    em_0 = my_si.get_interpolator("primordial", e_min, e_max)
+    emp_0 = my_si.get_interpolator("primordial", e_min, e_max, energy=False)
+    if metallicity is not None:
+        em_Z = my_si.get_interpolator("metals", e_min, e_max)
+        emp_Z = my_si.get_interpolator("metals", e_min, e_max, energy=False)
+
+    def _emissivity_field(field, data):
+        dd = {"log_nH": np.log10(data["gas", "H_nuclei_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
+
+        my_emissivity = np.power(10, em_0(dd))
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity.name]
+            else:
+                my_Z = metallicity
+            my_emissivity += my_Z * np.power(10, em_Z(dd))
+
+        return data["gas","H_nuclei_density"]**2 * \
+            YTArray(my_emissivity, "erg*cm**3/s")
+
+    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", emiss_name), function=_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 sampling_type="cell", units="erg/cm**3/s")
+
+    def _luminosity_field(field, data):
+        return data[emiss_name] * data["cell_volume"]
+
+    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", lum_name), function=_luminosity_field,
+                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
+                 sampling_type="cell", units="erg/s")
+
+    def _photon_emissivity_field(field, data):
+        dd = {"log_nH": np.log10(data["gas", "H_nuclei_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
+
+        my_emissivity = np.power(10, emp_0(dd))
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity.name]
+            else:
+                my_Z = metallicity
+            my_emissivity += my_Z * np.power(10, emp_Z(dd))
+
+        return data["gas", "H_nuclei_density"]**2 * \
+            YTArray(my_emissivity, "photons*cm**3/s")
+
+    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 sampling_type="cell", units="photons/cm**3/s")
+
+    fields = [emiss_name, lum_name, phot_name]
+
+    if redshift > 0.0:
+
+        if cosmology is None:
+            if hasattr(ds, "cosmology"):
+                cosmology = ds.cosmology
+            else:
+                cosmology = Cosmology()
+
+        D_L = cosmology.luminosity_distance(0.0, redshift)
+        angular_scale = 1.0/cosmology.angular_scale(0.0, redshift)
+        dist_fac = 1.0/(4.0*np.pi*D_L*D_L*angular_scale*angular_scale)
+
+        ei_name = "xray_intensity_%s_%s_keV" % (e_min, e_max)
+        def _intensity_field(field, data):
+            I = dist_fac*data[emiss_name]
+            return I.in_units("erg/cm**3/s/arcsec**2")
+        ds.add_field(("gas", ei_name), function=_intensity_field,
+                     display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
+                     sampling_type="cell", units="erg/cm**3/s/arcsec**2")
+
+        i_name = "xray_photon_intensity_%s_%s_keV" % (e_min, e_max)
+        def _photon_intensity_field(field, data):
+            I = (1.0+redshift)*dist_fac*data[phot_name]
+            return I.in_units("photons/cm**3/s/arcsec**2")
+        ds.add_field(("gas", i_name), function=_photon_intensity_field,
+                     display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
+                     sampling_type="cell", units="photons/cm**3/s/arcsec**2")
+
+        fields += [ei_name, i_name]
+
+    [mylog.info("Adding %s field." % field) for field in fields]
+
+    return fields


https://bitbucket.org/yt_analysis/yt/commits/defc74f819ae/
Changeset:   defc74f819ae
Branch:      yt
User:        jzuhone
Date:        2017-01-17 16:39:32+00:00
Summary:     Fix the TOC
Affected #:  1 file

diff -r 0bf3a2e0c0f127eedbb4f1e3edf1c7dc15b11e34 -r defc74f819ae1b255370145b07642b580a71af57 doc/source/analyzing/xray_emission_fields.rst
--- a/doc/source/analyzing/xray_emission_fields.rst
+++ b/doc/source/analyzing/xray_emission_fields.rst
@@ -1,3 +1,3 @@
-.. _xray-emission-fields:
+.. _xray_emission_fields:
 
 .. notebook:: XrayEmissionFields.ipynb


https://bitbucket.org/yt_analysis/yt/commits/f0fa273ba464/
Changeset:   f0fa273ba464
Branch:      yt
User:        ngoldbaum
Date:        2017-01-19 17:10:29+00:00
Summary:     Merged in jzuhone/yt (pull request #2465)

Port the spectral_integrator analysis module into yt.fields
Affected #:  11 files

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb doc/source/analyzing/XrayEmissionFields.ipynb
--- /dev/null
+++ b/doc/source/analyzing/XrayEmissionFields.ipynb
@@ -0,0 +1,220 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note: If you came here trying to figure out how to create simulated X-ray photons and observations,\n",
+    "  you should go [here](analysis_modules/photon_simulator.html) instead."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "This functionality provides the ability to create metallicity-dependent X-ray luminosity, emissivity, and photon emissivity fields for a given photon energy range.  This works by interpolating from emission tables created from the photoionization code [Cloudy](http://nublado.org/) or the collisional ionization database [AtomDB](http://www.atomdb.org). These can be downloaded from http://yt-project.org/data from the command line like so:\n",
+    "\n",
+    "`# Put the data in a directory you specify`  \n",
+    "`yt download cloudy_emissivity_v2.h5 /path/to/data`\n",
+    "\n",
+    "`# Put the data in the location set by \"supp_data_dir\"`  \n",
+    "`yt download apec_emissivity_v2.h5 supp_data_dir`\n",
+    "\n",
+    "The data path can be a directory on disk, or it can be \"supp_data_dir\", which will download the data to the directory specified by the `\"supp_data_dir\"` yt configuration entry. It is easiest to put these files in the directory from which you will be running yt or `\"supp_data_dir\"`, but see the note below about putting them in alternate locations."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Emission fields can be made for any energy interval between 0.1 keV and 100 keV, and will always be created for luminosity $(\\rm{erg~s^{-1}})$, emissivity $\\rm{(erg~s^{-1}~cm^{-3})}$, and photon emissivity $\\rm{(photons~s^{-1}~cm^{-3})}$.  The only required arguments are the\n",
+    "dataset object, and the minimum and maximum energies of the energy band. However, typically one needs to decide what will be used for the metallicity. This can either be a floating-point value representing a spatially constant metallicity, or a prescription for a metallicity field, e.g. `(\"gas\", \"metallicity\")`. For this first example, where the dataset has no metallicity field, we'll just assume $Z = 0.3~Z_\\odot$ everywhere:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "import yt\n",
+    "\n",
+    "ds = yt.load(\"GasSloshing/sloshing_nomag2_hdf5_plt_cnt_0150\")\n",
+    "\n",
+    "xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, table_type='apec', metallicity=0.3)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Note: If you place the HDF5 emissivity tables in a location other than the current working directory or the location \n",
+    "  specified by the \"supp_data_dir\" configuration value, you will need to specify it in the call to \n",
+    "  `add_xray_emissivity_field`:  \n",
+    "  `xray_fields = yt.add_xray_emissivity_field(ds, 0.5, 7.0, data_dir=\"/path/to/data\", table_type='apec', metallicity=0.3)`"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Having made the fields, one can see which fields were made:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "print (xray_fields)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The luminosity field is useful for summing up in regions like this:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "sp = ds.sphere(\"c\", (2.0, \"Mpc\"))\n",
+    "print (sp.quantities.total_quantity(\"xray_luminosity_0.5_7.0_keV\"))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Whereas the emissivity fields may be useful in derived fields or for plotting:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "slc = yt.SlicePlot(ds, 'z', ['xray_emissivity_0.5_7.0_keV','xray_photon_emissivity_0.5_7.0_keV'],\n",
+    "                   width=(0.75, \"Mpc\"))\n",
+    "slc.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "The emissivity and the luminosity fields take the values one would see in the frame of the source. However, if one wishes to make projections of the X-ray emission from a cosmologically distant object, the energy band will be redshifted. For this case, one can supply a `redshift` parameter and a `Cosmology` object (either from the dataset or one made on your own) to compute X-ray intensity fields along with the emissivity and luminosity fields.\n",
+    "\n",
+    "This example shows how to do that, Where we also use a spatially dependent metallicity field and the Cloudy tables instead of the APEC tables we used previously:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "ds2 = yt.load(\"D9p_500/10MpcBox_HartGal_csf_a0.500.d\")\n",
+    "\n",
+    "# In this case, use the redshift and cosmology from the dataset, \n",
+    "# but in theory you could put in something different\n",
+    "xray_fields2 = yt.add_xray_emissivity_field(ds2, 0.5, 2.0, redshift=ds2.current_redshift, cosmology=ds2.cosmology,\n",
+    "                                            metallicity=(\"gas\", \"metallicity\"), table_type='cloudy')"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Now, one can see that two new fields have been added, corresponding to X-ray intensity / surface brightness when projected:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false
+   },
+   "outputs": [],
+   "source": [
+    "print (xray_fields2)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Note also that the energy range now corresponds to the *observer* frame, whereas in the source frame the energy range is between `emin*(1+redshift)` and `emax*(1+redshift)`. Let's zoom in on a galaxy and make a projection of the intensity fields:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {
+    "collapsed": false,
+    "scrolled": false
+   },
+   "outputs": [],
+   "source": [
+    "prj = yt.ProjectionPlot(ds2, \"x\", [\"xray_intensity_0.5_2.0_keV\", \"xray_photon_intensity_0.5_2.0_keV\"],\n",
+    "                        center=\"max\", width=(40, \"kpc\"))\n",
+    "prj.set_zlim(\"xray_intensity_0.5_2.0_keV\", 1.0e-32, 5.0e-24)\n",
+    "prj.set_zlim(\"xray_photon_intensity_0.5_2.0_keV\", 1.0e-24, 5.0e-16)\n",
+    "prj.show()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> Warning: The X-ray fields depend on the number density of hydrogen atoms, given by the yt field\n",
+    "  `H_nuclei_density`. In the case of the APEC model, this assumes that all of the hydrogen in your\n",
+    "  dataset is ionized, whereas in the Cloudy model the ionization level is taken into account. If \n",
+    "  this field is not defined (either in the dataset or by the user), it will be constructed using\n",
+    "  abundance information from your dataset. Finally, if your dataset contains no abundance information,\n",
+    "  a primordial hydrogen mass fraction (X = 0.76) will be assumed."
+   ]
+  }
+ ],
+ "metadata": {
+  "anaconda-cloud": {},
+  "kernelspec": {
+   "display_name": "Python [default]",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.5.2"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb doc/source/analyzing/analysis_modules/photon_simulator.rst
--- a/doc/source/analyzing/analysis_modules/photon_simulator.rst
+++ b/doc/source/analyzing/analysis_modules/photon_simulator.rst
@@ -3,6 +3,14 @@
 Constructing Mock X-ray Observations
 ------------------------------------
 
+.. warning:: 
+
+  The ``photon_simulator`` analysis module has been deprecated; it is
+  no longer being updated, and it will be removed in a future version
+  of yt. Users are encouraged to download and use the
+  `pyXSIM <http://hea-www.cfa.harvard.edu/~jzuhone/pyxsim>`_ package 
+  instead. 
+
 .. note::
 
   If you just want to create derived fields for X-ray emission,

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb doc/source/analyzing/analysis_modules/xray_emission_fields.rst
--- a/doc/source/analyzing/analysis_modules/xray_emission_fields.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-.. _xray_emission_fields:
-
-X-ray Emission Fields
-=====================
-.. sectionauthor:: Britton Smith <brittonsmith at gmail.com>, John ZuHone <jzuhone at gmail.com>
-
-.. note::
-
-  If you came here trying to figure out how to create simulated X-ray photons and observations,
-  you should go `here <photon_simulator.html>`_ instead.
-
-This functionality provides the ability to create metallicity-dependent
-X-ray luminosity, emissivity, and photon emissivity fields for a given
-photon energy range.  This works by interpolating from emission tables
-created from the photoionization code `Cloudy <http://nublado.org/>`_ or
-the collisional ionization database `AtomDB <http://www.atomdb.org>`_. If
-you installed yt with the install script, these data files should be located in
-the *data* directory inside the installation directory, or can be downloaded
-from `<http://yt-project.org/data>`_. Emission fields can be made for any
-interval between 0.1 keV and 100 keV.
-
-Adding Emission Fields
-----------------------
-
-Fields will be created for luminosity :math:`{\rm (erg~s^{-1})}`, emissivity :math:`{\rm (erg~s^{-1}~cm^{-3})}`,
-and photon emissivity :math:`{\rm (photons~s^{-1}~cm^{-3})}`.  The only required arguments are the
-dataset object, and the minimum and maximum energies of the energy band.
-
-.. code-block:: python
-
-  import yt
-  from yt.analysis_modules.spectral_integrator.api import \
-       add_xray_emissivity_field
-
-  xray_fields = add_xray_emissivity_field(ds, 0.5, 7.0)
-
-Additional keyword arguments are:
-
- * **filename** (*string*): Path to data file containing emissivity values. If None,
-   a file called "cloudy_emissivity.h5" is used, for photoionized plasmas. A second
-   option, for collisionally ionized plasmas, is in the file "apec_emissivity.h5",
-   available at http://yt-project.org/data. These files contain emissivity tables
-   for primordial elements and for metals at solar metallicity for the energy range
-   0.1 to 100 keV. Default: None.
-
- * **with_metals** (*bool*): If True, use the metallicity field to add the
-   contribution from metals.  If False, only the emission from H/He is
-   considered.  Default: True.
-
- * **constant_metallicity** (*float*): If specified, assume a constant
-   metallicity for the emission from metals.  The *with_metals* keyword
-   must be set to False to use this. It should be given in unit of solar metallicity.
-   Default: None.
-
-The resulting fields can be used like all normal fields. The function will return the names of
-the created fields in a Python list.
-
-.. code-block:: python
-
-  import yt
-  from yt.analysis_modules.spectral_integrator.api import \
-       add_xray_emissivity_field
-
-  xray_fields = add_xray_emissivity_field(ds, 0.5, 7.0, filename="apec_emissivity.h5")
-
-  ds = yt.load("enzo_tiny_cosmology/DD0046/DD0046")
-  plot = yt.SlicePlot(ds, 'x', 'xray_luminosity_0.5_7.0_keV')
-  plot.save()
-  plot = yt.ProjectionPlot(ds, 'x', 'xray_emissivity_0.5_7.0_keV')
-  plot.save()
-  plot = yt.ProjectionPlot(ds, 'x', 'xray_photon_emissivity_0.5_7.0_keV')
-  plot.save()
-
-.. warning::
-
-  The X-ray fields depend on the number density of hydrogen atoms, in the yt field
-  ``H_number_density``. If this field is not defined (either in the dataset or by the user),
-  the primordial hydrogen mass fraction (X = 0.76) will be used to construct it.

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb doc/source/analyzing/xray_emission_fields.rst
--- /dev/null
+++ b/doc/source/analyzing/xray_emission_fields.rst
@@ -0,0 +1,3 @@
+.. _xray_emission_fields:
+
+.. notebook:: XrayEmissionFields.ipynb

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb tests/tests.yaml
--- a/tests/tests.yaml
+++ b/tests/tests.yaml
@@ -53,7 +53,7 @@
   local_tipsy_002:
     - yt/frontends/tipsy/tests/test_outputs.py
 
-  local_varia_007:
+  local_varia_008:
     - yt/analysis_modules/radmc3d_export
     - yt/frontends/moab/tests/test_c5.py
     - yt/analysis_modules/photon_simulator/tests/test_spectra.py
@@ -61,6 +61,7 @@
     - yt/visualization/volume_rendering/tests/test_vr_orientation.py
     - yt/visualization/volume_rendering/tests/test_mesh_render.py
     - yt/visualization/tests/test_mesh_slices.py:test_tri2
+    - yt/fields/tests/test_xray_fields.py
 
   local_orion_001:
     - yt/frontends/boxlib/tests/test_orion.py

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb yt/__init__.py
--- a/yt/__init__.py
+++ b/yt/__init__.py
@@ -125,7 +125,8 @@
     ValidateSpatial, \
     ValidateGridType, \
     add_field, \
-    derived_field
+    derived_field, \
+    add_xray_emissivity_field
 
 from yt.data_objects.api import \
     DatasetSeries, ImageArray, \

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb yt/analysis_modules/spectral_integrator/api.py
--- a/yt/analysis_modules/spectral_integrator/api.py
+++ b/yt/analysis_modules/spectral_integrator/api.py
@@ -1,18 +1,8 @@
-"""
-API for spectral_integrator
-
-
-
-"""
+from yt.funcs import issue_deprecation_warning
 
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
+issue_deprecation_warning("The spectral_integrator module is deprecated. "
+                          "'add_xray_emissivity_field' can now be imported "
+                          "from the yt module.")
 
-from .spectral_frequency_integrator import \
-    EmissivityIntegrator, \
+from yt.fields.xray_emission_fields import \
     add_xray_emissivity_field
\ No newline at end of file

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb yt/analysis_modules/spectral_integrator/spectral_frequency_integrator.py
--- a/yt/analysis_modules/spectral_integrator/spectral_frequency_integrator.py
+++ /dev/null
@@ -1,279 +0,0 @@
-"""
-Integrator classes to deal with interpolation and integration of input spectral
-bins.  Currently only supports Cloudy and APEC-style data.
-
-
-
-"""
-
-#-----------------------------------------------------------------------------
-# Copyright (c) 2013, yt Development Team.
-#
-# Distributed under the terms of the Modified BSD License.
-#
-# The full license is in the file COPYING.txt, distributed with this software.
-#-----------------------------------------------------------------------------
-
-from yt.utilities.on_demand_imports import _h5py as h5py
-import numpy as np
-import os
-
-from yt.funcs import \
-     download_file, \
-     mylog, \
-     only_on_root
-
-from yt.utilities.exceptions import YTFieldNotFound
-from yt.utilities.exceptions import YTException
-from yt.utilities.linear_interpolators import \
-    UnilinearFieldInterpolator, BilinearFieldInterpolator
-from yt.utilities.physical_constants import \
-    hcgs, mp
-from yt.units.yt_array import YTArray, YTQuantity
-from yt.utilities.physical_ratios import \
-    primordial_H_mass_fraction, erg_per_keV
-
-xray_data_version = 1
-
-def _get_data_file(data_file=None):
-    if data_file is None:
-        data_file = "cloudy_emissivity.h5"
-    data_url = "http://yt-project.org/data"
-    if "YT_DEST" in os.environ and \
-      os.path.isdir(os.path.join(os.environ["YT_DEST"], "data")):
-        data_dir = os.path.join(os.environ["YT_DEST"], "data")
-    else:
-        data_dir = "."
-    data_path = os.path.join(data_dir, data_file)
-    if not os.path.exists(data_path):
-        mylog.info("Attempting to download supplementary data from %s to %s." % 
-                   (data_url, data_dir))
-        fn = download_file(os.path.join(data_url, data_file), data_path)
-        if fn != data_path:
-            raise RuntimeError("Failed to download supplementary data.")
-    return data_path
-
-class EnergyBoundsException(YTException):
-    def __init__(self, lower, upper):
-        self.lower = lower
-        self.upper = upper
-
-    def __str__(self):
-        return "Energy bounds are %e to %e keV." % \
-          (self.lower, self.upper)
-
-class ObsoleteDataException(YTException):
-    def __str__(self):
-        return "X-ray emissivity data is out of date.\n" + \
-               "Download the latest data from http://yt-project.org/data/cloudy_emissivity.h5 and move it to %s." % \
-          os.path.join(os.environ["YT_DEST"], "data", "cloudy_emissivity.h5")
-          
-class EmissivityIntegrator(object):
-    r"""Class for making X-ray emissivity fields with hdf5 data tables 
-    from Cloudy.
-    
-    Initialize an EmissivityIntegrator object.
-
-    Parameters
-    ----------
-    filename: string, default None
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-        
-    """
-    def __init__(self, filename=None):
-
-        default_filename = False
-        if filename is None:
-            filename = _get_data_file()
-            default_filename = True
-
-        if not os.path.exists(filename):
-            mylog.warning("File %s does not exist, will attempt to find it." % filename)
-            filename = _get_data_file(data_file=filename)
-        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
-        in_file = h5py.File(filename, "r")
-        if "info" in in_file.attrs:
-            only_on_root(mylog.info, in_file.attrs["info"])
-        if default_filename and \
-          in_file.attrs["version"] < xray_data_version:
-            raise ObsoleteDataException()
-        else:
-            only_on_root(mylog.info, "X-ray emissivity data version: %s." % \
-                         in_file.attrs["version"])
-
-        for field in ["emissivity_primordial", "emissivity_metals",
-                      "log_nH", "log_T", "log_E"]:
-            if field in in_file:
-                setattr(self, field, in_file[field][:])
-        in_file.close()
-
-        E_diff = np.diff(self.log_E)
-        self.E_bins = \
-                  YTArray(np.power(10, np.concatenate([self.log_E[:-1] - 0.5 * E_diff,
-                                                      [self.log_E[-1] - 0.5 * E_diff[-1],
-                                                       self.log_E[-1] + 0.5 * E_diff[-1]]])),
-                          "keV")
-        self.dnu = (np.diff(self.E_bins)/hcgs).in_units("Hz")
-
-    def get_interpolator(self, data, e_min, e_max):
-        e_min = YTQuantity(e_min, "keV")
-        e_max = YTQuantity(e_max, "keV")
-        if (e_min - self.E_bins[0]) / e_min < -1e-3 or \
-          (e_max - self.E_bins[-1]) / e_max > 1e-3:
-            raise EnergyBoundsException(self.E_bins[0], self.E_bins[-1])
-        e_is, e_ie = np.digitize([e_min, e_max], self.E_bins)
-        e_is = np.clip(e_is - 1, 0, self.E_bins.size - 1)
-        e_ie = np.clip(e_ie, 0, self.E_bins.size - 1)
-
-        my_dnu = self.dnu[e_is: e_ie].copy()
-        # clip edge bins if the requested range is smaller
-        my_dnu[0] -= ((e_min - self.E_bins[e_is])/hcgs).in_units("Hz")
-        my_dnu[-1] -= ((self.E_bins[e_ie] - e_max)/hcgs).in_units("Hz")
-
-        interp_data = (data[..., e_is:e_ie] * my_dnu).sum(axis=-1)
-        if len(data.shape) == 2:
-            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
-                                               [self.log_T[0],  self.log_T[-1]],
-                                               "log_T", truncate=True)
-        else:
-            emiss = BilinearFieldInterpolator(np.log10(interp_data),
-                                              [self.log_nH[0], self.log_nH[-1],
-                                               self.log_T[0],  self.log_T[-1]],
-                                              ["log_nH", "log_T"], truncate=True)
-
-        return emiss
-
-def add_xray_emissivity_field(ds, e_min, e_max,
-                              filename=None,
-                              with_metals=True,
-                              constant_metallicity=None):
-    r"""Create X-ray emissivity fields for a given energy range.
-
-    Parameters
-    ----------
-    e_min: float
-        the minimum energy in keV for the energy band.
-    e_min: float
-        the maximum energy in keV for the energy band.
-    filename: string, optional
-        Path to data file containing emissivity values.  If None,
-        a file called "cloudy_emissivity.h5" is used, for photoionized
-        plasmas. A second option, for collisionally ionized plasmas, is
-        in the file "apec_emissivity.h5", available at http://yt-project.org/data.
-        These files contain emissivity tables for primordial elements and
-        for metals at solar metallicity for the energy range 0.1 to 100 keV.
-        Default: None.
-    with_metals: bool, optional
-        If True, use the metallicity field to add the contribution from 
-        metals.  If False, only the emission from H/He is considered.
-        Default: True.
-    constant_metallicity: float, optional
-        If specified, assume a constant metallicity for the emission 
-        from metals.  The *with_metals* keyword must be set to False 
-        to use this. It should be given in unit of solar metallicity.
-        Default: None.
-
-    This will create three fields:
-
-    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
-    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
-    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
-
-    Examples
-    --------
-
-    >>> from yt.mods import *
-    >>> from yt.analysis_modules.spectral_integrator.api import *
-    >>> ds = load(dataset)
-    >>> add_xray_emissivity_field(ds, 0.5, 2)
-    >>> p = ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
-    >>> p.save()
-
-    """
-
-    if with_metals:
-        try:
-            ds._get_field_info("metal_density")
-        except YTFieldNotFound:
-            raise RuntimeError("Your dataset does not have a \"metal_density\" field! " +
-                               "Perhaps you should specify a constant metallicity?")
-
-    my_si = EmissivityIntegrator(filename=filename)
-
-    em_0 = my_si.get_interpolator(my_si.emissivity_primordial, e_min, e_max)
-    em_Z = None
-    if with_metals or constant_metallicity is not None:
-        em_Z = my_si.get_interpolator(my_si.emissivity_metals, e_min, e_max)
-
-    energy_erg = np.power(10, my_si.log_E) * erg_per_keV
-    emp_0 = my_si.get_interpolator((my_si.emissivity_primordial[..., :] / energy_erg),
-                                   e_min, e_max)
-    emp_Z = None
-    if with_metals or constant_metallicity is not None:
-        emp_Z = my_si.get_interpolator((my_si.emissivity_metals[..., :] / energy_erg),
-                                       e_min, e_max)
-
-    try:
-        ds._get_field_info("H_number_density")
-    except YTFieldNotFound:
-        mylog.warning("Could not find a field for \"H_number_density\". Assuming primordial H " +
-                      "mass fraction.")
-        def _nh(field, data):
-            return primordial_H_mass_fraction*data["gas","density"]/mp
-        ds.add_field(("gas", "H_number_density"), function=_nh, units="cm**-3")
-
-    def _emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
-
-        my_emissivity = np.power(10, em_0(dd))
-        if em_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
-            my_emissivity += my_Z * np.power(10, em_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "erg*cm**3/s")
-
-    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", emiss_name), function=_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/cm**3/s")
-
-    def _luminosity_field(field, data):
-        return data[emiss_name] * data["cell_volume"]
-
-    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", lum_name), function=_luminosity_field,
-                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="erg/s")
-
-    def _photon_emissivity_field(field, data):
-        dd = {"log_nH" : np.log10(data["gas","H_number_density"]),
-              "log_T"   : np.log10(data["gas","temperature"])}
-
-        my_emissivity = np.power(10, emp_0(dd))
-        if emp_Z is not None:
-            if with_metals:
-                my_Z = data["gas","metallicity"]
-            elif constant_metallicity is not None:
-                my_Z = constant_metallicity
-            my_emissivity += my_Z * np.power(10, emp_Z(dd))
-
-        return data["gas","H_number_density"]**2 * \
-            YTArray(my_emissivity, "photons*cm**3/s")
-
-    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
-    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
-                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
-                 units="photons/cm**3/s")
-
-    return emiss_name, lum_name, phot_name

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb yt/fields/api.py
--- a/yt/fields/api.py
+++ b/yt/fields/api.py
@@ -43,3 +43,5 @@
     FieldDetector
 from .field_info_container import \
     FieldInfoContainer
+from .xray_emission_fields import \
+    add_xray_emissivity_field
\ No newline at end of file

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb yt/fields/tests/test_xray_fields.py
--- /dev/null
+++ b/yt/fields/tests/test_xray_fields.py
@@ -0,0 +1,40 @@
+from yt.fields.xray_emission_fields import \
+    add_xray_emissivity_field
+from yt.utilities.answer_testing.framework import \
+    requires_ds, can_run_ds, data_dir_load, \
+    ProjectionValuesTest, FieldValuesTest
+
+def setup():
+    from yt.config import ytcfg
+    ytcfg["yt","__withintesting"] = "True"
+
+def check_xray_fields(ds_fn, fields):
+    if not can_run_ds(ds_fn): return
+    dso = [ None, ("sphere", ("m", (0.1, 'unitary')))]
+    for field in fields:
+        for axis in [0, 1, 2]:
+            for dobj_name in dso:
+                yield ProjectionValuesTest(ds_fn, axis, field, 
+                                           None, dobj_name)
+                yield FieldValuesTest(ds_fn, field, dobj_name)
+
+sloshing = "GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_0300"
+ at requires_ds(sloshing, big_data=True)
+def test_sloshing_apec():
+    ds = data_dir_load(sloshing)
+    fields = add_xray_emissivity_field(ds, 0.5, 7.0, table_type="apec", 
+                                       metallicity=0.3)
+    for test in check_xray_fields(ds, fields):
+        test_sloshing_apec.__name__ = test.description
+        yield test
+
+d9p = "D9p_500/10MpcBox_HartGal_csf_a0.500.d"
+ at requires_ds(d9p, big_data=True)
+def test_d9p_cloudy():
+    ds = data_dir_load(d9p)
+    fields = add_xray_emissivity_field(ds, 0.5, 2.0, redshift=ds.current_redshift,
+                                       table_type="cloudy", cosmology=ds.cosmology,
+                                       metallicity=("gas", "metallicity"))
+    for test in check_xray_fields(ds, fields):
+        test_d9p_cloudy.__name__ = test.description
+        yield test

diff -r f00742795e0970ba78ded9ea01644a6a40821b13 -r f0fa273ba464df9040cf988857236495897424bb yt/fields/xray_emission_fields.py
--- /dev/null
+++ b/yt/fields/xray_emission_fields.py
@@ -0,0 +1,304 @@
+"""
+Integrator classes to deal with interpolation and integration of input spectral
+bins.  Currently only supports Cloudy and APEC-style data.
+
+
+
+"""
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, yt Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+from yt.utilities.on_demand_imports import _h5py as h5py
+import numpy as np
+import os
+
+from yt.config import ytcfg
+from yt.fields.derived_field import DerivedField
+from yt.funcs import mylog, only_on_root, issue_deprecation_warning
+from yt.utilities.exceptions import YTFieldNotFound
+from yt.utilities.exceptions import YTException
+from yt.utilities.linear_interpolators import \
+    UnilinearFieldInterpolator, BilinearFieldInterpolator
+from yt.units.yt_array import YTArray, YTQuantity
+from yt.utilities.cosmology import Cosmology
+
+data_version = {"cloudy": 2,
+                "apec": 2}
+
+data_url = "http://yt-project.org/data"
+
+def _get_data_file(table_type, data_dir=None):
+    data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
+    if data_dir is None:
+        supp_data_dir = ytcfg.get("yt", "supp_data_dir")
+        data_dir = supp_data_dir if os.path.exists(supp_data_dir) else "."
+    data_path = os.path.join(data_dir, data_file)
+    if not os.path.exists(data_path):
+        msg = "Failed to find emissivity data file %s! " % data_file + \
+            "Please download from http://yt-project.org/data!"
+        mylog.error(msg)
+        raise IOError(msg)
+    return data_path
+
+class EnergyBoundsException(YTException):
+    def __init__(self, lower, upper):
+        self.lower = lower
+        self.upper = upper
+
+    def __str__(self):
+        return "Energy bounds are %e to %e keV." % \
+          (self.lower, self.upper)
+
+class ObsoleteDataException(YTException):
+    def __init__(self, table_type):
+        data_file = "%s_emissivity_v%d.h5" % (table_type, data_version[table_type])
+        self.msg = "X-ray emissivity data is out of date.\n"
+        self.msg += "Download the latest data from %s/%s." % (data_url, data_file)
+
+    def __str__(self):
+        return self.msg
+
+class XrayEmissivityIntegrator(object):
+    r"""Class for making X-ray emissivity fields. Uses hdf5 data tables
+    generated from Cloudy and AtomDB/APEC.
+
+    Initialize an XrayEmissivityIntegrator object.
+
+    Parameters
+    ----------
+    table_type: string
+        The type of data to use when computing the emissivity values. If "cloudy",
+        a file called "cloudy_emissivity.h5" is used, for photoionized
+        plasmas. If, "apec", a file called "apec_emissivity.h5" is used for 
+        collisionally ionized plasmas. These files contain emissivity tables 
+        for primordial elements and for metals at solar metallicity for the 
+        energy range 0.1 to 100 keV.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the location of the YT_DEST environment variable
+        or in the current working directory.
+    use_metals : boolean, optional
+        If set to True, the emissivity will include contributions from metals.
+        Default: True
+    """
+    def __init__(self, table_type, redshift=0.0, data_dir=None, use_metals=True):
+
+        filename = _get_data_file(table_type, data_dir=data_dir)
+        only_on_root(mylog.info, "Loading emissivity data from %s." % filename)
+        in_file = h5py.File(filename, "r")
+        if "info" in in_file.attrs:
+            only_on_root(mylog.info, in_file.attrs["info"].decode('utf8'))
+        if in_file.attrs["version"] != data_version[table_type]:
+            raise ObsoleteDataException(table_type)
+        else:
+            only_on_root(mylog.info, "X-ray '%s' emissivity data version: %s." % \
+                         (table_type, in_file.attrs["version"]))
+
+        self.log_T = in_file["log_T"][:]
+        self.emissivity_primordial = in_file["emissivity_primordial"][:]
+        if "log_nH" in in_file:
+            self.log_nH = in_file["log_nH"][:]
+        if use_metals:
+            self.emissivity_metals = in_file["emissivity_metals"][:]
+        self.ebin = YTArray(in_file["E"], "keV")
+        in_file.close()
+        self.dE = np.diff(self.ebin)
+        self.emid = 0.5*(self.ebin[1:]+self.ebin[:-1]).to("erg")
+        self.redshift = redshift
+
+    def get_interpolator(self, data_type, e_min, e_max, energy=True):
+        data = getattr(self, "emissivity_%s" % data_type)
+        if not energy:
+            data = data[..., :] / self.emid.v
+        e_min = YTQuantity(e_min, "keV")*(1.0+self.redshift)
+        e_max = YTQuantity(e_max, "keV")*(1.0+self.redshift)
+        if (e_min - self.ebin[0]) / e_min < -1e-3 or \
+          (e_max - self.ebin[-1]) / e_max > 1e-3:
+            raise EnergyBoundsException(self.ebin[0], self.ebin[-1])
+        e_is, e_ie = np.digitize([e_min, e_max], self.ebin)
+        e_is = np.clip(e_is - 1, 0, self.ebin.size - 1)
+        e_ie = np.clip(e_ie, 0, self.ebin.size - 1)
+
+        my_dE = self.dE[e_is: e_ie].copy()
+        # clip edge bins if the requested range is smaller
+        my_dE[0] -= e_min - self.ebin[e_is]
+        my_dE[-1] -= self.ebin[e_ie] - e_max
+
+        interp_data = (data[..., e_is:e_ie]*my_dE).sum(axis=-1)
+        if data.ndim == 2:
+            emiss = UnilinearFieldInterpolator(np.log10(interp_data),
+                                               [self.log_T[0],  self.log_T[-1]],
+                                               "log_T", truncate=True)
+        else:
+            emiss = BilinearFieldInterpolator(np.log10(interp_data),
+                                              [self.log_nH[0], self.log_nH[-1],
+                                               self.log_T[0],  self.log_T[-1]],
+                                              ["log_nH", "log_T"], truncate=True)
+
+        return emiss
+
+def add_xray_emissivity_field(ds, e_min, e_max, redshift=0.0,
+                              metallicity=("gas", "metallicity"), 
+                              table_type="cloudy", data_dir=None,
+                              cosmology=None, **kwargs):
+    r"""Create X-ray emissivity fields for a given energy range.
+
+    Parameters
+    ----------
+    e_min : float
+        The minimum energy in keV for the energy band.
+    e_min : float
+        The maximum energy in keV for the energy band.
+    redshift : float, optional
+        The cosmological redshift of the source of the field. Default: 0.0.
+    metallicity : field or float, optional
+        Either the name of a metallicity field or a single floating-point
+        number specifying a spatially constant metallicity. Must be in
+        solar units. If set to None, no metals will be assumed. Default: 
+        ("gas", "metallicity")
+    table_type : string, optional
+        The type of emissivity table to be used when creating the fields. 
+        Options are "cloudy" or "apec". Default: "cloudy"
+    data_dir : string, optional
+        The location to look for the data table in. If not supplied, the file
+        will be looked for in the location of the YT_DEST environment variable
+        or in the current working directory.
+    cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional
+        If set and redshift > 0.0, this cosmology will be used when computing the
+        cosmological dependence of the emission fields. If not set, yt's default
+        LCDM cosmology will be used.
+
+    This will create three fields:
+
+    "xray_emissivity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3)
+    "xray_luminosity_{e_min}_{e_max}_keV" (erg s^-1)
+    "xray_photon_emissivity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3)
+
+    Examples
+    --------
+
+    >>> import yt
+    >>> ds = yt.load("sloshing_nomag2_hdf5_plt_cnt_0100")
+    >>> yt.add_xray_emissivity_field(ds, 0.5, 2)
+    >>> p = yt.ProjectionPlot(ds, 'x', "xray_emissivity_0.5_2_keV")
+    >>> p.save()
+    """
+    # The next several if constructs are for backwards-compatibility
+    if "constant_metallicity" in kwargs:
+        issue_deprecation_warning("The \"constant_metallicity\" parameter is deprecated. Set "
+                                  "the \"metallicity\" parameter to a constant float value instead.")
+        metallicity = kwargs["constant_metallicity"]
+    if "with_metals" in kwargs:
+        issue_deprecation_warning("The \"with_metals\" parameter is deprecated. Use the "
+                                  "\"metallicity\" parameter to choose a constant or "
+                                  "spatially varying metallicity.")
+        if kwargs["with_metals"] and isinstance(metallicity, float):
+            raise RuntimeError("\"with_metals=True\", but you specified a constant metallicity!")
+        if not kwargs["with_metals"] and not isinstance(metallicity, float):
+            raise RuntimeError("\"with_metals=False\", but you didn't specify a constant metallicity!")
+    if not isinstance(metallicity, float) and metallicity is not None:
+        try:
+            metallicity = ds._get_field_info(*metallicity)
+        except YTFieldNotFound:
+            raise RuntimeError("Your dataset does not have a {} field! ".format(metallicity) +
+                               "Perhaps you should specify a constant metallicity instead?")
+
+    my_si = XrayEmissivityIntegrator(table_type, data_dir=data_dir, redshift=redshift)
+
+    em_0 = my_si.get_interpolator("primordial", e_min, e_max)
+    emp_0 = my_si.get_interpolator("primordial", e_min, e_max, energy=False)
+    if metallicity is not None:
+        em_Z = my_si.get_interpolator("metals", e_min, e_max)
+        emp_Z = my_si.get_interpolator("metals", e_min, e_max, energy=False)
+
+    def _emissivity_field(field, data):
+        dd = {"log_nH": np.log10(data["gas", "H_nuclei_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
+
+        my_emissivity = np.power(10, em_0(dd))
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity.name]
+            else:
+                my_Z = metallicity
+            my_emissivity += my_Z * np.power(10, em_Z(dd))
+
+        return data["gas","H_nuclei_density"]**2 * \
+            YTArray(my_emissivity, "erg*cm**3/s")
+
+    emiss_name = "xray_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", emiss_name), function=_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 sampling_type="cell", units="erg/cm**3/s")
+
+    def _luminosity_field(field, data):
+        return data[emiss_name] * data["cell_volume"]
+
+    lum_name = "xray_luminosity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", lum_name), function=_luminosity_field,
+                 display_name=r"\rm{L}_{X} (%s-%s keV)" % (e_min, e_max),
+                 sampling_type="cell", units="erg/s")
+
+    def _photon_emissivity_field(field, data):
+        dd = {"log_nH": np.log10(data["gas", "H_nuclei_density"]),
+              "log_T": np.log10(data["gas", "temperature"])}
+
+        my_emissivity = np.power(10, emp_0(dd))
+        if metallicity is not None:
+            if isinstance(metallicity, DerivedField):
+                my_Z = data[metallicity.name]
+            else:
+                my_Z = metallicity
+            my_emissivity += my_Z * np.power(10, emp_Z(dd))
+
+        return data["gas", "H_nuclei_density"]**2 * \
+            YTArray(my_emissivity, "photons*cm**3/s")
+
+    phot_name = "xray_photon_emissivity_%s_%s_keV" % (e_min, e_max)
+    ds.add_field(("gas", phot_name), function=_photon_emissivity_field,
+                 display_name=r"\epsilon_{X} (%s-%s keV)" % (e_min, e_max),
+                 sampling_type="cell", units="photons/cm**3/s")
+
+    fields = [emiss_name, lum_name, phot_name]
+
+    if redshift > 0.0:
+
+        if cosmology is None:
+            if hasattr(ds, "cosmology"):
+                cosmology = ds.cosmology
+            else:
+                cosmology = Cosmology()
+
+        D_L = cosmology.luminosity_distance(0.0, redshift)
+        angular_scale = 1.0/cosmology.angular_scale(0.0, redshift)
+        dist_fac = 1.0/(4.0*np.pi*D_L*D_L*angular_scale*angular_scale)
+
+        ei_name = "xray_intensity_%s_%s_keV" % (e_min, e_max)
+        def _intensity_field(field, data):
+            I = dist_fac*data[emiss_name]
+            return I.in_units("erg/cm**3/s/arcsec**2")
+        ds.add_field(("gas", ei_name), function=_intensity_field,
+                     display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
+                     sampling_type="cell", units="erg/cm**3/s/arcsec**2")
+
+        i_name = "xray_photon_intensity_%s_%s_keV" % (e_min, e_max)
+        def _photon_intensity_field(field, data):
+            I = (1.0+redshift)*dist_fac*data[phot_name]
+            return I.in_units("photons/cm**3/s/arcsec**2")
+        ds.add_field(("gas", i_name), function=_photon_intensity_field,
+                     display_name=r"I_{X} (%s-%s keV)" % (e_min, e_max),
+                     sampling_type="cell", units="photons/cm**3/s/arcsec**2")
+
+        fields += [ei_name, i_name]
+
+    [mylog.info("Adding %s field." % field) for field in fields]
+
+    return fields

Repository URL: https://bitbucket.org/yt_analysis/yt/

--

This is a commit notification from bitbucket.org. You are receiving
this because you have the service enabled, addressing the recipient of
this email.


More information about the yt-svn mailing list