from dataclasses import dataclass, replace
import jax.numpy as jnp
from jax import Array
from ..abstracts import AbstractCoupledState, AtmosT, LandT
from .standard import StandardRadiationModel, StandardRadiationState
[docs]
@dataclass
class CloudyRadiationState(StandardRadiationState):
"""Standard radiation model with clouds state."""
pass
StateAlias = AbstractCoupledState[StandardRadiationState, LandT, AtmosT]
[docs]
class CloudyRadiationModel(StandardRadiationModel):
"""Standard radiation model with solar position and atmospheric effects including prognostic cloud transmittance.
Calculates time-varying solar rad based on geographic location and
atmospheric conditions. Includes both shortwave (solar) and longwave (thermal)
rad components.
Args:
lat: latitude [degrees], range -90 to +90. Default is 51.97.
lon: longitude [degrees], range -180 to +180. Default is -4.93.
doy: day of year [-], range 1 to 365. Default is 268.0.
"""
def __init__(self, lat: float = 51.97, lon: float = -4.93, doy: float = 268.0):
self.lat = lat
self.lon = lon
self.doy = doy
[docs]
def init_state(self, net_rad: float = 400.0) -> CloudyRadiationState:
"""Initialize the model state.
Args:
net_rad: Net surface radiation [W m-2]. Default is 400.0.
Returns:
The initial radiation state.
"""
return CloudyRadiationState(
net_rad=jnp.array(net_rad),
)
[docs]
def run(
self,
state: StateAlias,
t: Array,
dt: float,
tstart: float,
) -> StandardRadiationState:
"""Calculate rad components and net surface rad.
Args:
state: CoupledState.
t: Current time step index [-].
dt: Time step duration [s].
tstart: Start time of day [hours UTC], range 0 to 24.
Returns:
The updated rad state object.
"""
# needed components
rad_state = state.rad
ml_state = state.atmos.mixed
land_state = state.land
cloud_state = state.atmos.clouds
# computations
solar_declination = self.compute_solar_declination(self.doy)
solar_elevation = self.compute_solar_elevation(t, dt, tstart, solar_declination)
air_temp = self.compute_air_temperature(
ml_state.surf_pressure,
ml_state.h_abl,
ml_state.theta,
)
atmospheric_transmission = self.compute_atmospheric_transmission_w_clouds(
solar_elevation,
cloud_state.cl_trans,
)
(
net_rad,
in_srad,
out_srad,
in_lrad,
out_lrad,
) = self.compute_rad_components(
solar_elevation,
atmospheric_transmission,
air_temp,
land_state.alpha,
land_state.surf_temp,
)
return replace(
rad_state,
net_rad=net_rad,
in_srad=in_srad,
out_srad=out_srad,
in_lrad=in_lrad,
out_lrad=out_lrad,
)
[docs]
def compute_atmospheric_transmission_w_clouds(
self, solar_elevation: Array, cl_trans: Array
) -> Array:
"""Calculate atmospheric transmission coefficient for solar rad.
Args:
solar_elevation: sine of the solar elevation angle [-].
cl_trans: prognostic cloud layer transmittance [-].
Returns:
Atmospheric transmission coefficient [-].
Notes:
This is a simplified empirical parameterization (linear model) for
atmospheric transmission :math:`\\tau`.
1. A clear-sky transmission :math:`\\tau_{\\text{clear}}` is
calculated based on the solar elevation :math:`\\sin(\\alpha)` as
.. math::
\\tau_{\\text{clear}} = 0.6 + 0.2 \\cdot \\sin(\\alpha).
2. The prognostic cloud transmittance :math:`\\tau_{\\text{cloud}}`
is provided directly by the model state (``cl_trans``).
3. The final transmission is then the product of these two factors, giving
.. math::
\\tau = \\tau_{\\text{clear}} \\cdot \\tau_{\\text{cloud}}.
"""
# clear-sky transmission increases with solar elevation
clear_sky_trans = 0.6 + 0.2 * solar_elevation
# apply cloud layer transmissivity and return
return clear_sky_trans * cl_trans