예제 #1
0
파일: test_units.py 프로젝트: ulyssed/radis
def test_convert():

    # Dimensionned input: should be converted
    assert convert_and_strip_units(4500 * u.nm, u.um) == 4.5
    assert convert_and_strip_units(4.5 * u.um, u.nm) == 4500  # uses round(10)
    assert convert_and_strip_units(2000 * (1 / u.cm), 1 / u.m) == 200000

    # Convert using the spectral equivalencies (astropy.units.equivalencies)
    assert convert_and_strip_units(5 * u.um, 1 / u.cm) == 2000
    assert convert_and_strip_units(60 * u.THz, 1 / u.cm) == 2001.3845711889

    # Covert using temperature equivalencies
    assert convert_and_strip_units(0 * u.deg_C, u.K) == 273.15
    assert convert_and_strip_units(300 * u.K, u.imperial.deg_F) == 80.33

    # Undimensionned input : shouldnt change
    assert convert_and_strip_units(4500, u.um) == 4500

    # None type : should return None
    assert convert_and_strip_units(None) is None
    assert convert_and_strip_units(None, u.nm) is None
예제 #2
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
        """

        # Convert units
        Tgas = convert_and_strip_units(Tgas, u.K)
        path_length = convert_and_strip_units(path_length, u.cm)
        pressure = convert_and_strip_units(pressure, u.bar)

        # 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 a float or Astropy unit")
        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  HWHM
        self._calc_broadening_HWHM()

        # ... 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
예제 #3
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
        """

        # Convert units
        Tvib = convert_and_strip_units(Tvib, u.K)
        Trot = convert_and_strip_units(Trot, u.K)
        Ttrans = convert_and_strip_units(Ttrans, u.K)
        path_length = convert_and_strip_units(path_length, u.cm)
        pressure = convert_and_strip_units(pressure, u.bar)

        # 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 isinstance(Tvib, tuple):
            Tvib = tuple([convert_and_strip_units(T, u.K) for T in Tvib])
        elif not is_float(Tvib):
            raise TypeError("Tvib should be float, or tuple (got {0})".format(
                type(Tvib)))
        singleTvibmode = is_float(Tvib)
        if not is_float(Trot):
            raise ValueError("Trot should be float.")
        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._calc_noneq_parameters(vib_distribution, singleTvibmode)

        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()

        # ----------------------------------------------------------------------
        # 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  HWHM
        self._calc_broadening_HWHM()

        # ... 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 lines is not None and 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