Skin temperature and ground heat flux

Warning

This page is a work in progress. If you have any questions or notice any errors, please raise an issue.

Overview

The skin temperature $T_s$ is the effective radiative temperature of the land surface. It represents the temperature at which the surface emits longwave radiation and is the temperature that directly controls evaporation and sensible heat flux in the surface energy balance.

Skin temperature differs from the air temperature at height $z$ by the effects of turbulent mixing and surface properties. It is also not necessarily the same as the subsurface ground temperature $T_g$ (the temperature of the uppermost soil or snow layer) due to the thermal resistance between the surface and the ground.

The skin temperature $T_s$ is determined by balancing the energy arriving at the skin from below (the ground heat flux $G$) with the radiative and turbulent losses above,

\[R_{\text{net}}(T_s) + H_s(T_s) + H_l(T_s) = G(T_s, T_g)\]

where $R_{\text{net}}$ is the net radiation budget, $H_s$ is the sensible heat flux, and $H_l$ is the latent heat flux from sublimation and evapotranspiration.

Implicit skin temperature

Terrarium.ImplicitSkinTemperature — Type
struct ImplicitSkinTemperature{NF, Solver} <: Terrarium.AbstractSkinTemperature{NF}

Scheme for an implicit skin temperature $T_s$ satisfying:

\[R_{\text{net}}(T_s) + H_s(T_s) + H_l(T_s) - (1 - f_{\text{snow}})\, G(T_s, T_g) - f_{\text{snow}}\, S(T_s, T_{\text{snow}}) = 0\]

where $R_{\text{net}}$ is the net radiation budget, $H_s$ is the sensible heat flux, $H_l$ is the latent heat flux from sublimation and evapotranspiration, $G$ is the conductive flux from the skin into the snow-free ground ($T_g$ its temperature), $S$ is the conductive flux from the skin into the top of the snowpack over the snow-covered fraction ($T_{\text{snow}}$ its temperature), and $f_{\text{snow}}$ is the snow-covered area fraction ($f_{\text{snow}} = 0$ and $S$ absent without snow). $G$ and $S$ are each computed from their own unblended conduction target (see ground_thermal_interface and snow_thermal_interface).

Properties:

  • κₛ: Assumed thermal conductivity at the surface

  • solver: Numerical solver for the implicit skin temperature

source

Skin temperature can be determined instantaneously at each time step by finding the roots of the above nonlinear energy balance equation at each grid point. Given a trial skin temperature $T_s$, the demanded flux required to close the energy balance is

\[G^\star = R_{\text{net}}(T_s) + H_s(T_s) + H_l(T_s)\]

This demanded flux is equated to the area-weighted sum of two unblended conductive targets: the flux $G$ between the skin and the snow-free ground surface (uppermost soil layer) at temperature $T_g$, across the half-cell of thickness $\Delta z_1 / 2$,

\[G(T_s, T_g) = \frac{2 \kappa_s}{\Delta z_1} (T_g - T_s)\]

and, over the snow-covered fraction $f_{\text{snow}}$, the flux $S$ between the skin and the top of the snowpack at temperature $T_{\text{snow}}$, across its (floored) depth $d_{\text{snow}}$,

\[S(T_s, T_{\text{snow}}) = \frac{2 \kappa_{\text{snow}}}{d_{\text{snow}}} (T_{\text{snow}} - T_s)\]

Keeping $G$ and $S$ unblended (rather than averaging the ground and snow conduction targets into a single cell-wide target) prevents the flux delivered to a thin or patchy snowpack from being diluted by the bare-ground share. Setting the demanded flux equal to the area-weighted conductive fluxes and solving for $T_s$ (both conductive terms are affine in $T_s$, so this is an exact linear solve) yields

\[T_s^\star = \frac{(1 - f_{\text{snow}}) \frac{2\kappa_s}{\Delta z_1} T_g + f_{\text{snow}} \frac{2\kappa_{\text{snow}}}{d_{\text{snow}}} T_{\text{snow}} - G^\star}{(1 - f_{\text{snow}}) \frac{2\kappa_s}{\Delta z_1} + f_{\text{snow}} \frac{2\kappa_{\text{snow}}}{d_{\text{snow}}}}\]

which reduces to $T_s^\star = T_g - G^\star \Delta z_1 / (2\kappa_s)$ without snow ($f_{\text{snow}} = 0$). The residual driving the nonlinear solve is the difference between the current and implied skin temperatures,

\[r(T_s) = T_s - T_s^\star\]

The root $r(T_s) = 0$ is the skin temperature at which the conductive fluxes balance the radiative and turbulent fluxes, i.e. the solution of the surface energy balance. Note that the stored ground_heat_flux field is not $G^\star$ but the explicit conductive flux $G(T_s, T_g)$ evaluated at the converged $T_s$ — the two coincide only at convergence (see compute_ground_heat_flux).

The solve is performed by solve_skin_temperature!, which wraps compute_skin_temperature_residual! in an ObjectiveFunction targeting the skin_temperature field and hands it to the configured solver. The default solver, default_skin_temperature_solver, is a Newton-Raphson (RootSolver provided by RootSolvers.jl) with a fixed iteration budget of $n = 5$ to balance accuracy with GPU efficiency. The prognostic skin_temperature is seeded with the current ground_temperature by initialize! so that the iteration starts from a physically sensible guess close to the root. After the solve converges, the surface energy fluxes are recomputed from the final skin temperature.

Prescribed skin temperature

Terrarium.PrescribedSkinTemperature — Type
struct PrescribedSkinTemperature{NF} <: Terrarium.AbstractSkinTemperature{NF}

Simple scheme for prescribed skin temperatures from input variables.

Properties:

  • κₛ: Assumed thermal conductivity at the surface
source

When the skin temperature is prescribed, it is supplied directly as the skin_temperature input field (for example by an external coupler) and no nonlinear solve is performed: solve_skin_temperature! is a no-op for PrescribedSkinTemperature. The fused surface-energy-balance kernel then evaluates the radiative and turbulent fluxes from the prescribed skin temperature and closes the ground heat flux as the residual

\[G = R_{\text{net}} + H_s + H_l\]

This configuration can be used to defer the surface energy balance to an external solver, often in tandem with PrescribedTurbulentFluxes when the turbulent fluxes are also supplied as inputs (see Surface energy balance).

Process interface

Terrarium.initialize! — Method
initialize!(
    state,
    grid,
    _::ImplicitSkinTemperature,
    args...
)

Seed the prognostic skin_temperature with the current ground_temperature so the implicit nonlinear solve starts from a physically sensible guess close to the root.

source

Methods

Terrarium.compute_ground_heat_flux_demand — Method
compute_ground_heat_flux_demand(
    _::Terrarium.AbstractSkinTemperature,
    R_net,
    H_s,
    H_l
) -> Any

Compute the residual ground heat flux that would close the surface energy balance. With all fluxes positive upward (aligned with +z), the energy arriving at the skin from below must balance the radiative and turbulent losses above, so G = R_net + H_s + H_l.

source
Terrarium.compute_ground_heat_flux! — Method
compute_ground_heat_flux!(
    state,
    grid,
    skinT::Terrarium.AbstractSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance
)

Compute and store ground_heat_flux on grid, dispatching on skinT to the type-specific kernel function: the atmosphere-side demand $R_\text{net} + H_s + H_l$ for PrescribedSkinTemperature (which has no separate conduction target), or the explicit conductive flux $2\kappa_g(T_g - T_s)/\Delta z_g$ for ImplicitSkinTemperature (all fluxes positive upward).

source
Terrarium.default_skin_temperature_solver — Function
default_skin_temperature_solver(
    _::Type{NF}
) -> Terrarium.RootSolver{NF, RootSolvers.NewtonsMethod{T}, RootSolvers.CompactSolution, RootSolvers.ResidualTolerance{NF}} where {NF, T<:Union{Real, AbstractArray}}

Construct the default solver for the implicit skin temperature: a Newton root-finder (RootSolver backed by RootSolvers.jl) with a small iteration budget.

source

Kernel functions

Terrarium.compute_ground_heat_flux_demand — Method
compute_ground_heat_flux_demand(
    i,
    j,
    grid,
    fields,
    skinT::Terrarium.AbstractSkinTemperature,
    _::Terrarium.AbstractSurfaceEnergyBalance
) -> Any

Compute the ground heat flux demand from the surface net radiation and sensible/latent heat flux at grid cell i, j: i.e. the flux implied by the radiative budget and turbulent fluxes, G = R_net + H_s + H_l.

source
Terrarium.compute_ground_heat_flux — Method
compute_ground_heat_flux(
    i,
    j,
    grid,
    fields,
    skinT::PrescribedSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance
) -> Any

For PrescribedSkinTemperature, set the ground heat flux directly to the demand, i.e. G₀ = R_net + H_s + H_l.

source
Terrarium.compute_ground_heat_flux — Method
compute_ground_heat_flux(
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature,
    _::Terrarium.AbstractSurfaceEnergyBalance
) -> Any

Compute the conductive ground heat flux from the current skin_temperature and ground_temperature.

source
Terrarium.compute_ground_heat_flux! — Method
compute_ground_heat_flux!(
    out,
    i,
    j,
    grid,
    fields,
    skinT::Terrarium.AbstractSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance
)

Per-cell mutating variant used by the fused surface-energy-balance kernel: store the ground heat flux into the auxiliary output field out.

source
Terrarium.compute_skin_temperature — Method
compute_skin_temperature(
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature{NF},
    args...
) -> Any

Invert the (linear) ground-only conduction relation for the implicit skin temperature Ts given the atmosphere-side demanded flux G (= R_net + H_s + H_l): Ts = Tg − G/(2κg/Δzg). This is the no-snow special case (f_snow = 0) of the snow-aware method below; it is a separate method (rather than a default snow = nothing) purely so it can skip the unused snow_thermal_interface/snow_cover_fraction calls.

source
Terrarium.compute_skin_temperature — Method
compute_skin_temperature(
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature{NF},
    constants::PhysicalConstants,
    snow::Terrarium.AbstractSnow
) -> Any

Invert the (linear) area-weighted conduction relation for the implicit skin temperature Ts given the atmosphere-side demanded flux G (= R_net + H_s + H_l), by equating G to the area-weighted sum of the unblended ground and snow-top conductive fluxes, (1 − f_snow)·2κg(Tg − Ts)/Δzg + f_snow·2κsnow(Tsnow − Ts)/dsnow.

source
Terrarium.compute_skin_temperature_residual! — Function
compute_skin_temperature_residual!(
    out,
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance,
    constants::PhysicalConstants,
    atmos::Terrarium.AbstractAtmosphere
) -> Any
compute_skin_temperature_residual!(
    out,
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance,
    constants::PhysicalConstants,
    atmos::Terrarium.AbstractAtmosphere,
    hydrology::Union{Nothing, Terrarium.AbstractSurfaceHydrology}
) -> Any
compute_skin_temperature_residual!(
    out,
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance,
    constants::PhysicalConstants,
    atmos::Terrarium.AbstractAtmosphere,
    hydrology::Union{Nothing, Terrarium.AbstractSurfaceHydrology},
    snow::Union{Nothing, Terrarium.AbstractSnow}
) -> Any

Surface-energy-balance residual at grid cell i, j, in temperature space: Ts_prev − Ts_implicit, where Ts_implicit is the exact conduction-side inverse (see compute_skin_temperature) of the atmosphere-side demanded flux G_demand = R_net(Ts_prev) + H(Ts_prev) + LE(Ts_prev).

source
Terrarium.solve_skin_temperature! — Function
solve_skin_temperature!(
    out,
    i,
    j,
    grid,
    fields,
    skinT::ImplicitSkinTemperature,
    seb::Terrarium.AbstractSurfaceEnergyBalance,
    args...
) -> Any

Run a full nonlinear solve to determine the skin_temperature at grid cell i, j that solves the surface energy balance.

source