def test_cut_recombine(verbose=True, *args, **kwargs): """ Use :func:`~radis.spectrum.operations.crop` and :func:`~radis.los.slabs.MergeSlabs` to cut a Spectrum and recombine it Assert we still get the same spectrum at the end """ s = load_spec(getTestFile("CO_Tgas1500K_mole_fraction0.01.spec"), binary=True) # Cut in half cut = 2177 s1 = crop(s, 2000, cut, "cm-1", inplace=False) s2 = crop(s, cut, 2300, "cm-1", inplace=False) # Recombine s_new = MergeSlabs(s1, s2, resample="full", out="transparent") # Compare assert s.compare_with(s_new, spectra_only=True, plot=False, verbose=verbose)
def non_eq_spectrum( self, Tvib=None, Trot=None, Ttrans=None, vib_distribution="boltzmann", overpopulation=None, mole_fraction=None, path_length=None, save_rescaled_bands=False, ): """See :py:meth:`~radis.lbl.factory.SpectrumFactory.non_eq_spectrum` .. warning:: only valid under optically thin conditions!! Parameters ---------- ... same as usually. If None, then the reference value (used to calculate bands) is used Other Parameters ---------------- save_rescaled_bands: boolean save updated bands. Take some time as it requires rescaling all bands individually (which is only done on the MergedSlabs usually) Default ``False`` Notes ----- Implementation: Generation of a new spectrum is done by recombination of the precalculated bands with """ from radis.los import MergeSlabs # Restart from a copy each time (else reference bands are modified by rescaling # and we may loose information if rescaling to 0 for instance) bands_ref = self.bands_ref bands = { br: bands_ref[br].copy(copy_lines=self.copy_lines) for br in bands_ref } # Initialize inputs if Trot is None: Trot = self.Trot_ref if Tvib is None: Tvib = self.Tvib_ref if Trot != self.Trot_ref: raise ValueError( "Trot {0} doesnt match the reference Trot: {1}".format( Trot, self.Trot_ref)) if mole_fraction is None: mole_fraction = self.mole_fraction_ref if path_length is None: path_length = self.path_length_ref if overpopulation is None: overpopulation = {} # # Fill missing overpopulation with 1 # if overpopulation is None: # overpopulation = dict.fromkeys(list(bands.keys()), 1) # else: # for br in bands.keys(): # if not br in overpopulation: # overpopulation[br] = 1 # Tvib_ref = self.Tvib_ref # pop_correction = dict.fromkeys(list(bands.keys()), 1) # if Tvib != Tvib_ref: # raise NotImplementedError # E_bands = self.E_bands # # Correct for vibrational temperature # for br in bands: # pop_correction[br] = exp(-E_bands[br]*hc_k/Tvib)/exp(-E_bands[br]*hc_k/Tvib_ref) # Recalculate populations from reference everytime vib_levels = self.vib_levels # Recalculate partition function Qvib = self._calc_vib_populations(Tvib=Tvib, vib_distribution=vib_distribution, overpopulation=overpopulation) # TODO: note: Qvib doesnt take gvib into account. Wrong Qvib but correct populations # if rescaled with ratio??? # ... warning: update_populations set to False here not updated (because we dont # ... care much about all levels here. only the one from vib_levels matter) if overpopulation is not None: Q, _, _ = self.parfunc.at_noneq( Tvib, Trot, overpopulation=overpopulation, returnQvibQrot=True, update_populations=False, ) else: Q = self.parfunc.at_noneq(Tvib, Trot, update_populations=False) Qref = self.Qref Qvib_ref = self.Qvib_ref for br, band in bands.items(): # type(band): Spectrum if br == "others": continue viblvl_u = band.conditions["viblvl_u"] viblvl_l = band.conditions["viblvl_l"] nu_vib = vib_levels.loc[viblvl_u, "nvib"] nu_vib_old = vib_levels.loc[viblvl_u, "nvib_ref"] nl_vib = vib_levels.loc[viblvl_l, "nvib"] nl_vib_old = vib_levels.loc[viblvl_l, "nvib_ref"] # with new, old = (2, 1): # n2/n1 = exp(-Evib2/kT2)/exp(-Evib1/kT1) * Qvib1/Qvib2 # = n2vib / n1vib * Q1/Q2 * Q2vib / Q1vib if vib_distribution == "boltzmann": corfactor_u = nu_vib / nu_vib_old * Qref / Q * Qvib / Qvib_ref corfactor_l = nl_vib / nl_vib_old * Qref / Q * Qvib / Qvib_ref else: raise NotImplementedError( "vib_distribution: {0}".format(vib_distribution)) rescale_updown_levels(band, corfactor_u, 1, corfactor_l, 1) # Update lines if self.copy_lines: band.lines["nu"] *= corfactor_u band.lines["nl"] *= corfactor_l band.lines["Qvib"] = Qvib band.lines["nu_vib"] *= corfactor_u band.lines["nl_vib"] *= corfactor_l band.lines["Ei"] *= np.nan band.lines["S"] = np.nan # Get total spectrum s = MergeSlabs(*list(bands.values())) # populations s.populations = pd.DataFrame(vib_levels[["nvib", "Evib"]]) # rebuild lines if self.copy_lines: s.lines = pd.concat([band.lines for band in bands.values()]) # Update total mole fraction if it changed mole_fraction_ref = self.mole_fraction_ref if mole_fraction != mole_fraction_ref: s.rescale_mole_fraction( mole_fraction, mole_fraction_ref, ignore_warnings=True, # mole_fraction is 'N/A' force=True, ) path_length_ref = self.path_length_ref if path_length != path_length_ref: s.rescale_path_length(path_length, path_length_ref, force=True) # Add parameters in conditions: s.conditions["overpopulation"] = overpopulation s.conditions["mole_fraction"] = mole_fraction # was 'N/A' after Merge # because it's different for all bands s.conditions["path_length"] = path_length s.conditions["Tvib"] = Tvib s.conditions["Trot"] = Trot if save_rescaled_bands: for br, band in bands.items(): # type(band): Spectrum if mole_fraction != mole_fraction_ref: band.rescale_mole_fraction( mole_fraction, mole_fraction_ref, ignore_warnings=True, # mole_fraction is 'N/A' force=True, ) if path_length != path_length_ref: band.rescale_path_length(path_length, path_length_ref, force=True) self.bands = bands return s
def test_compare_torch_CO2(verbose=True, plot=False, save=False, warnings=True, use_cache=True, *args, **kwargs): """Reproduce the plasma torch experiment of Packan 2003 in in atmospheric air Notes ----- Thresholds parameters are reduced for a faster calculation time. For instance: - broadening_max_width should be 50 rather than 20 - cutoff should be 1e-27 rather than 1e-25 - wstep should be 0.01 or even 0.008 rather than 0.1 Performance: - neq==0.9.20: Finished test_compare_torch_CO2 in 758.640324s - neq==0.9.21: Finished test_compare_torch_CO2 in 672s - neq==0.9.21*: (with ParallelFactory) Finished test_compare_torch_CO2 in 298s - neq==0.9.22: (Parallel + continuum) Finished in 65s RADIS 0.9.9 == neq 0.9.24 - RADIS 0.9.26: Finished test_compare_torch_CO2 in 57s (DLM, no continuum) Reference -------- Packan et al 2003 "Measurement and Modeling of OH, NO, and CO Infrared Radiation at 3400 K", JTHT """ if plot: # Make sure matplotlib is interactive so that test are not stuck in pytest plt.ion() t0 = time() # %% Conditions wlmin = 4100 wlmax = 4900 wmin = nm2cm(wlmax) wmax = nm2cm(wlmin) wstep = 0.1 # Load concentration profile conds = pd.read_csv( getValidationCase( "test_compare_torch_CO2_data/test_compare_torch_CO2_conditions_JTHT2003.dat" ), comment="#", delim_whitespace=True, ) slab_width = 0.05 # cm, WARNING. Hardcoded (look up the table) # %% Calculate slabs # CO2 sf = ParallelFactory( wavenum_min=wmin, wavenum_max=wmax, mole_fraction=None, path_length=None, molecule="CO2", isotope="1,2", parallel=False, wstep=wstep, db_use_cached=use_cache, export_lines=False, # saves some memory export_populations=False, # saves some memory cutoff=1e-25, Nprocs=cpu_count() - 1, # warning with memory if too many procs broadening_max_width=20, # pseudo_continuum_threshold=0.01, # use pseudo-continuum, no DLM. Note : 56s on 20/08. # optimization=None, pseudo_continuum_threshold= 0, # use DLM, no pseudo-continuum. Note : 84s on 20/08 optimization="min-RMS", verbose=False, ) sf.warnings["MissingSelfBroadeningWarning"] = "ignore" sf.warnings["NegativeEnergiesWarning"] = "ignore" # # Init database: only if we want to save all spectra being calculated # (discarded for the moment) # if use_cache: # sf.init_database('test_compare_torch_CO2_SpectrumDatabase_HITEMP_1e25', # add_info=['Tgas']) sf.init_databank("HITEMP-CO2-DUNHAM", load_energies=False) # saves some memory # sf.init_databank('CDSD-4000') # CO sfco = ParallelFactory( wavenum_min=wmin, wavenum_max=wmax, mole_fraction=None, path_length=None, molecule="CO", isotope="1,2", parallel=False, wstep=wstep, db_use_cached=use_cache, export_lines=False, # saves some memory export_populations=False, # saves some memory cutoff=1e-25, Nprocs=cpu_count() - 1, broadening_max_width=20, optimization=None, verbose=False, ) sfco.warnings["MissingSelfBroadeningWarning"] = "ignore" # Init database: only if we want to save all spectra being calculated # (discarded for the moment) # if use_cache: # sfco.init_database( # 'test_compare_torch_CO_SpectrumDatabase_HITEMP_1e25', add_info=['Tgas']) sfco.init_databank("HITEMP-CO-DUNHAM") # Calculate all slabs # .. CO2 at equilibrium slabsco2 = sf.eq_spectrum(list(conds.T_K), mole_fraction=list(conds.CO2), path_length=slab_width) # .. CO at equilibrium, but with non_eq to calculate PartitionFunctions instead of using # ... tabulated ones (which are limited to 3000 K in HAPI) slabsco = sfco.non_eq_spectrum( list(conds.T_K), list(conds.T_K), mole_fraction=list(conds.CO), path_length=slab_width, ) # Room absorption with pytest.warns( UserWarning ): # we expect a "using ParallelFactory for single case" warning s0 = sf.eq_spectrum(Tgas=300, mole_fraction=330e-6, path_length=600)[0] # Warning: This slab includes the CO2 emission from the 300 K air # within the spectrometer. But experimentally it is substracted # by there the chopper (this is corrected below) # ... see RADIS Article for more details # Add radiance for everyone if not calculated (ex: loaded from database) for s in slabsco2 + slabsco + [s0]: s.update() # Merge CO + CO2 for each slab slabstot = [MergeSlabs(s, sco) for (s, sco) in zip(slabsco2, slabsco)] # %% Line-of-sight # Solve ETR along line of sight # -------- # two semi profile, + room absortion line_of_sight = slabstot[1:][::-1] + slabstot + [s0] stot = SerialSlabs(*line_of_sight) # stot = SerialSlabs(*slabstot[1:][::-1], *slabstot, s0) # Python 3 syntax only # Also calculate the contribution of pure CO # --------- s0tr = s0.copy() # transmittance only s0tr.conditions["thermal_equilibrium"] = False s0tr._q["radiance_noslit"] *= 0 # hack to remove CO2 emission s0tr._q["emisscoeff"] *= 0 # hack to remove CO2 emission line_of_sight = slabsco[1:][::-1] + slabsco + [s0tr] sco = SerialSlabs(*line_of_sight) # sco = SerialSlabs(*slabsco[1:][::-1], *slabsco, s0tr) # Python 3 syntax only # Generate Slit # ------ disp = 4 # dispersion conversion function (nm/mm) s_in = 1 # entrance slit (mm) s_out = 2.8 # exit slit (mm) top = (s_out - s_in) * disp base = (s_out + s_in) * disp slit_function = (top, base) # (nm) # Final plot unit unit = "µW/cm2/sr" norm_by = "max" # should be 'max' if unit ~ 'µW/cm2/sr', # 'area' if unit ~ 'µW/cm2/sr/nm' # Convolve with Slit # ------- sco.apply_slit(slit_function, unit="nm", norm_by=norm_by, shape="trapezoidal") stot.apply_slit(slit_function, unit="nm", norm_by=norm_by, shape="trapezoidal") # Remove emission from within the spectrometer (substracted # by the chopper experimentaly) # ------- s0spectro = s0.copy() s0spectro.rescale_path_length(75 * 4) # spectrometer length s0spectro.apply_slit(slit_function, unit="nm", norm_by=norm_by, shape="trapezoidal") _, I0spectro = s0spectro.get("radiance", Iunit=unit) wtot, Itot = stot.get("radiance", Iunit=unit) stot_corr = experimental_spectrum( wtot, Itot - I0spectro, # hack to remove # emission from within # spectrometer Iunit=unit, conditions={"medium": "air"}, ) # %% Compare with experiment data # Plot experimental data # ------ exp = pd.read_csv( getValidationCase( "test_compare_torch_CO2_data/test_compare_torch_CO2_spectrum_JTHT2003.dat" ), skiprows=5, delim_whitespace=True, ) exp.w /= 10 # Angstrom to nm exp = exp[(exp.w > wlmin) & (exp.w < wlmax)] sexp = experimental_spectrum(exp.w, exp.I, Iunit="mW/cm2/sr", conditions={"medium": "air"}) if plot: sexp.plot("radiance", wunit="nm", Iunit=unit, lw=2, label="Packan 2003") # Plot calculated # ---------- stot.plot( "radiance", wunit="nm", Iunit=unit, nfig="same", color="r", ls="--", label="All: CO$_2$+CO", ) # Plot internal spectrometer emission s0spectro.plot( "radiance", wunit="nm", Iunit=unit, nfig="same", color="b", label="CO$_2$ in Spectrometer", ) stot_corr.plot( "radiance", wunit="nm", Iunit=unit, nfig="same", color="r", lw=2, label="All-CO$_2$ in Spectro", zorder=10, ) sco.plot( "radiance", wunit="nm", Iunit=unit, nfig="same", color="g", ls="-", label="CO", ) plt.ylim((0, 9)) plt.legend(loc="upper right") if save: plt.savefig("test_compare_torch_CO2_brd20.png") plt.savefig("test_compare_torch_CO2_brd20k.pdf") assert get_residual(stot_corr, sexp, "radiance") < 0.02 if verbose: print(("Finished test_compare_torch_CO2 in {0:.0f}s".format(time() - t0)))
def test_plot_all_CO2_bandheads(verbose=True, plot=False, *args, **kwargs): """In this test we use the :meth:`~radis.lbl.bands.BandFactory.non_eq_bands` method to calculate separately all vibrational bands of CO2, and compare them with the final Spectrum. """ # Note: only with iso1 at the moment if plot: # Make sure matplotlib is interactive so that test are not stuck in pytest plt.ion() Tgas = 1000 sf = SpectrumFactory( wavelength_min=4160, wavelength_max=4220, mole_fraction=1, path_length=0.3, cutoff=1e-23, molecule="CO2", isotope=1, optimization=None, verbose=verbose, ) sf.warnings["MissingSelfBroadeningWarning"] = "ignore" sf.warnings["NegativeEnergiesWarning"] = "ignore" sf.warnings["HighTemperatureWarning"] = "ignore" sf.fetch_databank("hitran") s_tot = sf.non_eq_spectrum(Tvib=Tgas, Trot=Tgas) s_bands = sf.non_eq_bands(Tvib=Tgas, Trot=Tgas) if verbose: printm("{0} bands in spectrum".format(len(s_bands))) assert len(s_bands) == 6 # Ensure that recombining gives the same s_merged = MergeSlabs(*list(s_bands.values())) assert s_tot.compare_with(s_merged, "radiance_noslit", plot=False) if verbose: printm("Recombining bands give the same Spectrum") # %% if plot: s_tot.apply_slit(1, "nm") s_tot.name = "Full spectrum" s_tot.plot(wunit="nm", lw=3) for band, s in s_bands.items(): s.plot(wunit="nm", nfig="same") plt.legend(loc="upper left") plt.ylim(ymax=0.25) # %% Compare with equilibrium bands now s_bands_eq = sf.eq_bands(Tgas) s_merged_eq = MergeSlabs(*list(s_bands_eq.values())) assert get_residual(s_tot, s_merged_eq, "radiance_noslit") < 1.5e-5 return True
def test_plot_all_CO2_bandheads(verbose=True, plot=False, *args, **kwargs): ''' In this test we use the :meth:`~radis.lbl.bands.BandFactory.non_eq_bands` method to calculate separately all vibrational bands of CO2, and compare them with the final Spectrum. ''' # Note: only with iso1 at the moment if plot: # Make sure matplotlib is interactive so that test are not stuck in pytest plt.ion() verbose = True Tgas = 1000 sf = SpectrumFactory(wavelength_min=4160, wavelength_max=4220, mole_fraction=1, path_length=0.3, cutoff=1e-23, molecule='CO2', isotope=1, db_use_cached=True, lvl_use_cached=True, verbose=verbose) sf.warnings['MissingSelfBroadeningWarning'] = 'ignore' sf.warnings['NegativeEnergiesWarning'] = 'ignore' sf.warnings['HighTemperatureWarning'] = 'ignore' sf.fetch_databank() s_tot = sf.non_eq_spectrum(Tvib=Tgas, Trot=Tgas) s_bands = sf.non_eq_bands(Tvib=Tgas, Trot=Tgas) if verbose: printm('{0} bands in spectrum'.format(len(s_bands))) assert len(s_bands) == 6 # Ensure that recombining gives the same s_merged = MergeSlabs(*list(s_bands.values())) assert s_tot.compare_with(s_merged, 'radiance_noslit', plot=False) if verbose: printm('Recombining bands give the same Spectrum') # %% if plot: s_tot.apply_slit(1, 'nm') s_tot.name = 'Full spectrum' s_tot.plot(wunit='nm', lw=3) for band, s in s_bands.items(): s.plot(wunit='nm', nfig='same') plt.legend(loc='upper left') plt.ylim(ymax=0.25) # %% Compare with equilibrium bands now s_bands_eq = sf.eq_bands(Tgas) s_merged_eq = MergeSlabs(*list(s_bands_eq.values())) assert get_residual(s_tot, s_merged_eq, 'radiance_noslit') < 1e-5 return True