SlabLandSurface
WRF's 5-layer slab (thermal diffusion) land-surface scheme, SUBROUTINE SLAB1D of phys/module_sf_slab.F (sf_surface_physics = 1), written as instantaneous tendencies. Surface energy balance: net radiation gsw + emiss (glw - sigma tsk^4), sensible and latent heat fluxes from consumer-supplied bulk exchange coefficients (the surface-layer scheme's FLHC, FLQC), the latent flux driven by the saturation deficit at the skin temperature, and the residual G = rnet - (hfx + lh) conducted into the soil. Soil: the heat equation capg dT/dt = d/dz( capg difsl dT/dz ) on a column of NLEV prognostic layers, written as the flux-form operator D(K_soil D(T_s) - F_soil) and lowered by the EarthSciDiscretizations column_nonuniform_1d rule varcoeff_face_flux_laplacian_lev, with the surface heat flux G entering through the prescribed interface flux F_soil[1] and the deepest WRF soil layer -- which SLAB1D never updates -- acting as a fixed reservoir at temperature T_deep and depth z_deep that closes the bottom boundary through F_soil[NLEV+1]. Orientation: `lev` is the soil layer index with layer 1 at the surface and `ze` the interface DEPTH below ground (positive downward, ze[1] = 0, increasing with the index, the grid's required orientation); the grid-derived zc reproduce WRF's ZS and dz its DZS, and the lowered stencil is exactly SLAB1D's FLUX(L+1) = -DIFSL CAPG (T(L+1)-T(L))/(ZS(L+1)-ZS(L)), DTSDT = -(FLUX(L+1)-FLUX(L))/(DZS(L) CAPG). NLEV = num_soil_layers - 1 (4 for WRF's default 5 layers). The skin temperature follows layer 1: SLAB1D advances TSK by the layer-1 tendency (DTHGDT), so dTsk_dt = dTs_dt[1]; the surface fluxes themselves are evaluated at the consumer-supplied tsk. Water points (xland >= 1.5) are frozen (tendencies multiplied by the land indicator). Not expressible as a rate and therefore reported as the indicator snow_cap_active only: with IFSNOW = 1 SLAB1D caps the layer-1 and skin temperatures at 273.16 K wherever snow covers a land point, a projection of the state applied after each step. The one-layer force-restore form (num_soil_layers = 1: dTg/dt = (rnet - qs)/capg - hm) is not transcribed; hm is computed as WRF does but does not enter the multi-layer tendency. Consumer-supplied column fields (ze, T_s) are rewrite-target ops `input_<name>` lowered by the consumer, in the inline tests through per-test expression_template_imports libraries under tests/ (esm-spec 6.6 parameter_overrides admit scalars only). Test provenance: every inline-test input and expected value comes from a WRF v4.8.0 em_scm_xy single-column run of the instrumented fork ctessum-claude/WRF, commit e42a269 (v4.8.0-2-ge42a269e, branch earthsciml-instrumented), whose module_esm_dump hooks in phys/module_sf_slab.F dump SLAB1D's arguments; the dumps and the standalone replay driver are not part of this repository, so the values are recorded data rather than regenerable output.
Description
WRF’s 5-layer slab (thermal diffusion) land-surface scheme, SUBROUTINE SLAB1D of phys/module_sf_slab.F (sf_surface_physics = 1), written as instantaneous tendencies. Surface energy balance: net radiation gsw + emiss (glw - sigma tsk^4), sensible and latent heat fluxes from consumer-supplied bulk exchange coefficients (the surface-layer scheme’s FLHC, FLQC), the latent flux driven by the saturation deficit at the skin temperature, and the residual G = rnet - (hfx + lh) conducted into the soil. Soil: the heat equation capg dT/dt = d/dz( capg difsl dT/dz ) on a column of NLEV prognostic layers, written as the flux-form operator D(K_soil D(T_s) - F_soil) and lowered by the EarthSciDiscretizations column_nonuniform_1d rule varcoeff_face_flux_laplacian_lev, with the surface heat flux G entering through the prescribed interface flux F_soil[1] and the deepest WRF soil layer – which SLAB1D never updates – acting as a fixed reservoir at temperature T_deep and depth z_deep that closes the bottom boundary through F_soil[NLEV+1]. Orientation: lev is the soil layer index with layer 1 at the surface and ze the interface DEPTH below ground (positive downward, ze[1] = 0, increasing with the index, the grid’s required orientation); the grid-derived zc reproduce WRF’s ZS and dz its DZS, and the lowered stencil is exactly SLAB1D’s FLUX(L+1) = -DIFSL CAPG (T(L+1)-T(L))/(ZS(L+1)-ZS(L)), DTSDT = -(FLUX(L+1)-FLUX(L))/(DZS(L) CAPG). NLEV = num_soil_layers - 1 (4 for WRF’s default 5 layers). The skin temperature follows layer 1: SLAB1D advances TSK by the layer-1 tendency (DTHGDT), so dTsk_dt = dTs_dt[1]; the surface fluxes themselves are evaluated at the consumer-supplied tsk. Water points (xland >= 1.5) are frozen (tendencies multiplied by the land indicator). Not expressible as a rate and therefore reported as the indicator snow_cap_active only: with IFSNOW = 1 SLAB1D caps the layer-1 and skin temperatures at 273.16 K wherever snow covers a land point, a projection of the state applied after each step. The one-layer force-restore form (num_soil_layers = 1: dTg/dt = (rnet - qs)/capg - hm) is not transcribed; hm is computed as WRF does but does not enter the multi-layer tendency. Consumer-supplied column fields (ze, T_s) are rewrite-target ops input_<name> lowered by the consumer, in the inline tests through per-test expression_template_imports libraries under tests/ (esm-spec 6.6 parameter_overrides admit scalars only). Test provenance: every inline-test input and expected value comes from a WRF v4.8.0 em_scm_xy single-column run of the instrumented fork ctessum-claude/WRF, commit e42a269 (v4.8.0-2-ge42a269e, branch earthsciml-instrumented), whose module_esm_dump hooks in phys/module_sf_slab.F dump SLAB1D’s arguments; the dumps and the standalone replay driver are not part of this repository, so the values are recorded data rather than regenerable output.
Reference
- Dudhia, J. (1996). A multi-layer soil temperature model for MM5. Preprints, 6th PSU/NCAR Mesoscale Model Users’ Workshop, Boulder, CO, 49-50.
- Blackadar, A. K. (1978). Modeling pollutant transfer during daytime convection. Preprints, 4th Symp. on Atmospheric Turbulence, Diffusion and Air Quality, Reno, NV, AMS, 443-447.
- wrf-model/WRF v4.8.0 phys/module_sf_slab.F, SUBROUTINE SLAB1D lines 232-562 (instrumented in ctessum-claude/WRF branch earthsciml-instrumented, commit e42a269).
Parameters
| Name | Units | Default | Description |
|---|---|---|---|
t_air | K | 285.0 | Temperature of the lowest model layer (WRF T3D(:,1,:), T1D). |
qv_air | kg/kg | 0.005 | Water-vapor mixing ratio of the lowest model layer (QV1D). |
p_air | Pa | 97000.0 | Pressure of the lowest model layer (P1D). |
psfc | Pa | 97300.0 | Surface pressure (PSFCPA). |
flhc | W/(m^2*K) | 40.0 | Bulk exchange coefficient for heat from the surface-layer scheme, already multiplied by rho cp |U| (FLHC), so that hfx = flhc (theta_g - theta_air). |
flqc | kg/(m^2*s) | 0.01 | Bulk exchange coefficient for moisture from the surface-layer scheme, already multiplied by rho |U| and the moisture availability (FLQC), so that qfx = flqc (qsat_g - qv_air). |
tsk | K | 286.0 | Skin (ground surface) temperature at which the surface fluxes are evaluated (TSK = TG0). |
gsw | W/m^2 | 300.0 | Net downward shortwave flux at the ground, albedo already applied (GSW). |
glw | W/m^2 | 280.0 | Downward longwave flux at the ground (GLW). |
tmn | K | 288.0 | Deep-soil (annual mean) temperature TMN; enters only the force-restore term hm. |
thc | cal/(cm*K*s^0.5) | 0.04 | Soil thermal inertia THC (WRF land-use table, cal cm^-1 K^-1 s^-1/2). |
emiss | 1 | 0.92 | Surface emissivity EMISS. |
mavail | 1 | 0.6 | Surface moisture availability MAVAIL; SLAB1D only copies it to chklowq (it is already inside flqc). |
xland | 1 | 1.0 | Land mask XLAND, 1 land, 2 water. |
snowc | 1 | 0.0 | Snow-cover flag SNOWC (1 where snow covers the ground). |
radiation | 1 | 1.0 | 1 when a radiation scheme is active (WRF logical radiation, RADSWTCH): without radiation SLAB1D drops the upward longwave emission. |
ifsnow | 1 | 1.0 | Namelist IFSNOW: 1 activates the snow-cover temperature cap. |
T_deep | K | 288.0 | Temperature of the deepest WRF soil layer (TSLB(num_soil_layers)), never updated by SLAB1D and therefore the fixed reservoir closing the soil column at the bottom. |
z_deep | m | 0.23 | Depth of the centre of the deepest WRF soil layer (ZS(num_soil_layers)). |
Observed
| Name | Units | Default | Description |
|---|---|---|---|
ze | m | Interface depths below ground, ze[1] = 0, ze[k+1] = ze[k] + DZS(k), consumer-supplied through input_ze; the grid’s single geometry input. | |
zc | m | Layer-centre depths (WRF ZS), grid-derived from ze. | |
dz | m | Layer thicknesses (WRF DZS), grid-derived from ze; the finite-volume measure read by the rule. | |
T_s | K | Soil temperature of the prognostic layers (WRF TSLB(1:num_soil_layers-1)), consumer-supplied through input_T_s. | |
land | 1 | Land indicator XLD1: 1 where xland < 1.5, else 0; water points keep their temperatures. | |
capg | J/(K*m^3) | Volumetric soil heat capacity CAPG = 5.9114e7 THC of the multi-layer soil. | |
exner_air | 1 | Exner function of the lowest model layer, (p_air / p1000mb)^(r_d/cp) (1/THCON). | |
theta_air | K | Potential temperature of the lowest model layer (THX). | |
exner_sfc | 1 | Exner function at the surface pressure. | |
theta_g | K | Potential temperature of the ground surface, tsk / exner_sfc (THG). | |
upflux | W/m^2 | Upward black-body emission of the ground, radiation sigma tsk^4 (UPFLUX). | |
xinet | W/m^2 | Net longwave absorbed, emiss (glw - upflux) (XINET). | |
rnet | W/m^2 | Net radiation absorbed by the surface, gsw + xinet (RNET). | |
hm | K/s | Force-restore restoring rate 1.18 eomeg (tsk - tmn) of the one-layer scheme (HM); diagnosed but not applied in the multi-layer form. | |
psfc_kpa | kPa | Surface pressure in kPa, the unit of the Bolton constant svp1 (PSFC = PSFCPA/1000). | |
es_g | kPa | Saturation vapor pressure at the skin temperature (ESG). | |
qsat_g | kg/kg | Saturation mixing ratio at the skin temperature and surface pressure, ep_2 es_g / (psfc - es_g) (QSG). | |
hfx | W/m^2 | Upward sensible heat flux flhc (theta_g - theta_air) (HFX). | |
qfx | kg/(m^2*s) | Upward moisture flux flqc (qsat_g - qv_air) (QFX). | |
lh | W/m^2 | Latent heat flux xlv qfx (LH). | |
qs | W/m^2 | Total turbulent enthalpy flux leaving the surface, hfx + lh (QS). | |
G_sfc | W/m^2 | Heat flux conducted into the soil at the surface, rnet - qs, positive downward (FLUX(1)). | |
K_soil | W/(m*K) | Soil thermal conductivity capg difsl on the interfaces, the coefficient handed to the diffusion rule. | |
F_soil | W/m^2 | Prescribed part of the interface flux in the rule’s K dT/dz - F convention (depth coordinate): G_sfc on the surface interface, the conductive flux to the fixed reservoir, K_soil (T_s[NLEV] - T_deep)/(z_deep - zc[NLEV]), on the bottom interface, 0 on the interior interfaces (where the rule computes the conductive flux itself). | |
dTs_dt | K/s | Soil temperature tendency of the prognostic layers, land (1/capg) d/dz(K_soil dT_s/dz - F_soil) (SLAB1D DTSDT, exact in the limit dt -> 0 where the sub-step count nsoil is 1). | |
dTsk_dt | K/s | Skin temperature tendency, equal to the layer-1 soil tendency (SLAB1D DTHGDT with nsoil = 1). | |
qsfc | kg/kg | Surface mixing ratio handed to the PBL scheme, qv_air + qfx/flqc (= qsat_g when flqc is nonzero) (QSFC). | |
chklowq | 1 | Moisture availability passed through for the PBL scheme, CHKLOWQ = MAVAIL. | |
snow_cap_active | 1 | 1 when SLAB1D’s snow-cover cap would act (a land point, ifsnow = 1, snowc > 0 and T_s[1] > 273.16 K; SLAB1D skips water points before both caps): the cap resets the layer-1 and skin temperatures to 273.16 K after the step, a state projection that has no rate form. |
Observed expressions
$$ ze = \mathrm{input_ze}\left(\right) $$Interface depths below ground, ze[1] = 0, ze[k+1] = ze[k] + DZS(k), consumer-supplied through input_ze; the grid’s single geometry input.
Layer-centre depths (WRF ZS), grid-derived from ze.
$$ dz = \mathrm{column_cell_thickness}\left(ze=ze\right) $$Layer thicknesses (WRF DZS), grid-derived from ze; the finite-volume measure read by the rule.
$$ T_{s} = \mathrm{input_T_s}\left(\right) $$Soil temperature of the prognostic layers (WRF TSLB(1:num_soil_layers-1)), consumer-supplied through input_T_s.
Land indicator XLD1: 1 where xland < 1.5, else 0; water points keep their temperatures.
$$ capg = par.capg_{per\,thc} \cdot thc $$Volumetric soil heat capacity CAPG = 5.9114e7 THC of the multi-layer soil.
$$ exner_{air} = \mathrm{exner_function}\left(p=p_{air}, p0=wrf.p1000mb, kappa=wrf.rcp\right) $$Exner function of the lowest model layer, (p_air / p1000mb)^(r_d/cp) (1/THCON).
$$ theta_{air} = \mathrm{theta_from_temperature}\left(T=t_{air}, exner=exner_{air}\right) $$Potential temperature of the lowest model layer (THX).
$$ exner_{sfc} = \mathrm{exner_function}\left(p=psfc, p0=wrf.p1000mb, kappa=wrf.rcp\right) $$Exner function at the surface pressure.
$$ theta_{g} = \mathrm{theta_from_temperature}\left(T=tsk, exner=exner_{sfc}\right) $$Potential temperature of the ground surface, tsk / exner_sfc (THG).
$$ upflux = radiation \cdot \mathrm{blackbody_emission}\left(sigma=wrf.stbolt, T=tsk\right) $$Upward black-body emission of the ground, radiation sigma tsk^4 (UPFLUX).
$$ xinet = emiss \cdot \left(glw - upflux\right) $$Net longwave absorbed, emiss (glw - upflux) (XINET).
$$ rnet = \mathrm{net_radiation}\left(gsw=gsw, glw=glw, emiss=emiss, upflux=upflux\right) $$Net radiation absorbed by the surface, gsw + xinet (RNET).
$$ hm = par.hm_{coef} \cdot wrf.eomeg \cdot \left(tsk - tmn\right) $$Force-restore restoring rate 1.18 eomeg (tsk - tmn) of the one-layer scheme (HM); diagnosed but not applied in the multi-layer form.
$$ psfc_{kpa} = psfc \cdot par.kpa_{per\,pa} $$Surface pressure in kPa, the unit of the Bolton constant svp1 (PSFC = PSFCPA/1000).
$$ es_{g} = \mathrm{bolton_saturation_vapor_pressure}\left(T=tsk, svp1=wrf.svp1, svp2=wrf.svp2, svp3=wrf.svp3, svpt0=wrf.svpt0\right) $$Saturation vapor pressure at the skin temperature (ESG).
$$ qsat_{g} = \mathrm{saturation_mixing_ratio}\left(ep_{2}=wrf.ep_{2}, es=es_{g}, p=psfc_{kpa}\right) $$Saturation mixing ratio at the skin temperature and surface pressure, ep_2 es_g / (psfc - es_g) (QSG).
$$ hfx = \mathrm{bulk_surface_flux}\left(coefficient=flhc, surface_{value}=theta_{g}, air_{value}=theta_{air}\right) $$Upward sensible heat flux flhc (theta_g - theta_air) (HFX).
$$ qfx = \mathrm{bulk_surface_flux}\left(coefficient=flqc, surface_{value}=qsat_{g}, air_{value}=qv_{air}\right) $$Upward moisture flux flqc (qsat_g - qv_air) (QFX).
$$ lh = wrf.xlv \cdot qfx $$Latent heat flux xlv qfx (LH).
$$ qs = hfx + lh $$Total turbulent enthalpy flux leaving the surface, hfx + lh (QS).
$$ G_{sfc} = rnet - qs $$Heat flux conducted into the soil at the surface, rnet - qs, positive downward (FLUX(1)).
$$ K_{soil} = \mathrm{faq}\left(\right) $$Soil thermal conductivity capg difsl on the interfaces, the coefficient handed to the diffusion rule.
$$ F_{soil} = \mathrm{faq}\left(K_{soil}, T_{s}, zc\right) $$Prescribed part of the interface flux in the rule’s K dT/dz - F convention (depth coordinate): G_sfc on the surface interface, the conductive flux to the fixed reservoir, K_soil (T_s[NLEV] - T_deep)/(z_deep - zc[NLEV]), on the bottom interface, 0 on the interior interfaces (where the rule computes the conductive flux itself).
$$ dTs_{dt} = land \cdot \frac{\mathrm{D}\left(K_{soil} \cdot \mathrm{D}\left(T_{s}\right) - F_{soil}\right)}{capg} $$Soil temperature tendency of the prognostic layers, land (1/capg) d/dz(K_soil dT_s/dz - F_soil) (SLAB1D DTSDT, exact in the limit dt -> 0 where the sub-step count nsoil is 1).
$$ dTsk_{dt} = \mathrm{index}\left(dTs_{dt}, 1\right) $$Skin temperature tendency, equal to the layer-1 soil tendency (SLAB1D DTHGDT with nsoil = 1).
$$ qsfc = \mathrm{ifelse}\left(flqc = 0.0, qv_{air}, qv_{air} + \frac{qfx}{flqc}\right) $$Surface mixing ratio handed to the PBL scheme, qv_air + qfx/flqc (= qsat_g when flqc is nonzero) (QSFC).
$$ chklowq = mavail $$Moisture availability passed through for the PBL scheme, CHKLOWQ = MAVAIL.
$$ snow_{cap\,active} = land \cdot ifsnow \cdot \mathrm{ifelse}\left(\mathrm{and}\left(\mathrm{>}\left(snowc, 0.0\right), \mathrm{>}\left(\mathrm{index}\left(T_{s}, 1\right), par.t_{snowmelt}\right)\right), \mathrm{const}\left(\right), \mathrm{const}\left(\right)\right) $$1 when SLAB1D’s snow-cover cap would act (a land point, ifsnow = 1, snowc > 0 and T_s[1] > 273.16 K; SLAB1D skips water points before both caps): the cap resets the layer-1 and skin temperatures to 273.16 K after the step, a state projection that has no rate form.
Raw .esm
Raw .esm JSON (this component)
{
"expression_template_imports": [
{
"ref": "${ESD_ROOT}/grids/column_nonuniform_1d/grid.esm",
"bindings": {
"NLEV": "NLEV"
}
},
{
"ref": "${ESD_ROOT}/grids/column_nonuniform_1d/rules/varcoeff_face_flux_laplacian_lev.esm",
"bindings": {
"NLEV": "NLEV"
}
},
{
"ref": "../../../lib/wrf_thermo.esm"
},
{
"ref": "./slab_templates.esm"
}
],
"subsystems": {
"wrf": {
"ref": "../../../lib/wrf_constants.esm"
},
"par": {
"ref": "./slab_parameters.esm"
}
},
"variables": {
"t_air": {
"type": "parameter",
"units": "K",
"default": 285.0,
"description": "Temperature of the lowest model layer (WRF T3D(:,1,:), T1D)."
},
"qv_air": {
"type": "parameter",
"units": "kg/kg",
"default": 0.005,
"description": "Water-vapor mixing ratio of the lowest model layer (QV1D)."
},
"p_air": {
"type": "parameter",
"units": "Pa",
"default": 97000.0,
"description": "Pressure of the lowest model layer (P1D)."
},
"psfc": {
"type": "parameter",
"units": "Pa",
"default": 97300.0,
"description": "Surface pressure (PSFCPA)."
},
"flhc": {
"type": "parameter",
"units": "W/(m^2*K)",
"default": 40.0,
"description": "Bulk exchange coefficient for heat from the surface-layer scheme, already multiplied by rho cp |U| (FLHC), so that hfx = flhc (theta_g - theta_air)."
},
"flqc": {
"type": "parameter",
"units": "kg/(m^2*s)",
"default": 0.01,
"description": "Bulk exchange coefficient for moisture from the surface-layer scheme, already multiplied by rho |U| and the moisture availability (FLQC), so that qfx = flqc (qsat_g - qv_air)."
},
"tsk": {
"type": "parameter",
"units": "K",
"default": 286.0,
"description": "Skin (ground surface) temperature at which the surface fluxes are evaluated (TSK = TG0)."
},
"gsw": {
"type": "parameter",
"units": "W/m^2",
"default": 300.0,
"description": "Net downward shortwave flux at the ground, albedo already applied (GSW)."
},
"glw": {
"type": "parameter",
"units": "W/m^2",
"default": 280.0,
"description": "Downward longwave flux at the ground (GLW)."
},
"tmn": {
"type": "parameter",
"units": "K",
"default": 288.0,
"description": "Deep-soil (annual mean) temperature TMN; enters only the force-restore term hm."
},
"thc": {
"type": "parameter",
"units": "cal/(cm*K*s^0.5)",
"default": 0.04,
"description": "Soil thermal inertia THC (WRF land-use table, cal cm^-1 K^-1 s^-1/2)."
},
"emiss": {
"type": "parameter",
"units": "1",
"default": 0.92,
"description": "Surface emissivity EMISS."
},
"mavail": {
"type": "parameter",
"units": "1",
"default": 0.6,
"description": "Surface moisture availability MAVAIL; SLAB1D only copies it to chklowq (it is already inside flqc)."
},
"xland": {
"type": "parameter",
"units": "1",
"default": 1.0,
"description": "Land mask XLAND, 1 land, 2 water."
},
"snowc": {
"type": "parameter",
"units": "1",
"default": 0.0,
"description": "Snow-cover flag SNOWC (1 where snow covers the ground)."
},
"radiation": {
"type": "parameter",
"units": "1",
"default": 1.0,
"description": "1 when a radiation scheme is active (WRF logical `radiation`, RADSWTCH): without radiation SLAB1D drops the upward longwave emission."
},
"ifsnow": {
"type": "parameter",
"units": "1",
"default": 1.0,
"description": "Namelist IFSNOW: 1 activates the snow-cover temperature cap."
},
"T_deep": {
"type": "parameter",
"units": "K",
"default": 288.0,
"description": "Temperature of the deepest WRF soil layer (TSLB(num_soil_layers)), never updated by SLAB1D and therefore the fixed reservoir closing the soil column at the bottom."
},
"z_deep": {
"type": "parameter",
"units": "m",
"default": 0.23,
"description": "Depth of the centre of the deepest WRF soil layer (ZS(num_soil_layers))."
},
"ze": {
"type": "unknown",
"units": "m",
"shape": [
"lev_nodes"
],
"description": "Interface depths below ground, ze[1] = 0, ze[k+1] = ze[k] + DZS(k), consumer-supplied through `input_ze`; the grid's single geometry input."
},
"zc": {
"type": "unknown",
"units": "m",
"shape": [
"lev"
],
"description": "Layer-centre depths (WRF ZS), grid-derived from ze."
},
"dz": {
"type": "unknown",
"units": "m",
"shape": [
"lev"
],
"description": "Layer thicknesses (WRF DZS), grid-derived from ze; the finite-volume measure read by the rule."
},
"T_s": {
"type": "unknown",
"units": "K",
"shape": [
"lev"
],
"description": "Soil temperature of the prognostic layers (WRF TSLB(1:num_soil_layers-1)), consumer-supplied through `input_T_s`."
},
"land": {
"type": "unknown",
"units": "1",
"description": "Land indicator XLD1: 1 where xland < 1.5, else 0; water points keep their temperatures."
},
"capg": {
"type": "unknown",
"units": "J/(K*m^3)",
"description": "Volumetric soil heat capacity CAPG = 5.9114e7 THC of the multi-layer soil."
},
"exner_air": {
"type": "unknown",
"units": "1",
"description": "Exner function of the lowest model layer, (p_air / p1000mb)^(r_d/cp) (1/THCON)."
},
"theta_air": {
"type": "unknown",
"units": "K",
"description": "Potential temperature of the lowest model layer (THX)."
},
"exner_sfc": {
"type": "unknown",
"units": "1",
"description": "Exner function at the surface pressure."
},
"theta_g": {
"type": "unknown",
"units": "K",
"description": "Potential temperature of the ground surface, tsk / exner_sfc (THG)."
},
"upflux": {
"type": "unknown",
"units": "W/m^2",
"description": "Upward black-body emission of the ground, radiation sigma tsk^4 (UPFLUX)."
},
"xinet": {
"type": "unknown",
"units": "W/m^2",
"description": "Net longwave absorbed, emiss (glw - upflux) (XINET)."
},
"rnet": {
"type": "unknown",
"units": "W/m^2",
"description": "Net radiation absorbed by the surface, gsw + xinet (RNET)."
},
"hm": {
"type": "unknown",
"units": "K/s",
"description": "Force-restore restoring rate 1.18 eomeg (tsk - tmn) of the one-layer scheme (HM); diagnosed but not applied in the multi-layer form."
},
"psfc_kpa": {
"type": "unknown",
"units": "kPa",
"description": "Surface pressure in kPa, the unit of the Bolton constant svp1 (PSFC = PSFCPA/1000)."
},
"es_g": {
"type": "unknown",
"units": "kPa",
"description": "Saturation vapor pressure at the skin temperature (ESG)."
},
"qsat_g": {
"type": "unknown",
"units": "kg/kg",
"description": "Saturation mixing ratio at the skin temperature and surface pressure, ep_2 es_g / (psfc - es_g) (QSG)."
},
"hfx": {
"type": "unknown",
"units": "W/m^2",
"description": "Upward sensible heat flux flhc (theta_g - theta_air) (HFX)."
},
"qfx": {
"type": "unknown",
"units": "kg/(m^2*s)",
"description": "Upward moisture flux flqc (qsat_g - qv_air) (QFX)."
},
"lh": {
"type": "unknown",
"units": "W/m^2",
"description": "Latent heat flux xlv qfx (LH)."
},
"qs": {
"type": "unknown",
"units": "W/m^2",
"description": "Total turbulent enthalpy flux leaving the surface, hfx + lh (QS)."
},
"G_sfc": {
"type": "unknown",
"units": "W/m^2",
"description": "Heat flux conducted into the soil at the surface, rnet - qs, positive downward (FLUX(1))."
},
"K_soil": {
"type": "unknown",
"units": "W/(m*K)",
"shape": [
"lev_nodes"
],
"description": "Soil thermal conductivity capg difsl on the interfaces, the coefficient handed to the diffusion rule."
},
"F_soil": {
"type": "unknown",
"units": "W/m^2",
"shape": [
"lev_nodes"
],
"description": "Prescribed part of the interface flux in the rule's K dT/dz - F convention (depth coordinate): G_sfc on the surface interface, the conductive flux to the fixed reservoir, K_soil (T_s[NLEV] - T_deep)/(z_deep - zc[NLEV]), on the bottom interface, 0 on the interior interfaces (where the rule computes the conductive flux itself)."
},
"dTs_dt": {
"type": "unknown",
"units": "K/s",
"shape": [
"lev"
],
"description": "Soil temperature tendency of the prognostic layers, land (1/capg) d/dz(K_soil dT_s/dz - F_soil) (SLAB1D DTSDT, exact in the limit dt -> 0 where the sub-step count nsoil is 1)."
},
"dTsk_dt": {
"type": "unknown",
"units": "K/s",
"description": "Skin temperature tendency, equal to the layer-1 soil tendency (SLAB1D DTHGDT with nsoil = 1)."
},
"qsfc": {
"type": "unknown",
"units": "kg/kg",
"description": "Surface mixing ratio handed to the PBL scheme, qv_air + qfx/flqc (= qsat_g when flqc is nonzero) (QSFC)."
},
"chklowq": {
"type": "unknown",
"units": "1",
"description": "Moisture availability passed through for the PBL scheme, CHKLOWQ = MAVAIL."
},
"snow_cap_active": {
"type": "unknown",
"units": "1",
"description": "1 when SLAB1D's snow-cover cap would act (a land point, ifsnow = 1, snowc > 0 and T_s[1] > 273.16 K; SLAB1D skips water points before both caps): the cap resets the layer-1 and skin temperatures to 273.16 K after the step, a state projection that has no rate form."
}
},
"equations": [
{
"lhs": "ze",
"rhs": {
"op": "input_ze",
"args": []
}
},
{
"lhs": "T_s",
"rhs": {
"op": "input_T_s",
"args": []
}
},
{
"lhs": "zc",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "column_cell_center",
"bindings": {
"ze": "ze"
}
}
},
{
"lhs": "dz",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "column_cell_thickness",
"bindings": {
"ze": "ze"
}
}
},
{
"lhs": "land",
"rhs": {
"op": "ifelse",
"args": [
{
"op": "<",
"args": [
"xland",
"par.land_water_threshold"
]
},
{
"op": "const",
"args": [],
"value": 1.0,
"units": "1"
},
{
"op": "const",
"args": [],
"value": 0.0,
"units": "1"
}
]
}
},
{
"lhs": "capg",
"rhs": {
"op": "*",
"args": [
"par.capg_per_thc",
"thc"
]
}
},
{
"lhs": "exner_air",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "exner_function",
"bindings": {
"p": "p_air",
"p0": "wrf.p1000mb",
"kappa": "wrf.rcp"
}
}
},
{
"lhs": "theta_air",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "theta_from_temperature",
"bindings": {
"T": "t_air",
"exner": "exner_air"
}
}
},
{
"lhs": "exner_sfc",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "exner_function",
"bindings": {
"p": "psfc",
"p0": "wrf.p1000mb",
"kappa": "wrf.rcp"
}
}
},
{
"lhs": "theta_g",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "theta_from_temperature",
"bindings": {
"T": "tsk",
"exner": "exner_sfc"
}
}
},
{
"lhs": "upflux",
"rhs": {
"op": "*",
"args": [
"radiation",
{
"op": "apply_expression_template",
"args": [],
"name": "blackbody_emission",
"bindings": {
"sigma": "wrf.stbolt",
"T": "tsk"
}
}
]
}
},
{
"lhs": "xinet",
"rhs": {
"op": "*",
"args": [
"emiss",
{
"op": "-",
"args": [
"glw",
"upflux"
]
}
]
}
},
{
"lhs": "rnet",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "net_radiation",
"bindings": {
"gsw": "gsw",
"glw": "glw",
"emiss": "emiss",
"upflux": "upflux"
}
}
},
{
"lhs": "hm",
"rhs": {
"op": "*",
"args": [
"par.hm_coef",
"wrf.eomeg",
{
"op": "-",
"args": [
"tsk",
"tmn"
]
}
]
}
},
{
"lhs": "psfc_kpa",
"rhs": {
"op": "*",
"args": [
"psfc",
"par.kpa_per_pa"
]
}
},
{
"lhs": "es_g",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "bolton_saturation_vapor_pressure",
"bindings": {
"T": "tsk",
"svp1": "wrf.svp1",
"svp2": "wrf.svp2",
"svp3": "wrf.svp3",
"svpt0": "wrf.svpt0"
}
}
},
{
"lhs": "qsat_g",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "saturation_mixing_ratio",
"bindings": {
"ep_2": "wrf.ep_2",
"es": "es_g",
"p": "psfc_kpa"
}
}
},
{
"lhs": "hfx",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "bulk_surface_flux",
"bindings": {
"coefficient": "flhc",
"surface_value": "theta_g",
"air_value": "theta_air"
}
}
},
{
"lhs": "qfx",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "bulk_surface_flux",
"bindings": {
"coefficient": "flqc",
"surface_value": "qsat_g",
"air_value": "qv_air"
}
}
},
{
"lhs": "lh",
"rhs": {
"op": "*",
"args": [
"wrf.xlv",
"qfx"
]
}
},
{
"lhs": "qs",
"rhs": {
"op": "+",
"args": [
"hfx",
"lh"
]
}
},
{
"lhs": "G_sfc",
"rhs": {
"op": "-",
"args": [
"rnet",
"qs"
]
}
},
{
"lhs": "K_soil",
"rhs": {
"op": "faq",
"output_idx": [
"kn"
],
"args": [],
"ranges": {
"kn": {
"from": "lev_nodes"
}
},
"expr": {
"op": "*",
"args": [
"par.difsl",
"capg"
]
}
}
},
{
"lhs": "F_soil",
"rhs": {
"op": "faq",
"output_idx": [
"kn"
],
"args": [
"K_soil",
"T_s",
"zc"
],
"ranges": {
"kn": {
"from": "lev_nodes"
}
},
"expr": {
"op": "ifelse",
"args": [
{
"op": "==",
"args": [
"kn",
1
]
},
"G_sfc",
{
"op": "ifelse",
"args": [
{
"op": "==",
"args": [
"kn",
{
"op": "+",
"args": [
"NLEV",
1
]
}
]
},
{
"op": "/",
"args": [
{
"op": "*",
"args": [
{
"op": "index",
"args": [
"K_soil",
"kn"
]
},
{
"op": "-",
"args": [
{
"op": "index",
"args": [
"T_s",
"NLEV"
]
},
"T_deep"
]
}
]
},
{
"op": "-",
"args": [
"z_deep",
{
"op": "index",
"args": [
"zc",
"NLEV"
]
}
]
}
]
},
0.0
]
}
]
}
}
},
{
"lhs": "dTs_dt",
"rhs": {
"op": "*",
"args": [
"land",
{
"op": "/",
"args": [
{
"op": "D",
"args": [
{
"op": "-",
"args": [
{
"op": "*",
"args": [
"K_soil",
{
"op": "D",
"args": [
"T_s"
],
"wrt": "lev"
}
]
},
"F_soil"
]
}
],
"wrt": "lev"
},
"capg"
]
}
]
}
},
{
"lhs": "dTsk_dt",
"rhs": {
"op": "index",
"args": [
"dTs_dt",
1
]
}
},
{
"lhs": "qsfc",
"rhs": {
"op": "ifelse",
"args": [
{
"op": "==",
"args": [
"flqc",
0.0
]
},
"qv_air",
{
"op": "+",
"args": [
"qv_air",
{
"op": "/",
"args": [
"qfx",
"flqc"
]
}
]
}
]
}
},
{
"lhs": "chklowq",
"rhs": "mavail"
},
{
"lhs": "snow_cap_active",
"rhs": {
"op": "*",
"args": [
"land",
"ifsnow",
{
"op": "ifelse",
"args": [
{
"op": "and",
"args": [
{
"op": ">",
"args": [
"snowc",
0.0
]
},
{
"op": ">",
"args": [
{
"op": "index",
"args": [
"T_s",
1
]
},
"par.t_snowmelt"
]
}
]
},
{
"op": "const",
"args": [],
"value": 1.0,
"units": "1"
},
{
"op": "const",
"args": [],
"value": 0.0,
"units": "1"
}
]
}
]
}
}
],
"tests": [
{
"id": "scm_call120_daytime_heating",
"description": "Reference: WRF 4.8 em_scm_xy single-column run (GNU real32 build, dt = 60 s, mp=6 ra_lw=1 ra_sw=1 sf_sfclay=1 sf_surface=1 (slab, num_soil_layers = 5) bl_pbl=1) dumped by the instrumented ctessum-claude/WRF fork (commit e42a269 = v4.8.0-2-ge42a269e, module_esm_dump hooks in module_sf_slab.F) and replayed through the real64 (-fdefault-real-8) standalone kernels/slab_driver at ESM_DT = 0.01 s. Inputs are the dumped SLAB arguments of tile column 1 (real32 values widened); every expected value is the real64 replay. SLAB1D is an explicit Euler step with nsoil soil sub-steps (nsoil = 2 at dt = 60 s), so its dt = 60 s ground-temperature change is NOT the instantaneous tendency (it differs by 0.4-11 % across the regimes); at dt <= 0.04 s nsoil = 1 and the dumped DTHGDT is the exact derivative at the initial state -- verified bit-identical for dt = 0.04, 0.02, 0.01 and 0.005 s, so no Richardson extrapolation is needed and the 0.01 s replay is used directly. Layer tendencies are asserted against SLAB1D's own arithmetic on the dumped real64 interface fluxes, -(FLUX(L+1)-FLUX(L))/(DZS(L) CAPG), and spot-checked against (TSLB_out - TSLB)/dt. Residual esm-vs-Fortran mismatch comes from the real32 WRF constants handed to the kernel (svpt0 = 273.1499939, svp1 = 0.61119998, ep2 = 0.62175041, rovcp = 0.28571430, stbolt = 5.6705101e-8) versus the nominal values of lib/wrf_constants.esm: measured with zero tolerances over the four regimes it is <= 8.3e-8 relative in theta_air, upflux, xinet, rnet, hm and hfx; 4.6e-7 to 2.6e-6 in qfx, lh, qs and qsfc (the Bolton exponent svp2 (T - svpt0)/(T - svp3) turns the 6.1e-6 K error of svpt0 into 4.2e-7 of es_g, and the saturation deficit qsat_g - qv_air amplifies it, most at night); and the cancellation in G = rnet - qs (|G| << qs by day) and in the layer-1 flux divergence amplifies it further to <= 4.9e-6 in G_sfc and <= 4.8e-5 in dTsk_dt = dTs_dt[1] (call 120: G = -32.6 W/m^2 out of qs = 270; the interior layers stay <= 1.9e-6, <= 1.7e-5 only on the ~1e-8 K/s tendencies of the isothermal regime). Tolerances are set 5-10x above the worst measured residual: rel 1e-6 on the radiative terms and hfx, 2e-5 on the moisture terms, 5e-5 on G_sfc, 5e-4 on dTsk_dt, Linf abs 5e-8 K/s on the tendency column (the layer-1 residual, <= 6.3e-9 K/s) and rel 5e-4 / abs 1e-11 K/s on the per-layer spot checks. Regime: call 120 (step 60, 20:00 UTC = 13:34 LST), midday: gsw = 315 W/m^2, rnet = 237 W/m^2, hfx = 71, lh = 199 W/m^2, G = -32.6 W/m^2 (the surface loses heat to the turbulent fluxes while the soil below is warming from the morning), soil warmer at depth (285.7 -> 288 K).",
"parameter_overrides": {
"t_air": 283.92919921875,
"qv_air": 0.0032298772130161524,
"p_air": 96827.421875,
"psfc": 97126.0078125,
"flhc": 46.444984436035156,
"flqc": 0.012818618677556515,
"tsk": 285.68798828125,
"gsw": 315.13812255859375,
"glw": 292.805419921875,
"tmn": 288.0,
"thc": 0.03999999910593033,
"emiss": 0.9200000166893005,
"mavail": 0.6000000238418579,
"xland": 1.0,
"snowc": 0.0,
"radiation": 1.0,
"ifsnow": 1.0,
"T_deep": 288.0,
"z_deep": 0.22999998927116394
},
"expression_template_imports": [
{
"ref": "./tests/scm_call120_daytime_heating_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "land",
"time": 0.0,
"expected": 1.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "capg",
"time": 0.0,
"expected": 2364559.9471479654,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "theta_air",
"time": 0.0,
"expected": 286.5566714433966,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "upflux",
"time": 0.0,
"expected": 377.73745713208905,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "xinet",
"time": 0.0,
"expected": -78.13747565085322,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "rnet",
"time": 0.0,
"expected": 237.00064690774053,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hm",
"time": 0.0,
"expected": -0.00019894116518699432,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hfx",
"time": 0.0,
"expected": 70.66749182598637,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "qfx",
"time": 0.0,
"expected": 7.95558726523883e-05,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "lh",
"time": 0.0,
"expected": 198.88968163097076,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qs",
"time": 0.0,
"expected": 269.5571734569571,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qsfc",
"time": 0.0,
"expected": 0.009436152214524603,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "chklowq",
"time": 0.0,
"expected": 0.6000000238418579,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "G_sfc",
"time": 0.0,
"expected": -32.556526549216585,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "dTsk_dt",
"time": 0.0,
"expected": 0.00010029903511631255,
"tolerance": {
"rel": 0.0005
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"reduce": "Linf_error",
"expected": 0.0,
"reference": {
"op": "faq",
"output_idx": [
"k"
],
"args": [],
"ranges": {
"k": {
"from": "lev"
}
},
"expr": {
"op": "index",
"args": [
{
"op": "const",
"args": [],
"value": [
0.00010029903511631255,
-2.3600217080122603e-05,
-0.00017942430504969007,
-8.088748574696803e-05
]
},
"k"
]
}
},
"tolerance": {
"abs": 5e-08
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 1
},
"expected": 0.00010029903592112532,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 2
},
"expected": -2.3600216536578955e-05,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 3
},
"expected": -0.00017942430758921546,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": -8.088748586487782e-05,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "snow_cap_active",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
}
]
},
{
"id": "scm_call1440_night_cooling",
"description": "Reference: WRF 4.8 em_scm_xy single-column run (GNU real32 build, dt = 60 s, mp=6 ra_lw=1 ra_sw=1 sf_sfclay=1 sf_surface=1 (slab, num_soil_layers = 5) bl_pbl=1) dumped by the instrumented ctessum-claude/WRF fork (commit e42a269 = v4.8.0-2-ge42a269e, module_esm_dump hooks in module_sf_slab.F) and replayed through the real64 (-fdefault-real-8) standalone kernels/slab_driver at ESM_DT = 0.01 s. Inputs are the dumped SLAB arguments of tile column 1 (real32 values widened); every expected value is the real64 replay. SLAB1D is an explicit Euler step with nsoil soil sub-steps (nsoil = 2 at dt = 60 s), so its dt = 60 s ground-temperature change is NOT the instantaneous tendency (it differs by 0.4-11 % across the regimes); at dt <= 0.04 s nsoil = 1 and the dumped DTHGDT is the exact derivative at the initial state -- verified bit-identical for dt = 0.04, 0.02, 0.01 and 0.005 s, so no Richardson extrapolation is needed and the 0.01 s replay is used directly. Layer tendencies are asserted against SLAB1D's own arithmetic on the dumped real64 interface fluxes, -(FLUX(L+1)-FLUX(L))/(DZS(L) CAPG), and spot-checked against (TSLB_out - TSLB)/dt. Residual esm-vs-Fortran mismatch comes from the real32 WRF constants handed to the kernel (svpt0 = 273.1499939, svp1 = 0.61119998, ep2 = 0.62175041, rovcp = 0.28571430, stbolt = 5.6705101e-8) versus the nominal values of lib/wrf_constants.esm: measured with zero tolerances over the four regimes it is <= 8.3e-8 relative in theta_air, upflux, xinet, rnet, hm and hfx; 4.6e-7 to 2.6e-6 in qfx, lh, qs and qsfc (the Bolton exponent svp2 (T - svpt0)/(T - svp3) turns the 6.1e-6 K error of svpt0 into 4.2e-7 of es_g, and the saturation deficit qsat_g - qv_air amplifies it, most at night); and the cancellation in G = rnet - qs (|G| << qs by day) and in the layer-1 flux divergence amplifies it further to <= 4.9e-6 in G_sfc and <= 4.8e-5 in dTsk_dt = dTs_dt[1] (call 120: G = -32.6 W/m^2 out of qs = 270; the interior layers stay <= 1.9e-6, <= 1.7e-5 only on the ~1e-8 K/s tendencies of the isothermal regime). Tolerances are set 5-10x above the worst measured residual: rel 1e-6 on the radiative terms and hfx, 2e-5 on the moisture terms, 5e-5 on G_sfc, 5e-4 on dTsk_dt, Linf abs 5e-8 K/s on the tendency column (the layer-1 residual, <= 6.3e-9 K/s) and rel 5e-4 / abs 1e-11 K/s on the per-layer spot checks. Regime: call 1440 (step 720, 07:00 UTC = 00:34 LST), night: gsw = 0, rnet = xinet = -79.5 W/m^2, hfx = -23 W/m^2 (downward), lh = 12 W/m^2, G = -67.9 W/m^2, all soil layers cooling at 6-8e-5 K/s, strong soil temperature gradient (278.4 -> 288 K).",
"parameter_overrides": {
"t_air": 279.5789489746094,
"qv_air": 0.004902889020740986,
"p_air": 96835.6484375,
"psfc": 97134.734375,
"flhc": 16.106531143188477,
"flqc": 0.005810157395899296,
"tsk": 278.38031005859375,
"gsw": 0.0,
"glw": 254.17991638183594,
"tmn": 288.0,
"thc": 0.03999999910593033,
"emiss": 0.9200000166893005,
"mavail": 0.6000000238418579,
"xland": 1.0,
"snowc": 0.0,
"radiation": 1.0,
"ifsnow": 1.0,
"T_deep": 288.0,
"z_deep": 0.22999998927116394
},
"expression_template_imports": [
{
"ref": "./tests/scm_call1440_night_cooling_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "land",
"time": 0.0,
"expected": 1.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "capg",
"time": 0.0,
"expected": 2364559.9471479654,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "theta_air",
"time": 0.0,
"expected": 282.1593150195128,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "upflux",
"time": 0.0,
"expected": 340.5463153379293,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "xinet",
"time": 0.0,
"expected": -79.45708848100067,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "rnet",
"time": 0.0,
"expected": -79.45708848100067,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hm",
"time": 0.0,
"expected": -0.0008277433501572594,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hfx",
"time": 0.0,
"expected": -23.469407684641126,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "qfx",
"time": 0.0,
"expected": 4.776515990615822e-06,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "lh",
"time": 0.0,
"expected": 11.941289976539556,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qs",
"time": 0.0,
"expected": -11.52811770810157,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qsfc",
"time": 0.0,
"expected": 0.005724986541539132,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "chklowq",
"time": 0.0,
"expected": 0.6000000238418579,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "G_sfc",
"time": 0.0,
"expected": -67.92897077289909,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "dTsk_dt",
"time": 0.0,
"expected": -7.219725288791173e-05,
"tolerance": {
"rel": 0.0005
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"reduce": "Linf_error",
"expected": 0.0,
"reference": {
"op": "faq",
"output_idx": [
"k"
],
"args": [],
"ranges": {
"k": {
"from": "lev"
}
},
"expr": {
"op": "index",
"args": [
{
"op": "const",
"args": [],
"value": [
-7.219725288791173e-05,
-7.334383365091484e-05,
-7.502874539851151e-05,
-6.702425222110667e-05
]
},
"k"
]
}
},
"tolerance": {
"abs": 5e-08
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 1
},
"expected": -7.219725262075372e-05,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 2
},
"expected": -7.334383553825319e-05,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 3
},
"expected": -7.502874268539017e-05,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": -6.70242513933772e-05,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "snow_cap_active",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
}
]
},
{
"id": "scm_call2400_morning_flux_reversal",
"description": "Reference: WRF 4.8 em_scm_xy single-column run (GNU real32 build, dt = 60 s, mp=6 ra_lw=1 ra_sw=1 sf_sfclay=1 sf_surface=1 (slab, num_soil_layers = 5) bl_pbl=1) dumped by the instrumented ctessum-claude/WRF fork (commit e42a269 = v4.8.0-2-ge42a269e, module_esm_dump hooks in module_sf_slab.F) and replayed through the real64 (-fdefault-real-8) standalone kernels/slab_driver at ESM_DT = 0.01 s. Inputs are the dumped SLAB arguments of tile column 1 (real32 values widened); every expected value is the real64 replay. SLAB1D is an explicit Euler step with nsoil soil sub-steps (nsoil = 2 at dt = 60 s), so its dt = 60 s ground-temperature change is NOT the instantaneous tendency (it differs by 0.4-11 % across the regimes); at dt <= 0.04 s nsoil = 1 and the dumped DTHGDT is the exact derivative at the initial state -- verified bit-identical for dt = 0.04, 0.02, 0.01 and 0.005 s, so no Richardson extrapolation is needed and the 0.01 s replay is used directly. Layer tendencies are asserted against SLAB1D's own arithmetic on the dumped real64 interface fluxes, -(FLUX(L+1)-FLUX(L))/(DZS(L) CAPG), and spot-checked against (TSLB_out - TSLB)/dt. Residual esm-vs-Fortran mismatch comes from the real32 WRF constants handed to the kernel (svpt0 = 273.1499939, svp1 = 0.61119998, ep2 = 0.62175041, rovcp = 0.28571430, stbolt = 5.6705101e-8) versus the nominal values of lib/wrf_constants.esm: measured with zero tolerances over the four regimes it is <= 8.3e-8 relative in theta_air, upflux, xinet, rnet, hm and hfx; 4.6e-7 to 2.6e-6 in qfx, lh, qs and qsfc (the Bolton exponent svp2 (T - svpt0)/(T - svp3) turns the 6.1e-6 K error of svpt0 into 4.2e-7 of es_g, and the saturation deficit qsat_g - qv_air amplifies it, most at night); and the cancellation in G = rnet - qs (|G| << qs by day) and in the layer-1 flux divergence amplifies it further to <= 4.9e-6 in G_sfc and <= 4.8e-5 in dTsk_dt = dTs_dt[1] (call 120: G = -32.6 W/m^2 out of qs = 270; the interior layers stay <= 1.9e-6, <= 1.7e-5 only on the ~1e-8 K/s tendencies of the isothermal regime). Tolerances are set 5-10x above the worst measured residual: rel 1e-6 on the radiative terms and hfx, 2e-5 on the moisture terms, 5e-5 on G_sfc, 5e-4 on dTsk_dt, Linf abs 5e-8 K/s on the tendency column (the layer-1 residual, <= 6.3e-9 K/s) and rel 5e-4 / abs 1e-11 K/s on the per-layer spot checks. Regime: call 2400 (step 1200, 15:00 UTC = 08:34 LST), morning: gsw = 300 W/m^2, G = +64 W/m^2 into the soil; the soil profile has a minimum at 5 cm (282.0, 281.5, 281.2, 282.7, 288 K) so the conductive flux changes sign inside the column (+44, +11.5, -31, -52 W/m^2 at the interior interfaces).",
"parameter_overrides": {
"t_air": 279.709228515625,
"qv_air": 0.005124646704643965,
"p_air": 96836.9453125,
"psfc": 97136.09375,
"flhc": 38.715145111083984,
"flqc": 0.010536261834204197,
"tsk": 282.0179138183594,
"gsw": 300.071044921875,
"glw": 254.10433959960938,
"tmn": 288.0,
"thc": 0.03999999910593033,
"emiss": 0.9200000166893005,
"mavail": 0.6000000238418579,
"xland": 1.0,
"snowc": 0.0,
"radiation": 1.0,
"ifsnow": 1.0,
"T_deep": 288.0,
"z_deep": 0.22999998927116394
},
"expression_template_imports": [
{
"ref": "./tests/scm_call2400_morning_flux_reversal_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "land",
"time": 0.0,
"expected": 1.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "capg",
"time": 0.0,
"expected": 2364559.9471479654,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "theta_air",
"time": 0.0,
"expected": 282.28971681258736,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "upflux",
"time": 0.0,
"expected": 358.6979637981378,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "xinet",
"time": 0.0,
"expected": -96.22613600824056,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "rnet",
"time": 0.0,
"expected": 203.84490891363444,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hm",
"time": 0.0,
"expected": -0.0005147392574065445,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hfx",
"time": 0.0,
"expected": 80.49919221261646,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "qfx",
"time": 0.0,
"expected": 2.3607710483229137e-05,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "lh",
"time": 0.0,
"expected": 59.019276208072846,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qs",
"time": 0.0,
"expected": 139.51846842068932,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qsfc",
"time": 0.0,
"expected": 0.007365262100760136,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "chklowq",
"time": 0.0,
"expected": 0.6000000238418579,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "G_sfc",
"time": 0.0,
"expected": 64.32644049294512,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "dTsk_dt",
"time": 0.0,
"expected": 0.0008513402904672975,
"tolerance": {
"rel": 0.0005
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"reduce": "Linf_error",
"expected": 0.0,
"reference": {
"op": "faq",
"output_idx": [
"k"
],
"args": [],
"ranges": {
"k": {
"from": "lev"
}
},
"expr": {
"op": "index",
"args": [
{
"op": "const",
"args": [],
"value": [
0.0008513402904672975,
0.0006913248855948941,
0.0004489326956473429,
0.00011041799658542456
]
},
"k"
]
}
},
"tolerance": {
"abs": 5e-08
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 1
},
"expected": 0.0008513402917742496,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 2
},
"expected": 0.0006913248853379628,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 3
},
"expected": 0.0004489326954626449,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": 0.00011041799439226452,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "snow_cap_active",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
}
]
},
{
"id": "scm_call1_isothermal_first_step",
"description": "Reference: WRF 4.8 em_scm_xy single-column run (GNU real32 build, dt = 60 s, mp=6 ra_lw=1 ra_sw=1 sf_sfclay=1 sf_surface=1 (slab, num_soil_layers = 5) bl_pbl=1) dumped by the instrumented ctessum-claude/WRF fork (commit e42a269 = v4.8.0-2-ge42a269e, module_esm_dump hooks in module_sf_slab.F) and replayed through the real64 (-fdefault-real-8) standalone kernels/slab_driver at ESM_DT = 0.01 s. Inputs are the dumped SLAB arguments of tile column 1 (real32 values widened); every expected value is the real64 replay. SLAB1D is an explicit Euler step with nsoil soil sub-steps (nsoil = 2 at dt = 60 s), so its dt = 60 s ground-temperature change is NOT the instantaneous tendency (it differs by 0.4-11 % across the regimes); at dt <= 0.04 s nsoil = 1 and the dumped DTHGDT is the exact derivative at the initial state -- verified bit-identical for dt = 0.04, 0.02, 0.01 and 0.005 s, so no Richardson extrapolation is needed and the 0.01 s replay is used directly. Layer tendencies are asserted against SLAB1D's own arithmetic on the dumped real64 interface fluxes, -(FLUX(L+1)-FLUX(L))/(DZS(L) CAPG), and spot-checked against (TSLB_out - TSLB)/dt. Residual esm-vs-Fortran mismatch comes from the real32 WRF constants handed to the kernel (svpt0 = 273.1499939, svp1 = 0.61119998, ep2 = 0.62175041, rovcp = 0.28571430, stbolt = 5.6705101e-8) versus the nominal values of lib/wrf_constants.esm: measured with zero tolerances over the four regimes it is <= 8.3e-8 relative in theta_air, upflux, xinet, rnet, hm and hfx; 4.6e-7 to 2.6e-6 in qfx, lh, qs and qsfc (the Bolton exponent svp2 (T - svpt0)/(T - svp3) turns the 6.1e-6 K error of svpt0 into 4.2e-7 of es_g, and the saturation deficit qsat_g - qv_air amplifies it, most at night); and the cancellation in G = rnet - qs (|G| << qs by day) and in the layer-1 flux divergence amplifies it further to <= 4.9e-6 in G_sfc and <= 4.8e-5 in dTsk_dt = dTs_dt[1] (call 120: G = -32.6 W/m^2 out of qs = 270; the interior layers stay <= 1.9e-6, <= 1.7e-5 only on the ~1e-8 K/s tendencies of the isothermal regime). Tolerances are set 5-10x above the worst measured residual: rel 1e-6 on the radiative terms and hfx, 2e-5 on the moisture terms, 5e-5 on G_sfc, 5e-4 on dTsk_dt, Linf abs 5e-8 K/s on the tendency column (the layer-1 residual, <= 6.3e-9 K/s) and rel 5e-4 / abs 1e-11 K/s on the per-layer spot checks. Regime: call 1 (first step, 19:00 UTC = 12:34 LST), isothermal soil at 288 K: every interior conductive flux is zero (interior tendencies ~1e-8 K/s from the single-precision ZS/DZS), so the only tendency is the surface budget G = -30.3 W/m^2 acting on layer 1; a check that the operator vanishes on a uniform field.",
"parameter_overrides": {
"t_air": 283.3756103515625,
"qv_air": 0.002500000176951289,
"p_air": 96825.0078125,
"psfc": 97123.375,
"flhc": 32.23353958129883,
"flqc": 0.014554488472640514,
"tsk": 288.0,
"gsw": 542.412109375,
"glw": 258.14453125,
"tmn": 288.0,
"thc": 0.03999999910593033,
"emiss": 0.9200000166893005,
"mavail": 0.6000000238418579,
"xland": 1.0,
"snowc": 0.0,
"radiation": 1.0,
"ifsnow": 1.0,
"T_deep": 288.0,
"z_deep": 0.22999998927116394
},
"expression_template_imports": [
{
"ref": "./tests/scm_call1_isothermal_first_step_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "land",
"time": 0.0,
"expected": 1.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "capg",
"time": 0.0,
"expected": 2364559.9471479654,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "theta_air",
"time": 0.0,
"expected": 285.9999969676229,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "upflux",
"time": 0.0,
"expected": 390.1144884042442,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "xinet",
"time": 0.0,
"expected": -121.41236278439092,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "rnet",
"time": 0.0,
"expected": 420.9997465906091,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hm",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "hfx",
"time": 0.0,
"expected": 142.20825545914465,
"tolerance": {
"rel": 1e-06
}
},
{
"variable": "qfx",
"time": 0.0,
"expected": 0.00012363862820143713,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "lh",
"time": 0.0,
"expected": 309.09657050359283,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qs",
"time": 0.0,
"expected": 451.3048259627375,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "qsfc",
"time": 0.0,
"expected": 0.010994879844749485,
"tolerance": {
"rel": 2e-05
}
},
{
"variable": "chklowq",
"time": 0.0,
"expected": 0.6000000238418579,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "G_sfc",
"time": 0.0,
"expected": -30.305079372128375,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "dTsk_dt",
"time": 0.0,
"expected": -0.0012815354811020149,
"tolerance": {
"rel": 0.0005
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"reduce": "Linf_error",
"expected": 0.0,
"reference": {
"op": "faq",
"output_idx": [
"k"
],
"args": [],
"ranges": {
"k": {
"from": "lev"
}
},
"expr": {
"op": "index",
"args": [
{
"op": "const",
"args": [],
"value": [
-0.0012815354811020149,
-7.62939503020893e-08,
1.2715658910009569e-08,
-0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 5e-08
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 1
},
"expected": -0.0012815354807571566,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 2
},
"expected": -7.629523679497652e-08,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 3
},
"expected": 1.2715872799162753e-08,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "dTs_dt",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": 0.0,
"tolerance": {
"rel": 0.0005,
"abs": 1e-11
}
},
{
"variable": "snow_cap_active",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
}
]
},
{
"id": "snow_cap_flag_on_land",
"description": "Snow-cover cap flag, not a WRF dump: the scm_call1440_night_cooling column (T_s[1] = 278.4 K) with snowc = 1 on a land point. SLAB1D's IFSNOW cap would reset T_s[1] and tsk to 273.16 K after the step, so snow_cap_active = 1.",
"parameter_overrides": {
"t_air": 279.5789489746094,
"qv_air": 0.004902889020740986,
"p_air": 96835.6484375,
"psfc": 97134.734375,
"flhc": 16.106531143188477,
"flqc": 0.005810157395899296,
"tsk": 278.38031005859375,
"gsw": 0.0,
"glw": 254.17991638183594,
"tmn": 288.0,
"thc": 0.03999999910593033,
"emiss": 0.9200000166893005,
"mavail": 0.6000000238418579,
"xland": 1.0,
"snowc": 1.0,
"radiation": 1.0,
"ifsnow": 1.0,
"T_deep": 288.0,
"z_deep": 0.22999998927116394
},
"expression_template_imports": [
{
"ref": "./tests/scm_call1440_night_cooling_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "land",
"time": 0.0,
"expected": 1.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "snow_cap_active",
"time": 0.0,
"expected": 1.0,
"tolerance": {
"abs": 0.0
}
}
]
},
{
"id": "snow_cap_flag_off_on_water",
"description": "Water points are frozen, snow cover or not: the scm_call1440_night_cooling column (T_s[1] = 278.4 K) with snowc = 1 and xland = 2. SLAB1D jumps past both IFSNOW caps on water points (module_sf_slab.F, IF(XLD1(I).LT.0.5) GOTO 75 / GOTO 85), so snow_cap_active = 0, and the land factor zeroes every tendency.",
"parameter_overrides": {
"t_air": 279.5789489746094,
"qv_air": 0.004902889020740986,
"p_air": 96835.6484375,
"psfc": 97134.734375,
"flhc": 16.106531143188477,
"flqc": 0.005810157395899296,
"tsk": 278.38031005859375,
"gsw": 0.0,
"glw": 254.17991638183594,
"tmn": 288.0,
"thc": 0.03999999910593033,
"emiss": 0.9200000166893005,
"mavail": 0.6000000238418579,
"xland": 2.0,
"snowc": 1.0,
"radiation": 1.0,
"ifsnow": 1.0,
"T_deep": 288.0,
"z_deep": 0.22999998927116394
},
"expression_template_imports": [
{
"ref": "./tests/scm_call1440_night_cooling_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "land",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "snow_cap_active",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
},
{
"variable": "dTsk_dt",
"time": 0.0,
"expected": 0.0,
"tolerance": {
"abs": 0.0
}
}
]
}
]
}