Esempio n. 1
0
def add_xray_emissivity_field(
    ds,
    e_min,
    e_max,
    redshift=0.0,
    metallicity=("gas", "metallicity"),
    table_type="cloudy",
    data_dir=None,
    cosmology=None,
    dist=None,
    ftype="gas",
):
    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 : str or tuple of str 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.
    dist : (value, unit) tuple or :class:`~yt.units.yt_array.YTQuantity`, optional
        The distance to the source, used for making intensity fields. You should
        only use this if your source is nearby (not cosmological). Default: None
    ftype : string, optional
        The field type to use when creating the fields, default "gas"

    This will create at least 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)

    and if a redshift or distance is specified it will create two others:

    "xray_intensity_{e_min}_{e_max}_keV" (erg s^-1 cm^-3 arcsec^-2)
    "xray_photon_intensity_{e_min}_{e_max}_keV" (photons s^-1 cm^-3 arcsec^-2)

    These latter two are really only useful when making projections.

    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", ("gas", "xray_emissivity_0.5_2_keV"), table_type="apec"
    ... )
    >>> p.save()
    """
    if not isinstance(metallicity, float) and metallicity is not None:
        try:
            metallicity = ds._get_field_info(*metallicity)
        except YTFieldNotFound as e:
            raise RuntimeError(
                f"Your dataset does not have a {metallicity} field! " +
                "Perhaps you should specify a constant metallicity instead?"
            ) from e

    if table_type == "cloudy":
        # Cloudy wants to scale by nH**2
        other_n = "H_nuclei_density"
    else:
        # APEC wants to scale by nH*ne
        other_n = "El_number_density"

    def _norm_field(field, data):
        return data[ftype, "H_nuclei_density"] * data[ftype, other_n]

    ds.add_field((ftype, "norm_field"),
                 _norm_field,
                 units="cm**-6",
                 sampling_type="local")

    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):
        with np.errstate(all="ignore"):
            dd = {
                "log_nH": np.log10(data[ftype, "H_nuclei_density"]),
                "log_T": np.log10(data[ftype, "temperature"]),
            }

        my_emissivity = np.power(10, em_0(dd))
        if metallicity is not None:
            if isinstance(metallicity, DerivedField):
                my_Z = data[metallicity.name].to("Zsun")
            else:
                my_Z = metallicity
            my_emissivity += my_Z * np.power(10, em_Z(dd))

        my_emissivity[np.isnan(my_emissivity)] = 0

        return data[ftype, "norm_field"] * YTArray(my_emissivity,
                                                   "erg*cm**3/s")

    emiss_name = (ftype, f"xray_emissivity_{e_min}_{e_max}_keV")
    ds.add_field(
        emiss_name,
        function=_emissivity_field,
        display_name=fr"\epsilon_{{X}} ({e_min}-{e_max} keV)",
        sampling_type="local",
        units="erg/cm**3/s",
    )

    def _luminosity_field(field, data):
        return data[emiss_name] * data[ftype, "mass"] / data[ftype, "density"]

    lum_name = (ftype, f"xray_luminosity_{e_min}_{e_max}_keV")
    ds.add_field(
        lum_name,
        function=_luminosity_field,
        display_name=fr"\rm{{L}}_{{X}} ({e_min}-{e_max} keV)",
        sampling_type="local",
        units="erg/s",
    )

    def _photon_emissivity_field(field, data):
        dd = {
            "log_nH": np.log10(data[ftype, "H_nuclei_density"]),
            "log_T": np.log10(data[ftype, "temperature"]),
        }

        my_emissivity = np.power(10, emp_0(dd))
        if metallicity is not None:
            if isinstance(metallicity, DerivedField):
                my_Z = data[metallicity.name].to("Zsun")
            else:
                my_Z = metallicity
            my_emissivity += my_Z * np.power(10, emp_Z(dd))

        return data[ftype, "norm_field"] * YTArray(my_emissivity,
                                                   "photons*cm**3/s")

    phot_name = (ftype, f"xray_photon_emissivity_{e_min}_{e_max}_keV")
    ds.add_field(
        phot_name,
        function=_photon_emissivity_field,
        display_name=fr"\epsilon_{{X}} ({e_min}-{e_max} keV)",
        sampling_type="local",
        units="photons/cm**3/s",
    )

    fields = [emiss_name, lum_name, phot_name]

    if redshift > 0.0 or dist is not None:

        if dist is None:
            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 = ds.quan(
                1.0 /
                (4.0 * np.pi * D_L * D_L * angular_scale * angular_scale).v,
                "rad**-2",
            )
        else:
            redshift = 0.0  # Only for local sources!
            try:
                # normal behaviour, if dist is a YTQuantity
                dist = ds.quan(dist.value, dist.units)
            except AttributeError as e:
                try:
                    dist = ds.quan(*dist)
                except (RuntimeError, TypeError):
                    raise TypeError(
                        "dist should be a YTQuantity or a (value, unit) tuple!"
                    ) from e

            angular_scale = dist / ds.quan(1.0, "radian")
            dist_fac = ds.quan(
                1.0 /
                (4.0 * np.pi * dist * dist * angular_scale * angular_scale).v,
                "rad**-2",
            )

        ei_name = (ftype, f"xray_intensity_{e_min}_{e_max}_keV")

        def _intensity_field(field, data):
            I = dist_fac * data[emiss_name]
            return I.in_units("erg/cm**3/s/arcsec**2")

        ds.add_field(
            ei_name,
            function=_intensity_field,
            display_name=fr"I_{{X}} ({e_min}-{e_max} keV)",
            sampling_type="local",
            units="erg/cm**3/s/arcsec**2",
        )

        i_name = (ftype, f"xray_photon_intensity_{e_min}_{e_max}_keV")

        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(
            i_name,
            function=_photon_intensity_field,
            display_name=fr"I_{{X}} ({e_min}-{e_max} keV)",
            sampling_type="local",
            units="photons/cm**3/s/arcsec**2",
        )

        fields += [ei_name, i_name]

    for field in fields:
        mylog.info("Adding ('%s','%s') field.", field[0], field[1])

    return fields
Esempio n. 2
0
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 : str or tuple of str 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):
        with np.errstate(all='ignore'):
            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))

        my_emissivity[np.isnan(my_emissivity)] = 0

        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