Exemple #1
0
    def get_label_cdsd_hitran(row, details):
        label = "CO2[iso{iso}] [{branch}{jl:.0f}]({v1l:.0f}{v2l:.0f}`{l2l:.0f}`{v3l:.0f})->({v1u:.0f}{v2u:.0f}`{l2u:.0f}`{v3u:.0f})".format(
            **dict(
                [
                    (k, row[k])
                    for k in [
                        "v1u",
                        "v2u",
                        "l2u",
                        "v3u",
                        "v1l",
                        "v2l",
                        "l2l",
                        "v3l",
                        "jl",
                        "iso",
                    ]
                ]
                + [("branch", _fix_branch_format[row["branch"]])]
            )
        )

        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += "<br>{0} {1}: {2:.3g} {3}".format(k, name, row[k], unit)
            else:
                label += "<br>{0} {1}: {2} {3}".format(name, k, row[k], unit)

        return label
Exemple #2
0
    def get_label_cdsd(row, details):
        label = "CO2[iso{iso}] [{branch}{jl:.0f}](p{polyl:.0f}c{wangl:.0f}n{rankl:.0f})->(p{polyu:.0f}c{wangu:.0f}n{ranku:.0f})".format(
            **dict(
                [
                    (k, row[k])
                    for k in [
                        "polyl",
                        "wangl",
                        "rankl",
                        "polyu",
                        "wangu",
                        "ranku",
                        "jl",
                        "iso",
                    ]
                ]
                + [("branch", _fix_branch_format[row["branch"]])]
            )
        )

        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += "<br>{0} {1}: {2:.3g} {3}".format(k, name, row[k], unit)
            else:
                label += "<br>{0} {1}: {2} {3}".format(name, k, row[k], unit)

        return label
Exemple #3
0
    def get_label_hitran_locglob(row, details):
        ''' 
        Todo
        -------
        
        replace with simple astype(str) statements and str operations
        
        ex:        
        > '['+df[locl].astype(str)+']('+df[globl].astype(str)+'->'+
        >     df[globu].astype(str)'+)'
    
        will be much faster! 
        '''

        molecule = get_molecule(row.id)

        label = ('{0}[iso{1}]'.format(molecule, row['iso']) +
                 '[{locl}]({globl})->({globu})'.format(
                     **dict([(k, row[k])
                             for k in ['locl', 'globl', 'globu']])))

        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += '\n{0} {1}: {2:.3g} {3}'.format(k, name, row[k], unit)
            else:
                label += '\n{0} {1}: {2} {3}'.format(k, name, row[k], unit)

        return label
Exemple #4
0
def _h5_compatible(a_dict):
    """ Make dictionary ``a_dict`` h5 compatible """
    out = {}
    for k, v in a_dict.items():
        if v is None:
            continue  # dont store None
        elif is_float(v):
            out[k] = v
        else:
            out[k] = str(v)  # convert to str
    return out
Exemple #5
0
    def get_label_hitran(row, details):
        ''' 
        Todo
        -------

        replace with simple astype(str) statements and str operations

        ex:        
        > '['+df[locl].astype(str)+']('+df[globl].astype(str)+'->'+
        >     df[globu].astype(str)'+)'

        will be much faster! 
        '''

        molecule = get_molecule(row.id)

        # Get global labels
        if molecule in HITRAN_CLASS1:
            label = (
                '{molec}[iso{iso:.0f}] [{branch}{jl:.0f}]({vl:.0f})->({vu:.0f})'
                .format(
                    **dict([(k, row[k]) for k in ['vu', 'vl', 'jl', 'iso']] +
                           [('molec', molecule),
                            ('branch', _fix_branch_format[row['branch']])])))
        elif molecule in HITRAN_CLASS4:
            label = (
                '{molec}[iso{iso:.0f}] [{branch}{jl:.0f}]({v1l:.0f}{v2l:.0f}`{l2l:.0f}`{v3l:.0f})->({v1u:.0f}{v2u:.0f}`{l2u:.0f}`{v3u:.0f})'
                .format(**dict([(k, row[k]) for k in [
                    'v1u', 'v2u', 'l2u', 'v3u', 'v1l', 'v2l', 'l2l', 'v3l',
                    'jl', 'iso'
                ]] + [('molec', molecule),
                      ('branch', _fix_branch_format[row['branch']])])))
        elif molecule in HITRAN_CLASS5:
            label = (
                '{molec}[iso{iso:.0f}] [{branch}{jl:.0f}]({v1l:.0f}{v2l:.0f}`{l2l:.0f}`{v3l:.0f} {rl:.0f})->({v1u:.0f}{v2u:.0f}`{l2u:.0f}`{v3u:.0f} {ru:.0f})'
                .format(**dict([(k, row[k]) for k in [
                    'v1u', 'v2u', 'l2u', 'v3u', 'v1l', 'v2l', 'l2l', 'v3l',
                    'rl', 'ru', 'jl', 'iso'
                ]] + [('molec', molecule),
                      ('branch', _fix_branch_format[row['branch']])])))
        else:
            raise NotImplementedError(
                'No label for {0}. Please add it!'.format(molecule))

        # Add details about some line properties
        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += '\n{0} {1}: {2:.3g} {3}'.format(k, name, row[k], unit)
            else:
                label += '\n{0} {1}: {2} {3}'.format(k, name, row[k], unit)

        return label
Exemple #6
0
    def get_label_cdsd(row, details):
        label = ('CO2[iso{iso}] [{branch}{jl:.0f}]({v1l:.0f}{v2l:.0f}`{l2l:.0f}`{v3l:.0f})->({v1u:.0f}{v2u:.0f}`{l2u:.0f}`{v3u:.0f})'.format(
            **dict([(k, row[k]) for k in ['v1u', 'v2u', 'l2u', 'v3u',
                                          'v1l', 'v2l', 'l2l', 'v3l',
                                          'jl', 'iso']]+
                    [('branch',_fix_branch_format[row['branch']])])))

        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += '\n{0} {1}: {2:.3g} {3}'.format(k, name, row[k], unit)
            else:
                label += '\n{0} {1}: {2} {3}'.format(name, k, row[k], unit)

        return label
Exemple #7
0
    def get_label_hitran_branchjv(row, details):
        molecule = get_molecule(row.id)

        label = ('{0}[iso{1}]'.format(molecule, row['iso']) +
                 '[{branch}{jl}]({v1l})->({v1u})'.format(
                     **dict([(k, row[k])
                             for k in ['branch', 'jl', 'v1l', 'v1u']])))

        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += '\n{0} {1}: {2:.3g} {3}'.format(k, name, row[k], unit)
            else:
                label += '\n{0} {1}: {2} {3}'.format(k, name, row[k], unit)

        return label
Exemple #8
0
    def _format_input(self, **kwargs):
        """ Format the input of parallel calculation request. Returns lists of 
        same lengths that can be parsed with zip(). User input can be lists, 
        or floats instead of constant-values list """
        N = None  # list length if there are list involved
        kwout = {}
        for k, v in kwargs.items():
            if is_list(v):
                if N is None:
                    N = len(v)
                else:
                    if len(v) != N:
                        raise ValueError(
                            "Invalid input for {0}: ".format(k)
                            + "all list should have the same length"
                            "(you may use floats too)"
                        )
                kwout[k] = v

        # Now let's turn floats into list of length N
        if N is None:
            warn("Using ParallelFactory for single case")
            N = 1
        for k, v in kwargs.items():
            if type(v) in [int, np.int64]:
                v = float(v)  # int is not serializable (for some reason)
            if is_list(v):
                continue  # already done
            elif is_float(v) or v is None:
                kwout[k] = [v] * N
            else:
                raise ValueError(
                    "Invalid input for {0}: ".format(k)
                    + "input should be list-like or float-like"
                    + "({0} instead)".format(type(v))
                )

        # check output is correct:
        for v in kwout.values():
            assert len(v) == N

        return kwout
Exemple #9
0
    def _calc_vib_populations(self,
                              Tvib,
                              vib_distribution="boltzmann",
                              overpopulation=None):
        """Calculate vibrational populations from Tref to new Tvib and store
        results in vib_levels dataframe
        This does not modify the spectra yet!

        By constructions populations are calculated divided by the state degeneracy,
        i.e,            g =   gv   *   (2J+1)   *    gi    *     gs
        This means we should only use ratios for rescaling

        The information on state degeneracy and isotopic abundance is already
        included in the linestrength / emission integral, hence in the pre-calculated
        emisscoeff / abscoeff

        """

        vib_levels = self.vib_levels
        if overpopulation is None:
            overpopulation = {}

        if is_float(Tvib):

            # Get new population
            E_vib = vib_levels["Evib"]
            g = 1  # explicitely calculate populations divided by degeneracy
            # this means we should only use ratios for rescaling
            if vib_distribution == "boltzmann":
                nvibQvib = g * exp(-hc_k * E_vib / Tvib)
            else:
                raise NotImplementedError(
                    "vib_distribution: {0}".format(vib_distribution))

        else:
            Tvib1, Tvib2, Tvib3 = Tvib

            # Get new population
            E_vib1 = vib_levels["Evib1"]
            E_vib2 = vib_levels["Evib2"]
            E_vib3 = vib_levels["Evib3"]
            g = 1  # explicitely calculate populations divided by degeneracy
            # this means we should only use ratios for rescaling
            if vib_distribution == "boltzmann":
                nvibQvib = (g * exp(-hc_k * E_vib1 / Tvib1) *
                            exp(-hc_k * E_vib2 / Tvib2) *
                            exp(-hc_k * E_vib3 / Tvib3))
            else:
                raise NotImplementedError(
                    "vib_distribution: {0}".format(vib_distribution))

        # Add overpopulation
        if overpopulation != {}:
            for k, ov in overpopulation.items():
                nvibQvib.loc[k] *= ov
                # TODO: test
            warn(
                "NotImplemented: partition function overpopulation correction not tested"
            )

        # Normalize with partition function
        Qvib = nvibQvib.sum()
        nvib = nvibQvib / Qvib

        # update dataframe
        vib_levels["nvib"] = nvib
        vib_levels["Qvib"] = Qvib

        return Qvib
Exemple #10
0
def _get_fout_name(path, if_exists_then, add_date, add_info, sjson, verbose):
    ''' Get final output name   (add info, extension, increment number if needed) '''

    conditions = sjson['conditions']

    if isdir(path):
        fold, name = path, ''
    else:
        fold, name = split(path)

    # ... add date info
    if add_date not in ['', None, False]:
        date = strftime(add_date)
    else:
        date = ''

    # ... add conditions info
    if add_info not in [[], {}, None, False]:
        # complete name with info about calculation conditions
        info = []
        for k in add_info:
            if k in conditions:
                v = conditions[k]
                # Format info
                # ... special cases
                if k in ['Tvib', 'Tgas', 'Trot']:
                    vs = "{0:.0f}".format(v)
                # ... general case
                elif is_float(v):
                    vs = "{0:.3g}".format(v)
                else:
                    vs = "{0}".format(v)

                try:
                    un = sjson['cond_units'][k]
                except KeyError:  # units not defined, or no units for this condition
                    un = ''
                info.append("{0}{1}{2}".format(k, vs, un))
                # Note: should test for filename validity here.
                # See https://stackoverflow.com/questions/9532499/check-whether-a-path-is-valid-in-python-without-creating-a-file-at-the-paths-ta
                # but it looks long. Best is probably to just test write a file
            else:
                if verbose:
                    print(('Warning. {0} not a valid condition'.format(k)))
        info = '_'.join([_f for _f in info if _f])
    else:
        info = ''

    # ... clean from forbidden characters
    for c in [r'/']:
        info = info.replace(c, '')

    # ... get extension
    rad, ext = splitext(name)
    if ext == '':
        ext = '.spec'  # default extension

    # ... Write full name
    name = '_'.join([_f for _f in [date, rad, info] if _f]) + ext
    fout = join(fold, name)

    # ... Test for existence, replace if needed
    if exists(fout):
        if if_exists_then == 'increment':
            if verbose:
                print('Warning. File already exists. Filename is incremented')
            i = 0
            while exists(fout):
                i += 1
                name = '_'.join(
                    [_f for _f in [date, rad, info, str(i)] if _f]) + ext
                fout = join(fold, name)
        elif if_exists_then == 'replace':
            if verbose:
                print(('File exists and will be replaced: {0}'.format(name)))
        else:
            raise ValueError('File already exists {0}. Choose another filename'.format(fout)+\
                             ', or set the `if_exists_then` option to `replace` or ìncrement`')

    return fout
Exemple #11
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
Exemple #12
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
Exemple #13
0
    def get_label_hitran(row, details):
        """
        Todo
        -------

        replace with simple astype(str) statements and str operations

        ex:
        > '['+df[locl].astype(str)+']('+df[globl].astype(str)+'->'+
        >     df[globu].astype(str)'+)'

        will be much faster!
        """

        molecule = get_molecule(row.id)

        # Get global labels
        if molecule in HITRAN_CLASS1:
            label = (
                "{molec}[iso{iso:.0f}] [{branch}{jl:.0f}]({vl:.0f})->({vu:.0f})"
                .format(
                    **dict([(k, row[k]) for k in ["vu", "vl", "jl", "iso"]] + [
                        ("molec", molecule),
                        ("branch", _fix_branch_format[row["branch"]]),
                    ])))
        elif molecule in HITRAN_CLASS4:
            label = "{molec}[iso{iso:.0f}] [{branch}{jl:.0f}]({v1l:.0f}{v2l:.0f}`{l2l:.0f}`{v3l:.0f})->({v1u:.0f}{v2u:.0f}`{l2u:.0f}`{v3u:.0f})".format(
                **dict([(k, row[k]) for k in [
                    "v1u",
                    "v2u",
                    "l2u",
                    "v3u",
                    "v1l",
                    "v2l",
                    "l2l",
                    "v3l",
                    "jl",
                    "iso",
                ]] + [
                    ("molec", molecule),
                    ("branch", _fix_branch_format[row["branch"]]),
                ]))
        elif molecule in HITRAN_CLASS5:
            label = "{molec}[iso{iso:.0f}] [{branch}{jl:.0f}]({v1l:.0f}{v2l:.0f}`{l2l:.0f}`{v3l:.0f} {rl:.0f})->({v1u:.0f}{v2u:.0f}`{l2u:.0f}`{v3u:.0f} {ru:.0f})".format(
                **dict([(k, row[k]) for k in [
                    "v1u",
                    "v2u",
                    "l2u",
                    "v3u",
                    "v1l",
                    "v2l",
                    "l2l",
                    "v3l",
                    "rl",
                    "ru",
                    "jl",
                    "iso",
                ]] + [
                    ("molec", molecule),
                    ("branch", _fix_branch_format[row["branch"]]),
                ]))
        else:
            raise NotImplementedError(
                "No label for {0}. Please add it!".format(molecule))

        # Add details about some line properties
        for k in details:
            name, _, unit = details[k]
            if is_float(row[k]):
                label += "<br>{0} {1}: {2:.3g} {3}".format(
                    k, name, row[k], unit)
            else:
                label += "<br>{0} {1}: {2} {3}".format(k, name, row[k], unit)

        return label
Exemple #14
0
def get_slit_function(slit_function,
                      unit='nm',
                      norm_by='area',
                      shape='triangular',
                      center_wavespace=None,
                      return_unit='same',
                      wstep=None,
                      plot=False,
                      resfactor=2,
                      *args,
                      **kwargs):
    ''' Import or generate slit function in correct wavespace
    Give a file path to import, or a float / tuple to generate arbitrary shapes

    Warning with units: read about unit and return_unit parameters.

    See :meth:`~radis.spectrum.spectrum.Spectrum.apply_slit` and 
    :func:`~radis.tools.slit.convolve_with_slit` for more info

    
    Parameters    
    ----------

    slit_function: tuple, or str
        If float:
            generate slit function with FWHM of `slit_function` (in `unit`)
        If .txt file:
            import experimental slit function (in `unit`): format must be 2-columns
            with wavelengths and intensity (doesn't have to be normalized)

    unit: 'nm' or 'cm-1'
        unit of slit_function FWHM, or unit of imported file

    norm_by: 'area', 'max', or None
        how to normalize. `area` conserves energy. With `max` the slit is normalized
        so that its maximum is one (that is what is done in Specair: it changes
        the outptut spectrum unit, e.g. from 'mW/cm2/sr/µm' to 'mW/cm2/sr')
        None doesnt normalize. Default 'area'

    shape: 'triangular', 'trapezoidal', 'gaussian'
        which shape to use when generating a slit. Default 'triangular'

    center_wavespace: float, or None
        center of slit when generated (in unit). Not used if slit is imported.

    return_unit: 'nm', 'cm-1', or 'same'
        if not 'same', slit is converted to the given wavespace.

    wstep: float
        which discretization step to use (in return_unit) when generating a slit
        function. Not used if importing


    Other Parameters
    ----------------

    resfactor: int
        resolution increase when resampling from nm to cm-1, or the other way
        round. Default 2.

    energy_threshold: float
         tolerance fraction. Only used when importing experimental slit as the
         theoretical slit functions are directly generated in spectrum wavespace
         Default 1e-3 (0.1%)
         

    Returns
    -------
    
    wslit, Islit: array
        wslit is in `return_unit` . Islit is normalized according to  `norm_by`
         
        
    Examples
    --------
    
    >>> wslit, Islit = get_slit_function(1, 'nm', shape='triangular', 
    center_wavespace=600, wstep=0.01)     
    
    Returns a triangular slit function of FWHM = 1 nm, centered on 600 nm, with
    a step of 0.01 nm
    
    >>> wslit, Islit = get_slit_function(1, 'nm', shape='triangular', 
    center_wavespace=600, return_unit='cm-1', wstep=0.01)   
    
    Returns a triangular slit function expressed in cm-1, with a FWHM = 1 nm 
    (converted in equivalent width in cm-1 at 600 nm), centered on 600 nm, with 
    a step of 0.01 cm-1 (!)   
    

    Notes
    -----

    In norm_by 'max' mode, slit is normalized by slit max. In RADIS, this is done
    in the spectrum wavespace (to avoid errors that would be caused by interpolating
    the spectrum). 
    
    A problem arise if the spectrum wavespace is different from the slit wavespace:
    typically, slit is in 'nm' but a spectrum calculated by RADIS is stored in 
    'cm-1': in that case, the convoluted spectrum is multiplied by /int(Islit*dν)
    instead of /int(Islit*dλ). The output unit is then [radiance]*[spec_unit]
    instead of [radiance]*[slit_unit], i.e, typically, [mW/cm2/sr/nm]*[cm-1]
    instead of [mW/cm2/sr/nm]*[nm]=[mW/cm2/sr]
    
    While this remains true if the units are taken into account, this is not the
    expected behaviour. For instance, Specair users are used to having a FWHM(nm) 
    factor between spectra convolved with slit normalized by max and slit normalized
    by area.
    
    The norm_by='max' behaviour adds a correction factor
    `/int(Islit*dλ)/int(Islit*dν)` to maintain an output spectrum in [radiance]*[slit_unit]
    
    See Also
    --------
    
    :meth:`~radis.spectrum.spectrum.Spectrum.apply_slit`, 
    :func:`~radis.tools.slit.convolve_with_slit`
    
    '''

    if 'waveunit' in kwargs:
        assert return_unit == 'same'  # default
        return_unit = kwargs.pop('waveunit')
        warn(DeprecationWarning('waveunit renamed return_unit'))
    if 'slit_unit' in kwargs:
        assert unit == 'nm'  # default
        unit = kwargs.pop('slit_unit')
        warn(DeprecationWarning('slit_unit renamed unit'))

    energy_threshold = kwargs.pop('energy_threshold', 1e-3)  # type: float

    # tolerance fraction
    # when resampling (only used in experimental slit as the)
    # theoretical slit functions are directly generated in
    # spectrum wavespace

    def check_input_gen():
        if center_wavespace is None:
            raise ValueError('center_wavespace has to be given when generating '+\
                             'slit function')
        if wstep is None:
            raise ValueError('wstep has to be given when generating '+\
                             'slit function')

    # Cast units
    if return_unit == 'same':
        return_unit = unit
    unit = cast_waveunit(unit)
    return_unit = cast_waveunit(return_unit)
    scale_slit = 1  # in norm_by=max mode, used to keep units in [Iunit]*return_unit in [Iunit]*unit
    # not used in norm_by=area mode

    # First get the slit in return_unit space
    if is_float(slit_function
                ):  # Generate slit function (directly in return_unit space)

        check_input_gen()

        # ... first get FWHM in return_unit  (it is in `unit` right now)
        FWHM = slit_function
        if return_unit == 'cm-1' and unit == 'nm':
            # center_wavespace ~ nm, FWHM ~ nm
            FWHM = dnm2dcm(FWHM, center_wavespace)  # wavelength > wavenumber
            center_wavespace = nm2cm(center_wavespace)
            if norm_by == 'max':
                scale_slit = slit_function / FWHM  # [unit/return_unit]
        elif return_unit == 'nm' and unit == 'cm-1':
            # center_wavespace ~ cm-1, FWHM ~ cm-1
            FWHM = dcm2dnm(FWHM, center_wavespace)  # wavenumber > wavelength
            center_wavespace = cm2nm(center_wavespace)
            if norm_by == 'max':
                scale_slit = slit_function / FWHM  # [unit/return_unit]
        else:
            pass  # correct unit already
        # Now FWHM is in 'return_unit'

        # ... now, build it (in our wavespace)
        if __debug__:
            printdbg(
                'get_slit_function: {0} FWHM {1:.2f}{2}, center {3:.2f}{2}, norm_by {4}'
                .format(shape, FWHM, return_unit, center_wavespace, norm_by))

        if shape == 'triangular':
            wslit, Islit = triangular_slit(FWHM,
                                           wstep,
                                           center=center_wavespace,
                                           bplot=plot,
                                           norm_by=norm_by,
                                           waveunit=return_unit,
                                           scale=scale_slit,
                                           *args,
                                           **kwargs)

        # Insert other slit shapes here
        # ...

        elif shape == 'gaussian':
            wslit, Islit = gaussian_slit(FWHM,
                                         wstep,
                                         center=center_wavespace,
                                         bplot=plot,
                                         norm_by=norm_by,
                                         waveunit=return_unit,
                                         scale=scale_slit,
                                         *args,
                                         **kwargs)

        elif shape == 'trapezoidal':
            raise TypeError(
                'A (top, base) tuple must be given with a trapezoidal slit')

        else:
            raise TypeError(
                'Slit function ({0}) not in known slit shapes: {1}'.format(
                    shape, SLIT_SHAPES))

    elif isinstance(slit_function, tuple):

        check_input_gen()

        try:
            top, base = slit_function
        except:
            raise TypeError(
                'Wrong format for slit function: {0}'.format(slit_function))
        if shape == 'trapezoidal':
            pass
        elif shape == 'triangular':  # it's the default
            warn(
                'Triangular slit given with a tuple: we used trapezoidal slit instead'
            )
            shape = 'trapezoidal'
        else:
            raise TypeError(
                'A (top, base) tuple must be used with a trapezoidal slit')

        # ... first get FWHM in our wavespace unit
        if return_unit == 'cm-1' and unit == 'nm':
            # center_wavespace ~ nm, FWHM ~ nm
            top = dnm2dcm(top, center_wavespace)  # wavelength > wavenumber
            base = dnm2dcm(base, center_wavespace)  # wavelength > wavenumber
            center_wavespace = nm2cm(center_wavespace)
            if norm_by == 'max':
                scale_slit = sum(slit_function) / (top + base
                                                   )  # [unit/return_unit]
        elif return_unit == 'nm' and unit == 'cm-1':
            # center_wavespace ~ cm-1, FWHM ~ cm-1
            top = dcm2dnm(top, center_wavespace)  # wavenumber > wavelength
            base = dcm2dnm(base, center_wavespace)  # wavenumber > wavelength
            center_wavespace = cm2nm(center_wavespace)
            if norm_by == 'max':
                scale_slit = sum(slit_function) / (top + base
                                                   )  # [unit/return_unit]
        else:
            pass  # correct unit already

        FWHM = (top + base) / 2

        # ... now, build it (in our wavespace)
        if __debug__:
            printdbg(
                'get_slit_function: {0}, FWHM {1:.2f}{2}, center {3:.2f}{2}, norm_by {4}'
                .format(shape, FWHM, return_unit, center_wavespace, norm_by))

        wslit, Islit = trapezoidal_slit(top,
                                        base,
                                        wstep,
                                        center=center_wavespace,
                                        bplot=plot,
                                        norm_by=norm_by,
                                        waveunit=return_unit,
                                        scale=scale_slit,
                                        *args,
                                        **kwargs)

    elif isinstance(slit_function, string_types):  # import it
        if __debug__:
            printdbg(
                'get_slit_function: {0} in {1}, norm_by {2}, return in {3}'.
                format(slit_function, unit, norm_by, return_unit))
        wslit, Islit = import_experimental_slit(
            slit_function,
            norm_by=norm_by,  # norm is done later anyway
            waveunit=unit,
            bplot=False,  # we will plot after resampling
            *args,
            **kwargs)
        # ... get unit
        # Normalize
        if norm_by == 'area':  # normalize by the area
            #        I_slit /= np.trapz(I_slit, x=w_slit)
            Iunit = '1/{0}'.format(unit)
        elif norm_by == 'max':  # set maximum to 1
            Iunit = '1'
        elif norm_by is None:
            Iunit = None
        else:
            raise ValueError(
                'Unknown normalization type: `norm_by` = {0}'.format(norm_by))

        # ... check it looks correct
        unq, counts = np.unique(wslit, return_counts=True)
        dup = counts > 1
        if dup.sum() > 0:
            raise ValueError(
                'Not all wavespace points are unique: slit function ' +
                'format may be wrong. Duplicates for w={0}'.format(unq[dup]))

        # ... resample if needed
        if return_unit == 'cm-1' and unit == 'nm':  # wavelength > wavenumber
            wold, Iold = wslit, Islit
            wslit, Islit = resample_even(nm2cm(wslit),
                                         Islit,
                                         resfactor=resfactor,
                                         energy_threshold=energy_threshold,
                                         print_conservation=True)
            scale_slit = trapz(Iold, wold) / trapz(Islit,
                                                   wslit)  # [unit/return_unit]
            renormalize = True
        elif return_unit == 'nm' and unit == 'cm-1':  # wavenumber > wavelength
            wold, Iold = wslit, Islit
            wslit, Islit = resample_even(cm2nm(wslit),
                                         Islit,
                                         resfactor=resfactor,
                                         energy_threshold=energy_threshold,
                                         print_conservation=True)
            scale_slit = trapz(Iold, wold) / trapz(Islit,
                                                   wslit)  # [unit/return_unit]
            renormalize = True
        else:  # return_unit == unit
            renormalize = False
        # Note: if wstep dont match with quantity it's alright as it gets
        # interpolated in the `convolve_with_slit` function

        # re-Normalize if needed (after changing units)
        if renormalize:
            if __debug__: printdbg('get_slit_function: renormalize')
            if norm_by == 'area':  # normalize by the area
                Islit /= abs(np.trapz(Islit, x=wslit))
                Iunit = '1/{0}'.format(return_unit)
            elif norm_by == 'max':  # set maximum to 1
                Islit /= abs(np.max(Islit))
                Islit *= scale_slit
                Iunit = '1'
                if scale_slit != 1:
                    Iunit += 'x{0}'.format(scale_slit)


#            elif norm_by == 'max2': # set maximum to 1    # removed this mode for simplification
#                Islit /= abs(np.max(Islit))
            elif norm_by is None:
                Iunit = None
            else:
                raise ValueError(
                    'Unknown normalization type: `norm_by` = {0}'.format(
                        norm_by))

        if plot:  # (plot after resampling / renormalizing)
            # Plot slit
            plot_slit(wslit, Islit, waveunit=return_unit, Iunit=Iunit)

    else:
        raise TypeError('Unexpected type for slit function: {0}'.format(
            type(slit_function)))

    return wslit, Islit