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
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