Source code for trident.line_database

"""
Line, LineDatabase class and member functions.

"""

#-----------------------------------------------------------------------------
# Copyright (c) 2016, Trident Development Team.
#
# Distributed under the terms of the Modified BSD License.
#
# The full license is in the file LICENSE, distributed with this software.
#-----------------------------------------------------------------------------

import os
from yt.funcs import \
    mylog
from trident.config import \
    trident_path
from trident.roman import \
    from_roman


def uniquify(list):
    # order preserving method for reducing duplicates in a list
    checked = []
    for val in list:
        if val not in checked:
            checked.append(val)
    return checked

[docs]class Line: """A class representing an individual atomic transition. Each Line object is uniquely identified by element, ionic state, wavelength, gamma, oscillator strength, and identifier. **Parameters** :element: string The element of the transition using element's symbol on periodic table Example: 'H', 'C', 'Mg' :ion_state: string The roman numeral representing the ionic state of the transition Example: 'I' for neutral species, 'II' for singly ionized, etc. :wavelength: float The wavelength of the transition in angstroms Example: 1216 for Lyman alpha :gamma: float The gamma of the transition in Hertz :f_value: float The oscillator strength of the transition :field: string, optional The default yt field name associated with the ion responsible for this line Example: 'H_p1_number_density' for HII :identifier: string, optional An optional identifier for the transition Example: 'Ly a' for Lyman alpha **Example** Create a Line object for the neutral hydrogen 1215 Angstroms transition. >>> HI = Line('H', 'I', 1215.67, 469860000, 0.41641, 'Ly a') """
[docs] def __init__(self, element, ion_state, wavelength, gamma, f_value, field=None, identifier=None): self.element = element self.ion_state = ion_state self.wavelength = float(wavelength) self.gamma = float(gamma) self.f_value = float(f_value) self.name = '%s %s %d' % (element, ion_state, round(float(wavelength), 0)) if identifier is None: identifier = self.name self.identifier = identifier # Automatically populate the field if not defined if field is None: ion_number = from_roman(ion_state) keyword = "%s_p%d" % (element, (ion_number-1)) field = "%s_number_density" % keyword field = ("gas", field) self.field = field
def __repr__(self): return self.identifier
[docs]class LineDatabase: """ Class for storing and selecting collections of spectral lines. These lines will be used in the :class:`~trident.SpectrumGenerator` and :class:`~trident.add_ion_fields()` functionality. Without arguments, the LineDatabase will be empty, and you must manually add individual lines to it using the :class:`~trident.LineDatabase.add_line` function. If LineDatabase is provided with an optional :input_file:, it will automatically add spectral lines for each corresponding line in the list. Once created, you can select a subset of the total lines present in the database for further use. Use the :class:`~trident.LineDatabase.parse_subset` function to accomplish this. **Parameters** :input_file: string, optional An optional input_file can be provided to pre-store a list of Line objects. input_file should be a tab delimited text file of the format: element, ion_state, wavelength, gamma, f_value, (name) H, I, 1215.67, 4.69e8, 4.16e-1, Ly a **Example** >>> # Create a LineDatabase using the lines present in lines.txt >>> ldb = LineDatabase('lines.txt') >>> # Parse ldb and only select Lyman alpha, Mg II and Fe lines >>> lines = ldb.parse_subset(lines=['H I 1216', 'Mg II', 'Fe']) >>> print(lines) """
[docs] def __init__(self, input_file=None): self.lines_all = [] self.lines_subset = [] self.input_file = input_file if input_file is not None: self.load_line_list_from_file(input_file) else: self.input_file = 'Manually Entered'
[docs] def add_line(self, element, ion_state, wavelength, gamma, f_value, field=None, identifier=None): """ Manually add a line to the :class:`~trident.LineDatabase`. **Parameters** :element: string The element of the transition using element's symbol on periodic table Example: 'H', 'C', 'Mg' :ion_state: string The roman numeral representing the ionic state of the transition Example: 'I' for neutral species, 'II' for singly ionized, etc. :wavelength: float The wavelength of the transition in angstroms Example: 1216 for Lyman alpha :gamma: float The gamma of the transition in Hertz :f_value: float The oscillator strength of the transition :field: string, optional The default yt field name associated with the ion responsible for this line Example: 'H_p1_number_density' for HII :identifier: string, optional An optional identifier for the transition Example: 'Ly a' for Lyman alpha **Example** >>> # Create a LineDatabase using the lines present in lines.txt >>> ldb = LineDatabase('lines.txt') >>> # Manually add the neutral hydrogen line to ldb >>> ldb.add_line('H', 'I', 1215.67, 469860000, 0.41641, 'Ly a') >>> print(ldb.lines_all) """ self.lines_all.append(Line(element, ion_state, wavelength, gamma, f_value, field, identifier))
[docs] def load_line_list_from_file(self, filename): """ Load a line list from a file into the LineDatabase. Line list file is a tab-delimited text file in the format: element, ion_state, wavelength, gamma, f_value, (name) H, I, 1215.67, 4.69e8, 4.16e-1, Ly a **Parameters** filename : string The filename of the list to add. First looks in trident.__path__/data/line_lists directory, then in cwd. """ # check to see if file exists in trident/data/line_lists # if not, look in cwd filename = os.path.join(trident_path(), "data", "line_lists", filename) if not os.path.isfile(filename): filename = filename.split(os.sep)[-1] if not os.path.isfile(filename): raise RuntimeError("line_list %s is not found in local " "directory or in trident/data/line_lists " % (filename.split(os.sep)[-1])) # Step through each line of text in file and add to database for line in open(filename).readlines(): online = line.rstrip().split() if line.startswith("#") or len(online) < 5: continue element, ion_state, wavelength, gamma, f_value = online[:5] # optional identifier should be added if existent if len(online) > 5: identifier = " ".join(online[5:]) else: identifier = None self.add_line(element, ion_state, wavelength, gamma, f_value, identifier=identifier)
[docs] def select_lines(self, element=None, ion_state=None, wavelength=None, identifier=None, source_list=None): """ Select lines based on atom, ion state, identifier, and/or wavelength. Once you've created a LineDatabase, you can subselect certain lines from it based on line characteristics. Recommended to use :class:`~trident.LineDatabase.parse_subset` instead which allows selecting of multiple sets of lines simultaneously. **Parameters** :element: string, optional The element of the transition using element's symbol on periodic table Example: 'H', 'C', 'Mg' Default: None :ion_state: string, optional The roman numeral representing the ionic state of the transition Example: 'I' for neutral species, 'II' for singly ionized, etc. Default: None :wavelength: float, optional The wavelength of the transition in angstroms Example: 1216 for Lyman alpha Default: None :identifier: string, optional An optional identifier for the transition Example: 'Ly a' for Lyman alpha Default: None :source_list: list of :class:`~trident.Line` objects, optional The source list from which to select lines. If set to None, use the LineDatabase's list 'lines_all'. Default: None **Returns** :selected_lines: list A list of which lines were selected. **Example** >>> ldb = LineDatabase('lines.txt') >>> selected_lines = ldb.select_lines(element='Mg', ion_state='II') """ if source_list is None: source_list = self.lines_all selected_lines = [] for line in source_list: # identifier set; use it to find line if identifier is not None: if line.identifier == identifier: selected_lines.append(line) # element, ion, and wavelength set; use them to find line elif ion_state is not None and wavelength is not None: if line.element == element and line.ion_state == ion_state \ and round(float(line.wavelength), 0) == \ round(float(wavelength), 0): selected_lines.append(line) # element and ion set; use them to find line elif ion_state is not None: if line.element == element and line.ion_state == ion_state: selected_lines.append(line) # only element set; use it to find line else: if line.element == element: selected_lines.append(line) return selected_lines
[docs] def parse_subset(self, subsets='all'): """ Select multiple lines based on atom, ion state, identifier, and/or wavelength. Once you've created a LineDatabase, you can subselect certain lines from it based on line characteristics. Preferred to use this method over :class:`~trident.LineDatabase.select_lines`. Will return the unique union of all lines matching the specified subsets from the :class:`~trident.LineDatabase`. **Parameters** :subsets: list of strings, optional List strings matching possible lines. Strings can be of the form: * Atom - Examples: "H", "C", "Mg" * Ion - Examples: "H I", "H II", "C IV", "Mg II" * Line - Examples: "H I 1216", "C II 1336", "Mg II 1240" * Identifier - Examples: "Ly a", "Ly b" If set to None, selects **all** lines in :class:`~trident.LineDatabase`. Default: None **Returns** :line subset: list of :class:`trident.Line` objects A list of the Lines that were selected **Example** >>> # Get a list of all lines of Carbon, Mg II and Lyman alpha >>> ldb = LineDatabase('lines.txt') >>> lines = ldb.parse_subset(['C', 'Mg II', 'H I 1216']) >>> print(lines) """ # if all specified, then use all lines available if subsets == 'all': self.lines_subset = self.lines_all mylog.info("Using all %d available lines in '%s'." % \ (len(self.lines_all), self.input_file)) return self.lines_subset if subsets is None: subsets = [] if isinstance(subsets, str): subsets = [subsets] for val in subsets: # try to add line based on identifier new_lines = self.select_lines(identifier=val) if len(new_lines) > 0: self.lines_subset.extend(new_lines) continue val = val.split() if len(val) == 1: # add all lines associated with an element new_lines = self.select_lines(val[0]) self.lines_subset.extend(new_lines) if len(new_lines) == 0: mylog.info("No lines found in subset '%s'." % val[0]) elif len(val) == 2: # add all lines associated with an ion new_lines = self.select_lines(val[0], val[1]) self.lines_subset.extend(new_lines) if len(new_lines) == 0: mylog.info("No lines found in subset '%s %s'." % \ (val[0], val[1])) elif len(val) == 3: # add only one line new_lines = self.select_lines(val[0], val[1], val[2]) self.lines_subset.extend(new_lines) if len(new_lines) == 0: mylog.info("No lines found in subset '%s %s %s'." % (val[0], val[1], val[2])) # Get rid of duplicates in subset and re-sort self.lines_subset = uniquify(self.lines_subset) return self.lines_subset
[docs] def parse_subset_to_ions(self, subsets=None): """ Select ions based on those needed to create specific lines. Once you've created a LineDatabase, you can subselect certain ions from it based on the line characteristics of atom, ion state, identifier, and/or wavelength. Similar to :class:`~trident.LineDatabase.parse_subset` but outputs a list of ion tuples (e.g. ('H', 1), ('Fe', 2)), instead of a list of :class:`~trident.Line` objects. Will return the unique union of all ions matching the specified subsets from the :class:`~trident.LineDatabase`. **Parameters** :subsets: list of strings, optional List strings matching possible lines. Strings can be of the form: * Atom - Examples: "H", "C", "Mg" * Ion - Examples: "H I", "H II", "C IV", "Mg II" * Line - Examples: "H I 1216", "C II 1336", "Mg II 1240" * Identifier - Examples: "Ly a", "Ly b" If set to None, selects ions necessary to produce **all** lines in :class:`~trident.LineDatabase`. Default: None **Returns** :ion subset: list of ion tuples A list of the ions necessary to produce the desired lines Each ion tuple is of the form ('H', 1) = neutral hydrogen **Example** Get a list of all ions necessary to generate lines for Carbon, Mg II and Lyman alpha >>> ldb = LineDatabase('lines.txt') >>> ions = ldb.parse_subset_to_ions(['C', 'Mg II', 'H I 1216']) >>> print(ions) """ self.parse_subset(subsets) ions = [] for line in self.lines_subset: ions.append((line.element, from_roman(line.ion_state))) ions = uniquify(ions) return ions
def __repr__(self): disp = "" for line in self.lines_all: disp += "%s\n" % line return disp