colour.phenomenons.rayleigh — Colour 0.3.0 documentation

# Source code for colour.phenomenons.rayleigh

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """
    Rayleigh Optical Depth - Scattering in the Atmosphere
    =====================================================

    Implements rayleigh scattering / optical depth in the atmosphere computation:

    -   :func:`scattering_cross_section`
    -   :func:`rayleigh_optical_depth`
    -   :func:`rayleigh_scattering`

    See Also
    --------
    `Rayleigh Optical Depth - Scattering in the Atmosphere IPython Notebook
    <http://nbviewer.ipython.org/github/colour-science/colour-ipython/blob/master/notebooks/phenomenons/rayleigh.ipynb>`_  # noqa

    References
    ----------
    .. [1]  `On Rayleigh Optical Depth Calculations
            <http://journals.ametsoc.org/doi/pdf/10.1175/1520-0426(1999)016%3C1854%3AORODC%3E2.0.CO%3B2>`_  # noqa
    .. [2]  http://en.wikipedia.org/wiki/Rayleigh_scattering
    """

    from __future__ import division, unicode_literals

    import math

    from colour.colorimetry import (
        DEFAULT_SPECTRAL_SHAPE,
        SpectralPowerDistribution)
    from colour.constants import AVOGADRO_CONSTANT

    __author__ = 'Colour Developers'
    __copyright__ = 'Copyright (C) 2013 - 2014 - Colour Developers'
    __license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
    __maintainer__ = 'Colour Developers'
    __email__ = 'colour-science@googlegroups.com'
    __status__ = 'Production'

    __all__ = ['STANDARD_AIR_TEMPERATURE',
               'STANDARD_CO2_CONCENTRATION',
               'AVERAGE_PRESSURE_MEAN_SEA_LEVEL',
               'DEFAULT_LATITUDE',
               'DEFAULT_ALTITUDE',
               'air_refraction_index_penndorf1957',
               'air_refraction_index_edlen1966',
               'air_refraction_index_peck1972',
               'air_refraction_index_bodhaine1999',
               'N2_depolarisation',
               'O2_depolarisation',
               'F_air_penndorf1957',
               'F_air_young1981',
               'F_air_bates1984',
               'F_air_bodhaine1999',
               'molecular_density',
               'mean_molecular_weights',
               'gravity_list1968',
               'scattering_cross_section',
               'rayleigh_optical_depth',
               'rayleigh_scattering']

    STANDARD_AIR_TEMPERATURE = 288.15
    """
    *Standard air* temperature :math:`T[K]` in kelvin degrees (:math:`15\circ C`).

    STANDARD_AIR_TEMPERATURE : numeric
    """

    STANDARD_CO2_CONCENTRATION = 300
    """
    *Standard air* :math:`CO_2` concentration in parts per million (ppm).

    STANDARD_CO2_CONCENTRATION : numeric
    """

    AVERAGE_PRESSURE_MEAN_SEA_LEVEL = 101325
    """
    *Standard air* average pressure :math:`Hg` at mean sea-level in pascal (Pa).

    AVERAGE_PRESSURE_MEAN_SEA_LEVEL : numeric
    """

    DEFAULT_LATITUDE = 0
    """
    Default latitude in degrees (equator).

    DEFAULT_LATITUDE : numeric
    """

    DEFAULT_ALTITUDE = 0
    """
    Default altitude in meters (sea level).

    DEFAULT_ALTITUDE : numeric
    """

    def air_refraction_index_penndorf1957(wavelength, *args):
        """
        Returns the air refraction index :math:`n_s` from given wavelength
        :math:`\lambda` in  micrometers (:math:`\mu m`) using *Penndorf (1957)*
        method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).
        \*args : \*
            Arguments.

        Returns
        -------
        numeric
            Air refraction index :math:`n_s`.

        See Also
        --------
        air_refraction_index_edlen1966, air_refraction_index_peck1972,
        air_refraction_index_bodhaine1999

        Examples
        --------
        >>> air_refraction_index_penndorf1957(0.555)  # doctest: +ELLIPSIS
        1.0002777...
        """

        wl = wavelength
        n = 6432.8 + 2949810 / (146 - wl ** (-2)) + 25540 / (41 - wl ** (-2))
        n = n / 1.0e8 + 1
        return n

    def air_refraction_index_edlen1966(wavelength, *args):
        """
        Returns the air refraction index :math:`n_s` from given wavelength
        :math:`\lambda` in micrometers (:math:`\mu m`) using *Edlen (1966)* method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).
        \*args : \*
            Arguments.

        Returns
        -------
        numeric
            Air refraction index :math:`n_s`.

        See Also
        --------
        air_refraction_index_penndorf1957, air_refraction_index_peck1972,
        air_refraction_index_bodhaine1999

        Examples
        --------
        >>> air_refraction_index_edlen1966(0.555)  # doctest: +ELLIPSIS
        1.0002777...
        """

        wl = wavelength
        n = 8342.13 + 2406030 / (130 - wl ** (-2)) + 15997 / (38.9 - wl ** (-2))
        n = n / 1.0e8 + 1
        return n

    def air_refraction_index_peck1972(wavelength, *args):
        """
        Returns the air refraction index :math:`n_s` from given wavelength
        :math:`\lambda` in micrometers (:math:`\mu m`) using
        *Peck and Reeder (1972)* method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).
        \*args : \*
            Arguments.

        Returns
        -------
        numeric
            Air refraction index :math:`n_s`.

        See Also
        --------
        air_refraction_index_penndorf1957, air_refraction_index_edlen1966,
        air_refraction_index_bodhaine1999

        Examples
        --------
        >>> air_refraction_index_peck1972(0.555)  # doctest: +ELLIPSIS
        1.0002777...
        """

        wl = wavelength
        n = (8060.51 + 2480990 / (132.274 - wl ** (-2)) + 17455.7 /
             (39.32957 - wl ** (-2)))
        n = n / 1.0e8 + 1
        return n

    def air_refraction_index_bodhaine1999(
            wavelength,
            CO2_concentration=STANDARD_CO2_CONCENTRATION):
        """
        Returns the air refraction index :math:`n_s` from given wavelength
        :math:`\lambda` in micrometers (:math:`\mu m`) using
        *Bodhaine, Wood, Dutton and Slusser (1999)* method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).
        CO2_concentration : numeric
            :math:`CO_2` concentration in parts per million (ppm).

        Returns
        -------
        numeric
            Air refraction index :math:`n_s`.

        See Also
        --------
        air_refraction_index_penndorf1957, air_refraction_index_edlen1966,
        air_refraction_index_peck1972

        Examples
        --------
        >>> air_refraction_index_bodhaine1999(0.555)  # doctest: +ELLIPSIS
        1.0002777...
        """

        wl = wavelength
        CCO2 = CO2_concentration
        n = ((1 + 0.54 * ((CCO2 * 1e-6) - 300e-6)) *
             (air_refraction_index_peck1972(wl) - 1) + 1)
        return n

    def N2_depolarisation(wavelength):
        """
        Returns the depolarisation of nitrogen :math:`N_2` as function of
        wavelength :math:`\lambda` in micrometers (:math:`\mu m`).

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).

        Returns
        -------
        numeric
            Nitrogen :math:`N_2` depolarisation.

        Examples
        --------
        >>> N2_depolarisation(0.555)  # doctest: +ELLIPSIS
        1.0350291...
        """

        wl = wavelength
        N2 = 1.034 + 3.17 * 1.0e-4 * (1 / wl ** 2)

        return N2

    def O2_depolarisation(wavelength):
        """
        Returns the depolarisation of oxygen :math:`O_2` as function of
        wavelength :math:`\lambda` in micrometers (:math:`\mu m`).

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).

        Returns
        -------
        numeric
            Oxygen :math:`O_2` depolarisation.

        Examples
        --------
        >>> O2_depolarisation(0.555)  # doctest: +ELLIPSIS
        1.1020225...
        """

        wl = wavelength
        O2 = (1.096 + 1.385 * 1.0e-3 * (1 / wl ** 2) +
              1.448 * 1.0e-4 * (1 / wl ** 4))

        return O2

    def F_air_penndorf1957(*args):
        """
        Returns :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
        *King Factor* using *Penndorf (1957)* method.

        Parameters
        ----------
        \*args : \*
            Arguments.

        Returns
        -------
        numeric
            Air depolarisation.

        See Also
        --------
        F_air_young1981, F_air_bates1984, F_air_bodhaine1999

        Notes
        -----
        -   The argument *wavelength* is only provided for consistency with the
            other air depolarisation methods but is actually not used as this
            definition is essentially a constant in its current implementation.

        Examples
        --------
        >>> F_air_penndorf1957(0.555)
        1.0608
        """

        return 1.0608

    def F_air_young1981(*args):
        """
        Returns :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
        *King Factor* using *Young (1981)* method.

        Parameters
        ----------
        \*args : \*
            Arguments.

        Returns
        -------
        numeric
            Air depolarisation.

        See Also
        --------
        F_air_penndorf1957, F_air_bates1984, F_air_bodhaine1999

        Notes
        -----
        -   The argument *wavelength* is only provided for consistency with the
            other air depolarisation methods but is actually not used as this
            definition is essentially a constant in its current implementation.

        Examples
        --------
        >>> F_air_young1981(0.555)
        1.048
        """

        return 1.0480

    def F_air_bates1984(wavelength):
        """
        Returns :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
        *King Factor* as function of wavelength :math:`\lambda` in micrometers
        (:math:`\mu m`) using *Bates (1984)* method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).

        Returns
        -------
        numeric
            Air depolarisation.

        See Also
        --------
        F_air_penndorf1957, F_air_young1981, F_air_bodhaine1999

        Examples
        --------
        >>> F_air_bates1984(0.555)  # doctest: +ELLIPSIS
        1.0481535...
        """

        wl = wavelength
        O2 = O2_depolarisation(wl)
        N2 = N2_depolarisation(wl)
        Ar = 1.00
        CO2 = 1.15

        F_air = ((78.084 * N2 + 20.946 * O2 + CO2 + Ar) /
                 (78.084 + 20.946 + Ar + CO2))

        return F_air

    def F_air_bodhaine1999(wavelength,
                           CO2_concentration=STANDARD_CO2_CONCENTRATION):
        """
        Returns :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
        *King Factor* as function of wavelength :math:`\lambda` in micrometers
        (:math:`\mu m`) and :math:`CO_2` concentration in parts per million (ppm)
        using *Bodhaine, Wood, Dutton and Slusser (1999)* method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in micrometers (:math:`\mu m`).
        CO2_concentration : numeric, optional
            :math:`CO_2` concentration in parts per million (ppm).

        Returns
        -------
        numeric
            Air depolarisation.

        See Also
        --------
        F_air_penndorf1957, F_air_young1981, F_air_bates1984

        Examples
        --------
        >>> F_air_bodhaine1999(0.555)  # doctest: +ELLIPSIS
        1.1246916...
        """

        wl = wavelength
        O2 = O2_depolarisation(wl)
        N2 = N2_depolarisation(wl)
        CO2_c = CO2_concentration

        F_air = ((78.084 * N2 + 20.946 * O2 + 0.934 * 1 + CO2_c * 1.15) /
                 (78.084 + 20.946 + 0.934 + CO2_c))

        return F_air

    def molecular_density(temperature=STANDARD_AIR_TEMPERATURE,
                          avogadro_constant=AVOGADRO_CONSTANT):
        """
        Returns the molecular density :math:`N_s` (molecules :math:`cm^{-3}`)
        as function of air temperature :math:`T[K]` in kelvin degrees.

        Parameters
        ----------
        temperature : numeric, optional
            Air temperature :math:`T[K]` in kelvin degrees.
        avogadro_constant : numeric, optional
            *Avogadro*'s number (molecules :math:`mol^{-1}`).

        Returns
        -------
        numeric
            Molecular density :math:`N_s` (molecules :math:`cm^{-3}`).

        Notes
        -----
        -   The *Avogadro*'s number used in this implementation is the one given by
            by the Committee on Data for Science and Technology (CODATA):
            :math:`6.02214179x10^{23}`, which is different from the reference [1]_
            value :math:`6.0221367x10^{23}`.

        Examples
        --------
        >>> molecular_density(288.15)  # doctest: +ELLIPSIS
        2.5469021...e+19
        >>> molecular_density(288.15, 6.0221367e23)  # doctest: +ELLIPSIS
        2.5468999...e+19
        """

        T = temperature
        N_s = (avogadro_constant / 22.4141) * (273.15 / T) * (1 / 1000)
        return N_s

    def mean_molecular_weights(CO2_concentration=STANDARD_CO2_CONCENTRATION):
        """
        Returns the mean molecular weights :math:`m_a` for dry air as function of
        :math:`CO_2` concentration in parts per million (ppm).

        Parameters
        ----------
        CO2_concentration : numeric, optional
            :math:`CO_2` concentration in parts per million (ppm).

        Returns
        -------
        numeric
            Mean molecular weights :math:`m_a` for dry air.

        Examples
        --------
        >>> mean_molecular_weights()  # doctest: +ELLIPSIS
        28.9640166...
        """

        CO2_c = CO2_concentration * 1.0e-6

        m_a = 15.0556 * CO2_c + 28.9595
        return m_a

    def gravity_list1968(latitude=DEFAULT_LATITUDE, altitude=DEFAULT_ALTITUDE):
        """
        Returns the gravity :math:`g` in :math:`cm/s_2` (gal) representative of the
        mass-weighted column of air molecules above the site of given latitude and
        altitude using *List (1968)* method.

        Parameters
        ----------
        latitude : numeric, optional
            Latitude of the site in degrees.
        altitude : numeric, optional
            Altitude of the site in meters.

        Returns
        -------
        numeric
            Gravity :math:`g` in :math:`cm/s_2` (gal).

        Examples
        --------
        >>> gravity_list1968()  # doctest: +ELLIPSIS
        978.0356070...
        >>> gravity_list1968(0, 1500)  # doctest: +ELLIPSIS
        977.5726106...

        Gravity :math:`g` for Paris:

        >>> gravity_list1968(48.8567, 35)  # doctest: +ELLIPSIS
        980.9524178...
        """

        cos2phi = math.cos(2 * math.radians(latitude))

        # Sea level acceleration of gravity.
        g0 = 980.6160 * (1 - 0.0026373 * cos2phi + 0.0000059 * cos2phi ** 2)

        g = (g0 - (3.085462e-4 + 2.27e-7 * cos2phi) * altitude +
             (7.254e-11 + 1.0e-13 * cos2phi) * altitude ** 2 -
             (1.517e-17 + 6e-20 * cos2phi) * altitude ** 3)
        return g

    def scattering_cross_section(wavelength,
                                 CO2_concentration=STANDARD_CO2_CONCENTRATION,
                                 temperature=STANDARD_AIR_TEMPERATURE,
                                 avogadro_constant=AVOGADRO_CONSTANT,
                                 n_s=air_refraction_index_bodhaine1999,
                                 F_air=F_air_bodhaine1999):
        """
        Returns the scattering cross section per molecule :math:`\sigma` of dry air
        as function of wavelength :math:`\lambda` in centimeters (cm) using given
        :math:`CO_2` concentration in parts per million (ppm) and temperature
        :math:`T[K]` in kelvin degrees following *Van de Hulst (1957)* method.

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in centimeters (cm).
        CO2_concentration : numeric, optional
            :math:`CO_2` concentration in parts per million (ppm).
        temperature : numeric, optional
            Air temperature :math:`T[K]` in kelvin degrees.
        avogadro_constant : numeric, optional
            *Avogadro*'s number (molecules :math:`mol^{-1}`).
        n_s : object
            Air refraction index :math:`n_s` computation method.
        F_air : object
            :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
            *King Factor* computation method.

        Returns
        -------
        numeric
            Scattering cross section per molecule :math:`\sigma` of dry air.

        Warning
        -------
        Unlike most objects of :mod:`colour.phenomenons.rayleigh` module,
        :func:`colour.phenomenons.rayleigh.scattering_cross_section` expects
        wavelength :math:`\lambda` to be expressed in centimeters (cm).

        Examples
        --------
        >>> scattering_cross_section(555 * 10e-8)  # doctest: +ELLIPSIS
        4.6613309...e-27
        """

        wl = wavelength
        wl_micrometers = wl * 10e3
        n_s = n_s(wl_micrometers)
        N_s = molecular_density(temperature, avogadro_constant)
        F_air = F_air(wl_micrometers, CO2_concentration)

        sigma = (24 * math.pi ** 3 * (n_s ** 2 - 1) ** 2 /
                 (wl ** 4 * N_s ** 2 * (n_s ** 2 + 2) ** 2))
        sigma *= F_air

        return sigma

    def rayleigh_optical_depth(wavelength,
                               CO2_concentration=STANDARD_CO2_CONCENTRATION,
                               temperature=STANDARD_AIR_TEMPERATURE,
                               pressure=AVERAGE_PRESSURE_MEAN_SEA_LEVEL,
                               latitude=DEFAULT_LATITUDE,
                               altitude=DEFAULT_ALTITUDE,
                               avogadro_constant=AVOGADRO_CONSTANT,
                               n_s=air_refraction_index_bodhaine1999,
                               F_air=F_air_bodhaine1999):
        """
        Returns the rayleigh optical depth :math:`T_r(\lambda)` as function of
        wavelength :math:`\lambda` in centimeters (cm).

        Parameters
        ----------
        wavelength : numeric
            Wavelength :math:`\lambda` in centimeters (cm).
        CO2_concentration : numeric, optional
            :math:`CO_2` concentration in parts per million (ppm).
        temperature : numeric, optional
            Air temperature :math:`T[K]` in kelvin degrees.
        pressure : numeric
            Surface pressure :math:`P` of the measurement site.
        latitude : numeric, optional
            Latitude of the site in degrees.
        altitude : numeric, optional
            Altitude of the site in meters.
        avogadro_constant : numeric, optional
            *Avogadro*'s number (molecules :math:`mol^{-1}`).
        n_s : object
            Air refraction index :math:`n_s` computation method.
        F_air : object
            :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
            *King Factor* computation method.

        Returns
        -------
        numeric
            Rayleigh optical depth :math:`T_r(\lambda)`.

        Warning
        -------
        Unlike most objects of :mod:`colour.phenomenons.rayleigh` module,
        :func:`colour.phenomenons.rayleigh.rayleigh_optical_depth` expects
        wavelength :math:`\lambda` to be expressed in centimeters (cm).

        Examples
        --------
        >>> rayleigh_optical_depth(555 * 10e-8)  # doctest: +ELLIPSIS
        0.1004070...
        """

        CO2_c = CO2_concentration
        sigma = scattering_cross_section(wavelength,
                                         CO2_c,
                                         temperature,
                                         avogadro_constant,
                                         n_s,
                                         F_air)

        # Conversion from pascal to dyne/cm2.
        P = pressure * 10
        m_a = mean_molecular_weights(CO2_c)
        g = gravity_list1968(latitude, altitude)

        T_R = sigma * (P * avogadro_constant) / (m_a * g)

        return T_R

    rayleigh_scattering = rayleigh_optical_depth

    def rayleigh_scattering_spd(shape=DEFAULT_SPECTRAL_SHAPE,
                                CO2_concentration=STANDARD_CO2_CONCENTRATION,
                                temperature=STANDARD_AIR_TEMPERATURE,
                                pressure=AVERAGE_PRESSURE_MEAN_SEA_LEVEL,
                                latitude=DEFAULT_LATITUDE,
                                altitude=DEFAULT_ALTITUDE,
                                avogadro_constant=AVOGADRO_CONSTANT,
                                n_s=air_refraction_index_bodhaine1999,
                                F_air=F_air_bodhaine1999):
        """
        Returns the rayleigh spectral power distribution for given spectral shape.

        Parameters
        ----------
        shape : SpectralShape, optional
            Spectral shape used to create the rayleigh scattering spectral power
            distribution.
        CO2_concentration : numeric, optional
            :math:`CO_2` concentration in parts per million (ppm).
        temperature : numeric, optional
            Air temperature :math:`T[K]` in kelvin degrees.
        pressure : numeric
            Surface pressure :math:`P` of the measurement site.
        latitude : numeric, optional
            Latitude of the site in degrees.
        altitude : numeric, optional
            Altitude of the site in meters.
        avogadro_constant : numeric, optional
            *Avogadro*'s number (molecules :math:`mol^{-1}`).
        n_s : object
            Air refraction index :math:`n_s` computation method.
        F_air : object
            :math:`(6+3_p)/(6-7_p)`, the depolarisation term :math:`F(air)` or
            *King Factor* computation method.

        Returns
        -------
        SpectralPowerDistribution
            Rayleigh optical depth spectral power distribution.

        Examples
        --------
        >>> rayleigh_scattering_spd()  # doctest: +ELLIPSIS
        <colour.colorimetry.spectrum.SpectralPowerDistribution object at 0x...>
        """

        return SpectralPowerDistribution(
            name=('Rayleigh Scattering - {0} ppm, {1} K, {2} Pa, {3} Degrees, '
                  '{4} m').format(CO2_concentration,
                                  temperature,
                                  pressure,
                                  latitude,
                                  altitude),
            data=dict((wavelength, rayleigh_scattering(wavelength * 10e-8,
                                                       CO2_concentration,
                                                       temperature,
                                                       pressure,
                                                       latitude,
                                                       altitude,
                                                       avogadro_constant,
                                                       n_s,
                                                       F_air))
                      for wavelength in shape))