import pyarts
import numpy as np
from dataclasses import dataclass
@dataclass(slots=True)
class Flux:
name: str
up: np.ndarray
diffuse_down: np.ndarray
direct_down: np.ndarray
@property
def down(self):
return self.diffuse_down + self.direct_down
[docs]
class AtmosphericFlux:
"""Creates a Disort clearsky flux operator using the Czarnecki-scheme."""
[docs]
def __init__(
self,
visible_surface_reflectivity: float = 0.3,
thermal_surface_reflectivity: float = 0.05,
atmospheric_altitude: float = 50e3,
surface_temperature: float = 300.0,
max_level_step: float = 1e3,
NQuad: int = 16,
atm_latitude: float = 0.0,
atm_longitude: float = 0.0,
solar_latitude: float = 0.0,
solar_longitude: float = 0.0,
species: list = ["H2O-161", "O2-66", "N2-44", "CO2-626", "O3-XFIT"],
remove_lines_percentile: dict[pyarts.arts.SpeciesEnum, float] | float | None = None,
):
"""Compute the total flux for a given atmospheric profile and surface temperature
The operator allows you to change the
Args:
visible_surface_reflectivity (float, optional): The surface reflectivity constant for Disort in visible. Defaults to 0.3.
thermal_surface_reflectivity (float, optional): The surface reflectivity constant for Disort in thermal. Defaults to 0.05.
atmospheric_altitude (float, optional): The top-of-the-atmosphere altitude [m]. Defaults to 50e3.
surface_temperature (float, optional): The surface temperature [K]. Defaults to 300.0.
max_level_step (float, optional): The maximum thickness of layers [m]. Defaults to 1e3.
NQuad (int, optional): The number of quadratures used by Disort. Defaults to 16.
atm_latitude (float, optional): Latitude of profile [degrees]. Defaults to 0.0.
atm_longitude (float, optional): Longitude of profile [degrees]. Defaults to 0.0.
solar_latitude (float, optional): Latitude of sun [degrees]. Defaults to 0.0.
solar_longitude (float, optional): Longitude of sun [degrees]. Defaults to 0.0.
species (list, optional): The list of absorption species. Defaults to [ "H2O-161", "O2-66", "N2-44", "CO2-626", "O3-XFIT", ].
remove_lines_percentile (dict | float | None, optional): The percentile of lines to remove [0, 100]. Per species if dict. Defaults to None.
"""
self.visible_surface_reflectivity = visible_surface_reflectivity
self.thermal_surface_reflectivity = thermal_surface_reflectivity
self.ws = pyarts.Workspace()
self.ws.disort_quadrature_dimension = NQuad
self.ws.disort_fourier_mode_dimension = 1
self.ws.disort_legendre_polynomial_dimension = 1
self.ws.absorption_speciesSet(species=species)
self.ws.ReadCatalogData()
for band in self.ws.absorption_bands:
self.ws.absorption_bands[band].cutoff = "ByLine"
self.ws.absorption_bands[band].cutoff_value = 750e9
if remove_lines_percentile is not None:
self.ws.absorption_bands.keep_hitran_s(remove_lines_percentile)
self.ws.propagation_matrix_agendaAuto()
self.ws.surface_fieldSetPlanetEllipsoid(option="Earth")
self.ws.surface_field["t"] = surface_temperature
self.ws.absorption_bandsSelectFrequency(fmin=40e9, by_line=1)
self.ws.atmospheric_fieldRead(
toa=atmospheric_altitude,
basename="planets/Earth/afgl/tropical/",
missing_is_zero=1,
)
self.ws.ray_pathGeometricDownlooking(
latitude=atm_latitude,
longitude=atm_longitude,
max_step=max_level_step,
)
self.ws.ray_path_atmospheric_pointFromPath()
self.visf = pyarts.arts.AscendingGrid.fromxml(
"planets/Earth/Optimized-Flux-Frequencies/SW-flux-optimized-f_grid.xml"
)
self.ir_f = pyarts.arts.AscendingGrid.fromxml(
"planets/Earth/Optimized-Flux-Frequencies/LW-flux-optimized-f_grid.xml"
)
self.visw = pyarts.arts.Vector.fromxml(
"planets/Earth/Optimized-Flux-Frequencies/SW-flux-optimized-quadrature_weights.xml"
)
self.ir_w = pyarts.arts.Vector.fromxml(
"planets/Earth/Optimized-Flux-Frequencies/LW-flux-optimized-quadrature_weights.xml"
)
tmp = pyarts.arts.GriddedField2.fromxml("star/Sun/solar_spectrum_QUIET.xml")
self.ws.sunFromGrid(
frequency_grid=self.visf,
sun_spectrum_raw=tmp,
latitude=solar_latitude,
longitude=solar_longitude,
)
[docs]
def get_atmosphere(
self, core=True, specs=True, nlte=False, ssprops=False, isots=False
):
"""Return the atmospheric field as a dictionary of python types.
Args:
core (bool, optional): See :meth:`ArrayOfAtmPoint.to_dict`. Defaults to True.
specs (bool, optional): See :meth:`ArrayOfAtmPoint.to_dict`. Defaults to True.
nlte (bool, optional): See :meth:`ArrayOfAtmPoint.to_dict`. Defaults to False.
ssprops (bool, optional): See :meth:`ArrayOfAtmPoint.to_dict`. Defaults to False.
isots (bool, optional): See :meth:`ArrayOfAtmPoint.to_dict`. Defaults to False.
Returns:
dict: Atmospheric field dictionary
"""
return pyarts.arts.stringify_keys(
self.ws.ray_path_atmospheric_point.to_dict(
core=core, specs=specs, nlte=nlte, ssprops=ssprops, isots=isots
)
)
[docs]
def __call__(
self,
atmospheric_profile: dict = {},
surface_temperature: float = None,
):
"""Get the total flux profile
Args:
atmospheric_profile (dict, optional): A dictionary of atmospheric data. Defaults to {}.
surface_temperature (float, optional): A surface temperature. Defaults to None.
Returns:
Flux, Flux, numpy.ndarray: The solar and thermal fluxes and the center altitudes of the layers.
"""
if surface_temperature is not None:
self.ws.surface_field["t"] = surface_temperature
self.ws.ray_path_atmospheric_point.update(atmospheric_profile)
# Visible
self.ws.frequency_grid = self.visf
self.ws.ray_path_frequency_gridFromPath()
self.ws.ray_path_propagation_matrixFromPath()
self.ws.disort_settingsInit()
self.ws.disort_settingsOpticalThicknessFromPath()
self.ws.disort_settingsNoLayerThermalEmission()
self.ws.disort_settingsNoSurfaceEmission()
self.ws.disort_settingsNoSpaceEmission()
self.ws.disort_settingsSurfaceLambertian(
value=self.visible_surface_reflectivity
)
self.ws.disort_settingsNoSingleScatteringAlbedo()
self.ws.disort_settingsNoFractionalScattering()
self.ws.disort_settingsNoLegendre()
self.ws.disort_settingsSetSun(ray_path_point=self.ws.ray_path[-1])
self.ws.disort_spectral_flux_fieldCalc()
self.SOLAR = np.einsum(
"i,ijk->jk", self.visw, self.ws.disort_spectral_flux_field
)
# IR
self.ws.frequency_grid = self.ir_f
self.ws.ray_path_frequency_gridFromPath()
self.ws.ray_path_propagation_matrixFromPath()
self.ws.disort_settingsInit()
self.ws.disort_settingsOpticalThicknessFromPath()
self.ws.disort_settingsLayerThermalEmissionLinearInTau()
self.ws.disort_settingsSurfaceEmissionByTemperature(
ray_path_point=self.ws.ray_path[0]
)
self.ws.disort_settingsCosmicMicrowaveBackgroundRadiation()
self.ws.disort_settingsSurfaceLambertian(
value=self.thermal_surface_reflectivity
)
self.ws.disort_settingsNoSingleScatteringAlbedo()
self.ws.disort_settingsNoFractionalScattering()
self.ws.disort_settingsNoLegendre()
self.ws.disort_settingsNoSun()
self.ws.disort_spectral_flux_fieldCalc()
self.THERMAL = np.einsum(
"i,ijk->jk", self.ir_w, self.ws.disort_spectral_flux_field
)
return (
Flux("solar", *self.SOLAR),
Flux("thermal", *self.THERMAL),
np.array(
[
0.5 * (self.ws.ray_path[i].pos[0] + self.ws.ray_path[i + 1].pos[0])
for i in range(len(self.ws.ray_path) - 1)
]
),
)