Source code for sasktran2.constituent.emission
from __future__ import annotations
import numpy as np
import sasktran2 as sk
from sasktran2._core_rust import PyThermalEmission
from sasktran2.constants import K_BOLTZMANN, PLANCK, SPEED_OF_LIGHT
from .base import Constituent
def planck_blackbody_radiance(
temperature_k: np.ndarray, wavelengths_nm: np.ndarray
) -> np.ndarray:
"""
Calculates the Planck function for a given set of temperatures and wavelengths.
Parameters
----------
temperature_k : np.ndarray
Temperatures in [K]
wavelengths_nm : np.ndarray
Wavelengths in [nm] to calculate the radiance at
Returns
-------
np.ndarray
The blackbody radiance with units of W / (m^2 nm sr). The shape of the returned
array is shape(len(temperature_k), len(wavelengths_nm)).
"""
wavelengths_m = wavelengths_nm * 1e-9
exponent = (
PLANCK
* SPEED_OF_LIGHT
/ (wavelengths_m * K_BOLTZMANN * temperature_k[:, np.newaxis])
)
return (
(2 * PLANCK * SPEED_OF_LIGHT**2 / wavelengths_m**5)
/ (np.exp(exponent) - 1)
* 1e-9
)
def d_planck_blackbody_radiance_d_temperature(
temperature_k: np.ndarray, wavelengths_nm: np.ndarray
) -> np.ndarray:
"""
Calculates the derivative of the Planck function for a given set of temperatures and wavelengths
with respect to the temperature parameter
Parameters
----------
temperature_k : np.ndarray
Temperatures in [K]
wavelengths_nm : np.ndarray
Wavelengths in [nm] to calculate the radiance at
Returns
-------
np.ndarray
The derivative of the blackbody radiance with respect to temperature with units of W / (m^2 nm sr K). The shape of the returned
array is shape(len(temperature_k), len(wavelengths_nm)).
"""
wavelengths_m = wavelengths_nm * 1e-9
exponent = (
PLANCK
* SPEED_OF_LIGHT
/ (wavelengths_m * K_BOLTZMANN * temperature_k[:, np.newaxis])
)
return (
(2 * PLANCK * SPEED_OF_LIGHT**2 / wavelengths_m**5)
/ (np.exp(exponent) - 1) ** 2
* (
PLANCK
* SPEED_OF_LIGHT
/ (wavelengths_m * K_BOLTZMANN * temperature_k[:, np.newaxis] ** 2)
)
* np.exp(exponent)
* 1e-9
)
[docs]
class ThermalEmission(Constituent):
_thermal_emission: PyThermalEmission
[docs]
def __init__(self):
"""
An implementation of thermal emissions calculated from the Planck function. The emission is
calculated with units of [W / (m^2 nm sr)].
This Constituent requires that the atmosphere object have `temperature_k` and
`wavelength_nm` defined inside the :py:class:`sasktran2.Atmosphere` object.
"""
self._thermal_emission = PyThermalEmission()
def add_to_atmosphere(self, atmo: sk.Atmosphere):
"""
Parameters
----------
atmo : sk.Atmosphere
:meta private:
"""
self._thermal_emission.add_to_atmosphere(atmo)
def register_derivative(self, atmo: sk.Atmosphere, name: str):
self._thermal_emission.register_derivative(atmo, name)
[docs]
class SurfaceThermalEmission(Constituent):
[docs]
def __init__(
self,
temperature_k: float,
emissivity: np.ndarray,
) -> None:
"""
A thermally emissive surface that is defined by a temperature and emissivity. The
emission is calculated as the product of emissivity and the Planck function.
This can either operate in a "scalar" mode where the emissivity is constant in wavelength,
or a "native" mode where the emissivity is defined on the same grid as the atmosphere.
Parameters
----------
temperature_k : float
Surface temperature.
emissivity : np.ndarray
Surface emissivity. Can be a scalar to indicate it is constant in wavelength. If set to an
array it must match the atmosphere wavelength grid dimenstion.
"""
Constituent.__init__(self)
self._emissivity = np.atleast_1d(emissivity)
self._temperature_k = temperature_k
@property
def temperature_k(self) -> float:
return self._temperature_k
@temperature_k.setter
def temperature_k(self, temperature_k: float):
self._temperature_k = temperature_k
@property
def emissivity(self) -> np.ndarray:
return self._emissivity
@emissivity.setter
def emissivity(self, emissivity: np.ndarray):
self._emissivity = np.atleast_1d(emissivity)
def add_to_atmosphere(self, atmo: sk.Atmosphere):
atmo.surface.emission[:] += (
self.emissivity
* planck_blackbody_radiance(
np.atleast_1d(self.temperature_k), atmo.wavelengths_nm
).flatten()
)
def register_derivative(self, atmo: sk.Atmosphere, name: str):
planck = planck_blackbody_radiance(
np.atleast_1d(self.temperature_k), atmo.wavelengths_nm
)
# Surface temperature derivative
deriv_mapping = atmo.surface.get_derivative_mapping(f"wf_{name}_temperature_k")
deriv_mapping.d_emission[:] = (
self.emissivity
* d_planck_blackbody_radiance_d_temperature(
np.atleast_1d(self.temperature_k), atmo.wavelengths_nm
)
)
deriv_mapping.interp_dim = "dummy"
deriv_mapping.interpolator = np.ones((len(atmo.wavelengths_nm), 1))
# Surface emissivity derivative
deriv_mapping = atmo.surface.get_derivative_mapping(f"wf_{name}_emissivity")
deriv_mapping.d_emission[:] = planck.flatten()
deriv_mapping.interp_dim = "dummy"
# If emissivity is scalear, we interpolate
if len(self.emissivity) == 1:
deriv_mapping.interpolator = np.ones((len(atmo.wavelengths_nm), 1))
# Else don't need an interpolator