Source code for abcmodel.land.minimal

from dataclasses import dataclass, field, replace

import jax.numpy as jnp
from jax import Array

from ..abstracts import AbstractCoupledState, AbstractLandModel, AbstractLandState
from ..utils import compute_esat, compute_qsat


# limamau: this could be much simpler!
[docs] @dataclass class MinimalLandSurfaceState(AbstractLandState): """Minimal land surface model state.""" alpha: Array """surface albedo [-], range 0 to 1.""" surf_temp: Array """Surface temperature [K].""" rs: Array """Surface resistance [s m-1].""" wg: Array = field(default_factory=lambda: jnp.array(0.0)) """No moisture content in the root zone [m3 m-3].""" wl: Array = field(default_factory=lambda: jnp.array(0.0)) """No water content in the canopy [m].""" # the following variables are assigned during warmup/timestep esat: Array = field(default_factory=lambda: jnp.array(0.0)) """Saturation vapor pressure [Pa].""" qsat: Array = field(default_factory=lambda: jnp.array(0.0)) """Saturation specific humidity [kg/kg].""" dqsatdT: Array = field(default_factory=lambda: jnp.array(0.0)) """Derivative of saturation specific humidity with respect to temperature [kg/kg/K].""" e: Array = field(default_factory=lambda: jnp.array(0.0)) """Vapor pressure [Pa].""" qsatsurf: Array = field(default_factory=lambda: jnp.array(0.0)) """Saturation specific humidity at surface temperature [kg/kg].""" wtheta: Array = field(default_factory=lambda: jnp.array(0.0)) """Kinematic heat flux [K m/s].""" wq: Array = field(default_factory=lambda: jnp.array(0.0)) """Kinematic moisture flux [kg/kg m/s].""" wCO2: Array = field(default_factory=lambda: jnp.array(0.0)) """Kinematic CO2 flux [kg/kg m/s] or [mol m-2 s-1]."""
# alias
[docs] class MinimalLandSurfaceModel(AbstractLandModel): """Minimal land surface model with fixed surface properties.""" def __init__(self): self.d1 = 0.0
[docs] def init_state( self, alpha: float = 0.25, surf_temp: float = 290.0, rs: float = 1.0e6, wg: float = 0.0, wl: float = 0.0, wtheta: float = 0.0, ) -> MinimalLandSurfaceState: """Initialize the model state. Args: alpha: surface albedo [-], range 0 to 1. Default is 0.25. surf_temp: Surface temperature [K]. Default is 290.0. rs: Surface resistance [s m-1]. Default is 1.0e6. wg: Volumetric soil moisture [m3 m-3]. Default is 0.0. wl: Canopy water content [m]. Default is 0.0. wtheta: Kinematic heat flux [K m/s]. Default is 0.0. Returns: The initial land state. """ return MinimalLandSurfaceState( alpha=jnp.array(alpha), surf_temp=jnp.array(surf_temp), rs=jnp.array(rs), wg=jnp.array(wg), wl=jnp.array(wl), wtheta=jnp.array(wtheta), )
[docs] def run( self, state: AbstractCoupledState, ) -> MinimalLandSurfaceState: """Run the model. Args: state: CoupledState. Returns: The updated land state object. """ land_state = state.land atmos = state.atmos esat = compute_esat(atmos.theta) qsat = compute_qsat(atmos.theta, atmos.surf_pressure) dqsatdT = self.compute_dqsatdT(esat, atmos.theta, atmos.surf_pressure) e = self.compute_e(atmos.q, atmos.surf_pressure) return replace(land_state, esat=esat, qsat=qsat, dqsatdT=dqsatdT, e=e)
[docs] def compute_dqsatdT(self, esat: Array, theta: float, surf_pressure: float) -> Array: """Compute the derivative of saturation vapor pressure with respect to temperature ``dqsatdT``. Notes: Using :meth:`~abcmodel.utils.compute_esat`, the derivative of the saturated vapor pressure :math:`e_\\text{sat}` with respect to temperature :math:`T` is given by .. math:: \\frac{\\text{d}e_\\text{sat}}{\\text{d} T} = e_\\text{sat}\\frac{17.2694(T-237.16)}{(T-35.86)^2}, which combined with :meth:`~abcmodel.utils.compute_qsat` can be used to get .. math:: \\frac{\\text{d}q_{\\text{sat}}}{\\text{d} T} \\approx \\epsilon \\frac{\\frac{\\text{d}e_\\text{sat}}{\\text{d} T}}{p}. """ num = 17.2694 * (theta - 273.16) den = (theta - 35.86) ** 2.0 mult = num / den desatdT = esat * mult return 0.622 * desatdT / surf_pressure
[docs] def compute_e(self, q: Array, surf_pressure: Array) -> Array: """Compute the vapor pressure ``e``. Notes: This function uses the same formula used in :meth:`~abcmodel.utils.compute_esat`, but now factoring the vapor pressure :math:`e` as a function of specific humidity :math:`q` and surface pressure :math:`p`, which give us .. math:: e = q \\cdot p / 0.622. """ return q * surf_pressure / 0.622
[docs] def integrate( self, state: MinimalLandSurfaceState, dt: float ) -> MinimalLandSurfaceState: """Integrate the model forward in time. Args: state: the state object carrying all variables. dt: the time step. Returns: The updated state object. """ return state