示例#1
0
def _check_valid(s):
    # type: (Spectrum) -> bool
    ''' Check s is a valid Spectrum object. Raises an error if not 
    
    Valid if:
        
    - is a Spectrum
    
    Also print a warning if:
        
    - quantities used for solving the LOS have nan
    
    '''

    if not is_spectrum(s):
        raise TypeError('All inputs must be Spectrum objects (got: {0})'.format(
            type(s)))
    sdict = s._get_items()
    for k in sdict.keys():
        if (k in ['transmittance_noslit', 'radiance_noslit', 'abscoeff', 'emisscoeff'] 
                and count_nans(sdict.get(k)) > 0):
            warn('Nans detected in Spectrum object for multi-slab operation. ' +
                 'Results may be wrong!')

    return True
示例#2
0
def _check_valid(s):
    # type: (Spectrum) -> bool
    """Check s is a valid Spectrum object. Raises an error if not.

    Valid if:

    - is a Spectrum

    Also print a warning if:

    - quantities used for solving the LOS have nan
    """

    if not is_spectrum(s):
        raise TypeError(
            "All inputs must be Spectrum objects (got: {0})".format(type(s)))
    sdict = s._get_items()
    for k in sdict.keys():
        if (k in [
                "transmittance_noslit", "radiance_noslit", "abscoeff",
                "emisscoeff"
        ] and np.isnan(sdict.get(k)[1]).any()):
            warn(
                "Nans detected in Spectrum object for multi-slab operation. " +
                "Results may be wrong!")

    return True
示例#3
0
文件: slabs.py 项目: VBaillard/radis
def _check_valid(s):
    ''' Check s is a valid Spectrum object. Raises an error if not '''

    if not is_spectrum(s):
        raise TypeError(
            'All inputs must be Spectrum objects (got: {0})'.format(type(s)))
    sdict = s._get_items()
    for k in sdict.keys():
        if count_nans(sdict.get(k)) > 0:
            warn('Nans detected in Spectrum object for multi-slab operation. '+\
                 'Results may be wrong!')

    return True
示例#4
0
    def __init__(self,
                 parfunc,
                 bands,
                 levelsfmt,
                 sortby="Ei",
                 copy_lines=False,
                 verbose=True):
        """
        Parameters
        ----------

        bands: dict of bands
            bands are Spectrum objects calculated at equilibrium or non-equilibrim.
            Tgas, or (Tvib, Trot) must be given and the same in all bands conditions.

        """

        # Check inputs
        for br, s in bands.items():
            if not is_spectrum(s):
                raise ValueError(
                    "`bands` must be a list of Spectrum objects. " +
                    "Got {0}".format(type(s)))

        # Assert all conditions are the same
        bands_list = list(bands.values())
        cond_ref = bands_list[0].conditions
        for s in bands_list[1:]:  # type(s): Spectrum
            if list(s.conditions.keys()) != list(cond_ref.keys()):
                raise ValueError(
                    "Input conditions must be the same for all bands")
            for k in cond_ref.keys():
                if k in _IGNORE_KEYS:  # don't compare these entries
                    continue
                if s.conditions[k] != cond_ref[k]:
                    raise ValueError(
                        "Input conditions must be the same for all bands" +
                        ". Got a difference for entry {0}: {1} vs {2}".format(
                            k, s.conditions[k], cond_ref[k]))

        if "Tvib" in cond_ref:
            Tvib_ref = cond_ref["Tvib"]
        else:
            Tvib_ref = cond_ref["Tgas"]
        if "Trot" in cond_ref:
            Trot_ref = cond_ref["Trot"]
        else:
            Trot_ref = cond_ref["Tgas"]
        vib_distribution_ref = cond_ref["vib_distribution"]
        if "overpopulation" in cond_ref:
            raise NotImplementedError

        # stores input conditions
        self.Tvib_ref = Tvib_ref
        self.Trot_ref = Trot_ref
        self.mole_fraction_ref = cond_ref["mole_fraction"]
        self.path_length_ref = cond_ref["path_length"]
        self.vib_distribution_ref = vib_distribution_ref

        # stores computation parameters
        # TODO: store it in parfunc instead (and make parfunc an EnergyDatabase)
        self.levelsfmt = levelsfmt

        # %% Misc
        self.verbose = verbose
        self.copy_lines = copy_lines

        # %% Set up energies  ( use a partition function object for the moment)

        self.parfunc = parfunc
        self._init_levels(sortby)
        self._connect_bands_to_levels(bands)

        Qvib = self._calc_vib_populations(
            Tvib=self.Tvib_ref, vib_distribution=self.vib_distribution_ref)

        self.Qref = parfunc.at_noneq(
            Tvib_ref, Trot_ref, vib_distribution=vib_distribution_ref
        )  # TODO: add overpopulations in partition function
        self.Qvib_ref = Qvib

        self.vib_levels["nvib_ref"] = self.vib_levels["nvib"]
        del self.vib_levels["nvib"]

        # %% Set up bands
        self._init_bands_ref(bands, sortby)
示例#5
0
    def add(self, spectrum, **kwargs):
        ''' Add Spectrum to database, whether it's a Spectrum object or a file
        that stores one. Check it's not in database already.

        Parameters
        ----------

        spectrum: path to .spec file, or Spectrum object
            if Spectrum object: stores it in database first (using Spectrum.store()),
            then adds the file
            if path to file: will first copy the file in database folder, then
            adds the file

        **kwargs: **dict
            extra parameters used in the case where spectrum is a file and a .spec object
            has to be created (useless if `spectrum` is a file already). kwargs
            are forwarded to Spectrum.store() method. See :meth:`~radis.spectrum.spectrum.Spectrum.store` 
            or database.:meth:`~radis.tools.database.SpecDatabase.save` for more information

        Other Parameters
        ----------------
        
        :meth:`~radis.spectrum.spectrum.Spectrum.store` parameters given as kwargs arguments. 
            
        compress: boolean
            if True, removes all quantities that can be regenerated with s.update(),
            e.g, transmittance if abscoeff and path length are given, radiance if
            emisscoeff and abscoeff are given in non-optically thin case, etc.
            Default False

        add_info: list
            append these parameters and their values if they are in conditions
            example::

                nameafter = ['Tvib', 'Trot']

        discard: list of str
            parameters to exclude. To save some memory for instance
            Default [`lines`, `populations`]: retrieved Spectrum will loose the 
            line_survey ability, and plot_populations() (but it saves a ton of memory!)

        if_exists_then: 'increment', 'replace', 'error'
            what to do if file already exists. If increment an incremental digit
            is added. If replace file is replaced (yeah). If error (or anything else)
            an error is raised. Default `increment`

        Examples
        --------
        
        Simply write::
            
            db.add(s, discard=['populations'])

        
        See Also
        --------
        
        :meth:`~radis.tools.database.SpecDatabase.get`
        :meth:`~radis.tools.database.SpecDatabase.get_unique`
        :meth:`~radis.tools.database.SpecDatabase.get_closest`
        
        '''

        # Check inputs
        if 'path' in kwargs:
            raise ValueError('path is an invalid Parameter. The database path '+\
                             'is used')
        compress = kwargs.pop('compress', False)

        # First, store the spectrum on a file
        # ... input is a Spectrum. Store it in database and load it from there
        if is_spectrum(spectrum):
            # add defaults
            if not 'add_info' in kwargs:
                kwargs['add_info'] = self.add_info
            if not 'add_date' in kwargs:
                kwargs['add_date'] = self.add_date
            file = spectrum.store(self.path, compress=compress, **kwargs)
            # Note we could have added the Spectrum directly
            # (saves the load stage) but it also serves to
            # check the file we just stored is readable

        # ... input is a file name. Copy it in database and load it
        elif isinstance(spectrum, string_types):
            if not exists(spectrum):
                raise FileNotFoundError(
                    'File doesnt exist: {0}'.format(spectrum))

            fd, name = split(spectrum)

            # Assert a similar case name is not in database already
            if spectrum in list(self.df['file']):
                raise ValueError(
                    'File already in database: {0}. Database filenames should be unique'
                    .format(spectrum))

            if abspath(fd) != abspath(self.path):
                # Assert file doesnt exist in database already
                if name in os.listdir(self.path):
                    raise ValueError('File already in database folder: {0}'.format(name)+\
                                     '. Use db.update() if you added it there manually')
                # Ok. Copy it.
                file = join(self.path, name)
                copy2(spectrum, file)

            else:
                warn(
                    'You are manually adding a file that is in the database folder directly. Consider using db.update()'
                )
                file = spectrum

        else:
            raise ValueError('Unvalid Spectrum type: {0}'.format(
                type(spectrum)))

        # Then, load the Spectrum again (so we're sure it works!) and add the
        # information to the database
        self.df = self.df.append(self._load_file(file, binary=compress),
                                 ignore_index=True)

        # Update index .csv
        self.print_index()

        return file
示例#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 compare_spectra(first,
                    other,
                    spectra_only=False,
                    plot=True,
                    wunit="default",
                    verbose=True,
                    rtol=1e-5,
                    ignore_nan=False,
                    ignore_outliers=False,
                    normalize=False,
                    **kwargs):
    """Compare Spectrum with another Spectrum object

    Parameters
    ----------

    first: type Spectrum
        a Spectrum to be compared

    other: type Spectrum
        another Spectrum to compare with

    spectra_only: boolean, or str
        if ``True``, only compares spectral quantities (in the same waveunit)
        and not lines or conditions. If str, compare a particular quantity
        name. If False, compare everything (including lines and conditions
        and populations). Default ``False``

    plot: boolean
        if ``True``, use plot_diff to plot all quantities for the 2 spectra
        and the difference between them. Default ``True``.

    wunit: 'nm', 'cm-1', 'default'
        in which wavespace to compare (and plot). If default, natural wavespace
        of first Spectrum is taken

    rtol: float
        relative difference to use for spectral quantities comparison

    ignore_nan: boolean
        if ``True``, nans are ignored when comparing spectral quantities

    ignore_outliers: boolean, or float
        if not False, outliers are discarded. i.e, output is determined by::

            out = (~np.isclose(I, Ie, rtol=rtol, atol=0)).sum()/len(I) < ignore_outliers

    normalize: bool
        Normalize the spectra to be plotted

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

    kwargs: dict
        arguments are forwarded to :func:`~radis.spectrum.compare.plot_diff`

    Returns
    -------

    equals: boolean
        return True if spectra are equal (respective to tolerance defined by
        rtol and other input conditions)


    Examples
    --------

    Compare two Spectrum objects, or specifically the transmittance::

        s1.compare_with(s2)
        s1.compare_with(s2, 'transmittance')


    Note that you can also simply use `s1 == s2`, that uses
    :meth:`~radis.spectrum.spectrum.Spectrum.compare_with` internally::

        s1 == s2       # will return True or False

    """

    # Check inputs
    if not 0 <= ignore_outliers < 1:
        raise ValueError("ignore_outliers should be < 1, or False")
    if not is_spectrum(other):
        raise TypeError("2nd object is not a Spectrum: got class {0}".format(
            other.__class__))
    if isinstance(spectra_only, str):  # case where we compare all quantities
        if not spectra_only in first.get_vars():
            raise ValueError(
                "{0} is not a spectral quantity in our Spectrum ({1})".format(
                    spectra_only, first.get_vars()))
        if not spectra_only in other.get_vars():
            raise ValueError(
                "{0} is not a spectral quantity in the other Spectrum ({1})".
                format(spectra_only, other.get_vars()))
    if verbose:  # print conditions
        what = spectra_only if isinstance(spectra_only,
                                          str) else "all quantities"
        msg = "compare {0} with rtol={1}".format(what, rtol)
        if ignore_nan:
            msg += ", ignore_nan"
        if ignore_outliers:
            msg += ", ignore_outliers={0}".format(ignore_outliers)
        print(msg)
    if not plot and len(kwargs) > 0:
        raise ValueError("Unexpected argument: {0}".format(kwargs))

    if wunit == "default":
        wunit = first.get_waveunit()

    def _compare_dataframes(df1, df2, name):
        """

        Parameters
        ----------

        df1, df2: pandas Dataframe
            lines, or vib/rovib levels dataframes

        name: str
            for error message
        """

        #            if compare_lists(df1.keys(), df2.keys(), verbose=False) != 1:
        #                if verbose: print('... keys in {0} dont match:'.format(name))
        #                compare_lists(list(df1.keys()), list(df2.keys()),
        #                              verbose=True)
        #                out = False
        #            elif compare_lists(df1.index, df2.index, verbose=False) != 1:
        #                if verbose: print('... index in {0} dont match:'.format(name))
        #                compare_lists(list(df1.index), list(df2.index),
        #                              verbose=True)
        #                out = False
        #            else:
        #                out = (df1 == df2).all().all()
        #
        #            return out

        from pandas.util.testing import assert_frame_equal

        try:
            assert_frame_equal(
                df1.sort_index(axis=0).sort_index(axis=1),
                df2.sort_index(axis=0).sort_index(axis=1),
                check_names=True,
                check_column_type=
                False,  # solves problem in Python 2/3 dataframes (unicode/str)
            )
            out = True

        except AssertionError as err:
            if verbose:
                print("Comparing ", name)
                print(err.args[0])
            out = False

        return out

    def _compare_variables(I, Ie):
        """ Compare spectral quantities I and Ie """

        if ignore_nan:
            b = ~(np.isnan(I) + np.isnan(Ie))
            I = I[b]
            Ie = Ie[b]

        if ignore_outliers:
            out = (~np.isclose(I, Ie, rtol=rtol,
                               atol=0)).sum() / len(I) < ignore_outliers
        else:
            out = np.allclose(I, Ie, rtol=rtol, atol=0)

        return bool(out)

    def _display_difference(q, q0):
        error = np.nanmax(abs(q / q0 - 1))
        avgerr = np.nanmean(abs(q / q0 - 1))
        print(
            "...",
            k,
            "don't match (up to {0:.3}% diff.,".format(error * 100) +
            " average {0:.3f}%)".format(avgerr * 100),
        )

    b = True
    if isinstance(spectra_only, str):  # compare this quantity
        vars = [spectra_only]
    else:  # compare all quantities
        b = set(first.get_vars()) == set(other.get_vars())
        if not b and verbose:
            print("... list of quantities dont match: {0} vs {1}".format(
                first.get_vars(), other.get_vars()))
        vars = [k for k in first.get_vars() if k in other.get_vars()]

    if spectra_only:
        # Compare spectral variables
        # -----------
        for k in vars:
            w, q = first.get(k, wunit=wunit)
            w0, q0 = other.get(k, wunit=wunit)
            if len(w) != len(w0):
                print("Wavespaces have different length (for {0}: {1} vs {2})".
                      format(k, len(w), len(w0)))
                print("We interpolate one spectrum on the other one")

                from scipy.interpolate import interp1d

                if len(q) > len(q0):
                    f = interp1d(w, q, kind="cubic")
                    new_q = f(w0)
                    b1 = _compare_variables(new_q, q0)
                    if not b1 and verbose:
                        _display_difference(new_q, q0)
                else:
                    f = interp1d(w0, q0, kind="cubic")
                    new_q0 = f(w)
                    b1 = _compare_variables(q, new_q0)
                    if not b1 and verbose:
                        _display_difference(q, new_q0)

            else:  # no need to interpolate
                b1 = np.allclose(w, w0, rtol=rtol, atol=0)
                b1 *= _compare_variables(q, q0)

                if not b1 and verbose:
                    _display_difference(q, q0)
            b *= b1

            if plot:
                try:
                    plot_diff(first,
                              other,
                              var=k,
                              wunit=wunit,
                              normalize=normalize,
                              verbose=verbose,
                              **kwargs)
                except:
                    print("... couldn't plot {0}".format(k))

    else:
        # Compare spectral variables
        # -----------
        for k in vars:
            w, q = first.get(k, wunit=wunit)
            w0, q0 = other.get(k, wunit=wunit)
            if len(w) != len(w0):
                print("Wavespaces have different length (for {0}: {1} vs {2})".
                      format(k, len(w), len(w0)))
                b1 = False
            else:
                b1 = np.allclose(w, w0, rtol=rtol, atol=0)
                b1 *= _compare_variables(q, q0)
                if not b1 and verbose:
                    error = np.nanmax(abs(q / q0 - 1))
                    avgerr = np.nanmean(abs(q / q0 - 1))
                    print(
                        "...",
                        k,
                        "don't match (up to {0:.3}% diff.,".format(error * 100)
                        + " average {0:.3f}%)".format(avgerr * 100),
                    )
            b *= b1

            if plot:
                try:
                    plot_diff(first,
                              other,
                              var=k,
                              wunit=wunit,
                              normalize=normalize,
                              verbose=verbose,
                              **kwargs)
                except:
                    print(
                        "... there was an error while plotting {0}".format(k))

        # Compare conditions and units
        # -----------
        verbose_dict = "if_different" if verbose else False
        b1 = compare_dict(first.conditions,
                          other.conditions,
                          verbose=verbose_dict) == 1
        b2 = compare_dict(first.cond_units,
                          other.cond_units,
                          verbose=verbose_dict) == 1
        b3 = compare_dict(first.units, other.units, verbose=verbose_dict) == 1
        if not b1 and verbose:
            print("... conditions don't match")
        if not b2 and verbose:
            print("... conditions units don't match")
        if not b3 and verbose:
            print("... units don't match")
        b *= b1 * b2 * b3

        # Compare lines
        # -----------
        if first.lines is None and other.lines is None:
            b4 = True
        elif first.lines is None:
            b4 = False
        elif other.lines is None:
            b4 = False
        else:
            b4 = _compare_dataframes(first.lines, other.lines, "lines")
        if not b4 and verbose:
            print("... lines dont match")
        b *= b4

        # Compare populations
        # -----------
        if first.populations is None and other.populations is None:
            b5 = True
        elif first.populations is None:
            b5 = False
        elif other.populations is None:
            b5 = False
        else:
            # Compare keys in populations
            b5 = True
            if (compare_lists(first.populations,
                              other.populations,
                              verbose="if_different") == 1):
                # same molecules, compare isotopes
                for molecule, isotopes in first.populations.items():
                    if (compare_lists(
                            isotopes,
                            other.populations[molecule],
                            verbose="if_different",
                    ) == 1):
                        # same isotopes, compare electronic states
                        for isotope, states in isotopes.items():
                            if (compare_lists(
                                    states,
                                    other.populations[molecule][isotope],
                                    verbose="if_different",
                            ) == 1):
                                # same electronic states, compare levels + other information
                                for state, content in states.items():
                                    for k, v in content.items():
                                        if k in ["vib", "rovib"]:
                                            b5 *= _compare_dataframes(
                                                v,
                                                other.populations[molecule]
                                                [isotope][state][k],
                                                "populations of {0}({1})(iso{2})"
                                                .format(
                                                    molecule, state, isotope),
                                            )
                                        else:
                                            b5 *= (v == other.populations[
                                                molecule][isotope][state][k])
                            else:
                                b5 = False
                                if verbose:
                                    print(
                                        "{0}(iso{1}) states are different (see above)"
                                        .format(molecule, isotope))
                    else:
                        b5 = False
                        if verbose:
                            print("{0} isotopes are different (see above)".
                                  format(molecule))
            else:
                b5 = False
                if verbose:
                    print("Molecules are different (see above)")
        if not b5 and verbose:
            print("... populations dont match (see detail above)")
        b *= b5

        # Compare slit
        # -----------

        if len(first._slit) == len(other._slit) == 0:
            # no slit anywhere
            b6 = True
        elif len(first._slit) != len(other._slit):
            b6 = False
            if verbose:
                print(
                    "A spectrum has slit function array but the other doesnt")
        else:
            ws, Is = first.get_slit()
            ws0, Is0 = other.get_slit()
            if len(ws) != len(ws0):
                if verbose:
                    print("Slits have different length")
                b6 = False
            else:
                b6 = np.allclose(ws, ws0, rtol=rtol, atol=0)
                b6 *= _compare_variables(Is, Is0)
                if not b6 and verbose:
                    print("Slit functions dont match")
        b *= b6

    return bool(b)
示例#9
0
文件: slabs.py 项目: VBaillard/radis
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)
示例#10
0
文件: slabs.py 项目: VBaillard/radis
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)
示例#11
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)