Exemplo n.º 1
0
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)
Exemplo n.º 2
0
    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)))
Exemplo n.º 4
0
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
Exemplo n.º 5
0
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