Пример #1
0
def get_baseline(s, var="radiance", Iunit=None):
    """Calculate and returns a baseline

    Parameters
    ----------
    s: Spectrum
        Spectrum which needs a baseline

    var: str
        on which spectral quantity to read the baseline. Default ``'radiance'``.
        See :py:data:`~radis.spectrum.utils.SPECTRAL_QUANTITIES`

    Returns
    -------
    baseline: Spectrum
        Spectrum object where intenisity is the baseline of s is computed by peakutils

    See Also
    --------

    :py:func:`~radis.spectrum.operations.sub_baseline`
    """
    import peakutils

    w1, I1 = s.get(var=var, Iunit=Iunit)
    baseline = peakutils.baseline(I1, deg=1, max_it=500)
    baselineSpectrum = Spectrum.from_array(w1,
                                           baseline,
                                           var,
                                           unit=Iunit,
                                           name=s.get_name() + "_baseline")
    return baselineSpectrum
Пример #2
0
def test_all_slit_shapes(FWHM=0.4,
                         verbose=True,
                         plot=True,
                         close_plots=True,
                         *args,
                         **kwargs):
    """ Test all slit generation functions and make sure we get the expected FWHM"""

    if plot:
        plt.ion()  # dont get stuck with Matplotlib if executing through pytest
        if close_plots:
            plt.close("all")

    # get spectrum
    from radis.test.utils import getTestFile
    from radis.spectrum.spectrum import Spectrum

    s = Spectrum.from_txt(
        getTestFile("calc_N2C_spectrum_Trot1200_Tvib3000.txt"),
        quantity="radiance_noslit",
        waveunit="nm",
        unit="mW/cm2/sr/µm",
    )
    wstep = np.diff(s.get_wavelength())[0]

    # Plot all slits
    # ... gaussian
    s.apply_slit(FWHM, unit="nm", shape="gaussian", plot_slit=plot)
    assert np.isclose(get_FWHM(*s.get_slit()), FWHM, atol=2 * wstep)

    # ... triangular
    s.apply_slit(FWHM, unit="nm", shape="triangular", plot_slit=plot)
    assert np.isclose(get_FWHM(*s.get_slit()), FWHM, atol=2 * wstep)

    # ... trapezoidal
    s.apply_slit((FWHM * 0.9, FWHM * 1.1),
                 unit="nm",
                 shape="trapezoidal",
                 plot_slit=plot)
    assert np.isclose(get_FWHM(*s.get_slit()), FWHM, atol=2 * wstep)

    #    # ... trapezoidal
    #    s.apply_slit(FWHM, unit='nm', shape='trapezoidal', plot_slit=plot, norm='max')
    #    assert np.isclose(get_FWHM(*s.get_slit()), FWHM, atol=1.1*wstep)

    # ... experimental
    s.apply_slit(getTestFile("slitfunction.txt"), unit="nm", plot_slit=plot)
    assert np.isclose(get_effective_FWHM(*s.get_slit()), FWHM, atol=0.01)
    # note that we're applying a slit function measured at 632.5 nm to a Spectrum
    # at 4.7 µm. It's just good for testing the functions

    #    # ... experimental, convolve with max
    #    s.apply_slit(getTestFile('slitfunction.txt'), unit='nm', norm_by='max', plot_slit=plot)
    #    assert np.isclose(get_FWHM(*s.get_slit()), FWHM, atol=1.1*wstep)

    if verbose:
        print("\n>>> _test_all_slits yield correct FWHM (+- wstep) : OK\n")

    return True  # nothing defined yet
Пример #3
0
def experimental_spectrum(w,
                          I,
                          wunit='nm',
                          Iunit='counts',
                          conditions=None,
                          cond_units=None,
                          name=None):  # -> Spectrum:
    ''' Convert (w, I) into a Spectrum object that has unit conversion and plotting
    capabilities. Convolution is not available as the spectrum is assumed to
    be measured experimentally (hence deconvolution of the slit function would
    be required)


    Parameters    
    ----------

    w, I: np.array
        wavelength and intensity

    wunit: 'nm', 'cm-1'
        wavespace unit

    Iunit: str
        intensity unit (can be 'counts', 'mW/cm2/sr/nm', etc...). Default
        'counts' (default Winspec output)

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

    conditions: dict
        (optional) calculation conditions to be stored with Spectrum

    cond_units: dict
        (optional) calculation conditions units

    name: str
        (optional) give a name


    See Also
    --------

    :func:`~radis.spectrum.spectrum.calculated_spectrum`, 
    :func:`~radis.spectrum.spectrum.transmittance_spectrum`, 
    :meth:`~radis.spectrum.spectrum.Spectrum.from_array`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_txt`,
    :func:`~radis.tools.database.load_spec`

    '''

    return Spectrum.from_array(np.array(w),
                               np.array(I),
                               'radiance',
                               waveunit=wunit,
                               unit=Iunit,
                               conditions=conditions,
                               cond_units=cond_units,
                               name=name)
Пример #4
0
def test_slit_unit_conversions_spectrum_in_nm(verbose=True, plot=True, close_plots=True, *args, **kwargs):
    ''' Test that slit is consistently applied for different units

    Assert that:

    - calculated FWHM is the one that was applied

    '''

    from radis.test.utils import getTestFile
    from radis.spectrum.spectrum import Spectrum

    if plot:    # dont get stuck with Matplotlib if executing through pytest
        plt.ion()
        if close_plots:
            plt.close('all')

    # %% Get a Spectrum (stored in nm)

    s_nm = Spectrum.from_txt(getTestFile('calc_N2C_spectrum_Trot1200_Tvib3000.txt'),
                             quantity='radiance_noslit', waveunit='nm', unit='mW/cm2/sr/µm',
                             conditions={'self_absorption': False})

    with catch_warnings():
        filterwarnings(
            'ignore', 'Condition missing to know if spectrum is at equilibrium:')
        # just because it makes better units
        s_nm.rescale_path_length(1, 0.001)

    wstep = np.diff(s_nm.get_wavelength())[0]

    assert s_nm.get_waveunit() == 'nm'       # ensures it's stored in cm-1

    for shape in ['gaussian', 'triangular']:

        # Apply slit in nm
        slit_nm = 0.5
        s_nm.name = 'Spec in nm, slit {0:.2f} nm'.format(slit_nm)
        s_nm.apply_slit(slit_nm, unit='nm', shape=shape, mode='same')
        # ... mode=same to keep same output length. It helps compare both Spectra afterwards
        # in cm-1 as that's s.get_waveunit()
        fwhm = get_FWHM(*s_nm.get_slit())
        assert np.isclose(slit_nm, fwhm, atol=2*wstep)

        # Apply slit in nm this time
        s_cm = s_nm.copy()
        w_nm = s_nm.get_wavelength(which='non_convoluted')
        slit_cm = dnm2dcm(slit_nm, w_nm[len(w_nm)//2])
        s_cm.name = 'Spec in nm, slit {0:.2f} cm-1'.format(slit_cm)
        s_cm.apply_slit(slit_cm, unit='cm-1', shape=shape, mode='same')

        plotargs = {}
        if plot:
            plotargs['title'] = 'test_slit_unit_conversions: {0} ({1} nm)'.format(
                shape, slit_nm)
        s_nm.compare_with(s_cm, spectra_only='radiance', rtol=1e-3,
                          verbose=verbose, plot=plot, **plotargs)
Пример #5
0
def transmittance_spectrum(w,
                           T,
                           wunit='nm',
                           Tunit='I/I0',
                           conditions=None,
                           cond_units=None,
                           name=None):  # -> Spectrum:
    ''' Convert (w, I) into a Spectrum object that has unit conversion, plotting
    and slit convolution capabilities


    Parameters    
    ----------

    w, I: np.array
        wavelength and transmittance (no slit)

    wunit: 'nm', 'cm-1'
        wavespace unit

    Iunit: str
        intensity unit. Default 'I/I0'


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

    conditions: dict
        (optional) calculation conditions to be stored with Spectrum

    cond_units: dict
        (optional) calculation conditions units

    name: str
        (optional) give a name


    See Also
    --------

    :func:`~radis.spectrum.models.calculated_spectrum`, 
    :func:`~radis.spectrum.models.experimental_spectrum`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_array`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_txt`,
    :func:`~radis.tools.database.load_spec`

    '''

    return Spectrum.from_array(np.array(w),
                               np.array(T),
                               'transmittance_noslit',
                               waveunit=wunit,
                               unit=Tunit,
                               conditions=conditions,
                               cond_units=cond_units,
                               name=name)
Пример #6
0
def MergeSlabs(*slabs, **kwargs):
    # type: (*Spectrum, **dict) -> Spectrum
    r"""Combines several slabs into one. Useful to calculate multi-gas slabs.
    Linear absorption coefficient is calculated as the sum of all linear absorption
    coefficients, and the RTE is recalculated to get the total radiance.
    You can also simply use::

        s1//s2
    Merged spectrum ``1+2`` is calculated with Eqn (4.3) of the [RADIS-2018]_ article,
    generalized to N slabs :

    .. math::

        j_{\lambda, 1+2} = j_{\lambda, 1} + j_{\lambda, 2}

        k_{\lambda, 1+2} = k_{\lambda, 1} + k_{\lambda, 2}

    where

    .. math:: j_{\lambda}, k_{\lambda}

    are the emission coefficient and absorption coefficient of the two slabs ``1`` and ``2``.
    Emission and absorption coefficients are calculated if not given in the
    initial slabs (if possible).

    Parameters
    ----------
    slabs: list of Spectra, each representing a slab
        ``path_length`` must be given in Spectrum conditions, and equal for all
        spectra.

        line-of-sight::

            slabs
                        [0]        \====
            light       [1]  ->     )===  observer
                        [n]        /====

    Other Parameters
    ----------------
    kwargs input:
    resample: ``'never'``, ``'intersect'``, ``'full'``
        what to do when spectra have different wavespaces:

        - If ``'never'``, raises an error
        - If ``'intersect'``, uses the intersection of all ranges, and resample
          spectra on the most resolved wavespace.
        - If ``'full'``, uses the overlap of all ranges, resample spectra on the
          most resolved wavespace, and fill missing data with 0 emission and 0
          absorption

        Default ``'never'``
    out: ``'transparent'``, ``'nan'``, ``'error'``
        what to do if resampling is out of bounds:

        - ``'transparent'``: fills with transparent medium.
        - ``'nan'``: fills with nan.
        - ``'error'``: raises an error.

        Default ``'nan'``
    optically_thin: boolean
        if ``True``, merge slabs in optically thin mode. Default ``False``
    verbose: boolean
        if ``True``, print messages and warnings. Default ``False``
    modify_inputs: False
        if ``True``, slabs are modified directly when they are resampled. This
        avoids making a copy so is slightly faster. Default ``False``.

    Returns
    -------
    Spectrum object representing total emission and total transmittance as
    observed at the output. Conditions and units are transported too,
    unless there is a mismatch then conditions are dropped (and units mismatch
    raises an error because it doesnt make sense)

    Examples
    --------
    Merge two spectra calculated with different species (physically correct
    only if broadening coefficients dont change much)::
        from radis import calc_spectrum, MergeSlabs
        s1 = calc_spectrum(...)
        s2 = calc_spectrum(...)
        s3 = MergeSlabs(s1, s2)
    The last line is equivalent to::

        s3 = s1//s2
    Load a spectrum precalculated on several partial spectral ranges, for a same
    molecule (i.e, partial spectra are optically thin on the rest of the spectral
    range)::
        from radis import load_spec, MergeSlabs
        spectra = []
        for f in ['spec1.spec', 'spec2.spec', ...]:
            spectra.append(load_spec(f))
        s = MergeSlabs(*spectra, resample='full', out='transparent')
        s.update()   # Generate missing spectral quantities
        s.plot()

    See Also
    --------
    :func:`~radis.los.slabs.SerialSlabs`

    See more examples in :ref:`Line-of-Sight module <label_los_index>`

    """

    # Deprecation warnings
    if "resample_wavespace" in kwargs:
        warn(
            DeprecationWarning(
                "'resample_wavespace' replaced with 'resample'"))
        kwargs["resample"] = kwargs.pop("resample_wavespace")
    if "out_of_bounds" in kwargs:
        warn(DeprecationWarning("'out_of_bounds' replaced with 'out'"))
        kwargs["out"] = kwargs.pop("out_of_bounds")

    # Check inputs, get defaults
    # inputs (Python 2 compatible)
    resample_wavespace = kwargs.pop("resample", "never")  # default 'never'
    out_of_bounds = kwargs.pop("out", "nan")  # default 'nan'
    optically_thin = kwargs.pop("optically_thin", False)  # default False
    verbose = kwargs.pop("verbose", False)  # type: bool
    kwargs.pop("debug", False)  # type: bool
    modify_inputs = kwargs.pop("modify_inputs", False)  # type: bool
    if len(kwargs) > 0:
        raise ValueError("Unexpected input: {0}".format(list(kwargs.keys())))

    # Check inputs
    if resample_wavespace not in ["never", "intersect", "full"]:
        raise ValueError("'resample' should be one of: {0}".format(", ".join(
            ["never", "intersect", "full"])))

    if len(slabs) == 0:
        raise ValueError("Empty list of slabs")

    elif len(slabs) == 1:
        if not is_spectrum(slabs[0]):
            raise TypeError(
                "MergeSlabs takes an unfolded list of Spectrum as " +
                "argument: (got {0})".format(type(slabs[0])))
        return slabs[0]

    else:  # calculate serial slabs

        slabs = list(slabs)

        #        # Check all items are valid Spectrum objects
        for s in slabs:
            _check_valid(s)

        # Check all path_lengths are defined and they exist
        try:
            path_lengths = [s.conditions["path_length"] for s in slabs]
        except KeyError:
            raise ValueError(
                "path_length must be defined for all slabs in MergeSlabs. " +
                "Set it with `s.conditions['path_length']=`. ")
        if not all([L == path_lengths[0] for L in path_lengths[1:]]):
            raise ValueError(
                "path_length must be equal for all MergeSlabs inputs" +
                "  (got {0})".format(path_lengths))

        # make sure we use the same wavespace type (even if sn is in 'nm' and s in 'cm-1')
        waveunit = slabs[0].get_waveunit()
        # Make all our slabs copies with the same wavespace range
        # (note: wavespace range may be different for different quantities, but
        # equal for all slabs)
        slabs = resample_slabs(waveunit, resample_wavespace, out_of_bounds,
                               modify_inputs, *slabs)
        w_noconv = slabs[0]._get_wavespace()

        # %%

        # Get conditions of the Merged spectrum
        conditions = slabs[0].conditions
        conditions["waveunit"] = waveunit
        cond_units = slabs[0].cond_units
        units0 = slabs[0].units
        # Define conditions as intersection of everything (N/A if unknown)
        # ... this will only keep intensive parameters (same for all)
        for s in slabs[1:]:
            conditions = intersect(conditions, s.conditions)
            cond_units = intersect(cond_units, s.cond_units)
            # units = intersect(units0, s.units)  # we're actually using [slabs0].units insteads
        # ... Add extensive parameters
        for cond in ["molecule"]:
            if in_all(cond, [s.conditions for s in slabs]):
                conditions[cond] = set([s.conditions[cond] for s in slabs])

        # %% Get quantities that should be calculated
        # Try to keep all the quantities of the initial slabs:
        requested = merge_lists([s.get_vars() for s in slabs])
        recompute = requested[:]  # copy
        if "radiance_noslit" in requested and not optically_thin:
            recompute.append("emisscoeff")
            recompute.append("abscoeff")
        if "abscoeff" in recompute and "path_length" in conditions:
            recompute.append("absorbance")
            recompute.append("transmittance_noslit")

        # To make it easier, we start from abscoeff and emisscoeff of all slabs
        # Let's recompute them all
        # TODO: if that changes the initial Spectra, maybe we should just work on copies
        for s in slabs:
            if "abscoeff" in recompute and not "abscoeff" in list(s._q.keys()):
                s.update("abscoeff", verbose=False)
                # that may crash if Spectrum doesnt have the correct inputs.
                # let update() handle that
            if "emisscoeff" in recompute and not "emisscoeff" in list(
                    s._q.keys()):
                s.update("emisscoeff", verbose=False)
                # same

        # %% Calculate total emisscoeff and abscoeff
        added = {}

        # ... absorption coefficient (cm-1)
        if "abscoeff" in recompute:
            # TODO: deal with all cases
            if __debug__:
                printdbg("... merge: calculating abscoeff k=sum(k_i)")
            abscoeff_eq = np.sum(
                [
                    s.get("abscoeff", wunit=waveunit,
                          Iunit=units0["abscoeff"])[1] for s in slabs
                ],
                axis=0,
            )
            assert len(w_noconv) == len(abscoeff_eq)
            added["abscoeff"] = (w_noconv, abscoeff_eq)

        # ... emission coefficient
        if "emisscoeff" in recompute:
            if __debug__:
                printdbg("... merge: calculating emisscoeff j=sum(j_i)")
            emisscoeff_eq = np.sum(
                [
                    s.get("emisscoeff",
                          wunit=waveunit,
                          Iunit=units0["emisscoeff"])[1] for s in slabs
                ],
                axis=0,
            )
            assert len(w_noconv) == len(emisscoeff_eq)
            added["emisscoeff"] = (w_noconv, emisscoeff_eq)

        # name
        name = "//".join([s.get_name() for s in slabs])

        # TODO: check units are consistent in all slabs inputs
        s = Spectrum(
            quantities=added,
            conditions=conditions,
            cond_units=cond_units,
            units=units0,
            name=name,
        )

        # %% Calculate all quantities from emisscoeff and abscoeff

        if "emissivity_noslit" in requested and (
                "thermal_equilibrium" not in s.conditions
                or s.is_at_equilibrium() != True):
            requested.remove("emissivity_noslit")
            if __debug__:
                printdbg(
                    "... merge: all slabs are not proven to be at equilibrium. "
                    + "Emissivity was not calculated")

        # Add the rest of the spectral quantities afterwards:
        s.update(
            [k for k in requested if k not in ["emisscoeff", "abscoeff"]],
            optically_thin=optically_thin,
            verbose=verbose,
        )

        return s
Пример #7
0
def SerialSlabs(*slabs, **kwargs):
    # type: (*Spectrum, **dict) -> Spectrum
    r"""Adds several slabs along the line-of-sight.
    If adding two slabs only, you can also use::

        s1>s2
    Serial spectrum ``1>2`` is calculated with Eqn (4.2) of the [RADIS-2018]_ article,
    generalized to N slabs :

    .. math::
        I_{\lambda, 1>2} = I_{\lambda, 1} \tau_{\lambda, 2} + I_{\lambda, 2}

        \tau_{\lambda, 1+2} = \tau_{\lambda, 1} \cdot \tau_{\lambda, 2}

    where

        .. math:: I_{\lambda}, \tau_{\lambda}

    are the radiance and transmittance of the two slabs ``1`` and ``2``.
    Radiance and transmittance are calculated if not given in the
    initial slabs (if possible).

    Parameters
    ----------
    slabs: list of Spectra, each representing a slab
        line-of-sight::

            slabs       [0]     [1]  ............... [n]
                         :       :                    :         \====
            light        *   ->  *        ->          *    ->    )===  observer
                                                                /====
    resample_wavespace: ``'never'``, ``'intersect'``, ``'full'``
        what to do when spectra have different wavespaces:

        - If ``'never'``, raises an error
        - If ``'intersect'``, uses the intersection of all ranges, and resample
          spectra on the most resolved wavespace.
        - If ``'full``', uses the overlap of all ranges, resample spectra on the
          most resolved wavespace, and fill missing data with 0 emission and 0
          absorption

        Default ``'never'``
    out: ``'transparent'``, ``'nan'``, ``'error'``
        what to do if resampling is out of bounds:

        - ``'transparent'``: fills with transparent medium.
        - ``'nan'``: fills with nan.
        - ``'error'``: raises an error.

        Default ``'nan'``

    Other Parameters
    ----------------
    verbose: bool
        if ``True``, more blabla. Default ``False``
    modify_inputs: False
        if ``True``, slabs wavelengths/wavenumbers are modified directly when
        they are resampled. This avoids making a copy so it is slightly faster.
        Default ``False``.

        ..note::
            for large number of slabs (in radiative transfer calculations) you
            surely want to use this option !

    Returns
    -------
    Spectrum object representing total emission and total transmittance as
    observed at the output (slab[n+1]). Conditions and units are transported too,
    unless there is a mismatch then conditions are dropped (and units mismatch
    raises an error because it doesnt make sense)

    Examples
    --------
    Add s1 and s2 along the line of sight: s1 --> s2 ::
        s1 = calc_spectrum(...)
        s2 = calc_spectrum(...)
        s3 = SerialSlabs(s1, s2)

    The last line is equivalent to::

        s3 = s1>s2
    See Also
    --------
    :func:`~radis.los.slabs.MergeSlabs`
    See more examples in the :ref:`Line-of-Sight module <label_los_index>`

    """
    # TODO: rewrite with 'recompute' list like in MergeSlabs ?

    if "resample_wavespace" in kwargs:
        warn(
            DeprecationWarning(
                "'resample_wavespace' replaced with 'resample'"))
        kwargs["resample"] = kwargs.pop("resample_wavespace")
    if "out_of_bounds" in kwargs:
        warn(DeprecationWarning("'out_of_bounds' replaced with 'out'"))
        kwargs["out"] = kwargs.pop("out_of_bounds")

    # Check inputs, get defaults
    resample_wavespace = kwargs.pop("resample", "never")  # default 'never'
    out_of_bounds = kwargs.pop("out", "nan")  # default 'nan'
    verbose = kwargs.pop("verbose", False)  # type: bool
    modify_inputs = kwargs.pop("modify_inputs", False)  # type: bool
    if len(kwargs) > 0:
        raise ValueError("Unexpected input: {0}".format(list(kwargs.keys())))
    if resample_wavespace not in ["never", "intersect", "full"]:
        raise ValueError("resample should be one of: {0}".format(", ".join(
            ["never", "intersect", "full"])))

    if len(slabs) == 0:
        raise ValueError("Empty list of slabs")

    elif len(slabs) == 1:
        if not is_spectrum(slabs[0]):
            raise TypeError(
                "SerialSlabs takes an unfolded list of Spectrum as " +
                "argument: *list (got {0})".format(type(slabs[0])))
        return slabs[0]

    else:
        # recursively calculate serial slabs
        slabs = list(slabs)

        # Recursively deal with the rest of Spectra --> call it s
        sn = slabs.pop(-1)  # type: Spectrum
        _check_valid(sn)  # check it is a spectrum
        s = SerialSlabs(*slabs,
                        resample=resample_wavespace,
                        out=out_of_bounds,
                        modify_inputs=modify_inputs)

        # Now calculate sn and s in Serial
        quantities = {}
        unitsn = sn.units

        # make sure we use the same wavespace type (even if sn is in 'nm' and s in 'cm-1')
        # also make sure we use the same units
        waveunit = s.get_waveunit()

        # Make all our slabs copies with the same wavespace range
        # (note: wavespace range may be different for different quantities, but
        # equal for all slabs)
        s, sn = resample_slabs(waveunit, resample_wavespace, out_of_bounds,
                               modify_inputs, s, sn)
        try:
            w = s._q["wavespace"]
        except KeyError:
            raise KeyError(
                "Cannot calculate the RTE if non convoluted quantities " +
                "are not defined. Got: {0}".format(s.get_vars()))

        # Get all data
        # -------------

        I, In, T, Tn = None, None, None, None

        # To make it easier, the radiative transfer equation is solved with 'radiance_noslit' and
        # 'transmittance_noslit' only. Here we first try to get these quantities:

        # ... get sn quantities
        try:
            sn.update("transmittance_noslit", verbose=verbose)
        except ValueError:
            pass
        else:
            Tn = sn.get(
                "transmittance_noslit",
                wunit=waveunit,
                Iunit=unitsn["transmittance_noslit"],
                copy=False,
            )[1]
        try:
            sn.update("radiance_noslit", verbose=verbose)
        except ValueError:
            pass
        else:
            In = sn.get(
                "radiance_noslit",
                wunit=waveunit,
                Iunit=unitsn["radiance_noslit"],
                copy=False,
            )[1]
        # ... get s quantities
        try:
            s.update("transmittance_noslit", verbose=verbose)
        except ValueError:
            pass
        else:
            T = s.get(
                "transmittance_noslit",
                wunit=waveunit,
                Iunit=unitsn["transmittance_noslit"],
                copy=False,
            )[1]
        try:
            s.update("radiance_noslit", verbose=verbose)
        except ValueError:
            pass
        else:
            I = s.get(
                "radiance_noslit",
                wunit=waveunit,
                Iunit=unitsn["radiance_noslit"],
                copy=False,
            )[1]

        # Solve radiative transfer equation
        # ---------------------------------

        if I is not None and In is not None:
            # case where we may use SerialSlabs just to compute the products of all transmittances
            quantities["radiance_noslit"] = (w, I * Tn + In)

        if T is not None:  # note that we dont need the transmittance in the inner
            # slabs to calculate the total radiance
            quantities["transmittance_noslit"] = (w, Tn * T)

        # Get conditions (if they're different, fill with 'N/A')
        conditions = intersect(s.conditions, sn.conditions)
        conditions["waveunit"] = waveunit
        # sum path lengths
        if "path_length" in s.conditions and "path_length" in sn.conditions:
            conditions["path_length"] = (s.conditions["path_length"] +
                                         sn.conditions["path_length"])

        cond_units = intersect(s.cond_units, sn.cond_units)

        # name
        name = _serial_slab_names(s, sn)

        return Spectrum(
            quantities=quantities,
            conditions=conditions,
            cond_units=cond_units,
            units=unitsn,
            name=name,
            warnings=
            False,  # we already know waveranges are properly spaced, etc.
        )
Пример #8
0
def concat_spectra(s1, s2, var=None):
    """Concatenate two spectra ``s1`` and ``s2`` side by side.

    Note: their spectral range should not overlap

    Returns
    -------

    s: Spectrum
        Spectrum object with the same units and waveunits as ``s1``

    Parameters
    ----------

    s1, s2: Spectrum objects
        Spectrum you want to concatenate
    var: str
        quantity to manipulate: 'radiance', 'transmittance', ... If ``None``,
        get the unique spectral quantity of ``s1``, or the unique spectral
        quantity of ``s2``, or raises an error if there is any ambiguity

    Notes
    -----

    .. warning::

        the output Spectrum has the sum of the spectral ranges of s1 and s2.
        It won't be evenly spaced. This means that you cannot apply a slit without
        side effects. Typically, you want to use this function for convolved
        quantities only, such as experimental spectra. Else, use
        :func:`~radis.los.slabs.MergeSlabs` with the options
        ``resample='full', out='transparent'``

    See Also
    --------

    :func:`~radis.spectrum.operations.add_spectra`,
    :func:`~radis.los.slabs.MergeSlabs`

    """

    # Get variable
    if var is None:
        try:
            var = _get_unique_var(
                s2, var, inplace=False)  # unique variable of 2nd spectrum
        except KeyError:
            var = _get_unique_var(
                s1, var, inplace=False
            )  # if doesnt exist, unique variable of 1st spectrum
            # if it fails, let it fail
    # Make sure it is in both Spectra
    if var not in s1.get_vars():
        raise KeyError("Variable {0} not in Spectrum {1}".format(
            var, s1.get_name()))
    if var not in s2.get_vars():
        raise KeyError("Variable {0} not in Spectrum {1}".format(
            var, s1.get_name()))

    if var in ["transmittance_noslit", "transmittance"]:
        warn(
            "It does not make much physical sense to sum transmittances. Are "
            +
            "you sure of what you are doing? See also // (MergeSlabs) and > " +
            "(SerialSlabs)")

    # Use same units
    Iunit1 = s1.units[var]
    wunit1 = s1.get_waveunit()

    # Get the value, on the same wunit)
    w1, I1 = s1.get(var=var,
                    copy=False)  # @dev: faster to just get the stored value.
    # it's copied in hstack() below anyway).
    w2, I2 = s2.get(var=var, Iunit=Iunit1, wunit=wunit1)

    if not (w1.max() < w2.min() or w2.max() > w1.min()):
        raise ValueError(
            "You cannot use concat_spectra for overlapping spectral ranges. " +
            "Got: {0:.2f}-{1:.2f} and {2:.2f}-{3:.2f} {4}. ".format(
                w1.min(), w1.max(), w2.min(), w2.max(), wunit1) +
            "Use MergeSlabs instead, with the correct `out=` parameter " +
            "for your case")

    w_tot = hstack((w1, w2))
    I_tot = hstack((I1, I2))

    name = s1.get_name() + "&" + s2.get_name()  # use "&" instead of "+"

    concat = Spectrum.from_array(w_tot,
                                 I_tot,
                                 var,
                                 waveunit=wunit1,
                                 unit=Iunit1,
                                 name=name)

    return concat
Пример #9
0
def substract_spectra(s1, s2, var=None):
    """Return a new spectrum with ``s2`` substracted from ``s1``.
    Equivalent to::

        s1 - s2

    Parameters
    ----------

    s1, s2: Spectrum objects
        Spectrum you want to substract
    var: str
        quantity to manipulate: 'radiance', 'transmittance', ... If ``None``,
        get the unique spectral quantity of ``s1``, or the unique spectral
        quantity of ``s2``, or raises an error if there is any ambiguity

    Returns
    -------

    s: Spectrum
        Spectrum object with the same units and waveunits as ``s1``

    See Also
    --------

    :func:`~radis.spectrum.operations.add_spectra`

    """

    # Get variable
    if var is None:
        try:
            var = _get_unique_var(
                s2, var, inplace=False)  # unique variable of 2nd spectrum
        except KeyError:
            var = _get_unique_var(
                s1, var, inplace=False
            )  # if doesnt exist, unique variable of 1st spectrum
            # if it fails, let it fail
    # Make sure it is in both Spectra
    if var not in s1.get_vars():
        raise KeyError("Variable {0} not in Spectrum {1}".format(
            var, s1.get_name()))
    if var not in s2.get_vars():
        raise KeyError("Variable {0} not in Spectrum {1}".format(
            var, s1.get_name()))

    # Use same units
    Iunit1 = s1.units[var]
    wunit1 = s1.get_waveunit()

    # Resample s2 on s1
    s2 = s2.resample(s1, inplace=False)

    # Substract
    w1, I1 = s1.get(var=var, Iunit=Iunit1, wunit=wunit1)
    w2, I2 = s2.get(var=var, Iunit=Iunit1, wunit=wunit1)

    name = s1.get_name() + "-" + s2.get_name()

    sub = Spectrum.from_array(w1,
                              I1 - I2,
                              var,
                              waveunit=wunit1,
                              unit=Iunit1,
                              name=name)
    #    warn("Conditions of the left spectrum were copied in the substraction.", Warning)
    return sub
Пример #10
0
def SerialSlabs(*slabs, **kwargs):
    # type: (*Spectrum, **dict) -> Spectrum
    ''' Adds several slabs along the line-of-sight. 
    You can also use::
        
        s1>s2>s3 


    Parameters    
    ----------

    slabs: list of Spectra, each representing a slab
        line-of-sight::
                
            slabs       [0]     [1]  ............... [n]     
                         :       :                    :         \====
            light        *   ->  *        ->          *    ->    )===  observer
                                                                /====

    resample_wavespace: ``'never'``, ``'intersect'``, ``'full'``
        what to do when spectra have different wavespaces:
            
        - If ``'never'``, raises an error
        - If ``'intersect'``, uses the intersection of all ranges, and resample
          spectra on the most resolved wavespace. 
        - If ``'full``', uses the overlap of all ranges, resample spectra on the 
          most resolved wavespace, and fill missing data with 0 emission and 0
          absorption
        
        Default ``'never'``

    out: ``'transparent'``, ``'nan'``, ``'error'``
        what to do if resampling is out of bounds:
            
        - ``'transparent'``: fills with transparent medium. 
        - ``'nan'``: fills with nan. 
        - ``'error'``: raises an error. 
        
        Default ``'nan'``


    Returns
    -------

    Spectrum object representing total emission and total transmittance as 
    observed at the output (slab[n+1]). Conditions and units are transported too,
    unless there is a mismatch then conditions are dropped (and units mismatch
    raises an error because it doesnt make sense)

    Examples
    --------

    Add s1 and s2 along the line of sight: s1 --> s2 ::

        s1 = calc_spectrum(...)
        s2 = calc_spectrum(...)
        s3 = SerialSlabs(s1, s2)
        
    The last line is equivalent to::
        
        s3 = s1>s2

    Notes
    -----

    Todo:

    - rewrite with 'recompute' list like in MergeSlabs


    See Also
    --------

    :func:`~radis.los.slabs.MergeSlabs`

    See more examples in :ref:`Line-of-Sight module <label_los_index>`
    
    '''

    if 'resample_wavespace' in kwargs:
        warn(
            DeprecationWarning(
                "'resample_wavespace' replaced with 'resample'"))
        kwargs['resample'] = kwargs.pop('resample_wavespace')
    if 'out_of_bounds' in kwargs:
        warn(DeprecationWarning("'out_of_bounds' replaced with 'out'"))
        kwargs['out'] = kwargs.pop('out_of_bounds')

    # Check inputs, get defaults
    resample_wavespace = kwargs.pop('resample', 'never')  # default 'never'
    out_of_bounds = kwargs.pop('out', 'nan')  # default 'nan'
    if len(kwargs) > 0:
        raise ValueError('Unexpected input: {0}'.format(list(kwargs.keys())))

    if resample_wavespace not in ['never', 'intersect', 'full']:
        raise ValueError("resample should be one of: {0}".format(', '.join(
            ['never', 'intersect', 'full'])))

    if len(slabs) == 0:
        raise ValueError('Empty list of slabs')

    elif len(slabs) == 1:
        if not is_spectrum(slabs[0]):
            raise TypeError(
                'SerialSlabs takes an unfolded list of Spectrum as ' +
                'argument: *list (got {0})'.format(type(slabs[0])))
        return slabs[0]

    else:
        # recursively calculate serial slabs
        slabs = list(slabs)

        #        # Check all items are Spectrum
        for s in slabs:
            _check_valid(s)

        # Recursively deal with the rest of Spectra --> call it s
        sn = slabs.pop(-1)  # type: Spectrum
        s = SerialSlabs(*slabs, resample=resample_wavespace, out=out_of_bounds)

        # Now calculate sn and s in Serial
        quantities = {}
        unitsn = sn.units

        # make sure we use the same wavespace type (even if sn is in 'nm' and s in 'cm-1')
        # also make sure we use the same units
        waveunit = s.get_waveunit()

        # Make all our slabs copies with the same wavespace range
        # (note: wavespace range may be different for different quantities, but
        # equal for all slabs)
        s, sn = resample_slabs(waveunit, resample_wavespace, out_of_bounds, s,
                               sn)
        try:
            w = s._q['wavespace']
        except KeyError:
            raise KeyError('Cannot calculate the RTE if non convoluted quantities '+\
                           'are not defined. Got: {0}'.format(s.get_vars()))

        # Get all data
        # -------------

        I, In, T, Tn = None, None, None, None

        # To make it easier, the radiative transfer equation is solved with 'radiance_noslit' and
        # 'transmittance_noslit' only. Here we first try to get these quantities:

        # ... get sn quantities
        try:
            sn.update('transmittance_noslit', verbose=False)
        except ValueError:
            pass
        else:
            Tn = sn.get('transmittance_noslit',
                        wunit=waveunit,
                        Iunit=unitsn['transmittance_noslit'])[1]
        try:
            sn.update('radiance_noslit', verbose=False)
        except ValueError:
            pass
        else:
            In = sn.get('radiance_noslit',
                        wunit=waveunit,
                        Iunit=unitsn['radiance_noslit'])[1]
        # ... get s quantities
        try:
            s.update('transmittance_noslit', verbose=False)
        except ValueError:
            pass
        else:
            T = s.get('transmittance_noslit',
                      wunit=waveunit,
                      Iunit=unitsn['transmittance_noslit'])[1]
        try:
            s.update('radiance_noslit', verbose=False)
        except ValueError:
            pass
        else:
            I = s.get('radiance_noslit',
                      wunit=waveunit,
                      Iunit=unitsn['radiance_noslit'])[1]

        # Solve radiative transfer equation
        # ---------------------------------

        if I is not None and In is not None:
            # case where we may use SerialSlabs just to compute the products of all transmittances
            quantities['radiance_noslit'] = (w, I * Tn + In)

        if T is not None:  # note that we dont need the transmittance in the inner
            # slabs to calculate the total radiance
            quantities['transmittance_noslit'] = (w, Tn * T)

        # Get conditions (if they're different, fill with 'N/A')
        conditions = intersect(s.conditions, sn.conditions)
        conditions['waveunit'] = waveunit
        # sum path lengths
        if 'path_length' in s.conditions and 'path_length' in sn.conditions:
            conditions['path_length'] = s.conditions[
                'path_length'] + sn.conditions['path_length']

        cond_units = intersect(s.cond_units, sn.cond_units)

        # name
        name = _serial_slab_names(s, sn)

        return Spectrum(quantities=quantities,
                        conditions=conditions,
                        cond_units=cond_units,
                        units=unitsn,
                        name=name)
Пример #11
0
def test_broadening(rtol=1e-2, verbose=True, plot=False, *args, **kwargs):
    '''
    Test broadening against HAPI and tabulated data

    We're looking at CO(0->1) line 'R1' at 2150.86 cm-1
    '''
    from radis.io.hapi import db_begin, fetch, tableList, absorptionCoefficient_Voigt

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

    setup_test_line_databases()  # add HITRAN-CO-TEST in ~/.radis if not there

    # Conditions
    T = 3000
    p = 0.0001
    wstep = 0.001
    wmin = 2150  # cm-1
    wmax = 2152  # cm-1
    broadening_max_width = 10  # cm-1

    # %% HITRAN calculation
    # -----------

    # Generate HAPI database locally
    db_begin(join(dirname(__file__), __file__.replace('.py', '_HAPIdata')))
    if not 'CO' in tableList():  # only if data not downloaded already
        fetch('CO', 5, 1, wmin - broadening_max_width / 2,
              wmax + broadening_max_width / 2)
        # HAPI doesnt correct for side effects

    # Calculate with HAPI
    nu, coef = absorptionCoefficient_Voigt(
        SourceTables='CO',
        Environment={
            'T': T,  # K
            'p': p / 1.01325,  # atm
        },
        WavenumberStep=wstep,
        HITRAN_units=False)

    s_hapi = Spectrum.from_array(nu,
                                 coef,
                                 'abscoeff',
                                 'cm-1',
                                 'cm_1',
                                 conditions={'Tgas': T},
                                 name='HAPI')

    # %% Calculate with RADIS
    # ----------
    sf = SpectrumFactory(
        wavenum_min=wmin,
        wavenum_max=wmax,
        mole_fraction=1,
        path_length=1,  # doesnt change anything
        wstep=wstep,
        pressure=p,
        broadening_max_width=broadening_max_width,
        isotope=[1],
        warnings={
            'MissingSelfBroadeningWarning': 'ignore',
            'NegativeEnergiesWarning': 'ignore',
            'HighTemperatureWarning': 'ignore',
            'GaussianBroadeningWarning': 'ignore'
        })  # 0.2)
    sf.load_databank('HITRAN-CO-TEST')
    #    s = pl.non_eq_spectrum(Tvib=T, Trot=T, Ttrans=T)
    s = sf.eq_spectrum(Tgas=T, name='RADIS')

    if plot:  # plot broadening of line of largest linestrength
        sf.plot_broadening(i=sf.df1.S.idxmax())

    # Plot and compare
    res = abs(get_residual_integral(s, s_hapi, 'abscoeff'))
    if plot:
        plot_diff(s,
                  s_hapi,
                  var='abscoeff',
                  title='{0} bar, {1} K (residual {2:.2g}%)'.format(
                      p, T, res * 100),
                  show_points=False)
        plt.xlim((wmin, wmax))
    if verbose:
        printm('residual:', res)
    assert res < rtol
Пример #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
Пример #13
0
def SerialSlabs(*slabs, **kwargs):
    ''' Compute the result of several slabs 

    
    Parameters    
    ----------
    
    slabs: list of Spectra, each representing a slab
        slabs       [0]     [1]  ............... [n]     
                     :       :                    :         \====
        light        *   ->  *        ->          *    ->    )===  observer
                                                            /====
            
    resample_wavespace: 'never', 'intersect', 'full'
        what to do when spectra have different wavespaces. 
        - If 'never', raises an error
        - If 'intersect', uses the intersection of all ranges, and resample
        spectra on the most resolved wavespace. 
        - If 'full', uses the overlap of all ranges, resample spectra on the 
        most resolved wavespace, and fill missing data with 0 emission and 0
        absorption
        Default 'never'
            
    out_of_bounds: 'transparent', 'nan', 'error'
        what to do if resampling is out of bounds. 'transparent': fills with 
        transparent medium. 'nan': fills with nan. 'error': raises an error. 
        Default 'nan'
                       

    Returns
    -------
    
    Spectrum object representing total emission and total transmittance as 
    observed at the output (slab[n+1]). Conditions and units are transported too,
    unless there is a mismatch then conditions are dropped (and units mismatch
    raises an error because it doesnt make sense)
    
    Examples
    --------
    
    Add s1 and s2 along the line of sight: s1 --> s2 ::
    
        s1 = calc_spectrum(...)
        s2 = calc_spectrum(...)
        s3 = SerialSlabs(s1, s2)
        
    Notes
    -----
    
    Todo:
    
    - rewrite with 'recompute' list like in MergeSlabs
    
    
    See Also
    --------
    
    :func:`~radis.los.slabs.MergeSlabs`
    
    '''

    # Check inputs, get defaults
    resample_wavespace = kwargs.pop('resample_wavespace',
                                    'never')  # default 'never'
    out_of_bounds = kwargs.pop('out_of_bounds', 'nan')  # default 'nan'
    if len(kwargs) > 0:
        raise ValueError('Unexpected input: {0}'.format(list(kwargs.keys())))

    if resample_wavespace not in ['never', 'intersect', 'full']:
        raise ValueError("resample_wavespace should be one of: {0}".format(
            ', '.join(['never', 'intersect', 'full'])))

    if len(slabs) == 0:
        raise ValueError('Empty list of slabs')

    elif len(slabs) == 1:
        if not is_spectrum(slabs[0]):
            raise TypeError('SerialSlabs takes an unfolded list of Spectrum as '+\
                                 'argument: *list (got {0})'.format(type(slabs[0])))
        return slabs[0]

    else:
        # recursively calculate serial slabs
        slabs = list(slabs)

        #        # Check all items are Spectrum
        for s in slabs:
            _check_valid(s)

        sn = slabs.pop(-1)  # type: Spectrum
        s = SerialSlabs(*slabs,
                        resample_wavespace=resample_wavespace,
                        out_of_bounds=out_of_bounds)

        quantities = {}
        unitsn = sn.units

        # make sure we use the same wavespace type (even if sn is in 'nm' and s in 'cm-1')
        # also make sure we use the same units
        waveunit = s.get_waveunit()
        w = s.get('radiance_noslit',
                  wunit=waveunit,
                  Iunit=unitsn['radiance_noslit'])[0]
        wn = sn.get('radiance_noslit',
                    wunit=waveunit,
                    Iunit=unitsn['radiance_noslit'])[0]

        # Make all our slabs copies with the same wavespace range
        # (note: wavespace range may be different for different quantities, but
        # equal for all slabs)
        s, sn = resample_slabs(waveunit, resample_wavespace, out_of_bounds, s,
                               sn)

        w, I = s.get('radiance_noslit',
                     wunit=waveunit,
                     Iunit=unitsn['radiance_noslit'])
        wn, In = sn.get('radiance_noslit',
                        wunit=waveunit,
                        Iunit=unitsn['radiance_noslit'])
        _, Tn = sn.get('transmittance_noslit',
                       wunit=waveunit,
                       Iunit=unitsn['transmittance_noslit'])

        if 'radiance_noslit' in s._q and 'radiance_noslit' in sn._q:
            # case where we may use SerialSlabs just to compute the products of all transmittances
            quantities['radiance_noslit'] = (w, I * Tn + In)

        if 'transmittance_noslit' in s._q:  # note that we dont need the transmittance in the inner
            # slabs to calculate the total radiance
            _, T = s.get('transmittance_noslit',
                         wunit=waveunit,
                         Iunit=unitsn['transmittance_noslit'])
            quantities['transmittance_noslit'] = (w, Tn * T)

        # Get conditions (if they're different, fill with 'N/A')
        conditions = intersect(s.conditions, sn.conditions)
        conditions['waveunit'] = waveunit

        cond_units = intersect(s.cond_units, sn.cond_units)

        # name
        name = _serial_slab_names(s, sn)

        return Spectrum(quantities=quantities,
                        conditions=conditions,
                        cond_units=cond_units,
                        units=unitsn,
                        name=name)
Пример #14
0
def MergeSlabs(*slabs, **kwargs):
    ''' Combines several slabs into one. Useful to calculate multi-gas slabs. 
    Linear absorption coefficient is calculated as the sum of all linear absorption
    coefficients, and the RTE is recalculated to get the total radiance

    
    Parameters    
    ----------
    
    slabs: list of Spectra, each representing a slab
        If given in conditions, all path_length have to be same

    Other Parameters
    ----------------
    
    kwargs input:

    resample_wavespace: 'never', 'intersect', 'full'
        what to do when spectra have different wavespaces. 
        - If 'never', raises an error
        - If 'intersect', uses the intersection of all ranges, and resample
        spectra on the most resolved wavespace. 
        - If 'full', uses the overlap of all ranges, resample spectra on the 
        most resolved wavespace, and fill missing data with 0 emission and 0
        absorption
        Default 'never'
        
    out_of_bounds: 'transparent', 'nan', 'error'
        what to do if resampling is out of bounds. 'transparent': fills with 
        transparent medium. 'nan': fills with nan. 'error': raises an error. 
        Default 'nan'

    optically_thin: boolean
        if True, merge slabs in optically thin mode. Default False 
                            
    verbose: boolean
        if True, print messages and warnings. Default True
        

    Returns
    -------
    
    Spectrum object representing total emission and total transmittance as 
    observed at the output. Conditions and units are transported too,
    unless there is a mismatch then conditions are dropped (and units mismatch
    raises an error because it doesnt make sense)
    
    
    Examples
    --------
    
    Merge two spectra calculated with different species (true only if broadening
    coefficient dont change much):
    
        >>> from radis import calc_spectrum, MergeSlabs
        >>> s1 = calc_spectrum(...)
        >>> s2 = calc_spectrum(...)
        >>> s3 = MergeSlabs(s1, s2)
        
    Load a spectrum precalculated on several partial spectral ranges, for a same 
    molecule (i.e, partial spectra are optically thin on the rest of the spectral 
    range)
    
        >>> from radis import load_spec, MergeSlabs
        >>> spectra = []
        >>> for f in ['spec1.spec', 'spec2.spec', ...]:
        >>>     spectra.append(load_spec(f))
        >>> s = MergeSlabs(*spectra, resample_wavespace='full', out_of_bounds='transparent')
        >>> s.update()   # Generate missing spectral quantities
        >>> s.plot()
        
        
    See Also
    --------
    
    :func:`~radis.los.slabs.SerialSlabs`
    '''

    # inputs (Python 2 compatible)
    resample_wavespace = kwargs.pop('resample_wavespace',
                                    'never')  # default 'never'
    out_of_bounds = kwargs.pop('out_of_bounds', 'nan')  # default 'nan'
    optically_thin = kwargs.pop('optically_thin', False)  # default False
    verbose = kwargs.pop('verbose', True)  # type: bool
    debug = kwargs.pop('debug', False)  # type: bool
    if len(kwargs) > 0:
        raise ValueError('Unexpected input: {0}'.format(list(kwargs.keys())))

    # Check inputs
    if resample_wavespace not in ['never', 'intersect', 'full']:
        raise ValueError("resample_wavespace should be one of: {0}".format(
            ', '.join(['never', 'intersect', 'full'])))

    if len(slabs) == 0:
        raise ValueError('Empty list of slabs')

    elif len(slabs) == 1:
        if not is_spectrum(slabs[0]):
            raise TypeError('MergeSlabs takes an unfolded list of Spectrum as '+\
                                 'argument: (got {0})'.format(type(slabs[0])))
        return slabs[0]

    else:  # calculate serial slabs

        slabs = list(slabs)

        #        # Check all items are valid Spectrum objects
        for s in slabs:
            _check_valid(s)

        # Just check all path_lengths are the same if they exist
        path_lengths = [
            s.conditions['path_length'] for s in slabs
            if 'path_length' in s.conditions
        ]
        if not all([L == path_lengths[0] for L in path_lengths[1:]]):
            raise ValueError('path_length must be equal for all MergeSlabs inputs'+\
                             '  (got {0})'.format(path_lengths))

        # make sure we use the same wavespace type (even if sn is in 'nm' and s in 'cm-1')
        waveunit = slabs[0].get_waveunit()
        # Make all our slabs copies with the same wavespace range
        # (note: wavespace range may be different for different quantities, but
        # equal for all slabs)
        slabs = resample_slabs(waveunit, resample_wavespace, out_of_bounds,
                               *slabs)
        w_noconv = slabs[0]._get_wavespace()

        # %%

        # Get conditions
        conditions = slabs[0].conditions
        conditions['waveunit'] = waveunit
        cond_units = slabs[0].cond_units
        units0 = slabs[0].units
        for s in slabs[1:]:
            conditions = intersect(conditions, s.conditions)
            cond_units = intersect(cond_units, s.cond_units)
            #units = intersect(units0, s.units)  # we're actually using [slabs0].units insteads

        # %% Get quantities that should be calculated

        requested = merge_lists([s.get_vars() for s in slabs])
        recompute = requested[:]  #  copy
        if ('radiance_noslit' in requested and not optically_thin):
            recompute.append('emisscoeff')
            recompute.append('abscoeff')
        if 'abscoeff' in recompute and 'path_length' in conditions:
            recompute.append('absorbance')
            recompute.append('transmittance_noslit')

        # To make it easier, we start from abscoeff and emisscoeff of all slabs
        # Let's recompute them all
        # TODO: if that changes the initial Spectra, maybe we should just work on copies
        for s in slabs:
            if 'abscoeff' in recompute and not 'abscoeff' in list(s._q.keys()):
                s.update('abscoeff')
                # that may crash if Spectrum doesnt have the correct inputs.
                # let update() handle that
            if 'emisscoeff' in recompute and not 'emisscoeff' in list(
                    s._q.keys()):
                s.update('emisscoeff')
                # same

        path_length = conditions['path_length']

        # %% Calculate new quantites from emisscoeff and abscoeff
        # TODO: rewrite all of the above with simple calls to .update()
        added = {}

        # ... absorption coefficient (cm-1)
        if 'abscoeff' in recompute:
            #TODO: deal with all cases
            if __debug__:
                printdbg('... merge: calculating abscoeff k=sum(k_i)')
            abscoeff_eq = np.sum([
                s.get('abscoeff', wunit=waveunit, Iunit=units0['abscoeff'])[1]
                for s in slabs
            ],
                                 axis=0)
            assert len(w_noconv) == len(abscoeff_eq)
            added['abscoeff'] = (w_noconv, abscoeff_eq)

        if 'absorbance' in recompute:
            if 'abscoeff' in added:
                if __debug__:
                    printdbg('... merge: calculating absorbance A=k*L')
                _, abscoeff_eq = added['abscoeff']
                absorbance_eq = abscoeff_eq * path_length
            else:
                raise NotImplementedError('recalculate abscoeff first')
            added['absorbance'] = (w_noconv, absorbance_eq)

        # ... transmittance
        if 'transmittance_noslit' in recompute:
            if 'absorbance' in added:
                if __debug__:
                    printdbg('... merge: calculating transmittance T=exp(-A)')
                _, absorbance_eq = added['absorbance']
                transmittance_noslit_eq = exp(-absorbance_eq)
            else:
                raise NotImplementedError('recalculate absorbance first')
            added['transmittance_noslit'] = (w_noconv, transmittance_noslit_eq)

        # ... emission coefficient
        if 'emisscoeff' in recompute:
            emisscoeff_eq = np.zeros_like(w_noconv)
            for i, s in enumerate(slabs):
                # Manual loop in case all Slabs dont have the same keys
                # Could also do a slab.update() first then sum emisscoeff directly
                if 'emisscoeff' in list(s._q.keys()):
                    if __debug__:
                        printdbg('... merge: calculating emisscoeff: j+=j_i')
                    _, emisscoeff = s.get('emisscoeff',
                                          wunit=waveunit,
                                          Iunit=units0['emisscoeff'])
                elif optically_thin and 'radiance_noslit' in list(s._q.keys()):
                    if __debug__:                        printdbg('... merge: calculating emisscoeff: j+=I_i/L '+\
                          '(optically thin case)')
                    _, I = s.get('radiance_noslit',
                                 wunit=waveunit,
                                 Iunit=units0['radiance_noslit'])
                    emisscoeff = I / path_length
                    emisscoeff_eq += emisscoeff
                else:
                    wI, I = s.get('radiance_noslit',
                                  wunit=waveunit,
                                  Iunit=units0['radiance_noslit'])
                    if __debug__:
                        printdbg(
                            '... merge: calculating emisscoeff j+=[k*I/(1-T)]_i)'
                        )
                    try:
                        wT, T = s.get('transmittance_noslit',
                                      wunit=waveunit,
                                      Iunit=units0['transmittance_noslit'])
                        wk, k = s.get('abscoeff',
                                      wunit=waveunit,
                                      Iunit=units0['abscoeff'])
                    except KeyError:
                        raise KeyError('Need transmittance_noslit and abscoeff to '+\
                                       'recompute emission coefficient')
                    b = (T == 1)  # optically thin mask
                    emisscoeff = np.zeros_like(T)
                    emisscoeff[b] = I[b] / path_length  # optically thin case
                    emisscoeff[~b] = I[~b] / (1 - T[~b]) * k[~b]
                emisscoeff_eq += emisscoeff
            added['emisscoeff'] = (w_noconv, emisscoeff_eq)

        # ... derive global radiance (result of analytical RTE solving)
        if 'radiance_noslit' in recompute:
            if 'emisscoeff' in added and optically_thin:
                if __debug__:
                    printdbg(
                        '... merge: recalculating radiance_noslit I=j*L(optically_thin)'
                    )
                (_, emisscoeff_eq) = added['emisscoeff']
                radiance_noslit_eq = emisscoeff_eq * path_length  # optically thin
            elif ('emisscoeff' in added and 'transmittance_noslit' in added
                  and 'abscoeff' in added):
                if __debug__:
                    printdbg(
                        '... merge: recalculating radiance_noslit I=j/k*(1-T)')
                (_, emisscoeff_eq) = added['emisscoeff']
                (_, abscoeff_eq) = added['abscoeff']
                (_, transmittance_noslit_eq) = added['transmittance_noslit']
                b = (abscoeff_eq == 0)  # optically thin mask
                radiance_noslit_eq = np.zeros_like(emisscoeff_eq)
                radiance_noslit_eq[
                    b] = emisscoeff_eq[b] * path_length  # optically thin limit
                radiance_noslit_eq[~b] = emisscoeff_eq[~b] / abscoeff_eq[
                    ~b] * (1 - transmittance_noslit_eq[~b])
            elif optically_thin:
                if __debug__:
                    printdbg(
                        '... merge: recalculating radiance_noslit I=sum(I_i) (optically thin)'
                    )
                radiance_noslit_eq = np.zeros_like(w_noconv)
                for s in slabs:
                    if 'radiance_noslit' in list(s._q.keys()):
                        radiance_noslit_eq += s.get(
                            'radiance_noslit',
                            wunit=waveunit,
                            Iunit=units0['radiance_noslit'])[1]
                    else:
                        raise KeyError('Need radiance_noslit for all slabs to '+\
                                       'recalculate for the MergeSlab (could also '+\
                                       'get it from emisscoeff but not implemented)')
            else:
                if optically_thin:
                    raise ValueError('Missing data to recalculate radiance_noslit'+\
                                     '. Try optically_thin mode?')
                else:
                    raise ValueError(
                        'Missing data to recalculate radiance_noslit')
            added['radiance_noslit'] = (w_noconv, radiance_noslit_eq)

        # ... emissivity no slit
        if 'emissivity_noslit' in requested:
            added['emissivity_noslit'] = w_noconv, np.ones_like(
                w_noconv) * np.nan
#            if verbose:
#                warn('emissivity dropped during MergeSlabs')
# Todo: deal with equilibrium cases?

# Store output
        quantities = {}
        for k in requested:
            quantities[k] = added[k]

        # name
        name = '//'.join([s.get_name() for s in slabs])

        # TODO: check units are consistent in all slabs inputs
        return Spectrum(quantities=quantities,
                        conditions=conditions,
                        cond_units=cond_units,
                        units=units0,
                        name=name)
Пример #15
0
def sPlanck(wavenum_min=None,
            wavenum_max=None,
            wavelength_min=None,
            wavelength_max=None,
            T=None,
            eps=1,
            wstep=0.01,
            medium="air",
            **kwargs):
    """Return a RADIS Spectrum object with blackbody radiation.

    It's easier to plug in a MergeSlabs / SerialSlabs config than the Planck
    radiance calculated by iPlanck. And you don't need to worry about units as
    they are handled internally.

    See radis.lbl.Spectrum documentation for more information

    Parameters
    ----------

    wavenum_min / wavenum_max: (cm-1)
        minimum / maximum wavenumber to be processed in cm^-1.

    wavelength_min / wavelength_max: (nm)
        minimum / maximum wavelength to be processed in nm

    T: float (K)
        blackbody temperature

    eps: float [0-1]
        blackbody emissivity. Default 1

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

    wstep: float (cm-1 or nm)
        wavespace step for calculation

    **kwargs: other keyword inputs
        all are forwarded to spectrum conditions. For instance you can add
        a 'path_length=1' after all the other arguments

    Example
    -------

    Generate Earth blackbody::

        s = sPlanck(wavelength_min=3000, wavelength_max=50000,
                    T=288, eps=1)
        s.plot()
    """

    from radis.spectrum.spectrum import Spectrum

    # Check inputs
    if (wavelength_min is not None
            or wavelength_max is not None) and (wavenum_min is not None
                                                or wavenum_max is not None):
        raise ValueError("You cannot give both wavelengths and wavenumbers")

    if wavenum_min is not None and wavenum_max is not None:
        assert wavenum_min < wavenum_max
        waveunit = "cm-1"
    else:
        assert wavelength_min < wavelength_max
        if medium == "air":
            waveunit = "nm"
        elif medium == "vacuum":
            waveunit = "nm_vac"
        else:
            raise ValueError(medium)

    if T is None:
        raise ValueError("T must be defined")

    if not (eps >= 0 and eps <= 1):
        raise ValueError("Emissivity must be in [0-1]")

    # Test range is correct:
    if waveunit == "cm-1":
        # generate the vector of wavenumbers (shape M)
        w = arange(wavenum_min, wavenum_max + wstep, wstep)
        Iunit = "mW/sr/cm2/cm-1"
        I = planck_wn(w, T, eps=eps, unit=Iunit)
    else:
        # generate the vector of lengths (shape M)
        w = arange(wavelength_min, wavelength_max + wstep, wstep)
        Iunit = "mW/sr/cm2/nm"
        if waveunit == "nm_vac":
            w_vac = w
        elif waveunit == "nm":
            w_vac = air2vacuum(w)
        # calculate planck with wavelengths in vacuum
        I = planck(w_vac, T, eps=eps, unit=Iunit)

    conditions = {"wstep": wstep}
    # add all extra parameters in conditions (ex: path_length)
    conditions.update(**kwargs)

    return Spectrum(
        quantities={
            "radiance_noslit": (w, I),
            "transmittance_noslit": (w, zeros_like(w)),
            "absorbance": (w, ones_like(w) * inf),
        },
        conditions=conditions,
        units={
            "radiance_noslit": Iunit,
            "transmittance_noslit": "",
            "absorbance": ""
        },
        cond_units={"wstep": waveunit},
        waveunit=waveunit,
        name="Planck {0}K, eps={1:.2g}".format(T, eps),
    )
Пример #16
0
def calculated_spectrum(w,
                        I,
                        wunit='nm',
                        Iunit='mW/cm2/sr/nm',
                        conditions=None,
                        cond_units=None,
                        populations=None,
                        name=None):  # -> Spectrum:
    ''' Convert (w, I) into a Spectrum object that has unit conversion, plotting
    and slit convolution capabilities


    Parameters    
    ----------

    w, I: np.array
        wavelength and intensity

    wunit: 'nm', 'cm-1'
        wavespace unit

    Iunit: str
        intensity unit (can be 'counts', 'mW/cm2/sr/nm', etc...). Default
        'mW/cm2/sr/nm' (note that non-convoluted Specair spectra are in 'mW/cm2/sr/µm')


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

    conditions: dict
        (optional) calculation conditions to be stored with Spectrum. Default ``None``

    cond_units: dict
        (optional) calculation conditions units. Default ``None``

    populations: dict
        populations to be stored in Spectrum. Default ``None``

    name: str
        (optional) give a name


    See Also
    --------

    :func:`~radis.spectrum.spectrum.transmittance_spectrum`, 
    :func:`~radis.spectrum.spectrum.experimental_spectrum`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_array`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_txt`,
    :func:`~radis.tools.database.load_spec`

    '''

    return Spectrum.from_array(np.array(w),
                               np.array(I),
                               'radiance_noslit',
                               waveunit=wunit,
                               unit=Iunit,
                               conditions=conditions,
                               cond_units=cond_units,
                               populations=populations,
                               name=name)
Пример #17
0
def _json_to_spec(jsondict, file=''):
    ''' Builds a Spectrum object from a JSON dictionary. Called by load_spec
    
    Parameters
    ----------
    
    jsondict: dict
        Spectrum object content stored under a dictonary 
    
    Returns
    -------    
    
    s: Spectrum
        a :class:`~radis.spectrum.spectrum.Spectrum` object
    '''

    # Test format / correct deprecated format:
    sload = _fix_format(file, jsondict)

    # ... Back to real stuff:
    conditions = sload['conditions']

    # Get quantities
    if 'quantities' in sload:
        # old format -saved with tuples (w,I) under 'quantities'): heavier, but
        # easier to generate a spectrum
        quantities = {
            k: (np.array(v[0]), array(v[1]))
            for (k, v) in sload['quantities'].items()
        }
        warn("File {0}".format(basename(file))+" has a deprecrated structure ("+\
              "quantities are stored with shared wavespace: uses less space). "+\
            "Regenerate database ASAP.", DeprecationWarning)
    else:
        quantities = {
            k: (sload['_q']['wavespace'], v)
            for k, v in sload['_q'].items() if k != 'wavespace'
        }
        quantities.update({
            k: (sload['_q_conv']['wavespace'], v)
            for k, v in sload['_q_conv'].items() if k != 'wavespace'
        })

    # Generate spectrum:
    waveunit = sload['conditions']['waveunit']

    # Only `quantities` and `conditions` is required. The rest is just extra
    # details
    kwargs = {}

    # ... load slit if exists
    if 'slit' in sload:
        slit = {k: np.array(v) for k, v in sload['slit'].items()}
    else:
        slit = {}

    # ... load lines if exist
    if 'lines' in sload:
        df = sload['lines']
        kwargs['lines'] = df
    else:
        kwargs['lines'] = None

    # ... load populations if exist
    if 'populations' in sload:

        # Fix some problems in json-tricks
        # ... cast isotopes to int  (getting sload['populations'] directly doesnt do that)
        kwargs['populations'] = {}
        for molecule, isotopes in sload['populations'].items():
            kwargs['populations'][molecule] = {}
            for isotope, states in isotopes.items():
                try:
                    isotope = int(isotope)
                except ValueError:
                    pass  # keep isotope as it was
                kwargs['populations'][molecule][isotope] = states

    else:
        kwargs['populations'] = None

    # ... load other properties if exist
    for attr in ['units', 'cond_units', 'name']:
        try:
            kwargs[attr] = sload[attr]
        except KeyError:
            kwargs[attr] = None

    s = Spectrum(quantities=quantities,
                 conditions=conditions,
                 waveunit=waveunit,
                 **kwargs)

    # ... add slit
    s._slit = slit

    return s
Пример #18
0
def test_broadening_vs_hapi(rtol=1e-2,
                            verbose=True,
                            plot=False,
                            *args,
                            **kwargs):
    """
    Test broadening against HAPI and tabulated data

    We're looking at CO(0->1) line 'R1' at 2150.86 cm-1
    """
    from hapi import absorptionCoefficient_Voigt, db_begin, fetch, tableList

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

    setup_test_line_databases()  # add HITRAN-CO-TEST in ~/.radis if not there

    # Conditions
    T = 3000
    p = 0.0001
    wstep = 0.001
    wmin = 2150  # cm-1
    wmax = 2152  # cm-1
    broadening_max_width = 10  # cm-1

    # %% HITRAN calculation
    # -----------

    # Generate HAPI database locally
    hapi_data_path = join(dirname(__file__),
                          __file__.replace(".py", "_HAPIdata"))

    db_begin(hapi_data_path)
    if not "CO" in tableList():  # only if data not downloaded already
        fetch("CO", 5, 1, wmin - broadening_max_width / 2,
              wmax + broadening_max_width / 2)
        # HAPI doesnt correct for side effects

    # Calculate with HAPI
    nu, coef = absorptionCoefficient_Voigt(
        SourceTables="CO",
        Environment={
            "T": T,
            "p": p / 1.01325,
        },  # K  # atm
        WavenumberStep=wstep,
        HITRAN_units=False,
    )

    s_hapi = Spectrum.from_array(nu,
                                 coef,
                                 "abscoeff",
                                 "cm-1",
                                 "cm-1",
                                 conditions={"Tgas": T},
                                 name="HAPI")

    # %% Calculate with RADIS
    # ----------
    sf = SpectrumFactory(
        wavenum_min=wmin,
        wavenum_max=wmax,
        mole_fraction=1,
        path_length=1,  # doesnt change anything
        wstep=wstep,
        pressure=p,
        broadening_max_width=broadening_max_width,
        isotope=[1],
        warnings={
            "MissingSelfBroadeningWarning": "ignore",
            "NegativeEnergiesWarning": "ignore",
            "HighTemperatureWarning": "ignore",
            "GaussianBroadeningWarning": "ignore",
        },
    )  # 0.2)
    sf.load_databank(path=join(hapi_data_path, "CO.data"),
                     format="hitran",
                     parfuncfmt="hapi")
    #    s = pl.non_eq_spectrum(Tvib=T, Trot=T, Ttrans=T)
    s = sf.eq_spectrum(Tgas=T, name="RADIS")

    if plot:  # plot broadening of line of largest linestrength
        sf.plot_broadening(i=sf.df1.S.idxmax())

    # Plot and compare
    res = abs(get_residual_integral(s, s_hapi, "abscoeff"))
    if plot:
        plot_diff(
            s,
            s_hapi,
            var="abscoeff",
            title="{0} bar, {1} K (residual {2:.2g}%)".format(p, T, res * 100),
            show_points=False,
        )
        plt.xlim((wmin, wmax))
    if verbose:
        printm("residual:", res)
    assert res < rtol
Пример #19
0
def sPlanck(wavenum_min=None,
            wavenum_max=None,
            wavelength_min=None,
            wavelength_max=None,
            T=None,
            eps=1,
            wstep=0.01,
            medium='air',
            **kwargs):
    ''' Return a RADIS Spectrum object with blackbody radiation. 
    
    It's easier to plug in a MergeSlabs / SerialSlabs config than the Planck
    radiance calculated by iPlanck. And you don't need to worry about units as
    they are handled internally.
    
    See neq.spec.Spectrum documentation for more information
    
    Parameters
    ----------
    
    wavenum_min / wavenum_max: (cm-1)
        minimum / maximum wavenumber to be processed in cm^-1. 

    wavelength_min / wavelength_max: (nm)
        minimum / maximum wavelength to be processed in nm

    T: float (K)
        blackbody temperature
        
    eps: float [0-1]
        blackbody emissivity. Default 1
    
    Other Parameters
    ----------------
    
    wstep: float (cm-1 or nm)
        wavespace step for calculation
        
    **kwargs: other keyword inputs
        all are forwarded to spectrum conditions. For instance you can add
        a 'path_length=1' after all the other arguments

    Example
    -------
    
    Generate Earth blackbody::
        
        s = sPlanck(wavelength_min=3000, wavelength_max=50000,
                    T=288, eps=1)
        s.plot()

    '''

    from radis.spectrum.spectrum import Spectrum

    # Check inputs
    if ((wavelength_min is not None or wavelength_max is not None)
            and (wavenum_min is not None or wavenum_max is not None)):
        raise ValueError('Wavenumber and Wavelength both given... you twart')

    if (wavenum_min is not None and wavenum_max is not None):
        assert (wavenum_min < wavenum_max)
        waveunit = 'cm-1'
    else:
        assert (wavelength_min < wavelength_max)
        waveunit = 'nm'

    if T is None:
        raise ValueError('T must be defined')

    if not (eps >= 0 and eps <= 1):
        raise ValueError('Emissivity must be in [0-1]')

    # Test range is correct:
    if waveunit == 'cm-1':
        w = arange(wavenum_min, wavenum_max + wstep,
                   wstep)  #generate the vector of wavenumbers (shape M)
        Iunit = 'mW/sr/cm2/cm_1'
        I = planck_wn(w, T, eps=eps, unit=Iunit)
    else:
        w = arange(wavelength_min, wavelength_max + wstep,
                   wstep)  #generate the vector of wavenumbers (shape M)
        Iunit = 'mW/sr/cm2/nm'
        I = planck(w, T, eps=eps, unit=Iunit)

    conditions = {'wstep': wstep, 'medium': medium}
    conditions.update(
        **kwargs)  # add all extra parameters in conditions (ex: path_length)

    return Spectrum(quantities={
        'radiance_noslit': (w, I),
        'transmittance_noslit': (w, zeros_like(w)),
        'absorbance': (w, ones_like(w) * inf)
    },
                    conditions=conditions,
                    units={
                        'radiance_noslit': Iunit,
                        'transmittance_noslit': 'I/I0',
                        'absorbance': '-ln(I/I0)'
                    },
                    cond_units={'wstep': waveunit},
                    waveunit=waveunit)
Пример #20
0
def experimental_spectrum(w,
                          I,
                          wunit="nm",
                          Iunit="counts",
                          conditions={},
                          cond_units=None,
                          name=None):  # -> Spectrum:
    """Convert ``(w, I)`` into a :py:class:`~radis.spectrum.spectrum.Spectrum`
    object that has unit conversion and plotting
    capabilities. Convolution is not available as the spectrum is assumed to
    have be measured experimentally (hence it is already convolved with the slit function)


    Parameters
    ----------

    w: np.array
        wavelength, or wavenumber

    I: np.array
        intensity

    wunit: ``'nm'``, ``'cm-1'``, ``'nm_vac'``
        wavespace unit: wavelength in air (``'nm'``), wavenumber
        (``'cm-1'``), or wavelength in vacuum (``'nm_vac'``). Default ``'nm'``.

    Iunit: str
        intensity unit (can be 'counts', 'mW/cm2/sr/nm', etc...). Default
        'counts' (default Winspec output)

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

    conditions: dict
        (optional) calculation conditions to be stored with Spectrum

    cond_units: dict
        (optional) calculation conditions units

    name: str
        (optional) give a name

    Examples
    --------

    Load and plot an experimental spectrum::

        from numpy import loadtxt
        from radis import experimental_spectrum
        w, I = loadtxt('my_file.txt').T    # assuming 2 columns
        s = experimental_spectrum(w, I, Iunit='mW/cm2/sr/nm')             # creates 'radiance'
        s.plot()


    See Also
    --------

    :func:`~radis.spectrum.models.calculated_spectrum`,
    :func:`~radis.spectrum.models.transmittance_spectrum`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_array`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_txt`,
    :func:`~radis.tools.database.load_spec`

    """

    if np.shape(w) != np.shape(I):
        raise ValueError(
            "Wavelength {0} and intensity {1} do not have the same shape".
            format(np.shape(w), np.shape(I)))
    return Spectrum.from_array(
        np.array(w),
        np.array(I),
        "radiance",
        waveunit=wunit,
        unit=Iunit,
        conditions=conditions,
        cond_units=cond_units,
        name=name,
    )
Пример #21
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
Пример #22
0
def sPlanck(
    wavenum_min=None,
    wavenum_max=None,
    wavelength_min=None,
    wavelength_max=None,
    T=None,
    eps=1,
    wstep=0.01,
    medium="air",
    **kwargs
):
    r"""Return a RADIS :py:class:`~radis.spectrum.spectrum.Spectrum` object with blackbody radiation.

    It's easier to plug in a :py:func:`~radis.los.slabs.SerialSlabs` line-of-sight than the Planck
    radiance calculated by :py:func:`~radis.phys.blackbody.planck`.
    And you don't need to worry about units as they are handled internally.

    See :py:class:`~radis.spectrum.spectrum.Spectrum` documentation for more information

    Parameters
    ----------
    wavenum_min / wavenum_max: ():math:`cm^{-1}`)
        minimum / maximum wavenumber to be processed in :math:`cm^{-1}`.
    wavelength_min / wavelength_max: (:math:`nm`)
        minimum / maximum wavelength to be processed in :math:`nm`.
    T: float (K)
        blackbody temperature
    eps: float [0-1]
        blackbody emissivity. Default ``1``

    Other Parameters
    ----------------
    wstep: float (cm-1 or nm)
        wavespace step for calculation
    **kwargs: other keyword inputs
        all are forwarded to spectrum conditions. For instance you can add
        a 'path_length=1' after all the other arguments

    Examples
    --------
    Generate Earth blackbody::

        s = sPlanck(wavelength_min=3000, wavelength_max=50000,
                    T=288, eps=1)
        s.plot()

    Examples using sPlanck :

    .. minigallery:: radis.sPlanck

    References
    ----------
    In wavelength:

    .. math::
        \epsilon \frac{2h c^2}{{\lambda}^5} \frac{1}{\operatorname{exp}\left(\frac{h c}{\lambda k T}\right)-1}

    In wavenumber:

    .. math::
        \epsilon 2h c^2 {\nu}^3 \frac{1}{\operatorname{exp}\left(\frac{h c \nu}{k T}\right)-1}

    See Also
    --------
    :py:func:`~radis.phys.blackbody.planck`, :py:func:`~radis.phys.blackbody.planck_wn`

    """
    from radis.spectrum.spectrum import Spectrum

    # Check inputs
    if (wavelength_min is not None or wavelength_max is not None) and (
        wavenum_min is not None or wavenum_max is not None
    ):
        raise ValueError("You cannot give both wavelengths and wavenumbers")

    if wavenum_min is not None and wavenum_max is not None:
        assert wavenum_min < wavenum_max
        waveunit = "cm-1"
    else:
        assert wavelength_min < wavelength_max
        if medium == "air":
            waveunit = "nm"
        elif medium == "vacuum":
            waveunit = "nm_vac"
        else:
            raise ValueError(medium)

    if T is None:
        raise ValueError("T must be defined")

    if not (eps >= 0 and eps <= 1):
        raise ValueError("Emissivity must be in [0-1]")

    # Test range is correct:
    if waveunit == "cm-1":
        # generate the vector of wavenumbers (shape M)
        w = arange(wavenum_min, wavenum_max + wstep, wstep)
        Iunit = "mW/sr/cm2/cm-1"
        I = planck_wn(w, T, eps=eps, unit=Iunit)
    else:
        # generate the vector of lengths (shape M)
        w = arange(wavelength_min, wavelength_max + wstep, wstep)
        Iunit = "mW/sr/cm2/nm"
        if waveunit == "nm_vac":
            w_vac = w
        elif waveunit == "nm":
            w_vac = air2vacuum(w)
        # calculate planck with wavelengths in vacuum
        I = planck(w_vac, T, eps=eps, unit=Iunit)

    conditions = {"wstep": wstep}
    # add all extra parameters in conditions (ex: path_length)
    conditions.update(**kwargs)

    return Spectrum(
        quantities={
            "radiance_noslit": (w, I),
            "transmittance_noslit": (w, zeros_like(w)),
            "absorbance": (w, ones_like(w) * inf),
        },
        conditions=conditions,
        units={"radiance_noslit": Iunit, "transmittance_noslit": "", "absorbance": ""},
        cond_units={"wstep": waveunit},
        waveunit=waveunit,
        name="Planck {0}K, eps={1:.2g}".format(T, eps),
    )
Пример #23
0
def transmittance_spectrum(w,
                           T,
                           wunit="nm",
                           Tunit="",
                           conditions=None,
                           cond_units=None,
                           name=None):  # -> Spectrum:
    """Convert ``(w, I)`` into a :py:class:`~radis.spectrum.spectrum.Spectrum`
    object that has unit conversion, plotting and slit convolution capabilities


    Parameters
    ----------

    w: np.array
        wavelength, or wavenumber

    T: np.array
        transmittance (no slit)

    wunit: ``'nm'``, ``'cm-1'``, ``'nm_vac'``
        wavespace unit: wavelength in air (``'nm'``), wavenumber
        (``'cm-1'``), or wavelength in vacuum (``'nm_vac'``). Default ``'nm'``.

    Iunit: str
        intensity unit. Default ``""`` (adimensionned)


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

    conditions: dict
        (optional) calculation conditions to be stored with Spectrum

    cond_units: dict
        (optional) calculation conditions units

    name: str
        (optional) give a name


    Examples
    --------

    ::

        # w, T are numpy arrays for wavelength and transmittance
        from radis import transmittance_spectrum
        s2 = transmittance_spectrum(w, T, wunit='nm')                       # creates 'transmittance_noslit'



    See Also
    --------

    :func:`~radis.spectrum.models.calculated_spectrum`,
    :func:`~radis.spectrum.models.experimental_spectrum`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_array`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_txt`,
    :func:`~radis.tools.database.load_spec`

    """

    return Spectrum.from_array(
        np.array(w),
        np.array(T),
        "transmittance_noslit",
        waveunit=wunit,
        unit=Tunit,
        conditions=conditions,
        cond_units=cond_units,
        name=name,
    )
Пример #24
0
def add_spectra(s1, s2, var=None, force=False):
    """Return a new spectrum with ``s2`` added to ``s1``.
    Equivalent to::

        s1 + s2

    .. warning::
        we are just algebrically adding the quantities. If you want to merge
        spectra while preserving the radiative transfer equation, see
        :func:`~radis.los.slabs.MergeSlabs` and :func:`~radis.los.slabs.SerialSlabs`

    Parameters
    ----------

    s1, s2: Spectrum objects
        Spectrum you want to substract
    var: str
        quantity to manipulate: 'radiance', 'transmittance', ... If ``None``,
        get the unique spectral quantity of ``s1``, or the unique spectral
        quantity of ``s2``, or raises an error if there is any ambiguity

    Returns
    -------

    s: Spectrum
        Spectrum object with the same units and waveunits as ``s1``

    See Also
    --------

    :func:`~radis.los.slabs.MergeSlabs`,
    :func:`~radis.spectrum.operations.substract_spectra`

    """

    # Get variable
    if var is None:
        try:
            var = _get_unique_var(
                s2, var, inplace=False)  # unique variable of 2nd spectrum
        except KeyError:
            var = _get_unique_var(
                s1, var, inplace=False
            )  # if doesnt exist, unique variable of 1st spectrum
            # if it fails, let it fail
    # Make sure it is in both Spectra
    if var not in s1.get_vars():
        raise KeyError("Variable {0} not in Spectrum {1}".format(
            var, s1.get_name()))
    if var not in s2.get_vars():
        raise KeyError("Variable {0} not in Spectrum {1}".format(
            var, s1.get_name()))

    if var in ["transmittance_noslit", "transmittance"] and not force:
        raise ValueError(
            "It does not make much physical sense to sum transmittances. Are "
            +
            "you sure of what you are doing? See also `//` (MergeSlabs), `>` "
            +
            "(SerialSlabs) and `concat_spectra`. If you're sure, use `force=True`"
        )

    # Get s1 units
    Iunit1 = s1.units[var]
    wunit1 = s1.get_waveunit()

    # Resample s2 on s1
    s2 = s2.resample(s1, inplace=False)

    # Add, change output unit if needed.
    w1, I1 = s1.get(var=var, Iunit=Iunit1, wunit=wunit1)
    w2, I2 = s2.get(var=var, Iunit=Iunit1, wunit=wunit1)

    name = s1.get_name() + "+" + s2.get_name()

    sub = Spectrum.from_array(w1,
                              I1 + I2,
                              var,
                              waveunit=wunit1,
                              unit=Iunit1,
                              name=name)
    #    warn("Conditions of the left spectrum were copied in the substraction.", Warning)
    return sub
Пример #25
0
def calculated_spectrum(
    w,
    I,
    wunit="nm",
    Iunit="mW/cm2/sr/nm",
    conditions=None,
    cond_units=None,
    populations=None,
    name=None,
):  # -> Spectrum:
    """Convert ``(w, I)`` into a :py:class:`~radis.spectrum.spectrum.Spectrum`
    object that has unit conversion, plotting and slit convolution capabilities


    Parameters
    ----------

    w: np.array
        wavelength, or wavenumber

    I: np.array
        intensity (no slit)

    wunit: ``'nm'``, ``'cm-1'``, ``'nm_vac'``
        wavespace unit: wavelength in air (``'nm'``), wavenumber
        (``'cm-1'``), or wavelength in vacuum (``'nm_vac'``). Default ``'nm'``.

    Iunit: str
        intensity unit (can be 'counts', 'mW/cm2/sr/nm', etc...). Default
        'mW/cm2/sr/nm' (note that non-convoluted Specair spectra are in 'mW/cm2/sr/µm')


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

    conditions: dict
        (optional) calculation conditions to be stored with Spectrum. Default ``None``

    cond_units: dict
        (optional) calculation conditions units. Default ``None``

    populations: dict
        populations to be stored in Spectrum. Default ``None``

    name: str
        (optional) give a name


    Examples
    --------

    ::

        # w, I are numpy arrays for wavelength and radiance
        from radis import calculated_spectrum
        s = calculated_spectrum(w, I, wunit='nm', Iunit='W/cm2/sr/nm')     # creates 'radiance_noslit'



    See Also
    --------

    :func:`~radis.spectrum.models.transmittance_spectrum`,
    :func:`~radis.spectrum.models.experimental_spectrum`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_array`,
    :meth:`~radis.spectrum.spectrum.Spectrum.from_txt`,
    :func:`~radis.tools.database.load_spec`

    """

    return Spectrum.from_array(
        np.array(w),
        np.array(I),
        "radiance_noslit",
        waveunit=wunit,
        unit=Iunit,
        conditions=conditions,
        cond_units=cond_units,
        populations=populations,
        name=name,
    )
Пример #26
0
def test_slit_unit_conversions_spectrum_in_nm(
    verbose=True, plot=True, close_plots=True, *args, **kwargs
):
    """Test that slit is consistently applied for different units

    Assert that:

    - calculated FWHM is the one that was applied

    """

    from radis.spectrum.spectrum import Spectrum
    from radis.test.utils import getTestFile

    if plot:  # dont get stuck with Matplotlib if executing through pytest
        plt.ion()
        if close_plots:
            plt.close("all")

    # %% Get a Spectrum (stored in nm)

    s_nm = Spectrum.from_txt(
        getTestFile("calc_N2C_spectrum_Trot1200_Tvib3000.txt"),
        quantity="radiance_noslit",
        waveunit="nm",
        unit="mW/cm2/sr/µm",
        conditions={"self_absorption": False},
    )

    with catch_warnings():
        filterwarnings(
            "ignore", "Condition missing to know if spectrum is at equilibrium:"
        )
        # just because it makes better units
        s_nm.rescale_path_length(1, 0.001)

    wstep = np.diff(s_nm.get_wavelength())[0]

    assert s_nm.get_waveunit() == "nm"  # ensures it's stored in cm-1

    for shape in ["gaussian", "triangular"]:

        # Apply slit in nm
        slit_nm = 0.5
        s_nm.name = "Spec in nm, slit {0:.2f} nm".format(slit_nm)
        s_nm.apply_slit(slit_nm, unit="nm", shape=shape, mode="same")
        # ... mode=same to keep same output length. It helps compare both Spectra afterwards
        # in cm-1 as that's s.get_waveunit()
        fwhm = get_FWHM(*s_nm.get_slit())
        assert np.isclose(slit_nm, fwhm, atol=2 * wstep)

        # Apply slit in nm this time
        s_cm = s_nm.copy()
        w_nm = s_nm.get_wavelength(which="non_convoluted")
        slit_cm = dnm2dcm(slit_nm, w_nm[len(w_nm) // 2])
        s_cm.name = "Spec in nm, slit {0:.2f} cm-1".format(slit_cm)
        s_cm.apply_slit(slit_cm, unit="cm-1", shape=shape, mode="same")

        plotargs = {}
        if plot:
            plotargs["title"] = "test_slit_unit_conversions: {0} ({1} nm)".format(
                shape, slit_nm
            )
        s_nm.compare_with(
            s_cm,
            spectra_only="radiance",
            rtol=1e-3,
            verbose=verbose,
            plot=plot,
            **plotargs
        )