def setup_ds(ds): for atom, ion_state in fields_to_add_to_ds: add_ion_number_density_field(atom, ion_state, ds, ftype=ftype, ionization_table=ionization_table, sampling_type=sampling_type) if setup_function is not None: setup_function(ds)
def test_add_ion_number_density_fields_to_amr_ds(): """ Test to add various ion fields """ ds = fake_amr_ds(fields=("density", "velocity_x", "velocity_y", "velocity_z", "temperature", "metallicity")) ad = ds.all_data() add_ion_number_density_field('O', 6, ds) field = ('gas', 'O_p5_number_density') assert field in ds.derived_field_list assert isinstance(ad[field], np.ndarray) dirpath = tempfile.mkdtemp() SlicePlot(ds, 'x', field).save(dirpath) shutil.rmtree(dirpath)
def make_spectrum(self, ray, lines='all', output_file=None, use_peculiar_velocity=True, observing_redshift=0.0, ly_continuum=True, store_observables=False, min_tau=1e-3, njobs="auto"): """ Make a spectrum from ray data depositing the desired lines. Make sure to pass this function a LightRay object and potentially also a list of strings representing what lines you'd like to actually have be deposited in your final spectrum. **Parameters** :ray: string or dataset Ray dataset filename or a loaded ray dataset :lines: list of strings List of strings that determine which lines will be added to the spectrum. List can include things like "C", "O VI", or "Mg II ####", where #### would be the integer wavelength value of the desired line. If set to 'all', includes all lines in LineDatabase set in SpectrumGenerator. Default: 'all' :output_file: optional, string Filename of output if you wish to save the spectrum immediately without any further processing. File formats are chosen based on the filename extension. ".h5" for HDF5, ".fits" for FITS, and everything else is ASCII. Equivalent of calling :class:`~trident.SpectrumGenerator.save_spectrum`. Default: None :use_peculiar_velocity: optional, bool If True, include the effects of doppler redshift of the gas in shifting lines in the final spectrum. Default: True :observing_redshift: optional, float This is the value of the redshift at which the observer of this spectrum exists. In most cases, this will be a redshift of 0. Default: 0. :ly_continuum: optional, boolean If any H I lines are used in the line list, this assures a Lyman continuum will be included in the spectral generation. Lyman continuum begins at final Lyman line deposited (Ly 39 = 912.32 A) not at formal Lyman Limit (911.76 A) so as to not have a gap between final Lyman lines and continuum. Uses power law of index 3 and normalization to match opacity of final Lyman lines. Default: True :store_observables: optional, boolean If set to true, observable properties for each cell in the light ray will be saved for each line in the line list. Properties include the column density, tau, thermal b, and the wavelength where tau was deposited. Best applied for a reasonable number of lines. Default: False :min_tau: optional, float This value determines size of the wavelength window used to deposit lines or continua. The wavelength window is expanded until the optical depth at the edge is below this value. If too high, this will result in features appearing cut off at the edges. Decreasing this will make features smoother but will also increase run time. An increase by a factor of ten will result in roughly a 2x slow down. Default: 1e-3. :njobs: optional, int or "auto" The number of process groups into which the loop over absorption lines will be divided. If set to -1, each absorption line will be deposited by exactly one processor. If njobs is set to a value less than the total number of available processors (N), then the deposition of an individual line will be parallelized over (N / njobs) processors. If set to "auto", it will first try to parallelize over the list of lines and only parallelize the line deposition if there are more processors than lines. This is the optimal strategy for parallelizing spectrum generation. Default: "auto" **Example** Make a one zone ray and generate a COS spectrum for it including only Oxygen VI, Mg II, and all Carbon lines, and plot to disk. >>> import trident >>> ray = trident.make_onezone_ray() >>> sg = trident.SpectrumGenerator('COS') >>> sg.make_spectrum(ray, lines=['O VI', 'Mg II', 'C']) >>> sg.plot_spectrum('spec_raw.png') """ self.observing_redshift = observing_redshift if isinstance(ray, str): ray = load(ray) ad = ray.all_data() # Clear out any previous spectrum that existed first self.clear_spectrum() active_lines = self.line_database.parse_subset(lines) # Make sure we've produced all the necessary # derived fields if they aren't native to the data for line in active_lines: # if successful, means line.field is in ds.derived_field_list try: disk_field = ad._determine_fields(line.field)[0] # otherwise we probably need to add the field to the dataset except: my_ion = \ line.field[:line.field.find("number_density")] on_ion = my_ion.split("_") # Add the field if greater than level 1 ionization # because there is only one naming convention for these fields: # X_pY_number_density if on_ion[1]: my_lev = int(on_ion[1][1:]) + 1 mylog.info("Creating %s from ray's density, " "temperature, metallicity." % (line.field)) add_ion_number_density_field( on_ion[0], my_lev, ray, ionization_table=self.ionization_table) # If level 1 ionization, check to see if other name for # field is present in dataset else: my_lev = 1 alias_field = ('gas', "".join([my_ion, 'p0_number_density'])) # Don't add the X_number_density if X_p0_number_density is # in dataset already if alias_field in ray.derived_field_list: line.field = alias_field # But add the field if neither X_number_density nor # X_p0_number_density is in the dataset else: mylog.info("Creating %s from ray's density, " "temperature, metallicity." % (line.field)) add_ion_number_density_field( on_ion[0], my_lev, ray, ionization_table=self.ionization_table) self.add_line(line.identifier, line.field, float(line.wavelength), float(line.f_value), float(line.gamma), atomic_mass[line.element], label_threshold=1e3) # If there are H I lines present, add a Lyman continuum source # Lyman continuum source starts at wavelength where last Lyman line # is deposited (Ly 40), as opposed to true Lyman Limit at 911.763 A # so there won't be a gap between lines and continuum. Using # power law of index 3.0 and normalization to match the opacity of # the final Lyman line into the FUV. H_lines = self.line_database.select_lines(source_list=active_lines, element='H', ion_state='I') if (len(H_lines) > 0) and (ly_continuum == True): self.add_continuum('Ly C', H_lines[0].field, 912.32336, 1.6e17, 3.0) AbsorptionSpectrum.make_spectrum( self, ray, output_file=None, line_list_file=None, use_peculiar_velocity=use_peculiar_velocity, observing_redshift=observing_redshift, store_observables=store_observables, min_tau=min_tau, njobs=njobs)
def make_simple_ray(dataset_file, start_position, end_position, lines=None, ftype="gas", fields=None, solution_filename=None, data_filename=None, trajectory=None, redshift=None, setup_function=None, load_kwargs=None, line_database=None, ionization_table=None): """ Create a yt LightRay object for a single dataset (eg CGM). This is a wrapper function around yt's LightRay interface to reduce some of the complexity there. A simple ray is a straight line passing through a single dataset where each gas cell intersected by the line is sampled for the desired fields and stored. Several additional fields are created and stored including ``dl`` to represent the path length in space for each element in the ray, ``v_los`` to represent the line of sight velocity along the ray, and ``redshift``, ``redshift_dopp``, and ``redshift_eff`` to represent the cosmological redshift, doppler redshift and effective redshift (combined doppler and cosmological) for each element of the ray. A simple ray is typically specified by its start and end positions in the dataset volume. Because a simple ray only probes a single output, it lacks foreground absorbers between the observer at z=0 and the redshift of the dataset that one would naturally encounter. Thus it is usually only appropriate for studying the circumgalactic medium rather than the intergalactic medium. This function can accept a yt dataset already loaded in memory, or it can load a dataset if you pass it the dataset's filename and optionally any load_kwargs or setup_function necessary to load/process it properly before generating the LightRay object. The :lines: keyword can be set to automatically add all fields to the resulting ray necessary for later use with the SpectrumGenerator class. If the necessary fields do not exist for your line of choice, they will be added to your dataset before adding them to the ray. If using the :lines: keyword with an SPH dataset, it is very important to set the :ftype: keyword appropriately, or you may end up calculating ion fields by interpolating on data already smoothed to the grid. This is generally not desired. **Parameters** :dataset_file: string or yt Dataset object Either a yt dataset or the filename of a dataset on disk. If you are passing it a filename, consider usage of the ``load_kwargs`` and ``setup_function`` kwargs. :start_position, end_position: list of floats or YTArray object The coordinates of the starting and ending position of the desired ray. If providing a raw list, coordinates are assumed to be in code length units, but if providing a YTArray, any units can be specified. lines=None, fields=None, solution_filename=None, data_filename=None, trajectory=None, redshift=None, line_database=None, ftype="gas", setup_function=None, load_kwargs=None, ionization_table=None): :lines: list of strings, optional List of strings that determine which fields will be added to the ray to support line deposition to an absorption line spectrum. List can include things like "C", "O VI", or "Mg II ####", where #### would be the integer wavelength value of the desired line. If set to 'all', includes all possible ions from H to Zn. :lines: can be used in conjunction with :fields: as they will not override each other. If using the :lines: keyword with an SPH dataset, it is very important to set the :ftype: keyword appropriately, or you may end up calculating ion fields by interpolating on data already smoothed to the grid. This is generally not desired. Default: None :ftype: string, optional For use with the :lines: keyword. It is the field type of the fields to be added. It is the first string in the field tuple e.g. "gas" in ("gas", "O_p5_number_density"). For SPH datasets, it is important to set this to the field type of the gas particles in your dataset (e.g. 'PartType0'), as it determines the source data for the ion fields to be added. If you leave it set to "gas", it will calculate the ion fields based on the hydro fields already smoothed on the grid, which is usually not desired. Default: "gas" :fields: list of strings, optional The list of which fields to store in the output LightRay. See :lines: keyword for additional functionality that will add fields necessary for creating absorption line spectra for certain line features. Default: None :solution_filename: string, optional Output filename of text file containing trajectory of LightRay through the dataset. Default: None :data_filename: string, optional Output filename for ray data stored as an HDF5 file. Note that at present, you *must* save a ray to disk in order for it to be returned by this function. If set to None, defaults to 'ray.h5'. Default: None :trajectory: list of floats, optional The (r, theta, phi) direction of the LightRay. Use either end_position or trajectory, but not both. Default: None :redshift: float, optional Sets the highest cosmological redshift of the ray. By default, it will use the cosmological redshift of the dataset, if set, and if not set, it will use a redshift of 0. Default: None :setup_function: function, optional A function that will be called on the dataset as it is loaded but before the LightRay is generated. Very useful for adding derived fields and other manipulations of the dataset prior to LightRay creation. Default: None :load_kwargs: dict, optional Dictionary of kwargs to be passed to the yt "load" function prior to creating the LightRay. Very useful for many frontends like Gadget, Tipsy, etc. for passing in "bounding_box", "unit_base", etc. Default: None :line_database: string, optional For use with the :lines: keyword. If you want to limit the available ion fields to be added to those available in a particular subset, you can use a :class:`~trident.LineDatabase`. This means when you set :lines:='all', it will only use those ions present in the corresponding LineDatabase. If :LineDatabase: is set to None, and :lines:='all', it will add every ion of every element up to Zinc. Default: None :ionization_table: string, optional For use with the :lines: keyword. Path to an appropriately formatted HDF5 table that can be used to compute the ion fraction as a function of density, temperature, metallicity, and redshift. When set to None, it uses the table specified in ~/.trident/config Default: None **Example** Generate a simple ray passing from the lower left corner to the upper right corner through some Gizmo dataset where gas particles are ftype='PartType0': >>> import trident >>> import yt >>> ds = yt.load('path/to/dataset') >>> ray = trident.make_simple_ray(ds, ... start_position=ds.domain_left_edge, end_position=ds.domain_right_edge, ... lines=['H', 'O', 'Mg II'], ftype='PartType0') """ if load_kwargs is None: load_kwargs = {} if fields is None: fields = [] if data_filename is None: data_filename = 'ray.h5' if isinstance(dataset_file, str): ds = load(dataset_file, **load_kwargs) elif isinstance(dataset_file, Dataset): ds = dataset_file lr = LightRay(ds, load_kwargs=load_kwargs) if ionization_table is None: ionization_table = ion_table_filepath # Include some default fields in the ray to assure it's processed correctly. fields = _add_default_fields(ds, fields) # If 'lines' kwarg is set, we need to get all the fields required to # create the desired absorption lines in the grid format, since grid-based # fields are what are directly probed by the LightRay object. # We first determine what fields are necessary for the desired lines, and # inspect the dataset to see if they already exist. If so, we add them # to the field list for the ray. If not, we have to create them. if lines is not None: ion_list = _determine_ions_from_lines(line_database, lines) sampling_type = _check_sampling_types_match(ds, ftype) fields, fields_to_add_to_ds = _determine_fields_from_ions(ds, ion_list, fields, ftype, sampling_type) # actually add the fields we need to add to the dataset for atom, ion_state in fields_to_add_to_ds: add_ion_number_density_field(atom, ion_state, ds, ftype=ftype, ionization_table=ionization_table, sampling_type=sampling_type) # To assure there are no fields that are double specified or that collide # based on being specified as "density" as well as ("gas", "density"), # we will just assume that all non-tuple fields requested are ftype "gas". for i in range(len(fields)): if isinstance(fields[i], str): fields[i] = ('gas', fields[i]) fields = uniquify(fields) return lr.make_light_ray(start_position=start_position, end_position=end_position, trajectory=trajectory, fields=fields, setup_function=setup_function, solution_filename=solution_filename, data_filename=data_filename, redshift=redshift)