예제 #1
0
        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)
예제 #2
0
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)
예제 #3
0
    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)
예제 #4
0
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)