def flux_model(self, spec, smooth=0): """ Generate a LLS model given an input spectrum Parameters ---------- spec : Spectrum1D smooth : int, optional Number of pixels to smooth by Returns ------- model : XSpectrum1D Output model is passed back as a Spectrum """ from linetools.analysis import voigt as lav # Energies in LLS rest-frame wv_rest = spec.wavelength / (self.zabs + 1) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau tau_LL = (10.**self.NHI / u.cm**2) * ltaa.photo_cross(1, 1, energy) # Check for lines if 'lls_lines' not in self.__dict__.keys(): self.fill_lls_lines() tau_Lyman = lav.voigt_from_abslines(spec.wavelength, self.lls_lines, ret='tau') # Combine tau_model = tau_LL + tau_Lyman # Kludge around the limit pix_LL = np.argmin(np.fabs(wv_rest - 911.3 * u.AA)) pix_kludge = np.where((wv_rest > 911.5 * u.AA) & (wv_rest < 912.8 * u.AA))[0] tau_model[pix_kludge] = tau_model[pix_LL] # Fill in flux model = spec.copy() model.flux = np.exp(-1. * tau_model).value # Smooth? if smooth > 0: model.gauss_smooth(smooth) # Return return model
def __init__(self, radec, zabs, vlim, **kwargs): """Standard init NHI keyword is required Parameters ---------- radec : tuple or coordinate RA/Dec of the sightline or astropy.coordinate zabs : float Absorption redshift vlim : Quantity array (2) Velocity limits of the system Defaulted to +/- 500 km/s if None (see Prochaska et al. 2016 HDLLS) NHI= : float, required despite being a keyword log10 of HI column density **kwargs : keywords passed to IGMSystem.__init__ """ # NHI try: NHI = kwargs['NHI'] except KeyError: raise ValueError("NHI must be specified for LLSSystem") else: kwargs.pop('NHI') # vlim if vlim is None: vlim = [-500., 500.] * u.km / u.s # Generate with type IGMSystem.__init__(self, radec, zabs, vlim, NHI=NHI, abs_type='LLS', **kwargs) # Set tau_LL self.tau_LL = (10.**self.NHI) * ltaa.photo_cross( 1, 1, 1 * u.Ry).to('cm**2').value # Other self.zpeak = None # Optical depth weighted redshift self.ZH = 0. self.metallicity = None # MetallicityPDF class usually # Subsystems self.nsub = 0 self.subsys = {}
def flux_model(self, spec, smooth=0): """ Generate a LLS model given an input spectrum Parameters ---------- spec : Spectrum1D smooth : int, optional Number of pixels to smooth by Returns ------- model : XSpectrum1D Output model is passed back as a Spectrum """ from linetools.analysis import voigt as lav # Energies in LLS rest-frame wv_rest = spec.dispersion / (self.zabs+1) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau tau_LL = (10.**self.NHI / u.cm**2) * ltaa.photo_cross(1, 1, energy) # Check for lines if 'lls_lines' not in self.__dict__.keys(): self.fill_lls_lines() tau_Lyman = lav.voigt_from_abslines(spec.dispersion, self.lls_lines, ret='tau') # Combine tau_model = tau_LL + tau_Lyman # Kludge around the limit pix_LL = np.argmin(np.fabs( wv_rest- 911.3*u.AA)) pix_kludge = np.where((wv_rest > 911.5*u.AA) & (wv_rest < 912.8*u.AA))[0] tau_model[pix_kludge] = tau_model[pix_LL] # Fill in flux model = spec.copy() model.flux = np.exp(-1. * tau_model).value # Smooth? if smooth > 0: model.gauss_smooth(smooth) # Return return model
def generate_tau(iwave, HIlines, HI_comps, kludge=True): """Generate optical depth array given lines and components Parameters: ----------- iwave : Quantity array Input Spectrum wavelengths HIlines : list of AbsLines HI_comps : QTable of components kludge : bool, optional Kludge the opacity Returns: -------- tau : ndarray Optical depth at subgrid wavelengths. Will need to rebin back """ # Rebin to subgrid wmin = np.min(iwave.to('AA').value) wmax = np.max(iwave.to('AA').value) nsub = int(np.round((np.log10(wmax) - np.log10(wmin)) / 1.449E-6)) + 1 wave = 10.**(np.log10(wmin) + np.arange(nsub) * 1.449E-6) * u.AA # Voigt for Lyman series tau_Lyman = lav.voigt_from_abslines(wave, HIlines, fwhm=0., ret='tau') # Continuum opacity LL_comps = HI_comps['lgNHI'] > 15.0 tau_LL = np.zeros(wave.size) for row in HI_comps[LL_comps]: # Energies in LLS rest-frame wv_rest = wave / (row['z'] + 1) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau itau_LL = (10.**row['lgNHI'] / u.cm**2) * photo_cross(1, 1, energy) # Kludge around the limit if kludge: pix_LL = np.argmin(np.fabs(wv_rest - 911.3 * u.AA)) pix_kludge = np.where((wv_rest > 911.5 * u.AA) & (wv_rest < 912.8 * u.AA))[0] itau_LL[pix_kludge] = itau_LL[pix_LL] # Sum tau_LL += itau_LL.decompose().value # Total return wave, tau_LL + tau_Lyman
def generate_tau(iwave, HIlines, HI_comps, kludge=True): """Generate optical depth array given lines and components Parameters: ----------- iwave : Quantity array Input Spectrum wavelengths HIlines : list of AbsLines HI_comps : QTable of components kludge : bool, optional Kludge the opacity Returns: -------- tau : ndarray Optical depth at subgrid wavelengths. Will need to rebin back """ # Rebin to subgrid wmin = np.min(iwave.to('AA').value) wmax = np.max(iwave.to('AA').value) nsub = int(np.round( (np.log10(wmax)- np.log10(wmin)) / 1.449E-6)) + 1 wave = 10.**(np.log10(wmin) + np.arange(nsub)*1.449E-6) * u.AA # Voigt for Lyman series tau_Lyman = lav.voigt_from_abslines(wave,HIlines,fwhm=0.,ret='tau') # Continuum opacity LL_comps = HI_comps['lgNHI'] > 15.0 tau_LL = np.zeros(wave.size) for row in HI_comps[LL_comps]: # Energies in LLS rest-frame wv_rest = wave / (row['z']+1) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau itau_LL = (10.**row['lgNHI'] / u.cm**2) * photo_cross(1,1,energy) # Kludge around the limit if kludge: pix_LL = np.argmin(np.fabs(wv_rest- 911.3*u.AA)) pix_kludge = np.where((wv_rest > 911.5*u.AA) & (wv_rest < 912.8*u.AA))[0] itau_LL[pix_kludge] = itau_LL[pix_LL] # Sum tau_LL += itau_LL # Total return wave, tau_LL + tau_Lyman
def tau_multi_lls(wave, all_lls, **kwargs): """Calculate opacities on an input observed wavelength grid Parameters ---------- wave : Quantity array Wavelengths all_lls : list List of LLS Class **kwargs : dict extra keywords go to lav.voigt_from_abslines Returns ------- tau : ndarray Optical depth values at input wavelengths """ from linetools.analysis import voigt as lav # all_tau_model = np.zeros(len(wave)) # Loop on LLS for lls in all_lls: # LL wv_rest = wave / (lls.zabs + 1) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau tau_LL = (10.**lls.NHI / u.cm**2) * ltaa.photo_cross(1, 1, energy) # Lyman tau_Lyman = lav.voigt_from_abslines(wave, lls.lls_lines, ret='tau', **kwargs) tau_model = tau_LL + tau_Lyman # Kludge around the limit pix_LL = np.argmin(np.fabs(wv_rest - 911.3 * u.AA)) pix_kludge = np.where((wv_rest > 911.5 * u.AA) & (wv_rest < 912.8 * u.AA))[0] tau_model[pix_kludge] = tau_model[pix_LL] # Add all_tau_model += tau_model # Return return all_tau_model
def __init__(self, radec, zabs, vlim, **kwargs): """Standard init NHI keyword is required Parameters ---------- radec : tuple or coordinate RA/Dec of the sightline or astropy.coordinate zabs : float Absorption redshift vlim : Quantity array (2) Velocity limits of the system Defaulted to +/- 500 km/s if None (see Prochaska et al. 2016 HDLLS) NHI= : float, required despite being a keyword log10 of HI column density **kwargs : keywords passed to IGMSystem.__init__ """ # NHI try: NHI = kwargs['NHI'] except KeyError: raise ValueError("NHI must be specified for LLSSystem") else: kwargs.pop('NHI') # vlim if vlim is None: vlim = [-500.,500.]*u.km/u.s # Generate with type IGMSystem.__init__(self, 'LLS', radec, zabs, vlim, NHI=NHI, **kwargs) # Set tau_LL self.tau_LL = (10.**self.NHI)*ltaa.photo_cross(1, 1, 1*u.Ry).to('cm**2').value # Other self.zpeak = None # Optical depth weighted redshift self.ZH = 0. self.metallicity = None # MetallicityPDF class usually # Subsystems self.nsub = 0 self.subsys = {}
def tau_multi_lls(wave, all_lls, **kwargs): """Calculate opacities on an input observed wavelength grid Parameters ---------- wave : Quantity array Wavelengths all_lls : list List of LLS Class **kwargs : dict extra keywords go to lav.voigt_from_abslines Returns ------- tau : ndarray Optical depth values at input wavelengths """ from linetools.analysis import voigt as lav # all_tau_model = np.zeros(len(wave)) # Loop on LLS for lls in all_lls: # LL wv_rest = wave / (lls.zabs+1) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau tau_LL = (10.**lls.NHI / u.cm**2) * ltaa.photo_cross(1,1,energy) # Lyman tau_Lyman = lav.voigt_from_abslines(wave, lls.lls_lines, ret='tau', **kwargs) tau_model = tau_LL + tau_Lyman # Kludge around the limit pix_LL = np.argmin(np.fabs( wv_rest- 911.3*u.AA )) pix_kludge = np.where((wv_rest > 911.5*u.AA) & (wv_rest < 912.8*u.AA))[0] tau_model[pix_kludge] = tau_model[pix_LL] # Add all_tau_model += tau_model # Return return all_tau_model
def test_photocross(): phto = photo_cross(1, 1, 14.*u.eV) assert phto.unit == u.cm**2 np.testing.assert_allclose(phto.value, 5.870146496955153e-18)
def hi_model(abssys, spec, lya_only=False, add_lls=False, ret_tau=False, ignore_abslines=False, bval=30 * u.km / u.s, **kwargs): """ Generate a model of the absorption from the absorption system on an input spectrum. Parameters ---------- abssys : AbsSystem or list If list, must be a list of AbsSystem's spec : XSpectrum1D lya_only : bool, optional Only generate Lya ignore_abslines : bool, optional Ignore any existing abslines in the object NHI tag must be set add_lls : bool, optional Add Lyman continuum absorption bval : Quantity, optional Doppler parameter to use if abslines not adopted ret_tau : bool, optional Return only the optical depth (used for multiple systems) kwargs : Passed to voigt_from_abslines Returns ------- vmodel : XSpectrum1D or ndarray Model spectrum with same wavelength as input spectrum Assumes a normalized flux Or optical depth array [used for multiple systems] lyman_lines : list List of AbsLine's that contributed to the model """ from astropy.units import Quantity from linetools.spectra.xspectrum1d import XSpectrum1D from linetools.spectralline import AbsLine from linetools.analysis.voigt import voigt_from_abslines from linetools.analysis.absline import photo_cross # Input if isinstance(abssys, list): tau = None all_lines = [] for iabssys in abssys: itau, ly_lines = hi_model(iabssys, spec, lya_only=lya_only, add_lls=add_lls, ignore_abslines=ignore_abslines, bval=bval, ret_tau=True, **kwargs) all_lines += ly_lines if tau is None: tau = itau else: tau += itau # Flux flux = np.exp(-1 * tau) vmodel = XSpectrum1D.from_tuple((spec.wavelength, flux)) return vmodel, all_lines else: # Scan abs lines if not ignore_abslines: alines = [] else: alines = abssys.list_of_abslines() lyman_lines = [] lya_lines = [] logNHIs = [] # Scan alines for aline in alines: # Lya if aline.name == 'HI 1215': lya_lines.append(aline) logNHIs.append(np.log10(aline.attrib['N'].value)) # Any HI if 'HI' in aline.name: lyman_lines.append(aline) if len(lya_lines) > 0: # Use the lines # Check we have a DLA worth if np.log10(np.sum(10**np.array(logNHIs))) < abssys.NHI: raise ValueError( "Total NHI of the Lya lines is less than NHI of the system! Something is wrong.." ) else: # Generate one warnings.warn( "Generating the absorption lines from the system info, not abslines" ) if lya_only: lya_line = AbsLine('HI 1215', z=abssys.zabs) lya_line.attrib['N'] = 10**abssys.NHI / u.cm**2 lya_line.attrib['b'] = bval lyman_lines.append(lya_line) else: HIlines = LineList('HI') wrest = Quantity(HIlines._data['wrest']) for iwrest in wrest: # On the spectrum? if iwrest >= spec.wvmin / (1 + abssys.zabs): lyman_line = AbsLine(iwrest, linelist=HIlines, z=abssys.zabs) lyman_line.attrib['N'] = 10**abssys.NHI / u.cm**2 lyman_line.attrib['b'] = bval lyman_lines.append(lyman_line) # tau for abs lines if len(lyman_lines) == 0: pdb.set_trace() tau_Lyman = voigt_from_abslines(spec.wavelength, lyman_lines, ret='tau', **kwargs) # LLS? if add_lls: wv_rest = spec.wavelength / (1 + abssys.zabs) energy = wv_rest.to(u.eV, equivalencies=u.spectral()) # Get photo_cross and calculate tau tau_LL = (10.**abssys.NHI / u.cm**2) * photo_cross(1, 1, energy) # Kludge pix_LL = np.argmin(np.fabs(wv_rest - 911.3 * u.AA)) pix_kludge = np.where((wv_rest > 911.3 * u.AA) & (wv_rest < 913.0 * u.AA))[0] tau_LL[pix_kludge] = tau_LL[pix_LL] tau_Lyman[pix_kludge] = 0. # Generate the spectrum else: tau_LL = 0. # Sum tau_tot = tau_LL + tau_Lyman if ret_tau: vmodel = tau_tot else: flux = np.exp(-1 * tau_tot) vmodel = XSpectrum1D.from_tuple((spec.wavelength, flux)) # Return return vmodel, lyman_lines
def lyman_limit(fN_model, z912, zem, N_eval=5000, cosmo=None, debug=False): """ Calculate teff_LL Effective opacity from LL absorption at z912 from zem Parameters ---------- fN_model : FNModel f(N) model z912 : float Redshift for evaluation zem : float Redshift of source cosmo : astropy.cosmology, optional Cosmological model to adopt (as needed) N_eval : int, optional Discretization parameter (5000) debug : bool, optional Returns ------- zval : array teff_LL : array z values and Effective opacity from LL absorption from z912 to zem """ if not isinstance(fN_model,FNModel): raise IOError("Improper model") # NHI array lgNval = 11.5 + 10.5*np.arange(N_eval)/(N_eval-1.) #; This is base 10 [Max at 22] dlgN = lgNval[1]-lgNval[0] Nval = 10.**lgNval # z array zval = z912 + (zem-z912)*np.arange(N_eval)/(N_eval-1.) dz = np.fabs(zval[1]-zval[0]) teff_LL = np.zeros(N_eval) # dXdz dXdz = pyigmu.cosm_xz(zval, cosmo=cosmo, flg_return=1) # Evaluate f(N,X) velo = (zval-zem)/(1+zem) * (const.c.cgs.value/1e5) # Kludge for eval [km/s] log_fnX = fN_model.evaluate(lgNval, zem, cosmo=cosmo, vel_array=velo) log_fnz = log_fnX + np.outer(np.ones(N_eval), np.log10(dXdz)) # Evaluate tau(z,N) teff_engy = (const.Ryd.to(u.eV, equivalencies=u.spectral()) / ((1+zval)/(1+zem))) sigma_z = ltaa.photo_cross(1, 1, teff_engy) tau_zN = np.outer(Nval, sigma_z) # Integrand intg = 10.**(log_fnz) * (1. - np.exp(-1.*tau_zN)) # Sum sumz_first = False if sumz_first is False: #; Sum in N first N_summed = np.sum(intg * np.outer(Nval, np.ones(N_eval)), 0) * dlgN * np.log(10.) # Sum in z teff_LL = (np.cumsum(N_summed[::-1]))[::-1] * dz # Debug if debug is True: pdb.set_trace() # Return return zval, teff_LL
def lyman_limit(fN_model, z912, zem, N_eval=5000, cosmo=None, debug=False): """ Calculate teff_LL Effective opacity from LL absorption at z912 from zem Parameters ---------- fN_model : FNModel f(N) model z912 : float Redshift for evaluation zem : float Redshift of source cosmo : astropy.cosmology, optional Cosmological model to adopt (as needed) N_eval : int, optional Discretization parameter (5000) debug : bool, optional Returns ------- zval : array teff_LL : array z values and Effective opacity from LL absorption from z912 to zem """ if not isinstance(fN_model,FNModel): raise IOError("Improper model") # NHI array lgNval = 11.5 + 10.5*np.arange(N_eval)/(N_eval-1.) #; This is base 10 [Max at 22] dlgN = lgNval[1]-lgNval[0] Nval = 10.**lgNval # z array zval = z912 + (zem-z912)*np.arange(N_eval)/(N_eval-1.) dz = np.fabs(zval[1]-zval[0]) teff_LL = np.zeros(N_eval) # dXdz dXdz = pyigmu.cosm_xz(zval, cosmo=cosmo, flg_return=1) # Evaluate f(N,X) velo = (zval-zem)/(1+zem) * (const.c.cgs.value/1e5) # Kludge for eval [km/s] log_fnX = fN_model.evaluate(lgNval, zem, cosmo=cosmo, vel_array=velo) log_fnz = log_fnX + np.outer(np.ones(N_eval), np.log10(dXdz)) # Evaluate tau(z,N) teff_engy = (const.Ryd.to(u.eV, equivalencies=u.spectral()) / ((1+zval)/(1+zem))) sigma_z = ltaa.photo_cross(1, 1, teff_engy) tau_zN = np.outer(Nval, sigma_z) # Integrand intg = 10.**(log_fnz) * (1. - np.exp(-1.*tau_zN)) # Sum sumz_first = False if sumz_first is False: #; Sum in N first N_summed = np.sum(intg * np.outer(Nval, np.ones(N_eval)), 0) * dlgN * np.log(10.) # Sum in z teff_LL = (np.cumsum(N_summed[::-1]))[::-1] * dz # Debug if debug is True: pdb.set_trace() # Return return zval, teff_LL
def test_photocross(): phto = photo_cross(1, 1, 14. * u.eV) assert phto.unit == u.cm**2 np.testing.assert_allclose(phto.value, 5.870146496955153e-18)