def eq_bands(self, Tgas, mole_fraction=None, path_length=None, pressure=None, levels='all', drop_lines=True): ''' Return all vibrational bands as a list of spectra for a spectrum calculated under equilibrium. By default, drop_lines is set to True so line_survey cannot be done on spectra. See drop_lines for more information Parameters ---------- Tgas: float Gas temperature (K) mole_fraction: float database species mole fraction. If None, Factory mole fraction is used. path_length: float slab size (cm). If None, Factory mole fraction is used. pressure: float pressure (bar). If None, the default Factory pressure is used. Other Parameters ---------------- levels: ``'all'``, int, list of str calculate only bands that feature certain levels. If ``'all'``, all bands are returned. If N (int), return bands for the first N levels (sorted by energy). If list of str, return for all levels in the list. The remaining levels are also calculated and returned merged together in the ``'others'`` key. Default ``'all'`` drop_lines: boolean if False remove the line database from each bands. Helps save a lot of space, but line survey cannot be performed anymore. Default ``True``. Returns ------- bands: list of :class:`~radis.spectrum.spectrum.Spectrum` objects Use .get(something) to get something among ['radiance', 'radiance_noslit', 'absorbance', etc...] Or directly .plot(something) to plot it Notes ----- Process: - Calculate line strenghts correcting the CDSD reference one. - Then call the main routine that sums over all lines ''' try: # update defaults if path_length is not None: self.input.path_length = path_length if mole_fraction is not None: self.input.mole_fraction = mole_fraction if pressure is not None: self.input.pressure_mbar = pressure * 1e3 if not is_float(Tgas): raise ValueError( 'Tgas should be float. Use ParallelFactory for multiple cases' ) assert type(levels) in [str, list, int] if type(levels) == str: assert levels == 'all' # Temporary: if type(levels) == int: raise NotImplementedError # Get temperatures self.input.Tgas = Tgas self.input.Tvib = Tgas # just for info self.input.Trot = Tgas # just for info # Init variables pressure_mbar = self.input.pressure_mbar mole_fraction = self.input.mole_fraction path_length = self.input.path_length verbose = self.verbose # %% Retrieve from database if exists if self.autoretrievedatabase: s = self._retrieve_bands_from_database() if s is not None: return s # Print conditions if verbose: print('Calculating Equilibrium bands') self.print_conditions() # Start t0 = time() # %% Make sure database is loaded if self.df0 is None: raise AttributeError('Load databank first (.load_databank())') if not 'band' in self.df0: self._add_bands() # %% Calculate the spectrum # --------------------------------------------------- t0 = time() self._reinitialize() # -------------------------------------------------------------------- # First calculate the linestrength at given temperature self._calc_linestrength_eq(Tgas) self._cutoff_linestrength() # ---------------------------------------------------------------------- # Calculate line shift self._calc_lineshift() # ---------------------------------------------------------------------- # Line broadening # ... calculate broadening FWHM self._calc_broadening_FWHM() # ... find weak lines and calculate semi-continuum (optional) I_continuum = self._calculate_pseudo_continuum() if I_continuum: raise NotImplementedError( 'pseudo continuum not implemented for bands') # ... apply lineshape and get absorption coefficient # ... (this is the performance bottleneck) wavenumber, abscoeff_v_bands = self._calc_broadening_bands() # : : # cm-1 1/(#.cm-2) # # ... add semi-continuum (optional) # abscoeff_v_bands = self._add_pseudo_continuum(abscoeff_v_bands, I_continuum) # ---------------------------------------------------------------------- # Remove certain bands if levels != 'all': # Filter levels that feature the given energy levels. The rest # is stored in 'others' lines = self.df1 # We need levels to be explicitely stated for given molecule assert hasattr(lines, 'viblvl_u') assert hasattr(lines, 'viblvl_l') # Get bands to remove merge_bands = [] for band in abscoeff_v_bands: # note: could be vectorized with pandas str split. # TODO viblvl_l, viblvl_u = band.split('->') if not viblvl_l in levels and not viblvl_u in levels: merge_bands.append(band) # Remove bands from bandlist and add them to `others` abscoeff_others = np.zeros_like(wavenumber) for band in merge_bands: abscoeff = abscoeff_v_bands.pop(band) abscoeff_others += abscoeff abscoeff_v_bands['others'] = abscoeff_others if verbose: print('{0} bands grouped under `others`'.format( len(merge_bands))) # ---------------------------------------------------------------------- # Generate spectra # Progress bar for spectra generation Nbands = len(abscoeff_v_bands) if self.verbose: print('Generating bands ({0})'.format(Nbands)) pb = ProgressBar(Nbands, active=self.verbose) if Nbands < 100: pb.set_active(False) # hide for low line number # Generate spectra s_bands = {} for i, (band, abscoeff_v) in enumerate(abscoeff_v_bands.items()): # incorporate density of molecules (see equation (A.16) ) density = mole_fraction * ((pressure_mbar * 100) / (k_b * Tgas)) * 1e-6 # : # (#/cm3) abscoeff = abscoeff_v * density # cm-1 # ============================================================================== # Warning # --------- # if the code is extended to multi-species, then density has to be added # before lineshape broadening (as it would not be constant for all species) # ============================================================================== # get absorbance (technically it's the optical depth `tau`, # absorbance `A` being `A = tau/ln(10)` ) absorbance = abscoeff * path_length # Generate output quantities transmittance_noslit = exp(-absorbance) emissivity_noslit = 1 - transmittance_noslit radiance_noslit = calc_radiance( wavenumber, emissivity_noslit, Tgas, unit=self.units['radiance_noslit']) # ----------------------------- Export: lines = self.df1[self.df1.band == band] # if band == 'others': all lines will be None. # TODO populations = None # self._get_vib_populations(lines) # Store results in Spectrum class if drop_lines: lines = None if self.save_memory: try: del self.df1 # saves some memory except AttributeError: # already deleted pass conditions = self.get_conditions() # Add band name and hitran band name in conditions conditions.update({'band': band}) if lines: def add_attr(attr): if attr in lines: if band == 'others': val = 'N/A' else: # all have to be the same val = lines[attr].iloc[0] conditions.update({attr: val}) add_attr('band_htrn') add_attr('viblvl_l') add_attr('viblvl_u') s = Spectrum( quantities={ 'abscoeff': (wavenumber, abscoeff), 'absorbance': (wavenumber, absorbance), 'emissivity_noslit': (wavenumber, emissivity_noslit), 'transmittance_noslit': (wavenumber, transmittance_noslit), # (mW/cm2/sr/nm) 'radiance_noslit': (wavenumber, radiance_noslit), }, conditions=conditions, populations=populations, lines=lines, units=self.units, cond_units=self.cond_units, waveunit=self.params.waveunit, # cm-1 name=band, # dont check input (much faster, and Spectrum warnings=False, # is freshly baken so probably in a good format ) # # update database if asked so # if self.autoupdatedatabase: # self.SpecDatabase.add(s) # # Tvib=Trot=Tgas... but this way names in a database # # generated with eq_spectrum are consistent with names # # in one generated with non_eq_spectrum s_bands[band] = s pb.update(i) # progress bar pb.done() if verbose: print(('... process done in {0:.1f}s'.format(time() - t0))) return s_bands except: # An error occured: clean before crashing self._clean_temp_file() raise
def non_eq_bands(self, Tvib, Trot, Ttrans=None, mole_fraction=None, path_length=None, pressure=None, vib_distribution='boltzmann', rot_distribution='boltzmann', levels='all', return_lines=None): ''' Calculate vibrational bands in non-equilibrium case. Calculates absorption with broadened linestrength and emission with broadened Einstein coefficient. Parameters ---------- Tvib: float vibrational temperature [K] can be a tuple of float for the special case of more-than-diatomic molecules (e.g: CO2) Trot: float rotational temperature [K] Ttrans: float translational temperature [K]. If None, translational temperature is taken as rotational temperature (valid at 1 atm for times above ~ 2ns which is the RT characteristic time) mole_fraction: float database species mole fraction. If None, Factory mole fraction is used. path_length: float slab size (cm). If None, Factory mole fraction is used. pressure: float pressure (bar). If None, the default Factory pressure is used. Other Parameters ---------------- levels: ``'all'``, int, list of str calculate only bands that feature certain levels. If ``'all'``, all bands are returned. If N (int), return bands for the first N levels (sorted by energy). If list of str, return for all levels in the list. The remaining levels are also calculated and returned merged together in the ``'others'`` key. Default ``'all'`` return_lines: boolean if ``True`` returns each band with its line database. Can produce big spectra! Default ``True`` DEPRECATED. Now use export_lines attribute in Factory Returns ------- Returns :class:`~radis.spectrum.spectrum.Spectrum` object Use .get(something) to get something among ['radiance', 'radiance_noslit', 'absorbance', etc...] Or directly .plot(something) to plot it ''' try: # check inputs, update defaults if path_length is not None: self.input.path_length = path_length if mole_fraction is not None: self.input.mole_fraction = mole_fraction if pressure is not None: self.input.pressure_mbar = pressure * 1e3 if not (is_float(Tvib) or isinstance(Tvib, tuple)): raise TypeError( 'Tvib should be float, or tuple (got {0})'.format( type(Tvib)) + 'For parallel processing use ParallelFactory with a ' + 'list of float or a list of tuple') singleTvibmode = is_float(Tvib) if not is_float(Trot): raise ValueError( 'Trot should be float. Use ParallelFactory for multiple cases' ) assert type(levels) in [str, list, int] if type(levels) == str: assert levels == 'all' else: if len(levels) != len(set(levels)): raise ValueError('levels list has duplicates') if not vib_distribution in ['boltzmann']: raise ValueError( 'calculate per band not meaningful if not Boltzmann') # Temporary: if type(levels) == int: raise NotImplementedError if return_lines is not None: warn( DeprecationWarning( 'return_lines replaced with export_lines attribute in Factory' )) self.misc.export_lines = return_lines # Get translational temperature Tgas = Ttrans if Tgas is None: Tgas = Trot # assuming Ttrans = Trot self.input.Tgas = Tgas self.input.Tvib = Tvib self.input.Trot = Trot # Init variables path_length = self.input.path_length mole_fraction = self.input.mole_fraction pressure_mbar = self.input.pressure_mbar verbose = self.verbose # %% Retrieve from database if exists if self.autoretrievedatabase: s = self._retrieve_bands_from_database() if s is not None: return s # Print conditions if verbose: print('Calculating Non-Equilibrium bands') self.print_conditions() # %% Make sure database is loaded self._check_line_databank() self._check_noneq_parameters(vib_distribution, singleTvibmode) if self.df0 is None: raise AttributeError('Load databank first (.load_databank())') # Make sure database has pre-computed non equilibrium quantities # (Evib, Erot, etc.) if not 'Evib' in self.df0: self._calc_noneq_parameters() if not 'Aul' in self.df0: self._calc_weighted_trans_moment() self._calc_einstein_coefficients() if not 'band' in self.df0: self._add_bands() # %% Calculate the spectrum # --------------------------------------------------- t0 = time() self._reinitialize() # ---------------------------------------------------------------------- # Calculate Populations, Linestrength and Emission Integral # (Note: Emission Integral is non canonical quantity, equivalent to # Linestrength for absorption) self._calc_populations_noneq(Tvib, Trot) self._calc_linestrength_noneq() self._calc_emission_integral() # ---------------------------------------------------------------------- # Cutoff linestrength self._cutoff_linestrength() # ---------------------------------------------------------------------- # Calculate lineshift self._calc_lineshift() # ---------------------------------------------------------------------- # Line broadening # ... calculate broadening FWHM self._calc_broadening_FWHM() # ... find weak lines and calculate semi-continuum (optional) I_continuum = self._calculate_pseudo_continuum() if I_continuum: raise NotImplementedError( 'pseudo continuum not implemented for bands') # ... apply lineshape and get absorption coefficient # ... (this is the performance bottleneck) wavenumber, abscoeff_v_bands, emisscoeff_v_bands = self._calc_broadening_noneq_bands( ) # : : : # cm-1 1/(#.cm-2) mW/sr/cm_1 # # ... add semi-continuum (optional) # abscoeff_v_bands = self._add_pseudo_continuum(abscoeff_v_bands, I_continuum) # ---------------------------------------------------------------------- # Remove bands if levels != 'all': # Filter levels that feature the given energy levels. The rest # is stored in 'others' lines = self.df1 # We need levels to be explicitely stated for given molecule assert hasattr(lines, 'viblvl_u') assert hasattr(lines, 'viblvl_l') # Get bands to remove merge_bands = [] for band in abscoeff_v_bands: # note: could be vectorized with pandas str split. # TODO viblvl_l, viblvl_u = band.split('->') if not viblvl_l in levels and not viblvl_u in levels: merge_bands.append(band) # Remove bands from bandlist and add them to `others` abscoeff_others = np.zeros_like(wavenumber) emisscoeff_others = np.zeros_like(wavenumber) for band in merge_bands: abscoeff = abscoeff_v_bands.pop(band) emisscoeff = emisscoeff_v_bands.pop(band) abscoeff_others += abscoeff emisscoeff_others += emisscoeff abscoeff_v_bands['others'] = abscoeff_others emisscoeff_v_bands['others'] = emisscoeff_others if verbose: print('{0} bands grouped under `others`'.format( len(merge_bands))) # ---------------------------------------------------------------------- # Generate spectra # Progress bar for spectra generation Nbands = len(abscoeff_v_bands) if self.verbose: print('Generating bands ({0})'.format(Nbands)) pb = ProgressBar(Nbands, active=self.verbose) if Nbands < 100: pb.set_active(False) # hide for low line number # Create spectra s_bands = {} for i, band in enumerate(abscoeff_v_bands): abscoeff_v = abscoeff_v_bands[band] emisscoeff_v = emisscoeff_v_bands[band] # incorporate density of molecules (see equation (A.16) ) density = mole_fraction * ((pressure_mbar * 100) / (k_b * Tgas)) * 1e-6 # : # (#/cm3) abscoeff = abscoeff_v * density # cm-1 emisscoeff = emisscoeff_v * density # m/sr/cm3/cm_1 # ============================================================================== # Warning # --------- # if the code is extended to multi-species, then density has to be added # before lineshape broadening (as it would not be constant for all species) # ============================================================================== # get absorbance (technically it's the optical depth `tau`, # absorbance `A` being `A = tau/ln(10)` ) # Generate output quantities absorbance = abscoeff * path_length # (adim) transmittance_noslit = exp(-absorbance) if self.input.self_absorption: # Analytical output of computing RTE over a single slab of constant # emissivity and absorption coefficient b = abscoeff == 0 # optically thin mask radiance_noslit = np.zeros_like(emisscoeff) radiance_noslit[~b] = emisscoeff[~b] / \ abscoeff[~b]*(1-transmittance_noslit[~b]) radiance_noslit[b] = emisscoeff[b] * path_length else: # Note that for k -> 0, radiance_noslit = emisscoeff * \ path_length # (mW/sr/cm2/cm_1) # Convert `radiance_noslit` from (mW/sr/cm2/cm_1) to (mW/sr/cm2/nm) radiance_noslit = convert_rad2nm(radiance_noslit, wavenumber, 'mW/sr/cm2/cm_1', 'mW/sr/cm2/nm') # Convert 'emisscoeff' from (mW/sr/cm3/cm_1) to (mW/sr/cm3/nm) emisscoeff = convert_emi2nm(emisscoeff, wavenumber, 'mW/sr/cm3/cm_1', 'mW/sr/cm3/nm') # Note: emissivity not defined under non equilibrium # ----------------------------- Export: lines = self.df1[self.df1.band == band] # Note: if band == 'others': # for others: all will be None. # TODO. FIXME populations = self.get_populations( self.misc.export_populations) if not self.misc.export_lines: lines = None # Store results in Spectrum class if self.save_memory: try: # saves some memory (note: only once 'lines' is discarded) del self.df1 except AttributeError: # already deleted pass conditions = self.get_conditions() conditions.update({'thermal_equilibrium': False}) # Add band name and hitran band name in conditions def add_attr(attr): ''' # TODO: implement properly''' if attr in lines: if band == 'others': val = 'N/A' else: # all have to be the same val = lines[attr].iloc[0] conditions.update({attr: val}) add_attr('band_htrn') add_attr('viblvl_l') add_attr('viblvl_u') s = Spectrum( quantities={ 'abscoeff': (wavenumber, abscoeff), 'absorbance': (wavenumber, absorbance), # (mW/cm3/sr/nm) 'emisscoeff': (wavenumber, emisscoeff), 'transmittance_noslit': (wavenumber, transmittance_noslit), # (mW/cm2/sr/nm) 'radiance_noslit': (wavenumber, radiance_noslit), }, conditions=conditions, populations=populations, lines=lines, units=self.units, cond_units=self.cond_units, waveunit=self.params.waveunit, # cm-1 name=band, # dont check input (much faster, and Spectrum warnings=False, # is freshly baken so probably in a good format ) # # update database if asked so # if self.autoupdatedatabase: # self.SpecDatabase.add(s, add_info=['Tvib', 'Trot'], add_date='%Y%m%d') s_bands[band] = s pb.update(i) # progress bar pb.done() if verbose: print(('... process done in {0:.1f}s'.format(time() - t0))) return s_bands except: # An error occured: clean before crashing self._clean_temp_file() raise