Beispiel #1
0
    def _broaden_lines_bands(self, df):
        ''' Divide over chuncks not to process to many lines in memory at the
        same time (note that this is not where the parallelisation is done: all
        lines are processed on the same core)
        Band specific version: returns a list of all broadened vibrational
        bands :

        Implementation
        -----------
        note: there is no more splitting over line chuncks of given different
        (NotImplemented). This may result in large arrays and MemoryErrors for
        extreme spectral ranges. If that ever happens we may have to insert
        a chunck splitting loop in the band groupby loop

        See _calc_lineshape for more information
        '''

        # Reactivate one-time warnings for new run
        reset_warnings(self.warnings)
        # --------------------------

        gb = df.groupby('band')

        abscoeff_bands = {}
        pb = ProgressBar(len(gb), active=self.verbose)
        for i, (band, dg) in enumerate(gb):
            line_profile = self._calc_lineshape(dg)
            (wavenumber,
             absorption) = self._apply_lineshape(dg.S, line_profile,
                                                 dg.shiftwav)
            abscoeff_bands[band] = absorption
            pb.update(i)
        pb.done()

        return wavenumber, abscoeff_bands
Beispiel #2
0
    def _broaden_lines_noneq_bands(self, df):
        ''' Divide over chuncks not to process to many lines in memory at the
        same time (note that this is not where the parallelisation is done: all
        lines are processed on the same core)
        Band specific version: returns a list of all broadened vibrational
        bands

        Implementation
        -----------
        note: there is no more splitting over line chuncks of given different
        (NotImplemented). This may result in large arrays and MemoryErrors for
        extreme spectral ranges. If that ever happens we may have to insert
        a chunck splitting loop in the band groupby loop

        See _calc_lineshape for more information
        '''

        # Reactivate one-time warnings for new run
        reset_warnings(self.warnings)
        # --------------------------

        abscoeff_bands = {}
        emisscoeff_bands = {}

        gb = df.groupby('band')
        chunksize = self.misc.chunksize  # used for DLM keyword in 0.9.20 until proper implementation is done

        pb = ProgressBar(len(gb), active=self.verbose)
        for i, (band, dg) in enumerate(gb):
            if chunksize == 'DLM':
                line_profile_DLM, wL, wG, wL_dat, wG_dat = self._calc_lineshape_DLM(
                    dg)
                (wavenumber, absorption) = self._apply_lineshape_DLM(
                    dg.S.values, line_profile_DLM, dg.shiftwav.values, wL, wG,
                    wL_dat, wG_dat)
                (_, emission) = self._apply_lineshape_DLM(
                    dg.Ei.values, line_profile_DLM, dg.shiftwav.values, wL, wG,
                    wL_dat, wG_dat)

            else:
                line_profile = self._calc_lineshape(dg)
                (wavenumber,
                 absorption) = self._apply_lineshape(dg.S.values, line_profile,
                                                     dg.shiftwav.values)
                (_, emission) = self._apply_lineshape(dg.Ei.values,
                                                      line_profile,
                                                      dg.shiftwav.values)
            abscoeff_bands[band] = absorption  #
            emisscoeff_bands[band] = emission
            pb.update(i)
        pb.done()

        return wavenumber, abscoeff_bands, emisscoeff_bands
Beispiel #3
0
def test_progress_bar(*args, **kwargs):
    ''' Minimal example of a progress bar '''

    from radis.misc.progress_bar import ProgressBar
    from time import sleep
    from numpy.random import rand

    print('Testing progress bar')

    a = 0
    r = list(range(1000))
    N = len(r)
    pb = ProgressBar(N)
    for i in r:
        pb.update(i, modulo=10)
        a += i
        sleep(rand() * 3e-3)
    pb.done()

    return True  # nothing implemented
Beispiel #4
0
def fetch_hitemp(
    molecule,
    local_databases="~/.radisdb/",
    databank_name="HITEMP-{molecule}",
    isotope=None,
    load_wavenum_min=None,
    load_wavenum_max=None,
    cache=True,
    verbose=True,
    chunksize=100000,
    clean_cache_files=True,
):
    """Stream HITEMP file from HITRAN website. Unzip and build a HDF5 file directly.

    Returns a Pandas DataFrame containing all lines.

    Parameters
    ----------
    molecule: `"CO2", "N2O", "CO", "CH4", "NO", "NO2", "OH"`
        HITEMP molecule. See :py:attr:`~radis.io.hitemp.HITEMP_SOURCE_FILES`
    local_databases: str
        where to create the RADIS HDF5 files. Default ``"~/.radisdb/"``
    databank_name: str
        name of the databank in RADIS :ref:`Configuration file <label_lbl_config_file>`
        Default ``"HITEMP-{molecule}"``
    isotope: str
        load only certain isotopes : ``'2'``, ``'1,2'``, etc. If ``None``, loads
        everything. Default ``None``.
    load_wavenum_min, load_wavenum_max: float (cm-1)
        load only specific wavenumbers.

    Other Parameters
    ----------------
    cache: bool, or ``'regen'``
        if ``True``, use existing HDF5 file. If ``False`` or ``'regen'``, rebuild it.
    verbose: bool
    chunksize: int
        number of lines to process at a same time. Higher is usually faster
        but can create Memory problems and keep the user uninformed of the progress.
    clean_cache_files: bool
        if ``True`` clean downloaded cache files after HDF5 are created.

    Returns
    -------
    df: pd.DataFrame
        Line list
        A HDF5 file is also created in ``local_databases`` and referenced
        in the :ref:`RADIS config file <label_lbl_config_file>` with name
        ``databank_name``

    Notes
    -----
    if using ``load_only_wavenum_above/below`` or ``isotope``, the whole
    database is anyway downloaded and uncompressed to ``local_databases``
    fast access .HDF5 files (which will take a long time on first call). Only
    the expected wavenumber range & isotopes are returned. The .HFD5 parsing uses
    :py:func:`~radis.io.hdf5.hdf2df`

    See Also
    --------
    :py:func:`~radis.io.hdf5.hdf2df`

    """
    # TODO ? : unzip only parts of the database
    # see https://github.com/radis/radis/pull/194

    if databank_name == "HITEMP-{molecule}":
        databank_name = databank_name.format(**{"molecule": molecule})
    local_databases = abspath(local_databases.replace("~", expanduser("~")))

    if molecule in ["H2O", "CO2"]:
        raise NotImplementedError(
            "Automatic HITEMP download not implemented for {0} : multiple files. Download manually on https://hitran.org/hitemp/ "
            .format(molecule))

    try:
        inputf = HITEMP_SOURCE_FILES[molecule]
    except KeyError as err:
        raise KeyError(
            f"Please choose one of HITEMP molecules : {list(HITEMP_SOURCE_FILES.keys())}. Got '{molecule}'"
        ) from err
    urlname = BASE_URL + inputf

    try:
        os.mkdir(local_databases)
    except OSError:
        pass
    else:
        if verbose:
            print("Created folder :", local_databases)

    output = abspath(
        join(local_databases,
             molecule + "-" + inputf.replace(".par.bz2", ".h5")))

    if not cache or cache == "regen":
        # Delete existing HDF5 file
        if exists(output):
            if verbose:
                print("Removing existing file ", output)
                # TODO: also clean the getDatabankList? Todo once it is in JSON format. https://github.com/radis/radis/issues/167
            os.remove(output)

    if exists(output):
        # check metadata :
        check_not_deprecated(
            output,
            metadata_is={},
            metadata_keys_contain=["wavenumber_min", "wavenumber_max"],
        )
        # check database is registered in ~/.radis
        if not databank_name in getDatabankList():
            # if not, check number of rows is correct :
            error_msg = ""
            with pd.HDFStore(output, "r") as store:
                nrows = store.get_storer("df").nrows
                if nrows != INFO_HITEMP_LINE_COUNT[molecule]:
                    error_msg += (
                        f"\nNumber of lines in local database ({nrows:,}) " +
                        "differ from the expected number of lines for " +
                        f"HITEMP {molecule}: {INFO_HITEMP_LINE_COUNT[molecule]}"
                    )
                file_metadata = store.get_storer("df").attrs.metadata
                for k in [
                        "wavenumber_min",
                        "wavenumber_max",
                        "download_url",
                        "download_date",
                ]:
                    if k not in file_metadata:
                        error_msg += (
                            "\nMissing key in file metadata to register the database "
                            + f"automatically : {k}")

            if error_msg:
                raise ValueError(
                    f"{databank_name} not declared in your RADIS ~/.config file although "
                    + f"{output} exists. {error_msg}\n" +
                    "If you know this file, add it to ~/.radisdb manually. " +
                    "Else regenerate the database with:\n\t" +
                    ">>> radis.SpectrumFactory().fetch_databank(..., use_cached='regen')"
                    + "\nor\n\t" +
                    ">>> radis.io.hitemp.fetch_hitemp({molecule}, cache='regen')"
                    +
                    "\n\n⚠️ It will re-download & uncompress the whole database "
                    +
                    "from HITEMP.\n\nList of declared databanks: {getDatabankList()}.\n"
                    + f"{output} metadata: {file_metadata}")

            # Else database looks ok : register it
            if verbose:
                print(
                    f"{databank_name} not declared in your RADIS ~/.config file although "
                    +
                    f"{output} exists. Registering the database automatically."
                )

            register_database(
                databank_name,
                [output],
                molecule=molecule,
                wmin=file_metadata["wavenumber_min"],
                wmax=file_metadata["wavenumber_max"],
                download_date=file_metadata["download_date"],
                urlname=file_metadata["download_url"],
                verbose=verbose,
            )

        if verbose:
            print(f"Using existing database {databank_name}")
        return hdf2df(
            output,
            isotope=isotope,
            load_wavenum_min=load_wavenum_min,
            load_wavenum_max=load_wavenum_max,
            verbose=verbose,
        )

    # Doesnt exist : download
    ds = DataSource(join(local_databases, "downloads"))

    if verbose:
        print(f"Downloading {inputf} for {molecule}.")
    download_date = date.today().strftime("%d %b %Y")

    columns = columns_2004

    # Get linereturn (depends on OS, but file may also have been generated
    # on a different OS. Here we simply read the file to find out)
    with ds.open(urlname) as gfile:  # locally downloaded file

        dt = _create_dtype(
            columns, "a2"
        )  # 'a2' allocates space to get \n or \n\r for linereturn character
        b = np.zeros(1, dtype=dt)
        gfile.readinto(b)
        linereturnformat = _get_linereturnformat(b, columns)

    with ds.open(urlname) as gfile:  # locally downloaded file

        dt = _create_dtype(columns, linereturnformat)
        b = np.zeros(chunksize,
                     dtype=dt)  # receives the HITRAN 160-character data.
        wmin = np.inf
        wmax = 0
        if verbose:
            print(
                f"Download complete. Building {molecule} database to {output}")

        with pd.HDFStore(output, mode="a", complib="blosc", complevel=9) as f:
            Nlines = 0
            Ntotal_lines_expected = INFO_HITEMP_LINE_COUNT[molecule]
            pb = ProgressBar(N=Ntotal_lines_expected, active=verbose)
            for nbytes in iter(lambda: gfile.readinto(b), 0):

                if not b[-1]:
                    # End of file flag within the chunk (but does not start
                    # with End of file flag) so nbytes != 0
                    b = get_last(b)

                df = _ndarray2df(b, columns, linereturnformat)

                # df.to_hdf(
                #     output, "df", format="table", append=True, complib="blosc", complevel=9
                # )
                f.put(
                    key="df",
                    value=df,
                    append=True,
                    format="table",
                    data_columns=DATA_COLUMNS,
                )

                wmin = np.min((wmin, df.wav.min()))
                wmax = np.max((wmax, df.wav.max()))
                Nlines += len(df)
                pb.update(
                    Nlines,
                    message=
                    f"Parsed {Nlines:,} / {Ntotal_lines_expected:,} lines. Wavenumber range {wmin:.2f}-{wmax:.2f} cm-1 is complete.",
                )

                # Reinitialize for next read
                b = np.zeros(
                    chunksize,
                    dtype=dt)  # receives the HITRAN 160-character data.

            f.get_storer("df").attrs.metadata = {
                "wavenumber_min": wmin,
                "wavenumber_max": wmax,
                "download_date": download_date,
                "download_url": urlname,
                "version": radis.__version__,
            }
            pb.done()

    # Done: add final checks
    # ... check on the created file that all lines are there :
    with pd.HDFStore(output, "r") as store:
        nrows = store.get_storer("df").nrows
        assert nrows == Nlines
        if nrows != INFO_HITEMP_LINE_COUNT[molecule]:
            raise AssertionError(
                f"Number of lines in local database ({nrows:,}) " +
                "differ from the expected number of lines for " +
                f"HITEMP {molecule}: {INFO_HITEMP_LINE_COUNT[molecule]}" +
                ". Check that there was no recent update on HITEMP. " +
                "Else it may be a download error ?")

    # Add database to  ~/.radis
    register_database(databank_name, [output], molecule, wmin, wmax,
                      download_date, urlname, verbose)

    # Fully unzipped : clean
    if clean_cache_files:
        os.remove(ds._findfile(urlname))
        if verbose >= 3:
            from radis.misc.printer import printg

            printg("... removed downloaded cache file")

    return hdf2df(
        output,
        isotope=isotope,
        load_wavenum_min=load_wavenum_min,
        load_wavenum_max=load_wavenum_max,
        verbose=verbose,
    )
Beispiel #5
0
def test_optically_thick_limit_1iso(verbose=True, plot=True, *args, **kwargs):
    """ Test that we find Planck in the optically thick limit 
    
    In particular, this test will fail if :
        
    - linestrength are not properly calculated
    
    - at noneq, linestrength and emission integrals are mixed up
    
    The test should be run for 1 and several isotopes, because different
    calculations paths are used internally, and this can lead to different
    errors.
    
    Also, this test is used to run with DEBUG_MODE = True, which will 
    check that isotopes and molecule ids are what we expect in all the 
    groupby() loops that make the production code very fast. 
    
    Notes
    -----
    
    switched from large band calculation with [HITRAN-2016]_ to a calculation with 
    the embedded [HITEMP-2010]_ fragment (shorter range, but no need to download files)
    
    """

    if plot:  # Make sure matplotlib is interactive so that test are not stuck in pytest
        plt.ion()

    # Force DEBUG_MODE
    DEBUG_MODE = radis.DEBUG_MODE
    radis.DEBUG_MODE = True

    try:

        wavenum_min = 2284.2
        wavenum_max = 2284.6

        P = 0.017  # bar
        wstep = 0.001  # cm-1

        Tgas = 1200

        # %% Generate some CO2 emission spectra
        # --------------
        sf = SpectrumFactory(
            wavenum_min=wavenum_min,
            wavenum_max=wavenum_max,
            molecule="CO2",
            mole_fraction=1,
            path_length=0.05,
            cutoff=1e-25,
            broadening_max_width=1,
            export_populations=False,  #'vib',
            export_lines=False,
            isotope=1,
            use_cached=True,
            wstep=wstep,
            pseudo_continuum_threshold=0,
            pressure=P,
            verbose=False,
        )
        #        sf.fetch_databank('astroquery')
        sf.warnings["NegativeEnergiesWarning"] = "ignore"
        sf.load_databank("HITEMP-CO2-TEST")
        pb = ProgressBar(3, active=verbose)
        s_eq = sf.eq_spectrum(Tgas=Tgas, mole_fraction=1, name="Equilibrium")
        pb.update(1)
        s_2T = sf.non_eq_spectrum(Tvib=Tgas,
                                  Trot=Tgas,
                                  mole_fraction=1,
                                  name="Noneq (2T)")
        pb.update(2)
        s_4T = sf.non_eq_spectrum(Tvib=(Tgas, Tgas, Tgas),
                                  Trot=Tgas,
                                  mole_fraction=1,
                                  name="Noneq (4T)")
        pb.update(3)
        s_plck = sPlanck(
            wavelength_min=2000,  # =wavelength_min,
            wavelength_max=
            5000,  # =wavelength_max - wstep,   # there is a border effect on last point
            T=Tgas,
        )
        pb.done()

        # %% Post process:
        # MAke optically thick, and compare with Planck

        for s in [s_eq, s_2T, s_4T]:

            s.rescale_path_length(1e6)

            if plot:

                nfig = "test_opt_thick_limit_1iso {0}".format(s.name)
                plt.figure(nfig).clear()
                s.plot(wunit="nm", nfig=nfig, lw=4)
                s_plck.plot(wunit="nm", nfig=nfig, Iunit="mW/cm2/sr/nm", lw=2)
                plt.legend()

            if verbose:
                printm(
                    "Residual between opt. thick CO2 spectrum ({0}) and Planck: {1:.2g}"
                    .format(
                        s.name,
                        get_residual(s,
                                     s_plck,
                                     "radiance_noslit",
                                     ignore_nan=True),
                    ))

            #            assert get_residual(s, s_plck, 'radiance_noslit', ignore_nan=True) < 1e-3
            assert get_residual(s, s_plck, "radiance_noslit",
                                ignore_nan=True) < 0.9e-4

        if verbose:
            printm("Tested optically thick limit is Planck (1 isotope): OK")

    finally:
        # Reset DEBUG_MODE
        radis.DEBUG_MODE = DEBUG_MODE
Beispiel #6
0
    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
Beispiel #7
0
    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
# Calculate atmosphere layers

slabs = []
print('Calculating Atmosphere layers')
pb = ProgressBar(len(atm))
for i, r in atm.iterrows():
    pb.update(i)
    s = sf.eq_spectrum(
        Tgas=r.T_K,
        mole_fraction=x_CO2,
        path_length=r.path_length * 1e5,  # cm
        pressure=r.P_Pa * 1e-5,  # bar
    )
    slabs.append(s)
pb.done()

# now solve the line of sight for the atmosphere:
print('Solving radiative transfer equation')
s_atm = SerialSlabs(*slabs)

# %% Calculate the total Upward radiation

s_los_400 = SerialSlabs(s_earth_0, s_atm, resample='intersect')

# Note: RADIS does not have an irradiance unit by default.
# Here we create the irradiance from the radiance
s_los_400._q['irradiance'] = s_los_400.get('radiance_noslit')[1] * pi
s_los_400.units['irradiance'] = s_los_400.units['radiance_noslit'].replace(
    '/sr', '')
s_los_400.name = 'Earth + Atmosphere'