def from_data_source(cls, data_source, redshift, area, exp_time, source_model, point_sources=False, parameters=None, center=None, dist=None, cosmology=None, velocity_fields=None): r""" Initialize a :class:`~pyxsim.photon_list.PhotonList` from a yt data source. The redshift, collecting area, exposure time, and cosmology are stored in the *parameters* dictionary which is passed to the *source_model* function. Parameters ---------- data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer` The data source from which the photons will be generated. redshift : float The cosmological redshift for the photons. area : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The collecting area to determine the number of photons. If units are not specified, it is assumed to be in cm^2. exp_time : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The exposure time to determine the number of photons. If units are not specified, it is assumed to be in seconds. source_model : :class:`~pyxsim.source_models.SourceModel` A source model used to generate the photons. point_sources : boolean, optional If True, the photons will be assumed to be generated from the exact positions of the cells or particles and not smeared around within a volume. Default: False parameters : dict, optional A dictionary of parameters to be passed for the source model to use, if necessary. center : string or array_like, optional The origin of the photon spatial coordinates. Accepts "c", "max", or a coordinate. If not specified, pyxsim attempts to use the "center" field parameter of the data_source. dist : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The angular diameter distance, used for nearby sources. This may be optionally supplied instead of it being determined from the *redshift* and given *cosmology*. If units are not specified, it is assumed to be in kpc. To use this, the redshift must be set to zero. cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional Cosmological information. If not supplied, we try to get the cosmology from the dataset. Otherwise, LCDM with the default yt parameters is assumed. velocity_fields : list of fields The yt fields to use for the velocity. If not specified, the following will be assumed: ['velocity_x', 'velocity_y', 'velocity_z'] for grid datasets ['particle_velocity_x', 'particle_velocity_y', 'particle_velocity_z'] for particle datasets Examples -------- >>> thermal_model = ThermalSourceModel(apec_model, Zmet=0.3) >>> redshift = 0.05 >>> area = 6000.0 # assumed here in cm**2 >>> time = 2.0e5 # assumed here in seconds >>> sp = ds.sphere("c", (500., "kpc")) >>> my_photons = PhotonList.from_data_source(sp, redshift, area, ... time, thermal_model) """ ds = data_source.ds if parameters is None: parameters = {} if cosmology is None: if hasattr(ds, 'cosmology'): cosmo = ds.cosmology else: cosmo = Cosmology() else: cosmo = cosmology if dist is None: if redshift <= 0.0: msg = "If redshift <= 0.0, you must specify a distance to the " \ "source using the 'dist' argument!" mylog.error(msg) raise ValueError(msg) D_A = cosmo.angular_diameter_distance(0.0, redshift).in_units("Mpc") else: D_A = parse_value(dist, "kpc") if redshift > 0.0: mylog.warning("Redshift must be zero for nearby sources. " "Resetting redshift to 0.0.") redshift = 0.0 if isinstance(center, string_types): if center == "center" or center == "c": parameters["center"] = ds.domain_center elif center == "max" or center == "m": parameters["center"] = ds.find_max("density")[-1] elif iterable(center): if isinstance(center, YTArray): parameters["center"] = center.in_units("code_length") elif isinstance(center, tuple): if center[0] == "min": parameters["center"] = ds.find_min(center[1])[-1] elif center[0] == "max": parameters["center"] = ds.find_max(center[1])[-1] else: raise RuntimeError else: parameters["center"] = ds.arr(center, "code_length") elif center is None: if hasattr(data_source, "left_edge"): parameters["center"] = 0.5*(data_source.left_edge+data_source.right_edge) else: parameters["center"] = data_source.get_field_parameter("center") parameters["fid_exp_time"] = parse_value(exp_time, "s") parameters["fid_area"] = parse_value(area, "cm**2") parameters["fid_redshift"] = redshift parameters["fid_d_a"] = D_A parameters["hubble"] = cosmo.hubble_constant parameters["omega_matter"] = cosmo.omega_matter parameters["omega_lambda"] = cosmo.omega_lambda if redshift > 0.0: mylog.info("Cosmology: h = %g, omega_matter = %g, omega_lambda = %g" % (cosmo.hubble_constant, cosmo.omega_matter, cosmo.omega_lambda)) else: mylog.info("Observing local source at distance %s." % D_A) D_A = parameters["fid_d_a"].in_cgs() dist_fac = 1.0/(4.*np.pi*D_A.value*D_A.value*(1.+redshift)**2) spectral_norm = parameters["fid_area"].v*parameters["fid_exp_time"].v*dist_fac source_model.setup_model(data_source, redshift, spectral_norm) p_fields, v_fields, w_field = determine_fields(ds, source_model.source_type, point_sources) if velocity_fields is not None: v_fields = velocity_fields if p_fields[0] == ("index", "x"): parameters["data_type"] = "cells" else: parameters["data_type"] = "particles" citer = data_source.chunks([], "io") photons = defaultdict(list) for chunk in parallel_objects(citer): chunk_data = source_model(chunk) if chunk_data is not None: ncells, number_of_photons, idxs, energies = chunk_data photons["num_photons"].append(number_of_photons) photons["energy"].append(energies) photons["pos"].append(np.array([chunk[p_fields[0]].d[idxs], chunk[p_fields[1]].d[idxs], chunk[p_fields[2]].d[idxs]])) photons["vel"].append(np.array([chunk[v_fields[0]].d[idxs], chunk[v_fields[1]].d[idxs], chunk[v_fields[2]].d[idxs]])) if w_field is None: photons["dx"].append(np.zeros(ncells)) else: photons["dx"].append(chunk[w_field].d[idxs]) source_model.cleanup_model() photon_units = {"pos": ds.field_info[p_fields[0]].units, "vel": ds.field_info[v_fields[0]].units, "energy": "keV"} if w_field is None: photon_units["dx"] = "kpc" else: photon_units["dx"] = ds.field_info[w_field].units concatenate_photons(ds, photons, photon_units) c = parameters["center"].to("kpc") if sum(ds.periodicity) > 0: # Fix photon coordinates for regions crossing a periodic boundary dw = ds.domain_width.to("kpc") le, re = find_object_bounds(data_source) for i in range(3): if ds.periodicity[i] and photons["pos"].shape[0] > 0: tfl = photons["pos"][:,i] < le[i] tfr = photons["pos"][:,i] > re[i] photons["pos"][tfl,i] += dw[i] photons["pos"][tfr,i] -= dw[i] # Re-center all coordinates if photons["pos"].shape[0] > 0: photons["pos"] -= c mylog.info("Finished generating photons.") mylog.info("Number of photons generated: %d" % int(np.sum(photons["num_photons"]))) mylog.info("Number of cells with photons: %d" % photons["dx"].size) return cls(photons, parameters, cosmo)
def from_data_source(cls, data_source, redshift, area, exp_time, source_model, point_sources=False, parameters=None, center=None, dist=None, cosmology=None, velocity_fields=None): r""" Initialize a :class:`~pyxsim.photon_list.PhotonList` from a yt data source. The redshift, collecting area, exposure time, and cosmology are stored in the *parameters* dictionary which is passed to the *source_model* function. Parameters ---------- data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer` The data source from which the photons will be generated. redshift : float The cosmological redshift for the photons. area : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The collecting area to determine the number of photons. If units are not specified, it is assumed to be in cm^2. exp_time : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The exposure time to determine the number of photons. If units are not specified, it is assumed to be in seconds. source_model : :class:`~pyxsim.source_models.SourceModel` A source model used to generate the photons. point_sources : boolean, optional If True, the photons will be assumed to be generated from the exact positions of the cells or particles and not smeared around within a volume. Default: False parameters : dict, optional A dictionary of parameters to be passed for the source model to use, if necessary. center : string or array_like, optional The origin of the photon spatial coordinates. Accepts "c", "max", or a coordinate. If not specified, pyxsim attempts to use the "center" field parameter of the data_source. dist : float, (value, unit) tuple, :class:`~yt.units.yt_array.YTQuantity`, or :class:`~astropy.units.Quantity` The angular diameter distance, used for nearby sources. This may be optionally supplied instead of it being determined from the *redshift* and given *cosmology*. If units are not specified, it is assumed to be in kpc. To use this, the redshift must be set to zero. cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional Cosmological information. If not supplied, we try to get the cosmology from the dataset. Otherwise, LCDM with the default yt parameters is assumed. velocity_fields : list of fields The yt fields to use for the velocity. If not specified, the following will be assumed: ['velocity_x', 'velocity_y', 'velocity_z'] for grid datasets ['particle_velocity_x', 'particle_velocity_y', 'particle_velocity_z'] for particle datasets Examples -------- >>> thermal_model = ThermalSourceModel(apec_model, Zmet=0.3) >>> redshift = 0.05 >>> area = 6000.0 # assumed here in cm**2 >>> time = 2.0e5 # assumed here in seconds >>> sp = ds.sphere("c", (500., "kpc")) >>> my_photons = PhotonList.from_data_source(sp, redshift, area, ... time, thermal_model) """ ds = data_source.ds if parameters is None: parameters = {} if cosmology is None: if hasattr(ds, 'cosmology'): cosmo = ds.cosmology else: cosmo = Cosmology() else: cosmo = cosmology if dist is None: if redshift <= 0.0: msg = "If redshift <= 0.0, you must specify a distance to the " \ "source using the 'dist' argument!" mylog.error(msg) raise ValueError(msg) D_A = cosmo.angular_diameter_distance(0.0, redshift).in_units("Mpc") else: D_A = parse_value(dist, "kpc") if redshift > 0.0: mylog.warning("Redshift must be zero for nearby sources. " "Resetting redshift to 0.0.") redshift = 0.0 if isinstance(center, string_types): if center == "center" or center == "c": parameters["center"] = ds.domain_center elif center == "max" or center == "m": parameters["center"] = ds.find_max("density")[-1] elif iterable(center): if isinstance(center, YTArray): parameters["center"] = center.in_units("code_length") elif isinstance(center, tuple): if center[0] == "min": parameters["center"] = ds.find_min(center[1])[-1] elif center[0] == "max": parameters["center"] = ds.find_max(center[1])[-1] else: raise RuntimeError else: parameters["center"] = ds.arr(center, "code_length") elif center is None: if hasattr(data_source, "left_edge"): parameters["center"] = 0.5 * (data_source.left_edge + data_source.right_edge) else: parameters["center"] = data_source.get_field_parameter( "center") parameters["fid_exp_time"] = parse_value(exp_time, "s") parameters["fid_area"] = parse_value(area, "cm**2") parameters["fid_redshift"] = redshift parameters["fid_d_a"] = D_A parameters["hubble"] = cosmo.hubble_constant parameters["omega_matter"] = cosmo.omega_matter parameters["omega_lambda"] = cosmo.omega_lambda if redshift > 0.0: mylog.info( "Cosmology: h = %g, omega_matter = %g, omega_lambda = %g" % (cosmo.hubble_constant, cosmo.omega_matter, cosmo.omega_lambda)) else: mylog.info("Observing local source at distance %s." % D_A) D_A = parameters["fid_d_a"].in_cgs() dist_fac = 1.0 / (4. * np.pi * D_A.value * D_A.value * (1. + redshift)**2) spectral_norm = parameters["fid_area"].v * parameters[ "fid_exp_time"].v * dist_fac source_model.setup_model(data_source, redshift, spectral_norm) p_fields, v_fields, w_field = determine_fields( ds, source_model.source_type, point_sources) if velocity_fields is not None: v_fields = velocity_fields if p_fields[0] == ("index", "x"): parameters["data_type"] = "cells" else: parameters["data_type"] = "particles" citer = data_source.chunks([], "io") photons = defaultdict(list) for chunk in parallel_objects(citer): chunk_data = source_model(chunk) if chunk_data is not None: ncells, number_of_photons, idxs, energies = chunk_data photons["num_photons"].append(number_of_photons) photons["energy"].append(energies) photons["pos"].append( np.array([ chunk[p_fields[0]].d[idxs], chunk[p_fields[1]].d[idxs], chunk[p_fields[2]].d[idxs] ])) photons["vel"].append( np.array([ chunk[v_fields[0]].d[idxs], chunk[v_fields[1]].d[idxs], chunk[v_fields[2]].d[idxs] ])) if w_field is None: photons["dx"].append(np.zeros(ncells)) else: photons["dx"].append(chunk[w_field].d[idxs]) source_model.cleanup_model() photon_units = { "pos": ds.field_info[p_fields[0]].units, "vel": ds.field_info[v_fields[0]].units, "energy": "keV" } if w_field is None: photon_units["dx"] = "kpc" else: photon_units["dx"] = ds.field_info[w_field].units concatenate_photons(ds, photons, photon_units) c = parameters["center"].to("kpc") if sum(ds.periodicity) > 0: # Fix photon coordinates for regions crossing a periodic boundary dw = ds.domain_width.to("kpc") le, re = find_object_bounds(data_source) for i in range(3): if ds.periodicity[i] and photons["pos"].shape[0] > 0: tfl = photons["pos"][:, i] < le[i] tfr = photons["pos"][:, i] > re[i] photons["pos"][tfl, i] += dw[i] photons["pos"][tfr, i] -= dw[i] # Re-center all coordinates if photons["pos"].shape[0] > 0: photons["pos"] -= c mylog.info("Finished generating photons.") mylog.info("Number of photons generated: %d" % int(np.sum(photons["num_photons"]))) mylog.info("Number of cells with photons: %d" % photons["dx"].size) return cls(photons, parameters, cosmo)
def from_data_source(cls, data_source, redshift, area, exp_time, source_model, parameters=None, center=None, dist=None, cosmology=None, velocity_fields=None): r""" Initialize a :class:`~pyxsim.photon_list.PhotonList` from a yt data source. The redshift, collecting area, exposure time, and cosmology are stored in the *parameters* dictionary which is passed to the *source_model* function. Parameters ---------- data_source : :class:`~yt.data_objects.data_containers.YTSelectionContainer` The data source from which the photons will be generated. redshift : float The cosmological redshift for the photons. area : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`. The collecting area to determine the number of photons. If units are not specified, it is assumed to be in cm^2. exp_time : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`. The exposure time to determine the number of photons. If units are not specified, it is assumed to be in seconds. source_model : :class:`~pyxsim.source_models.SourceModel` A source model used to generate the photons. parameters : dict, optional A dictionary of parameters to be passed for the source model to use, if necessary. center : string or array_like, optional The origin of the photon spatial coordinates. Accepts "c", "max", or a coordinate. If not specified, pyxsim attempts to use the "center" field parameter of the data_source. dist : float, (value, unit) tuple, or :class:`~yt.units.yt_array.YTQuantity`, optional The angular diameter distance, used for nearby sources. This may be optionally supplied instead of it being determined from the *redshift* and given *cosmology*. If units are not specified, it is assumed to be in Mpc. To use this, the redshift must be set to zero. cosmology : :class:`~yt.utilities.cosmology.Cosmology`, optional Cosmological information. If not supplied, we try to get the cosmology from the dataset. Otherwise, LCDM with the default yt parameters is assumed. velocity_fields : list of fields The yt fields to use for the velocity. If not specified, the following will be assumed: ['velocity_x', 'velocity_y', 'velocity_z'] for grid datasets ['particle_velocity_x', 'particle_velocity_y', 'particle_velocity_z'] for particle datasets Examples -------- >>> thermal_model = ThermalSourceModel(apec_model, Zmet=0.3) >>> redshift = 0.05 >>> area = 6000.0 # assumed here in cm**2 >>> time = 2.0e5 # assumed here in seconds >>> sp = ds.sphere("c", (500., "kpc")) >>> my_photons = PhotonList.from_data_source(sp, redshift, area, ... time, thermal_model) """ ds = data_source.ds if parameters is None: parameters = {} if cosmology is None: if hasattr(ds, 'cosmology'): cosmo = ds.cosmology else: cosmo = Cosmology() else: cosmo = cosmology mylog.info( "Cosmology: h = %g, omega_matter = %g, omega_lambda = %g" % (cosmo.hubble_constant, cosmo.omega_matter, cosmo.omega_lambda)) if dist is None: if redshift <= 0.0: msg = "If redshift <= 0.0, you must specify a distance to the source using the 'dist' argument!" mylog.error(msg) raise ValueError(msg) D_A = cosmo.angular_diameter_distance(0.0, redshift).in_units("Mpc") else: D_A = parse_value(dist, "Mpc") if redshift > 0.0: mylog.warning( "Redshift must be zero for nearby sources. Resetting redshift to 0.0." ) redshift = 0.0 if center == "center" or center == "c": parameters["center"] = ds.domain_center elif center == "max" or center == "m": parameters["center"] = ds.find_max("density")[-1] elif iterable(center): if isinstance(center, YTArray): parameters["center"] = center.in_units("code_length") elif isinstance(center, tuple): if center[0] == "min": parameters["center"] = ds.find_min(center[1])[-1] elif center[0] == "max": parameters["center"] = ds.find_max(center[1])[-1] else: raise RuntimeError else: parameters["center"] = ds.arr(center, "code_length") elif center is None: parameters["center"] = data_source.get_field_parameter("center") parameters["FiducialExposureTime"] = parse_value(exp_time, "s") parameters["FiducialArea"] = parse_value(area, "cm**2") parameters["FiducialRedshift"] = redshift parameters["FiducialAngularDiameterDistance"] = D_A parameters["HubbleConstant"] = cosmo.hubble_constant parameters["OmegaMatter"] = cosmo.omega_matter parameters["OmegaLambda"] = cosmo.omega_lambda D_A = parameters["FiducialAngularDiameterDistance"].in_cgs() dist_fac = 1.0 / (4. * np.pi * D_A.value * D_A.value * (1. + redshift)**2) spectral_norm = parameters["FiducialArea"].v * parameters[ "FiducialExposureTime"].v * dist_fac source_model.setup_model(data_source, redshift, spectral_norm) p_fields, v_fields, w_field = determine_fields( ds, source_model.source_type) if velocity_fields is not None: v_fields = velocity_fields if p_fields[0] == ("index", "x"): parameters["DataType"] = "cells" else: parameters["DataType"] = "particles" if hasattr(data_source, "left_edge"): # Region or grid le = data_source.left_edge re = data_source.right_edge elif hasattr(data_source, "radius") and not hasattr(data_source, "height"): # Sphere le = -data_source.radius + data_source.center re = data_source.radius + data_source.center else: # Compute rough boundaries of the object # DOES NOT WORK for objects straddling periodic # boundaries yet if sum(ds.periodicity) > 0: mylog.warning("You are using a region that is not currently " "supported for straddling periodic boundaries. " "Check to make sure that this is not the case.") le = ds.arr(np.zeros(3), "code_length") re = ds.arr(np.zeros(3), "code_length") for i, ax in enumerate(p_fields): le[i], re[i] = data_source.quantities.extrema(ax) dds_min = get_smallest_dds(ds, parameters["DataType"]) le = np.rint((le - ds.domain_left_edge) / dds_min) * dds_min + ds.domain_left_edge re = ds.domain_right_edge - np.rint( (ds.domain_right_edge - re) / dds_min) * dds_min width = re - le parameters["Dimension"] = np.rint(width / dds_min).astype("int") parameters["Width"] = parameters["Dimension"] * dds_min.in_units("kpc") citer = data_source.chunks([], "io") photons = defaultdict(list) for chunk in parallel_objects(citer): chunk_data = source_model(chunk) if chunk_data is not None: number_of_photons, idxs, energies = chunk_data photons["NumberOfPhotons"].append(number_of_photons) photons["Energy"].append(ds.arr(energies, "keV")) photons["x"].append(chunk[p_fields[0]][idxs].in_units("kpc")) photons["y"].append(chunk[p_fields[1]][idxs].in_units("kpc")) photons["z"].append(chunk[p_fields[2]][idxs].in_units("kpc")) photons["vx"].append(chunk[v_fields[0]][idxs].in_units("km/s")) photons["vy"].append(chunk[v_fields[1]][idxs].in_units("km/s")) photons["vz"].append(chunk[v_fields[2]][idxs].in_units("km/s")) if w_field is None: photons["dx"].append(ds.arr(np.zeros(len(idxs)), "kpc")) else: photons["dx"].append(chunk[w_field][idxs].in_units("kpc")) source_model.cleanup_model() concatenate_photons(photons) # Translate photon coordinates to the source center # Fix photon coordinates for regions crossing a periodic boundary dw = ds.domain_width.to("kpc") for i, ax in enumerate("xyz"): if ds.periodicity[i] and len(photons[ax]) > 0: tfl = photons[ax] < le[i].to('kpc') tfr = photons[ax] > re[i].to('kpc') photons[ax][tfl] += dw[i] photons[ax][tfr] -= dw[i] photons[ax] -= parameters["center"][i].in_units("kpc") mylog.info("Finished generating photons.") mylog.info("Number of photons generated: %d" % int(np.sum(photons["NumberOfPhotons"]))) mylog.info("Number of cells with photons: %d" % len(photons["x"])) return cls(photons, parameters, cosmo)