Source code for sasktran2.constituent.collisioninducedabsorber

from __future__ import annotations

import numpy as np

import sasktran2 as sk
from sasktran2.optical import pressure_temperature_to_numberdensity

from ..optical.base import OpticalProperty
from .base import Constituent


[docs] class CollisionInducedAbsorber(Constituent):
[docs] def __init__(self, optical_property: OpticalProperty, name: str): """ An implementation of collision-induced absorption for quantities with known constant mole fractions. Air number density is estimated through the ideal gas law. Currently the mole fraction is applied to the air number density, though in the future it should be applied to dry air only. This Constituent requires that the atmosphere object have `temperature_k`, `pressure_pa`, and `wavelength_nm` all defined inside the :py:class:`sasktran2.Atmosphere` object. Parameters ---------- optical_property : OpticalProperty Optical property that computes the collision-induced cross section in [m^5]. name : str Molecule names to load mole fractions. Currently supported: O2O2 """ self._optical_property = optical_property if name.lower() == "o2o2": self._fraction_product = 0.20964**2 else: msg = f"Unknown name '{name}'" raise ValueError(msg)
def add_to_atmosphere(self, atmo: sk.Atmosphere): """ Parameters ---------- atmo : sk.Atmosphere :meta private: """ if atmo.wavelengths_nm is None: msg = "It is required to give the Atmosphere object wavelengths to use the O2O2 constituent" raise ValueError(msg) if atmo.pressure_pa is None: msg = "It is required to set the pressure_pa property in the Atmosphere object to use the O2O2 Constituent" raise ValueError(msg) if atmo.temperature_k is None: msg = "It is required to set the temperature_k property in the Atmosphere object to use the O2O2 Constituent" raise ValueError(msg) # Get the number density from the atmosphere object at the grid points (m^-3) num_dens = pressure_temperature_to_numberdensity( atmo.pressure_pa, atmo.temperature_k ) self._optical_quants = self._optical_property.atmosphere_quantities(atmo) atmo.storage.total_extinction[:] += ( # m^-1 self._optical_quants.extinction # m^5 (cross section) * (self._fraction_product * num_dens**2)[:, np.newaxis] ) def register_derivative(self, atmo: sk.Atmosphere, name: str): number_density, dN_dP, dN_dT = pressure_temperature_to_numberdensity( atmo.pressure_pa, atmo.temperature_k, include_derivatives=True ) derivs = {} deriv_names = [] d_vals = [] if atmo.calculate_pressure_derivative: deriv_names.append("pressure_pa") d_vals.append(dN_dP) if atmo.calculate_temperature_derivative: deriv_names.append("temperature_k") d_vals.append(dN_dT) # Contributions from the change in number density via pressure/temperature for deriv_name, dN_dX in zip(deriv_names, d_vals, strict=False): dk_dX = ( 2 * self._fraction_product * (number_density * dN_dX)[:, np.newaxis] * self._optical_quants.extinction ) deriv_mapping = atmo.storage.get_derivative_mapping( f"wf_{name}_{deriv_name}" ) deriv_mapping.d_extinction[:] += dk_dX deriv_mapping.d_ssa[:] += ( dk_dX * (self._optical_quants.ssa - atmo.storage.ssa) / atmo.storage.total_extinction ) deriv_mapping.interpolator = np.eye(len(number_density)) deriv_mapping.interp_dim = "altitude" deriv_mapping.assign_name = f"wf_{deriv_name}" if len(deriv_names) > 0: optical_derivs = self._optical_property.optical_derivatives(atmo=atmo) for key, val in optical_derivs.items(): deriv_mapping = atmo.storage.get_derivative_mapping( f"wf_{name}_{deriv_name}_{key}_xs" ) deriv_mapping.d_extinction[:] += val.cross_section deriv_mapping.d_ssa[:] += ( val.cross_section * (self._optical_quants.ssa - atmo.storage.ssa) / atmo.storage.total_extinction ) deriv_mapping.interpolator = ( np.eye(len(number_density)) * (self._fraction_product * number_density**2)[np.newaxis, :] ) deriv_mapping.interp_dim = "altitude" deriv_mapping.assign_name = f"wf_{deriv_name}" return derivs