diff --git a/linetools/isgm/abscomponent.py b/linetools/isgm/abscomponent.py index 115ee26c..a7e1ddfe 100644 --- a/linetools/isgm/abscomponent.py +++ b/linetools/isgm/abscomponent.py @@ -108,25 +108,31 @@ def from_component(cls, component, **kwargs): A=component.A, name=component.name, **kwargs) @classmethod - def from_dict(cls, idict, **kwargs): + def from_dict(cls, idict, coord=None, **kwargs): """ Instantiate from a dict Parameters ---------- idict : dict + use_line_list : str, optional + Name of the Linelist to use when generating the SpectralLines Returns ------- """ - slf = cls(SkyCoord(ra=idict['RA']*u.deg, dec=idict['DEC']*u.deg), - tuple(idict['Zion']), idict['zcomp'], idict['vlim']*u.km/u.s, + if coord is not None: + radec = coord + else: + radec = SkyCoord(ra=idict['RA']*u.deg, dec=idict['DEC']*u.deg) + # Init + slf = cls(radec, tuple(idict['Zion']), idict['zcomp'], idict['vlim']*u.km/u.s, Ej=idict['Ej']/u.cm, A=idict['A'], Ntup = tuple([idict[key] for key in ['flag_N', 'logN', 'sig_logN']]), comment=idict['comment'], name=idict['Name']) # Add lines for key in idict['lines'].keys(): - iline = SpectralLine.from_dict(idict['lines'][key]) + iline = SpectralLine.from_dict(idict['lines'][key], coord=coord, **kwargs) slf.add_absline(iline, **kwargs) # Return return slf @@ -201,7 +207,7 @@ def __init__(self, radec, Zion, z, vlim, Ej=0./u.cm, A=None, # Other self._abslines = [] - def add_absline(self, absline, tol=0.1*u.arcsec, skip_vel=False): + def add_absline(self, absline, tol=0.1*u.arcsec, skip_vel=False, chk_sep=True, **kwargs): """Add an AbsLine object to the component if it satisfies all of the rules. @@ -215,9 +221,14 @@ def add_absline(self, absline, tol=0.1*u.arcsec, skip_vel=False): Tolerance on matching coordinates skip_vel : bool, optional Skip velocity test? Not recommended + chk_sep : bool, optional + Perform coordinate check (expensive) """ # Perform easy checks - testc = bool(self.coord.separation(absline.attrib['coord']) < tol) + if chk_sep: + testc = bool(self.coord.separation(absline.attrib['coord']) < tol) + else: + testc = True testZ = self.Zion[0] == absline.data['Z'] testi = self.Zion[1] == absline.data['ion'] testE = bool(self.Ej == absline.data['Ej']) diff --git a/linetools/isgm/abssystem.py b/linetools/isgm/abssystem.py index a5f81e2f..b7029d59 100644 --- a/linetools/isgm/abssystem.py +++ b/linetools/isgm/abssystem.py @@ -127,13 +127,16 @@ def from_components(cls, components, vlim=None): return slf @classmethod - def from_dict(cls, idict, skip_components=False, **kwargs): + def from_dict(cls, idict, skip_components=False, use_coord=False, **kwargs): """ Instantiate from a dict. Usually read from the hard-drive Parameters ---------- idict : dict skip_components : bool, optional + use_coord : bool, optinal + Use coordinates from the system to build the components (and lines) + Speeds up performance, but you should know things are OK before using this Returns ------- @@ -148,10 +151,14 @@ def from_dict(cls, idict, skip_components=False, **kwargs): ) if not skip_components: # Components - components = ltiu.build_components_from_dict(idict, **kwargs) + if use_coord: # Speed up performance + coord = slf.coord + else: + coord = None + components = ltiu.build_components_from_dict(idict, coord=coord, **kwargs) for component in components: # This is to insure the components follow the rules - slf.add_component(component) + slf.add_component(component, **kwargs) # Return return slf @@ -195,7 +202,7 @@ def __init__(self, abs_type, radec, zabs, vlim, zem=0., # Refs (list of references) self.Refs = [] - def add_component(self, abscomp, toler=0.2*u.arcsec): + def add_component(self, abscomp, toler=0.2*u.arcsec, chk_sep=True, **kwargs): """Add an AbsComponent object if it satisfies all of the rules. For velocities, we demand that the new component has a velocity @@ -208,9 +215,14 @@ def add_component(self, abscomp, toler=0.2*u.arcsec): comp : AbsComponent toler : Angle, optional Tolerance on matching coordinates + chk_sep : bool, optional + Perform coordinate check (expensive) """ # Coordinates - test = bool(self.coord.separation(abscomp.coord) < toler) + if chk_sep: + test = bool(self.coord.separation(abscomp.coord) < toler) + else: + test = True # Now redshift/velocity zlim_comp = (1+abscomp.zcomp)*abscomp.vlim/const.c.to('km/s') zlim_sys = (1+self.zabs)*self.vlim/const.c.to('km/s') diff --git a/linetools/isgm/utils.py b/linetools/isgm/utils.py index 7a1f7c92..c28fff65 100644 --- a/linetools/isgm/utils.py +++ b/linetools/isgm/utils.py @@ -151,14 +151,14 @@ def build_components_from_dict(idict, coord=None, **kwargs): if 'components' in idict.keys(): # Components for key in idict['components']: - components.append(AbsComponent.from_dict(idict['components'][key], **kwargs)) + components.append(AbsComponent.from_dict(idict['components'][key], coord=coord, **kwargs)) elif 'lines' in idict.keys(): # to be deprecated lines = [] for key in idict['lines']: if isinstance(idict['lines'][key], AbsLine): line = idict['lines'][key] elif isinstance(idict['lines'][key], dict): - line = AbsLine.from_dict(idict['lines'][key]) + line = AbsLine.from_dict(idict['lines'][key], coord=coord) else: raise IOError("Need those lines") if coord is not None: diff --git a/linetools/lists/linelist.py b/linetools/lists/linelist.py index 55a4681d..da952c5d 100644 --- a/linetools/lists/linelist.py +++ b/linetools/lists/linelist.py @@ -29,6 +29,7 @@ # (e.g. MgII). Currently, priority is given to the first one loaded # but that may not be the best approach... + class LineList(object): """ This Class is designed to organize and handle information about @@ -83,7 +84,8 @@ def __init__(self, llst_key, verbose=False, closest=False, set_lines=True, # This sets self._data self.set_lines(use_ISM_table=use_ISM_table, verbose=verbose, use_cache=use_cache) - + # Memoize + self.memoize = {} # To speed up multiple calls def load_data(self, use_ISM_table=True, tol=1e-3 * u.AA, use_cache=True): """Grab the data for the lines of interest @@ -685,47 +687,53 @@ def __getitem__(self, k, tol=1e-3 * u.AA): dict (from row in the data table if only 1 line is found) or QTable (tuple when more than 1 lines are found) ''' - if isinstance(k, (float, Quantity)): # Wavelength - if isinstance(k, float): # Assuming Ang - inwv = k * u.AA - else: - inwv = k - mt = np.where(np.abs(inwv - self.wrest) < tol)[0] - elif isinstance(k, basestring): # Name - if k == 'unknown': - return self.unknown_line() + try: + return self.memoize[k] + except KeyError: + if isinstance(k, (float, Quantity)): # Wavelength + if isinstance(k, float): # Assuming Ang + inwv = k * u.AA + else: + inwv = k + mt = np.where(np.abs(inwv - self.wrest) < tol)[0] + elif isinstance(k, basestring): # Name + if k == 'unknown': + return self.unknown_line() + else: + mt = np.where(str(k) == self.name)[0] + elif isinstance(k, tuple): # Zion + mt = (self._data['Z'] == k[0]) & (self._data['ion'] == k[1]) else: - mt = np.where(str(k) == self.name)[0] - elif isinstance(k, tuple): # Zion - mt = (self._data['Z'] == k[0]) & (self._data['ion'] == k[1]) - else: - raise ValueError('Not prepared for this type', k) - - # No Match? - if len(mt) == 0: - # Take closest?? - if self.closest and (not isinstance(k, basestring)): - mt = [np.argmin(np.abs(inwv - self.wrest))] - if self.verbose: - print('WARNING: Using {:.4f} for your input {:.4f}'.format(self.wrest[mt[0]], - inwv)) + raise ValueError('Not prepared for this type', k) + + # No Match? + if len(mt) == 0: + # Take closest?? + if self.closest and (not isinstance(k, basestring)): + mt = [np.argmin(np.abs(inwv - self.wrest))] + if self.verbose: + print('WARNING: Using {:.4f} for your input {:.4f}'.format(self.wrest[mt[0]], + inwv)) + else: + if self.verbose: + print('No such line in the list', k) + return None + + # Now we have something + if len(mt) == 1: + # Pass back as dict + self.memoize[k] = dict(zip(self._data.dtype.names, self._data[mt][0])) + # return self._data[mt][0] # Pass back as a Row not a Table + elif isinstance(k, tuple): + self.memoize[k] = self._data[mt] else: - if self.verbose: - print('No such line in the list', k) - return None - - # Now we have something - if len(mt) == 1: - # Pass back as dict - return dict(zip(self._data.dtype.names, self._data[mt][0])) - # return self._data[mt][0] # Pass back as a Row not a Table - elif isinstance(k, tuple): - return self._data[mt] - else: - raise ValueError( - '{:s}: Multiple lines in the list'.format(self.__class__)) + raise ValueError( + '{:s}: Multiple lines in the list'.format(self.__class__)) + # Finish + return self.memoize[k] # Printing def __repr__(self): return ''.format(self.list, len(self._data)) + diff --git a/linetools/spectralline.py b/linetools/spectralline.py index 1366f745..1ccb6d1a 100644 --- a/linetools/spectralline.py +++ b/linetools/spectralline.py @@ -22,6 +22,26 @@ from linetools.lists.linelist import LineList from linetools import utils as ltu +# A few globals to speed performace +zero_coord = SkyCoord(ra=0.*u.deg, dec=0.*u.deg) # Coords +init_analy = { + 'spec': None, # Analysis inputs (e.g. spectrum; from .clm file or AbsID) + 'wvlim': [0., 0.]*u.AA, # Wavelength interval about the line (observed) + 'vlim': [0., 0.]*u.km/u.s, # Rest-frame velocity limit of line, relative to self.attrib['z'] + 'flag_kin': 0, # Use for kinematic analysis? + 'do_analysis': 1 # Analyze? + } +init_attrib = { + 'coord': zero_coord, # Coords + 'z': 0., 'sig_z': 0., # Redshift + 'v': 0.*u.km/u.s, 'sig_v': 0.*u.km/u.s, # rest-frame velocity relative to z + 'EW': 0.*u.AA, 'sig_EW': 0.*u.AA, 'flag_EW': 0 # EW + } + +#abs_attrib = {'N': 0./u.cm**2, 'sig_N': 0./u.cm**2, 'flag_N': 0, # Column ## NOT ENOUGH SPEED-UP +# 'b': 0.*u.km/u.s, 'sig_b': 0.*u.km/u.s # Doppler +# } + # Class for Spectral line class SpectralLine(object): """ @@ -57,7 +77,7 @@ class SpectralLine(object): """ @classmethod - def from_dict(cls, idict, warn_only=False): + def from_dict(cls, idict, coord=None, warn_only=False, skip_data_chk=False, **kwargs): """ Initialize from a dict (usually read from disk) Parameters @@ -76,29 +96,31 @@ def from_dict(cls, idict, warn_only=False): if idict['ltype'] == 'Abs': # TODO: remove this try/except eventually try: - sline = AbsLine(idict['name']) - except: # This is to be compatible JSON files already written with old notation - sline = AbsLine(idict['trans']) + sline = AbsLine(idict['name'], **kwargs) + except KeyError: # This is to be compatible JSON files already written with old notation + pdb.set_trace() + sline = AbsLine(idict['trans'], **kwargs) else: raise ValueError("Not prepared for type {:s}.".format(idict['ltype'])) # Check data - for key in idict['data']: - if isinstance(idict['data'][key], dict): # Assume Quantity - val = idict['data'][key]['value'] - else: - val = idict['data'][key] - try: - assert sline.data[key] == val - except AssertionError: - if warn_only: - warnings.warn("Different data value for {:s}: {}, {}".format(key,sline.data[key],val)) + if not skip_data_chk: + for key in idict['data']: + if isinstance(idict['data'][key], dict): # Assume Quantity + val = idict['data'][key]['value'] + else: + val = idict['data'][key] + try: + assert sline.data[key] == val + except AssertionError: + if warn_only: + warnings.warn("Different data value for {:s}: {}, {}".format(key,sline.data[key],val)) # Set analy for key in idict['analy']: if isinstance(idict['analy'][key], dict): # Assume Quantity #sline.analy[key] = Quantity(idict['analy'][key]['value'], # unit=idict['analy'][key]['unit']) - sline.analy[key] = ltu.convert_quantity_in_dict( - idict['analy'][key]) + #pdb.set_trace() + sline.analy[key] = ltu.convert_quantity_in_dict(idict['analy'][key]) elif key == 'spec_file': warnings.warn("You will need to load {:s} into attrib['spec'] yourself".format( idict['analy'][key])) @@ -107,18 +129,20 @@ def from_dict(cls, idict, warn_only=False): # Set attrib for key in idict['attrib']: if isinstance(idict['attrib'][key], dict): - sline.attrib[key] = ltu.convert_quantity_in_dict( - idict['attrib'][key]) + sline.attrib[key] = ltu.convert_quantity_in_dict(idict['attrib'][key]) elif key in ['RA','DEC']: - sline.attrib['coord'] = SkyCoord(ra=idict['attrib']['RA']*u.deg, - dec=idict['attrib']['DEC']*u.deg) + if coord is None: + sline.attrib['coord'] = SkyCoord(ra=idict['attrib']['RA']*u.deg, + dec=idict['attrib']['DEC']*u.deg) + else: + sline.attrib['coord'] = coord else: sline.attrib[key] = idict['attrib'][key] return sline # Initialize with wavelength def __init__(self, ltype, trans, linelist=None, closest=False, z=0., - verbose=True): + verbose=True, **kwargs): # Required self.ltype = ltype @@ -131,19 +155,8 @@ def __init__(self, ltype, trans, linelist=None, closest=False, z=0., # Other self.data = {} # Atomic/Molecular Data (e.g. f-value, A coefficient, Elow) - self.analy = { - 'spec': None, # Analysis inputs (e.g. spectrum; from .clm file or AbsID) - 'wvlim': [0., 0.]*u.AA, # Wavelength interval about the line (observed) - 'vlim': [0., 0.]*u.km/u.s, # Rest-frame velocity limit of line, relative to self.attrib['z'] - 'flag_kin': 0, # Use for kinematic analysis? - 'do_analysis': 1 # Analyze? - } - self.attrib = { - 'coord': SkyCoord(ra=0.*u.deg, dec=0.*u.deg), # Coords - 'z': z, 'sig_z': 0., # Redshift - 'v': 0.*u.km/u.s, 'sig_v': 0.*u.km/u.s, # rest-frame velocity relative to z - 'EW': 0.*u.AA, 'sig_EW': 0.*u.AA, 'flag_EW': 0 # EW - } + self.analy = init_analy + self.attrib = init_attrib #{ # Fill data self.fill_data(trans, linelist=linelist, closest=closest, verbose=verbose)