WSM6SaturationAdjustment
Saturation adjustment of cloud water in the WRF single-moment 6-class microphysics scheme (WSM6; Hong and Lim 2006), transcribed from the pcond block at the end of each sub-time step of mp_wsm6_run (phys/physics_mmm/mp_wsm6.F90, WRF 4.8, NCAR/MMM-physics; the statement function conden and the loop labelled 'pcond: condensational/evaporational rate of cloud water [HL A46] [RH83 A6]'). After every process rate has been applied and the hydrometeors have fallen, the kernel recomputes the saturation mixing ratio OVER WATER from the updated temperature and vapor (the inlined fpvs block, water branch only -- ice saturation plays no part here, whatever the temperature) and removes the remaining super- or sub-saturation with the one-step Newton (Asai 1965; Rutledge and Hobbs 1983 eq. A6) increment dq = (qv - qsat) / (1 + L^2 qsat / (r_v cpm T^2)), the linearisation of the Clausius-Clapeyron feedback of the latent heating on qsat. Condensation (dq > 0) is capped by the available vapor, evaporation (dq < 0) happens only where cloud water exists and is capped by the cloud water present, so that a layer that dries out loses all its cloud (the kernel then pads qc <= qmin to 0). The latent heat L and the moist heat capacity cpm are the START-OF-CALL values (EqWeFiC FORTRAN_BUGS.md note N4), so they are inputs here (saturation.esm xl, cpm); the saturation state at the adjusted temperature comes from saturation.esm mounted as subsystem `sat` on the same inputs. This is an INSTANTANEOUS ADJUSTMENT, not a rate: WSM6 applies the whole increment dq once per sub-step and stores pcond = dq/dtcld only for bookkeeping, so the stored 'rate' scales with 1/dtcld (EqWeFiC FORTRAN_BUGS.md note N3) and a small-dt replay of the kernel is meaningless for it. The model therefore exposes (i) the adjusted target state T*, qv*, qc* and the applied increment dq_applied, which are step-free and are what the tests check against the kernel, (ii) pcond = dq_applied/dtcld as the kernel reports it, and (iii) the relaxation-rate form pcond_relax = dq_adj/tau with an adjustment time tau as a parameter: for tau = dtcld it reproduces the kernel's unlimited rate, and a continuous model that cannot take an instantaneous step would use this form with a physical tau (a few seconds to tens of seconds) as the condensation tendency. Layer index `lev` (WRF k, layer 1 at the surface), pointwise, no grid geometry. Consumer-supplied inputs are the REWRITE-TARGET ops input_T, input_qv, input_p (read by the `sat` subsystem and re-exported), input_qc, input_xl and input_cpm, lowered in the inline tests through a per-test expression_template_imports library extracted from kernel dumps of the instrumented WRF fork ctessum-claude/WRF, commit e42a269 (v4.8.0-2-ge42a269e) (esm-spec 6.6 admits scalar overrides only). EqWeFiC, cited here for FORTRAN_BUGS.md and couplings/, is https://github.com/EarthSciML/EqWeFiC, where these components were developed and are coupled into a WRF column.
Description
Saturation adjustment of cloud water in the WRF single-moment 6-class microphysics scheme (WSM6; Hong and Lim 2006), transcribed from the pcond block at the end of each sub-time step of mp_wsm6_run (phys/physics_mmm/mp_wsm6.F90, WRF 4.8, NCAR/MMM-physics; the statement function conden and the loop labelled ‘pcond: condensational/evaporational rate of cloud water [HL A46] [RH83 A6]’). After every process rate has been applied and the hydrometeors have fallen, the kernel recomputes the saturation mixing ratio OVER WATER from the updated temperature and vapor (the inlined fpvs block, water branch only – ice saturation plays no part here, whatever the temperature) and removes the remaining super- or sub-saturation with the one-step Newton (Asai 1965; Rutledge and Hobbs 1983 eq. A6) increment dq = (qv - qsat) / (1 + L^2 qsat / (r_v cpm T^2)), the linearisation of the Clausius-Clapeyron feedback of the latent heating on qsat. Condensation (dq > 0) is capped by the available vapor, evaporation (dq < 0) happens only where cloud water exists and is capped by the cloud water present, so that a layer that dries out loses all its cloud (the kernel then pads qc <= qmin to 0). The latent heat L and the moist heat capacity cpm are the START-OF-CALL values (EqWeFiC FORTRAN_BUGS.md note N4), so they are inputs here (saturation.esm xl, cpm); the saturation state at the adjusted temperature comes from saturation.esm mounted as subsystem sat on the same inputs. This is an INSTANTANEOUS ADJUSTMENT, not a rate: WSM6 applies the whole increment dq once per sub-step and stores pcond = dq/dtcld only for bookkeeping, so the stored ‘rate’ scales with 1/dtcld (EqWeFiC FORTRAN_BUGS.md note N3) and a small-dt replay of the kernel is meaningless for it. The model therefore exposes (i) the adjusted target state T*, qv*, qc* and the applied increment dq_applied, which are step-free and are what the tests check against the kernel, (ii) pcond = dq_applied/dtcld as the kernel reports it, and (iii) the relaxation-rate form pcond_relax = dq_adj/tau with an adjustment time tau as a parameter: for tau = dtcld it reproduces the kernel’s unlimited rate, and a continuous model that cannot take an instantaneous step would use this form with a physical tau (a few seconds to tens of seconds) as the condensation tendency. Layer index lev (WRF k, layer 1 at the surface), pointwise, no grid geometry. Consumer-supplied inputs are the REWRITE-TARGET ops input_T, input_qv, input_p (read by the sat subsystem and re-exported), input_qc, input_xl and input_cpm, lowered in the inline tests through a per-test expression_template_imports library extracted from kernel dumps of the instrumented WRF fork ctessum-claude/WRF, commit e42a269 (v4.8.0-2-ge42a269e) (esm-spec 6.6 admits scalar overrides only). EqWeFiC, cited here for FORTRAN_BUGS.md and couplings/, is https://github.com/EarthSciML/EqWeFiC
, where these components were developed and are coupled into a WRF column.
Reference
- Hong, S.-Y., and J.-O. J. Lim (2006). The WRF single-moment 6-class microphysics scheme (WSM6). J. Korean Meteor. Soc. 42, 129-151.
- Rutledge, S. A., and P. V. Hobbs (1983). The mesoscale and microscale structure and organization of clouds and precipitation in midlatitude cyclones. VIII. J. Atmos. Sci. 40, 1185-1206 (eq. A6).
- Asai, T. (1965). A numerical study of the air-mass transformation over the Japan Sea in winter. J. Meteor. Soc. Japan 43, 1-15.
- NCAR/MMM-physics mp_wsm6.F90 at commit 550b5b4 (WRF 4.8 phys/physics_mmm submodule), conden statement function (line 434) and the pcond loop (lines 1546-1560).
Parameters
| Name | Units | Default | Description |
|---|---|---|---|
qc_in | kg/kg | 0.0 | Coupling-target parameter (additive): receives the cloud-water mixing ratio after the rate update from the upstream stage through a document coupling (EqWeFiC couplings/microphysics_column*.esm), where the mount-edge template library lowers input_qc to this name. Unused by the standalone inline tests, which lower input_qc to a dumped column profile instead. |
xl_in | J/kg | 0.0 | Coupling-target parameter (additive): receives the start-of-sub-step latent heat of vaporization from the upstream stage through a document coupling (EqWeFiC couplings/microphysics_column*.esm), where the mount-edge template library lowers input_xl to this name. Unused by the standalone inline tests, which lower input_xl to a dumped column profile instead. |
cpm_in | J/(kg*K) | 0.0 | Coupling-target parameter (additive): receives the start-of-sub-step moist heat capacity from the upstream stage through a document coupling (EqWeFiC couplings/microphysics_column*.esm), where the mount-edge template library lowers input_cpm to this name. Unused by the standalone inline tests, which lower input_cpm to a dumped column profile instead. |
dtcld | s | 60.0 | Length of the WSM6 sub-time step; the kernel reports the adjustment as the rate pcond = dq_applied/dtcld. |
tau | s | 60.0 | Adjustment (relaxation) time of the rate form pcond_relax = dq_adj/tau. WSM6 applies the full increment every sub-step, i.e. tau = dtcld; a continuous model uses a physical condensation time scale instead. |
Observed
| Name | Units | Default | Description |
|---|---|---|---|
T | K | Temperature after the sub-step’s process rates and sedimentation, before the adjustment (WSM6 t entering the pcond loop; the sat subsystem’s input). | |
qv | kg/kg | Water-vapor mixing ratio before the adjustment (WSM6 q entering the pcond loop). | |
qc | kg/kg | Cloud-water mixing ratio before the adjustment. | |
xl | J/kg | Latent heat of vaporization at the START-OF-CALL temperature (saturation.esm xl; WSM6 xl = xlcal(t) evaluated once per call, note N4). | |
cpm | J/(kg*K) | Moist heat capacity at the START-OF-CALL vapor (saturation.esm cpm; WSM6 cpm = cpmcal(q) evaluated once per call, note N4). | |
qsat | kg/kg | Saturation mixing ratio over WATER at the pre-adjustment T and p (WSM6 qsat(:,:,1) recomputed after the update loop); ice saturation is not used by the adjustment. | |
s_excess | kg/kg | Vapor excess over water saturation, max(qv, qmin) - qsat (positive: supersaturated). | |
latent_feedback | 1 | Clausius-Clapeyron feedback factor of the latent heating on the saturation mixing ratio, L^2 qsat/(r_v cpm T^2) = (L/cpm) d qsat/dT. | |
dq_adj | kg/kg | Unlimited one-step Newton adjustment increment s_excess/(1 + latent_feedback) (WSM6 work1 = conden(t, q, qsat, xl, cpm)): vapor condensed (> 0) or cloud evaporated (< 0) to reach saturation. | |
dq_applied | kg/kg | Adjustment increment actually applied after WSM6’s bounds (no evaporation without cloud, at most all the cloud, at most all the vapor) – WSM6 pcond*dtcld. | |
pcond | kg/kg/s | The applied increment expressed as the kernel’s bookkeeping rate dq_applied/dtcld (WSM6 pcond, [HL A46]); scales with 1/dtcld because the adjustment is instantaneous. | |
pcond_relax | kg/kg/s | Relaxation-rate form of the adjustment, dq_adj/tau: the condensation tendency a continuous model would apply; equals the kernel’s unlimited rate for tau = dtcld. | |
T_star | K | Adjusted temperature T + dq_applied xl/cpm (WSM6 t after the pcond loop). | |
qv_star | kg/kg | Adjusted vapor mixing ratio qv - dq_applied (WSM6 q after the pcond loop). | |
qc_star | kg/kg | Adjusted cloud-water mixing ratio max(qc + dq_applied, 0) (WSM6 qc after the pcond loop; the kernel then pads values <= qmin to exactly 0). | |
dqv_dt | kg/kg/s | Vapor tendency of the adjustment, (qv_star - qv)/dtcld = -pcond. The adjustment is applied once per sub-step, not integrated: this is the increment divided by the sub-step, which is the tendency a consumer integrating the column must add. A consumer that wants a genuine relaxation rate instead of a sub-step increment should use pcond_relax, which carries an explicit timescale tau. | |
dqc_dt | kg/kg/s | Cloud-water tendency of the adjustment, (qc_star - qc)/dtcld. Equal to +pcond except where the -qc bound of the limiter is active. See dqv_dt. | |
dT_dt | K/s | Latent-heating tendency of the adjustment, (T_star - T)/dtcld = xl pcond/cpm. See dqv_dt. |
Observed expressions
$$ T = sat.T $$Temperature after the sub-step’s process rates and sedimentation, before the adjustment (WSM6 t entering the pcond loop; the sat subsystem’s input).
Water-vapor mixing ratio before the adjustment (WSM6 q entering the pcond loop).
$$ qc = \mathrm{input_qc}\left(\right) $$Cloud-water mixing ratio before the adjustment.
$$ xl = \mathrm{input_xl}\left(\right) $$Latent heat of vaporization at the START-OF-CALL temperature (saturation.esm xl; WSM6 xl = xlcal(t) evaluated once per call, note N4).
$$ cpm = \mathrm{input_cpm}\left(\right) $$Moist heat capacity at the START-OF-CALL vapor (saturation.esm cpm; WSM6 cpm = cpmcal(q) evaluated once per call, note N4).
$$ qsat = sat.qsat_{w} $$Saturation mixing ratio over WATER at the pre-adjustment T and p (WSM6 qsat(:,:,1) recomputed after the update loop); ice saturation is not used by the adjustment.
$$ s_{excess} = \mathrm{max}\left(qv, par.qmin\right) - qsat $$Vapor excess over water saturation, max(qv, qmin) - qsat (positive: supersaturated).
$$ latent_{feedback} = \frac{xl \cdot xl \cdot qsat}{wrf.r_{v} \cdot cpm \cdot T \cdot T} $$Clausius-Clapeyron feedback factor of the latent heating on the saturation mixing ratio, L^2 qsat/(r_v cpm T^2) = (L/cpm) d qsat/dT.
$$ dq_{adj} = \mathrm{asai_saturation_adjustment}\left(qv=qv, qsat=qsat, xl=xl, cpm=cpm, T=T, r_{v}=wrf.r_{v}, qmin=par.qmin\right) $$Unlimited one-step Newton adjustment increment s_excess/(1 + latent_feedback) (WSM6 work1 = conden(t, q, qsat, xl, cpm)): vapor condensed (> 0) or cloud evaporated (< 0) to reach saturation.
$$ dq_{applied} = \mathrm{saturation_adjustment_limiter}\left(dq=dq_{adj}, qv=qv, qc=qc\right) $$Adjustment increment actually applied after WSM6’s bounds (no evaporation without cloud, at most all the cloud, at most all the vapor) – WSM6 pcond*dtcld.
$$ pcond = \frac{dq_{applied}}{dtcld} $$The applied increment expressed as the kernel’s bookkeeping rate dq_applied/dtcld (WSM6 pcond, [HL A46]); scales with 1/dtcld because the adjustment is instantaneous.
$$ pcond_{relax} = \frac{dq_{adj}}{tau} $$Relaxation-rate form of the adjustment, dq_adj/tau: the condensation tendency a continuous model would apply; equals the kernel’s unlimited rate for tau = dtcld.
$$ T_{star} = T + \frac{dq_{applied} \cdot xl}{cpm} $$Adjusted temperature T + dq_applied xl/cpm (WSM6 t after the pcond loop).
$$ qv_{star} = qv - dq_{applied} $$Adjusted vapor mixing ratio qv - dq_applied (WSM6 q after the pcond loop).
$$ qc_{star} = \mathrm{max}\left(qc + dq_{applied}, 0\right) $$Adjusted cloud-water mixing ratio max(qc + dq_applied, 0) (WSM6 qc after the pcond loop; the kernel then pads values <= qmin to exactly 0).
$$ dqv_{dt} = \frac{qv_{star} - qv}{dtcld} $$Vapor tendency of the adjustment, (qv_star - qv)/dtcld = -pcond. The adjustment is applied once per sub-step, not integrated: this is the increment divided by the sub-step, which is the tendency a consumer integrating the column must add. A consumer that wants a genuine relaxation rate instead of a sub-step increment should use pcond_relax, which carries an explicit timescale tau.
$$ dqc_{dt} = \frac{qc_{star} - qc}{dtcld} $$Cloud-water tendency of the adjustment, (qc_star - qc)/dtcld. Equal to +pcond except where the -qc bound of the limiter is active. See dqv_dt.
$$ dT_{dt} = \frac{T_{star} - T}{dtcld} $$Latent-heating tendency of the adjustment, (T_star - T)/dtcld = xl pcond/cpm. See dqv_dt.
Expression Templates
asai_saturation_adjustment
Parameters: qv, qsat, xl, cpm, T, r_v, qmin
saturation_adjustment_limiter
Parameters: dq, qv, qc
Raw .esm
Raw .esm JSON (this component)
{
"subsystems": {
"wrf": {
"ref": "../../../lib/wrf_constants.esm"
},
"par": {
"ref": "./wsm6_parameters.esm"
},
"sat": {
"ref": "./saturation.esm",
"bindings": {
"NLEV": "NLEV"
}
}
},
"expression_templates": {
"asai_saturation_adjustment": {
"params": [
"qv",
"qsat",
"xl",
"cpm",
"T",
"r_v",
"qmin"
],
"description": "One-step Newton saturation adjustment (Asai 1965; RH83 eq. A6; WSM6 conden): the vapor excess over water saturation divided by the Clausius-Clapeyron feedback factor 1 + L^2 qsat/(r_v cpm T^2), i.e. the amount of vapor that must condense (positive) or of cloud that must evaporate (negative) for the layer to reach saturation at its adjusted temperature, to first order. The vapor is floored at qmin as in the kernel.",
"body": {
"op": "/",
"args": [
{
"op": "-",
"args": [
{
"op": "max",
"args": [
"qv",
"qmin"
]
},
"qsat"
]
},
{
"op": "+",
"args": [
1,
{
"op": "/",
"args": [
{
"op": "*",
"args": [
"xl",
"xl",
"qsat"
]
},
{
"op": "*",
"args": [
"r_v",
"cpm",
"T",
"T"
]
}
]
}
]
}
]
}
},
"saturation_adjustment_limiter": {
"params": [
"dq",
"qv",
"qc"
],
"description": "WSM6's bounds on the adjustment increment: evaporation (dq < 0) only where cloud water exists and at most all of it, max(dq, -qc); otherwise condensation is non-negative and at most the vapor present, min(max(dq, 0), max(qv, 0)). A sub-saturated layer without cloud is left as it is.",
"body": {
"op": "ifelse",
"args": [
{
"op": "and",
"args": [
{
"op": ">",
"args": [
"qc",
0
]
},
{
"op": "<",
"args": [
"dq",
0
]
}
]
},
{
"op": "max",
"args": [
"dq",
{
"op": "-",
"args": [
"qc"
]
}
]
},
{
"op": "min",
"args": [
{
"op": "max",
"args": [
"dq",
0
]
},
{
"op": "max",
"args": [
"qv",
0
]
}
]
}
]
}
}
},
"variables": {
"qc_in": {
"type": "parameter",
"units": "kg/kg",
"shape": [
"lev"
],
"default": 0.0,
"description": "Coupling-target parameter (additive): receives the cloud-water mixing ratio after the rate update from the upstream stage through a document coupling (EqWeFiC couplings/microphysics_column*.esm), where the mount-edge template library lowers `input_qc` to this name. Unused by the standalone inline tests, which lower `input_qc` to a dumped column profile instead."
},
"xl_in": {
"type": "parameter",
"units": "J/kg",
"shape": [
"lev"
],
"default": 0.0,
"description": "Coupling-target parameter (additive): receives the start-of-sub-step latent heat of vaporization from the upstream stage through a document coupling (EqWeFiC couplings/microphysics_column*.esm), where the mount-edge template library lowers `input_xl` to this name. Unused by the standalone inline tests, which lower `input_xl` to a dumped column profile instead."
},
"cpm_in": {
"type": "parameter",
"units": "J/(kg*K)",
"shape": [
"lev"
],
"default": 0.0,
"description": "Coupling-target parameter (additive): receives the start-of-sub-step moist heat capacity from the upstream stage through a document coupling (EqWeFiC couplings/microphysics_column*.esm), where the mount-edge template library lowers `input_cpm` to this name. Unused by the standalone inline tests, which lower `input_cpm` to a dumped column profile instead."
},
"T": {
"type": "unknown",
"units": "K",
"shape": [
"lev"
],
"description": "Temperature after the sub-step's process rates and sedimentation, before the adjustment (WSM6 t entering the pcond loop; the `sat` subsystem's input)."
},
"qv": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Water-vapor mixing ratio before the adjustment (WSM6 q entering the pcond loop)."
},
"qc": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Cloud-water mixing ratio before the adjustment."
},
"xl": {
"type": "unknown",
"units": "J/kg",
"shape": [
"lev"
],
"description": "Latent heat of vaporization at the START-OF-CALL temperature (saturation.esm xl; WSM6 xl = xlcal(t) evaluated once per call, note N4)."
},
"cpm": {
"type": "unknown",
"units": "J/(kg*K)",
"shape": [
"lev"
],
"description": "Moist heat capacity at the START-OF-CALL vapor (saturation.esm cpm; WSM6 cpm = cpmcal(q) evaluated once per call, note N4)."
},
"qsat": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Saturation mixing ratio over WATER at the pre-adjustment T and p (WSM6 qsat(:,:,1) recomputed after the update loop); ice saturation is not used by the adjustment."
},
"dtcld": {
"type": "parameter",
"units": "s",
"default": 60.0,
"description": "Length of the WSM6 sub-time step; the kernel reports the adjustment as the rate pcond = dq_applied/dtcld."
},
"tau": {
"type": "parameter",
"units": "s",
"default": 60.0,
"description": "Adjustment (relaxation) time of the rate form pcond_relax = dq_adj/tau. WSM6 applies the full increment every sub-step, i.e. tau = dtcld; a continuous model uses a physical condensation time scale instead."
},
"s_excess": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Vapor excess over water saturation, max(qv, qmin) - qsat (positive: supersaturated)."
},
"latent_feedback": {
"type": "unknown",
"units": "1",
"shape": [
"lev"
],
"description": "Clausius-Clapeyron feedback factor of the latent heating on the saturation mixing ratio, L^2 qsat/(r_v cpm T^2) = (L/cpm) d qsat/dT."
},
"dq_adj": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Unlimited one-step Newton adjustment increment s_excess/(1 + latent_feedback) (WSM6 work1 = conden(t, q, qsat, xl, cpm)): vapor condensed (> 0) or cloud evaporated (< 0) to reach saturation."
},
"dq_applied": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Adjustment increment actually applied after WSM6's bounds (no evaporation without cloud, at most all the cloud, at most all the vapor) -- WSM6 pcond*dtcld."
},
"pcond": {
"type": "unknown",
"units": "kg/kg/s",
"shape": [
"lev"
],
"description": "The applied increment expressed as the kernel's bookkeeping rate dq_applied/dtcld (WSM6 pcond, [HL A46]); scales with 1/dtcld because the adjustment is instantaneous."
},
"pcond_relax": {
"type": "unknown",
"units": "kg/kg/s",
"shape": [
"lev"
],
"description": "Relaxation-rate form of the adjustment, dq_adj/tau: the condensation tendency a continuous model would apply; equals the kernel's unlimited rate for tau = dtcld."
},
"T_star": {
"type": "unknown",
"units": "K",
"shape": [
"lev"
],
"description": "Adjusted temperature T + dq_applied xl/cpm (WSM6 t after the pcond loop)."
},
"qv_star": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Adjusted vapor mixing ratio qv - dq_applied (WSM6 q after the pcond loop)."
},
"qc_star": {
"type": "unknown",
"units": "kg/kg",
"shape": [
"lev"
],
"description": "Adjusted cloud-water mixing ratio max(qc + dq_applied, 0) (WSM6 qc after the pcond loop; the kernel then pads values <= qmin to exactly 0)."
},
"dqv_dt": {
"type": "unknown",
"units": "kg/kg/s",
"shape": [
"lev"
],
"description": "Vapor tendency of the adjustment, (qv_star - qv)/dtcld = -pcond. The adjustment is applied once per sub-step, not integrated: this is the increment divided by the sub-step, which is the tendency a consumer integrating the column must add. A consumer that wants a genuine relaxation rate instead of a sub-step increment should use pcond_relax, which carries an explicit timescale tau."
},
"dqc_dt": {
"type": "unknown",
"units": "kg/kg/s",
"shape": [
"lev"
],
"description": "Cloud-water tendency of the adjustment, (qc_star - qc)/dtcld. Equal to +pcond except where the -qc bound of the limiter is active. See dqv_dt."
},
"dT_dt": {
"type": "unknown",
"units": "K/s",
"shape": [
"lev"
],
"description": "Latent-heating tendency of the adjustment, (T_star - T)/dtcld = xl pcond/cpm. See dqv_dt."
}
},
"equations": [
{
"lhs": "T",
"rhs": "sat.T"
},
{
"lhs": "qv",
"rhs": "sat.qv"
},
{
"lhs": "qc",
"rhs": {
"op": "input_qc",
"args": []
}
},
{
"lhs": "xl",
"rhs": {
"op": "input_xl",
"args": []
}
},
{
"lhs": "cpm",
"rhs": {
"op": "input_cpm",
"args": []
}
},
{
"lhs": "qsat",
"rhs": "sat.qsat_w"
},
{
"lhs": "s_excess",
"rhs": {
"op": "-",
"args": [
{
"op": "max",
"args": [
"qv",
"par.qmin"
]
},
"qsat"
]
}
},
{
"lhs": "latent_feedback",
"rhs": {
"op": "/",
"args": [
{
"op": "*",
"args": [
"xl",
"xl",
"qsat"
]
},
{
"op": "*",
"args": [
"wrf.r_v",
"cpm",
"T",
"T"
]
}
]
}
},
{
"lhs": "dq_adj",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "asai_saturation_adjustment",
"bindings": {
"qv": "qv",
"qsat": "qsat",
"xl": "xl",
"cpm": "cpm",
"T": "T",
"r_v": "wrf.r_v",
"qmin": "par.qmin"
}
}
},
{
"lhs": "dq_applied",
"rhs": {
"op": "apply_expression_template",
"args": [],
"name": "saturation_adjustment_limiter",
"bindings": {
"dq": "dq_adj",
"qv": "qv",
"qc": "qc"
}
}
},
{
"lhs": "pcond",
"rhs": {
"op": "/",
"args": [
"dq_applied",
"dtcld"
]
}
},
{
"lhs": "pcond_relax",
"rhs": {
"op": "/",
"args": [
"dq_adj",
"tau"
]
}
},
{
"lhs": "T_star",
"rhs": {
"op": "+",
"args": [
"T",
{
"op": "/",
"args": [
{
"op": "*",
"args": [
"dq_applied",
"xl"
]
},
"cpm"
]
}
]
}
},
{
"lhs": "qv_star",
"rhs": {
"op": "-",
"args": [
"qv",
"dq_applied"
]
}
},
{
"lhs": "qc_star",
"rhs": {
"op": "max",
"args": [
{
"op": "+",
"args": [
"qc",
"dq_applied"
]
},
0
]
}
},
{
"lhs": "dqv_dt",
"rhs": {
"op": "/",
"args": [
{
"op": "-",
"args": [
"qv_star",
"qv"
]
},
"dtcld"
]
}
},
{
"lhs": "dqc_dt",
"rhs": {
"op": "/",
"args": [
{
"op": "-",
"args": [
"qc_star",
"qc"
]
},
"dtcld"
]
}
},
{
"lhs": "dT_dt",
"rhs": {
"op": "/",
"args": [
{
"op": "-",
"args": [
"T_star",
"T"
]
},
"dtcld"
]
}
}
],
"tests": [
{
"id": "qss_step250_row23_col24_condensing_updraft",
"description": "Reference: the pcond block of mp_wsm6_run (phys/physics_mmm/mp_wsm6.F90, ctessum-claude/MMM-physics fork 52cfbd7, instrumented) dumped in-model by the ctessum-claude/WRF fork and replayed through the real64 (kind_phys = real64) kernels/wsm6_driver at the WRF step. Inputs are the dumped state entering the block (t_upd, q_upd, qc_upd after the process-rate update and sedimentation; p; the start-of-call xl and cpm), real32 values widened to real64 as the driver read them; the expected pcond, T*, qv* and qc* are the replay's pcond, t_out, q_out and qc_out (the kernel's outputs of this sub-step, which follow the adjustment directly apart from the qc <= qmin padding). POINTWISE and STEP-FREE: the adjustment is applied once per sub-step, so pcond = dq_applied/dtcld is the increment divided by the dumped dtcld and no small-dt replay is involved (a small-dt replay of the kernel would only rescale pcond by 1/dt, EqWeFiC FORTRAN_BUGS.md note N3). Real64 replay vs real32 dump: pcond 1.6e-5 (9983) to 8.4e-5 (7980) relative, qc_out 3e-6 to 8e-6, t_out and q_out <= 6e-7 -- the real32 kernel loses digits in the difference qv - qsat. Residual between this component and the real64 kernel (measured with zero tolerances over all four regimes): pcond <= 1.7e-10 kg/kg/s absolute (4.4e-6 of the column maximum in the condensing column, 1.3e-5 in the weaker evaporating one), T* <= 4.9e-6 K, qv* and qc* <= 2.0e-9 kg/kg; it comes from the real32 WRF constants inside the kernel (t0c = 273.1499939, psat = 610.78003, cpv = 1846.4000244, rv = 461.6000061, ep2 = 0.62175041, through qsat at 4.4e-7 of its value, as in saturation.esm) which this component takes at their nominal values from lib/wrf_constants.esm; near the saturation point the relative error of the small difference qv - qsat is correspondingly larger, so spot checks are placed on the strongly active layers and use rel 1e-4. Tolerances are 5-10x above the measured residual: Linf_error bounds pcond abs 1e-9 kg/kg/s, T* abs 2.5e-5 K, qv* and qc* abs 1e-8 kg/kg; spot checks on the four most active layers pcond rel 1e-4 (measured <= 3e-5), T* rel 1e-7 (<= 1.6e-8), qc* rel 5e-5 (<= 6e-6). The zero-condensate SCM regime is asserted exactly (abs 0): the limiter returns 0 and the adjusted state equals the input state bit-for-bit.. Regime: em_quarter_ss supercell step 250, tile row j = 23, column i = 24 (the warm_rain.esm reference column), dtcld = 12 s: a saturated updraft (rh up to 1.55 before the adjustment) in which 19 of the 21 cloudy layers are supersaturated after the process rates and condense up to 3.75e-5 kg/kg/s (4.5e-4 kg/kg per sub-step), while 2 layers (the cloud base and top) evaporate and both lose ALL their cloud water (the -qc bound is active there); the vapor cap is never active.",
"parameter_overrides": {
"dtcld": 12.0,
"tau": 12.0
},
"expression_template_imports": [
{
"ref": "./tests/qss_step250_row23_col24_condensing_updraft_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "pcond",
"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": [
-4.3137953345570534e-11,
-7.606928311031555e-09,
1.5020783585419976e-05,
1.964111160032293e-05,
2.3545716087421838e-05,
2.7880142976405045e-05,
3.1583965322536227e-05,
3.448686149812835e-05,
3.642227462129202e-05,
3.752610460522058e-05,
3.750705949021993e-05,
3.637837293010004e-05,
1.8193155071371715e-05,
2.593891560435118e-05,
3.16839874701328e-05,
2.959473589116702e-05,
2.4462086786749574e-05,
1.9177903198795078e-05,
1.3136323233412547e-05,
7.670548163467349e-06,
6.7435726073661244e-06,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-09,
"rel": 0.0
}
},
{
"variable": "T_star",
"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": [
297.6464788145005,
291.80874566388445,
288.9942747751919,
286.6135242349646,
284.0270604896647,
281.3018590258681,
278.5495113075514,
275.75844929458617,
272.8999707835888,
269.90546546437326,
266.8942885788101,
263.42780327050326,
261.3047402582031,
258.5913088866709,
255.19096665394136,
251.67750862702104,
248.01274861728106,
244.17833900481597,
240.2053846387468,
236.12383631176164,
231.9141826626691,
227.86441126668973,
223.66682747427637,
219.31342571902886,
214.91752512599305,
210.58119821664815,
206.47607691117798,
202.9432150538725,
202.85935255571985,
211.37560877649327,
214.5903188270448,
216.9422699528947,
219.01785278320312,
220.38127136230466,
221.00733947753906,
221.05303955078125,
220.74871826171875,
220.28884887695312,
219.79849243164062,
219.2654571533203
]
},
"k"
]
}
},
"tolerance": {
"abs": 2.5e-05,
"rel": 0.0
}
},
{
"variable": "qv_star",
"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.013846929986437152,
0.01431545246008718,
0.013406498535241767,
0.012247837187339947,
0.011006286044407372,
0.00976554190095171,
0.008616316545074238,
0.0075516864864423406,
0.006558786174679419,
0.005613339946519703,
0.00477437239028923,
0.0039012283979696074,
0.0035489224819867677,
0.0030243853306430287,
0.0024339708277143638,
0.001926357384978141,
0.0014903549403630968,
0.0011230442016815815,
0.0008248739432073163,
0.0005908604296220768,
0.00041115188424143236,
0.0001660910358643979,
0.00011123353671478709,
6.988452865904442e-05,
4.231984800555517e-05,
2.505321183266298e-05,
1.500512737996692e-05,
9.556008285669237e-06,
1.0146409744070776e-05,
3.602801224554805e-05,
4.106390282050772e-05,
4.628168599247572e-05,
5.233629053691403e-05,
5.885793143534648e-05,
6.558828317793539e-05,
7.24658602848649e-05,
7.955444016261026e-05,
8.704032370587811e-05,
9.51660840655677e-05,
0.00010392525291536003
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-08,
"rel": 0.0
}
},
{
"variable": "qc_star",
"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.0,
0.0,
0.0007603353180984085,
0.001595755782167166,
0.0020587423136598864,
0.0022451475540893823,
0.0021774907476297682,
0.0019006704566668642,
0.0015493713306829458,
0.001219702886460344,
0.0009402761540497286,
0.000856632438396176,
0.0005438860090000749,
0.0005165881852316499,
0.0006008113467785678,
0.0005880880588388344,
0.0005475881070174382,
0.00047665470330269106,
0.00038051769591503705,
0.0002771390889400404,
8.09228712883935e-05,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-08,
"rel": 0.0
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 9
},
"expected": 3.642227462129202e-05,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 3.752610460522058e-05,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 11
},
"expected": 3.750705949021993e-05,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 12
},
"expected": 3.637837293010004e-05,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 9
},
"expected": 272.8999707835888,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 269.90546546437326,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 11
},
"expected": 266.8942885788101,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 12
},
"expected": 263.42780327050326,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 9
},
"expected": 0.0015493713306829458,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 0.001219702886460344,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 11
},
"expected": 0.0009402761540497286,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 12
},
"expected": 0.000856632438396176,
"tolerance": {
"rel": 5e-05
}
}
]
},
{
"id": "qss_step250_row23_col30_evaporating_downdraft",
"description": "Reference: the pcond block of mp_wsm6_run (phys/physics_mmm/mp_wsm6.F90, ctessum-claude/MMM-physics fork 52cfbd7, instrumented) dumped in-model by the ctessum-claude/WRF fork and replayed through the real64 (kind_phys = real64) kernels/wsm6_driver at the WRF step. Inputs are the dumped state entering the block (t_upd, q_upd, qc_upd after the process-rate update and sedimentation; p; the start-of-call xl and cpm), real32 values widened to real64 as the driver read them; the expected pcond, T*, qv* and qc* are the replay's pcond, t_out, q_out and qc_out (the kernel's outputs of this sub-step, which follow the adjustment directly apart from the qc <= qmin padding). POINTWISE and STEP-FREE: the adjustment is applied once per sub-step, so pcond = dq_applied/dtcld is the increment divided by the dumped dtcld and no small-dt replay is involved (a small-dt replay of the kernel would only rescale pcond by 1/dt, EqWeFiC FORTRAN_BUGS.md note N3). Real64 replay vs real32 dump: pcond 1.6e-5 (9983) to 8.4e-5 (7980) relative, qc_out 3e-6 to 8e-6, t_out and q_out <= 6e-7 -- the real32 kernel loses digits in the difference qv - qsat. Residual between this component and the real64 kernel (measured with zero tolerances over all four regimes): pcond <= 1.7e-10 kg/kg/s absolute (4.4e-6 of the column maximum in the condensing column, 1.3e-5 in the weaker evaporating one), T* <= 4.9e-6 K, qv* and qc* <= 2.0e-9 kg/kg; it comes from the real32 WRF constants inside the kernel (t0c = 273.1499939, psat = 610.78003, cpv = 1846.4000244, rv = 461.6000061, ep2 = 0.62175041, through qsat at 4.4e-7 of its value, as in saturation.esm) which this component takes at their nominal values from lib/wrf_constants.esm; near the saturation point the relative error of the small difference qv - qsat is correspondingly larger, so spot checks are placed on the strongly active layers and use rel 1e-4. Tolerances are 5-10x above the measured residual: Linf_error bounds pcond abs 1e-9 kg/kg/s, T* abs 2.5e-5 K, qv* and qc* abs 1e-8 kg/kg; spot checks on the four most active layers pcond rel 1e-4 (measured <= 3e-5), T* rel 1e-7 (<= 1.6e-8), qc* rel 5e-5 (<= 6e-6). The zero-condensate SCM regime is asserted exactly (abs 0): the limiter returns 0 and the adjusted state equals the input state bit-for-bit.. Regime: em_quarter_ss supercell step 250, tile row j = 23, column i = 30, dtcld = 12 s: a sub-saturated column downstream of the updraft in which cloud water advected in evaporates in all 19 cloudy layers (down to -6.2e-6 kg/kg/s); in 17 of them the cloud is exhausted, so the -qc bound sets the increment and qc* = 0, and only 2 layers keep cloud after a partial evaporation. No condensation.",
"parameter_overrides": {
"dtcld": 12.0,
"tau": 12.0
},
"expression_template_imports": [
{
"ref": "./tests/qss_step250_row23_col30_evaporating_downdraft_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "pcond",
"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": [
-6.460911648983473e-15,
-3.7441666017003984e-22,
-2.4073971348162123e-10,
-1.6702511738876833e-11,
-2.3710647317500956e-08,
-1.8318288837079376e-10,
-6.452251796658558e-09,
-3.3891747550149513e-10,
-2.53282337288952e-06,
-2.6701570039242424e-06,
-3.33591342668189e-06,
-6.241620427200949e-06,
-1.4009766620800337e-06,
-1.3618832766995822e-07,
-2.3840920876617643e-08,
-1.3320191989436205e-09,
-2.7326512177637843e-09,
-9.727447404647867e-10,
-1.7204464638132255e-10,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-09,
"rel": 0.0
}
},
{
"variable": "T_star",
"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": [
297.6186161969907,
293.30029880304664,
289.09826515538333,
285.05384019768053,
281.52413645588933,
278.49894359743337,
274.86120813511985,
270.34259876220284,
266.33911970069744,
262.9553115891116,
259.58322717716754,
256.8733321182667,
255.22488424064673,
252.86568458287874,
250.1102483963789,
246.81272398615516,
243.36188201844857,
239.7386071784389,
235.89260248531116,
231.77751949953625,
227.48604371878378,
223.33311842750234,
219.85098862533144,
217.92732029272852,
219.29685721381534,
224.0411885147916,
226.48125836674467,
224.65031945750664,
222.20545309011914,
219.46618094829756,
216.53858675253414,
215.0118901245337,
216.40196410004168,
218.32457502993066,
219.20261967010862,
219.70721434844893,
219.89933776661522,
219.8866424560547,
219.6458282470703,
219.1829833984375
]
},
"k"
]
}
},
"tolerance": {
"abs": 2.5e-05,
"rel": 0.0
}
},
{
"variable": "qv_star",
"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.013996636198412621,
0.013981416947376804,
0.012975926652230518,
0.010829290626770844,
0.008384629508117055,
0.006059007712339952,
0.004798432626779862,
0.004433001505891278,
0.0039359051458509155,
0.0032696608350387653,
0.002660736088058579,
0.0022242263166609077,
0.0016405951912284128,
0.0012810665027443093,
0.0009349950120939868,
0.0007239019398785047,
0.0005504450252227789,
0.0004140830881088863,
0.0003021990653400352,
0.00021205695423357352,
0.0001431547748689193,
9.489638755788014e-05,
6.595708682878602e-05,
5.409202735283085e-05,
6.241700662952257e-05,
8.606780629355058e-05,
7.67249683136301e-05,
6.738421621549382e-05,
5.7865411554593874e-05,
5.1541124879263445e-05,
4.808193037546991e-05,
4.797181306242267e-05,
5.0551192761600604e-05,
5.5488666699663306e-05,
6.175095835192467e-05,
6.863366681549878e-05,
7.605514906721937e-05,
8.427674765698612e-05,
9.339208190795034e-05,
0.00010332708188798279
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-08,
"rel": 0.0
}
},
{
"variable": "qc_star",
"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.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.00036595816144653306,
0.00038177516905213513,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-08,
"rel": 0.0
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 9
},
"expected": -2.53282337288952e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": -2.6701570039242424e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 11
},
"expected": -3.33591342668189e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 12
},
"expected": -6.241620427200949e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 9
},
"expected": 266.33911970069744,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 262.9553115891116,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 11
},
"expected": 259.58322717716754,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 12
},
"expected": 256.8733321182667,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 9
},
"expected": 0.0,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 0.00036595816144653306,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 11
},
"expected": 0.00038177516905213513,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 12
},
"expected": 0.0,
"tolerance": {
"rel": 5e-05
}
}
]
},
{
"id": "qss_step200_row20_col22_mixed_adjustment",
"description": "Reference: the pcond block of mp_wsm6_run (phys/physics_mmm/mp_wsm6.F90, ctessum-claude/MMM-physics fork 52cfbd7, instrumented) dumped in-model by the ctessum-claude/WRF fork and replayed through the real64 (kind_phys = real64) kernels/wsm6_driver at the WRF step. Inputs are the dumped state entering the block (t_upd, q_upd, qc_upd after the process-rate update and sedimentation; p; the start-of-call xl and cpm), real32 values widened to real64 as the driver read them; the expected pcond, T*, qv* and qc* are the replay's pcond, t_out, q_out and qc_out (the kernel's outputs of this sub-step, which follow the adjustment directly apart from the qc <= qmin padding). POINTWISE and STEP-FREE: the adjustment is applied once per sub-step, so pcond = dq_applied/dtcld is the increment divided by the dumped dtcld and no small-dt replay is involved (a small-dt replay of the kernel would only rescale pcond by 1/dt, EqWeFiC FORTRAN_BUGS.md note N3). Real64 replay vs real32 dump: pcond 1.6e-5 (9983) to 8.4e-5 (7980) relative, qc_out 3e-6 to 8e-6, t_out and q_out <= 6e-7 -- the real32 kernel loses digits in the difference qv - qsat. Residual between this component and the real64 kernel (measured with zero tolerances over all four regimes): pcond <= 1.7e-10 kg/kg/s absolute (4.4e-6 of the column maximum in the condensing column, 1.3e-5 in the weaker evaporating one), T* <= 4.9e-6 K, qv* and qc* <= 2.0e-9 kg/kg; it comes from the real32 WRF constants inside the kernel (t0c = 273.1499939, psat = 610.78003, cpv = 1846.4000244, rv = 461.6000061, ep2 = 0.62175041, through qsat at 4.4e-7 of its value, as in saturation.esm) which this component takes at their nominal values from lib/wrf_constants.esm; near the saturation point the relative error of the small difference qv - qsat is correspondingly larger, so spot checks are placed on the strongly active layers and use rel 1e-4. Tolerances are 5-10x above the measured residual: Linf_error bounds pcond abs 1e-9 kg/kg/s, T* abs 2.5e-5 K, qv* and qc* abs 1e-8 kg/kg; spot checks on the four most active layers pcond rel 1e-4 (measured <= 3e-5), T* rel 1e-7 (<= 1.6e-8), qc* rel 5e-5 (<= 6e-6). The zero-condensate SCM regime is asserted exactly (abs 0): the limiter returns 0 and the adjusted state equals the input state bit-for-bit.. Regime: em_quarter_ss supercell step 200, tile row j = 20, column i = 22, dtcld = 12 s (early storm): 4 condensing layers (up to 8.5e-6 kg/kg/s) above 14 evaporating ones (down to -5.8e-6), of which all 14 exhaust their cloud water (the -qc bound is active); the unlimited Newton increment dq_adj exceeds the available cloud by up to an order of magnitude there, which is what distinguishes dq_adj from dq_applied in the assertions.",
"parameter_overrides": {
"dtcld": 12.0,
"tau": 12.0
},
"expression_template_imports": [
{
"ref": "./tests/qss_step200_row20_col22_mixed_adjustment_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "pcond",
"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.0,
-2.6320542737133807e-11,
8.004958422459434e-07,
5.207389802890467e-06,
8.189544946103624e-06,
8.53288929561939e-06,
-1.1841504261566577e-06,
-1.202548851636508e-06,
-4.65811231665599e-06,
-5.832528285063319e-06,
-5.018910370303872e-06,
-4.518666130930314e-06,
-4.0423985817194066e-06,
-3.578249784180266e-06,
-3.3087015874955487e-06,
-2.713599751964222e-06,
-1.490091831548393e-06,
-4.67661152462272e-09,
-2.5131291513197955e-08,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-09,
"rel": 0.0
}
},
{
"variable": "T_star",
"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": [
297.5309618244887,
293.16044179207034,
288.7768809377302,
285.3125438372751,
281.65092182628507,
277.72250593950565,
274.23170802166936,
271.69409544883365,
268.7093438212281,
265.79876078723294,
263.0064742727417,
259.658630258334,
255.97055456958313,
252.14764388148006,
248.41197493653726,
244.89751671474298,
241.49715095936727,
238.08466327861026,
234.76052017976812,
231.41173104203813,
228.03391226405085,
224.60780664462328,
221.2067332325399,
217.81140690213732,
215.46862699026107,
214.88911152720254,
215.1236283464478,
215.6008911116688,
216.17085252337858,
216.82620239198732,
217.49386596679688,
218.13502502441406,
218.66004943847656,
218.9818572998047,
219.10499572753906,
219.10073852539062,
219.04913330078125,
219.0023193359375,
218.97503662109375,
218.93907165527344
]
},
"k"
]
}
},
"tolerance": {
"abs": 2.5e-05,
"rel": 0.0
}
},
{
"variable": "qv_star",
"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.014001669397772717,
0.014034573559293393,
0.013240132097114726,
0.011256734697392775,
0.009385800834225026,
0.0076267692143346805,
0.0059055505600815895,
0.0050706959882398294,
0.004536049869154289,
0.003602740735097534,
0.0027973723012800255,
0.0022357232741742165,
0.0017706954534352087,
0.0013608025659492453,
0.0009973439410640081,
0.0006705848465258252,
0.00044531228032957944,
0.000304538059426524,
0.00018781106380589744,
0.0001248194414424895,
9.618154149597613e-05,
7.233245640270889e-05,
5.262333935982407e-05,
3.614306577312916e-05,
2.75927710401142e-05,
2.7083137642622392e-05,
2.8878987718566567e-05,
3.177952441991197e-05,
3.498307831742724e-05,
3.8553189578087686e-05,
4.2550389480311424e-05,
4.6978188038337996e-05,
5.183950997889042e-05,
5.7148561609210446e-05,
6.294988270383328e-05,
6.932640098966658e-05,
7.639359682798386e-05,
8.42995141283609e-05,
9.322058758698404e-05,
0.00010324391769245267
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-08,
"rel": 0.0
}
},
{
"variable": "qc_star",
"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.0,
0.0,
9.605950121872587e-06,
0.0003138846642316349,
0.0006166445794148918,
0.0004644854919954661,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 1e-08,
"rel": 0.0
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": 5.207389802890467e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 5
},
"expected": 8.189544946103624e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 6
},
"expected": 8.53288929561939e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "pcond",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": -5.832528285063319e-06,
"tolerance": {
"rel": 0.0001
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": 285.3125438372751,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 5
},
"expected": 281.65092182628507,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 6
},
"expected": 277.72250593950565,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "T_star",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 265.79876078723294,
"tolerance": {
"rel": 1e-07
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 4
},
"expected": 0.0003138846642316349,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 5
},
"expected": 0.0006166445794148918,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 6
},
"expected": 0.0004644854919954661,
"tolerance": {
"rel": 5e-05
}
},
{
"variable": "qc_star",
"time": 0.0,
"coords": {
"lev": 10
},
"expected": 0.0,
"tolerance": {
"rel": 5e-05
}
}
]
},
{
"id": "scm_call120_no_condensate_lowest_40_layers",
"description": "Reference: the pcond block of mp_wsm6_run (phys/physics_mmm/mp_wsm6.F90, ctessum-claude/MMM-physics fork 52cfbd7, instrumented) dumped in-model by the ctessum-claude/WRF fork and replayed through the real64 (kind_phys = real64) kernels/wsm6_driver at the WRF step. Inputs are the dumped state entering the block (t_upd, q_upd, qc_upd after the process-rate update and sedimentation; p; the start-of-call xl and cpm), real32 values widened to real64 as the driver read them; the expected pcond, T*, qv* and qc* are the replay's pcond, t_out, q_out and qc_out (the kernel's outputs of this sub-step, which follow the adjustment directly apart from the qc <= qmin padding). POINTWISE and STEP-FREE: the adjustment is applied once per sub-step, so pcond = dq_applied/dtcld is the increment divided by the dumped dtcld and no small-dt replay is involved (a small-dt replay of the kernel would only rescale pcond by 1/dt, EqWeFiC FORTRAN_BUGS.md note N3). Real64 replay vs real32 dump: pcond 1.6e-5 (9983) to 8.4e-5 (7980) relative, qc_out 3e-6 to 8e-6, t_out and q_out <= 6e-7 -- the real32 kernel loses digits in the difference qv - qsat. Residual between this component and the real64 kernel (measured with zero tolerances over all four regimes): pcond <= 1.7e-10 kg/kg/s absolute (4.4e-6 of the column maximum in the condensing column, 1.3e-5 in the weaker evaporating one), T* <= 4.9e-6 K, qv* and qc* <= 2.0e-9 kg/kg; it comes from the real32 WRF constants inside the kernel (t0c = 273.1499939, psat = 610.78003, cpv = 1846.4000244, rv = 461.6000061, ep2 = 0.62175041, through qsat at 4.4e-7 of its value, as in saturation.esm) which this component takes at their nominal values from lib/wrf_constants.esm; near the saturation point the relative error of the small difference qv - qsat is correspondingly larger, so spot checks are placed on the strongly active layers and use rel 1e-4. Tolerances are 5-10x above the measured residual: Linf_error bounds pcond abs 1e-9 kg/kg/s, T* abs 2.5e-5 K, qv* and qc* abs 1e-8 kg/kg; spot checks on the four most active layers pcond rel 1e-4 (measured <= 3e-5), T* rel 1e-7 (<= 1.6e-8), qc* rel 5e-5 (<= 6e-6). The zero-condensate SCM regime is asserted exactly (abs 0): the limiter returns 0 and the adjusted state equals the input state bit-for-bit.. Regime: em_scm_xy single column, call 120 (step 60, 20:00 UTC, convective boundary layer), dtcld = 60 s, WRF layers 1-40 of 59 (the metaparameter NLEV = 40 cannot be rebound by a test, gap (j)): no cloud water anywhere and rh <= 0.62, so the Newton increment is negative everywhere, the bound 'no evaporation without cloud' makes dq_applied = pcond = 0 exactly and the adjusted state equals the input state; a zero check of the limiter logic (dq_adj itself is non-zero and negative).",
"parameter_overrides": {
"dtcld": 60.0,
"tau": 60.0
},
"expression_template_imports": [
{
"ref": "./tests/scm_call120_no_condensate_lowest_40_layers_inputs.esm"
}
],
"time_span": {
"start": 0.0,
"end": 1.0
},
"assertions": [
{
"variable": "pcond",
"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.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 0.0,
"rel": 0.0
}
},
{
"variable": "T_star",
"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": [
283.9342956542969,
283.3187255859375,
282.76336669921875,
282.2115173339844,
281.65167236328125,
281.07928466796875,
280.4919738769531,
279.8880310058594,
279.2660827636719,
278.6249084472656,
277.96307373046875,
277.27874755859375,
276.5679016113281,
275.8076171875,
276.82666015625,
279.6024169921875,
279.5128479003906,
279.3697814941406,
279.20404052734375,
279.0183410644531,
278.81353759765625,
278.5884704589844,
278.342529296875,
278.0738220214844,
277.77960205078125,
277.45892333984375,
277.07861328125,
276.6678161621094,
276.22442626953125,
275.74554443359375,
275.22998046875,
274.6733093261719,
274.072509765625,
273.4240417480469,
272.7232360839844,
271.9638366699219,
270.9003601074219,
269.6949157714844,
268.2908935546875,
266.2558898925781
]
},
"k"
]
}
},
"tolerance": {
"abs": 0.0,
"rel": 0.0
}
},
{
"variable": "qv_star",
"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.003234610892832279,
0.0030440962873399258,
0.002965141786262393,
0.0029153896030038595,
0.002877975581213832,
0.002846771152690053,
0.0028188072610646486,
0.0027923069428652525,
0.002765954239293933,
0.002738496521487832,
0.0027083437889814377,
0.0026727765798568726,
0.0026251983363181353,
0.002534134779125452,
0.002147406106814742,
0.0005237564910203218,
0.0007352220709435642,
0.0009619109332561493,
0.0011943636927753687,
0.001432699616998434,
0.0016770724905654788,
0.001927630277350545,
0.002184529323130846,
0.002447942504659295,
0.0027180099859833717,
0.002994028152897954,
0.0029144608415663242,
0.002825165167450905,
0.0027336112689226866,
0.0026397379115223885,
0.002543484093621373,
0.002444799290969968,
0.0023436129558831453,
0.002239867812022567,
0.002133494708687067,
0.002024371176958084,
0.0018535359995439649,
0.001661421381868422,
0.001452375785447657,
0.0013129577273502946
]
},
"k"
]
}
},
"tolerance": {
"abs": 0.0,
"rel": 0.0
}
},
{
"variable": "qc_star",
"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.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0
]
},
"k"
]
}
},
"tolerance": {
"abs": 0.0,
"rel": 0.0
}
}
]
}
]
}