Version: dev

SurfaceRunoffInfiltration

Surface runoff and infiltration for the pervious-road urban surface, following Oleson et al. (2010, CLM Tech Note Ch. 5) §5.2, Eqs. 5.49-5.57. Migrated from UrbanCanopy.jl@9200edd4 src/hydrology.jl:384 (`SurfaceRunoffInfiltration` @component). Implements the SIMTOP-based (Niu et al., 2005) runoff partition with frozen-soil-modulated saturated fraction (Eqs. 5.50-5.51), a maximum infiltration capacity built from top-layer matric-potential and saturation (Eqs. 5.52-5.55), and snow-aware surface infiltration (Eqs. 5.56-5.57). Observed-only: all 14 inputs are parameters and the seven outputs (f_frz_1, f_sat, s, v, q_infl_max, q_over, q_infl) are given by algebraic equations.

Domain
urban_canopy
Subdomain
hydro
Type
model
.esm schema
1.0.0
Source
components/urban_canopy/hydro/surface_runoff_infiltration.esm
Tags
#urban_canopy #hydro #model #hydrology #surface-runoff #infiltration #simtop #observed-only #clmu

Description

Surface runoff and infiltration for the pervious-road urban surface, following Oleson et al. (2010, CLM Tech Note Ch. 5) §5.2, Eqs. 5.49-5.57. Migrated from UrbanCanopy.jl@9200edd4 src/hydrology.jl:384 (SurfaceRunoffInfiltration @component). Implements the SIMTOP-based (Niu et al., 2005) runoff partition with frozen-soil-modulated saturated fraction (Eqs. 5.50-5.51), a maximum infiltration capacity built from top-layer matric-potential and saturation (Eqs. 5.52-5.55), and snow-aware surface infiltration (Eqs. 5.56-5.57). Observed-only: all 14 inputs are parameters and the seven outputs (f_frz_1, f_sat, s, v, q_infl_max, q_over, q_infl) are given by algebraic equations.

Reference

_source_ref: UrbanCanopy.jl@9200edd4f15609f20339d3a7d104de8064544310:src/hydrology.jl:384 Upstream SurfaceRunoffInfiltration is an MTK @component whose 14 inputs (q_liq_0, q_seva, f_max, z_v, k_sat_1, B_1, ψ_sat_1, θ_liq_1, θ_sat_1, θ_ice_1, w_ice_1, w_liq_1, Δz_1, has_snow) are @parameters (driven by a parent column/atmosphere/soil-hydraulics coupler) and whose seven outputs (f_frz_1, f_sat, s, v, q_infl_max, q_over, q_infl) are time-indexed @variables with seven algebraic equations and no derivatives. Identical structure preserved here.

The upstream component declares six @constants (α=3.0 [dimensionless], f_over=0.5 [m^-1], θ_imp=0.05 [m^3/m^3], ρ_liq=1000.0 [kg/m^3], one_kgpm2=1.0 [kg/m^2], zero_kgpm2s=0.0 [kg/(m^2*s)]). Per the convention of sibling files in this directory (snow_compaction.esm, groundwater_drainage.esm), @constants are promoted to parameters here with the upstream default values so the seven equations reference them by name without re-injecting magic numbers. The two _kgpm2 / _kgpm2s constants exist upstream purely to make the unit-bearing max() floors well-typed under DynamicQuantities; they preserve unit-consistency of the equation AST and are kept here for round-trip fidelity.

The upstream component declares no defaults on the 14 input @parameters. The canonical Python runner (earthsci_ast.simulation.simulate) requires concrete numeric defaults to instantiate the system in isolation, so defaults are seeded from a mid-range CLMU pervious-road state representative of Oleson 2010 Tables 5.1-5.2: sandy-loam top layer (k_sat_1=1e-5 m/s, B_1=5.0, ψ_sat_1=-0.05 m, θ_sat_1=0.4 m^3/m^3, Δz_1=0.02 m), moderate water table (z_v=2.0 m), f_max=0.4 (mid-range maximum saturated fraction), zero precipitation/evaporation, and no snow. Each test below overrides the inputs that exercise its specific branch.

Observability classification: this component is functionally observed-only — all seven outputs are given by single algebraic equations in terms of the parameters (the (t) time index has no role since there are no derivatives). Test scenarios use time_span: [0, 1] and sample at t=0 to mirror the convention used by the other observed-only ESM files in this directory (aquifer_water_balance.esm, impervious_water_balance.esm, snow_compaction.esm).

Equation-ordering note (vs upstream comment ordering): the upstream Julia source orders the equations as (5.51, 5.50, 5.53, 5.54, 5.52, 5.49, 5.56-5.57). The same numerical order is preserved here verbatim because the AST is dependency-driven (each equation references only earlier outputs and the 14 inputs) and the canonical Python runner topologically sorts before solving — so ordering is informational, not load-bearing.

Tolerance rationale (model-level rel=1e-9): observed-only algebraic system (no ODE integration); the longest equation chain is O(10) IEEE-754 float64 operations including two exp() calls in f_frz_1 (each carrying ≈1 ULP of relative error) — worst-case roundoff is ~10ULP ≈ 1e-15. 1e-9 leaves seven orders of magnitude of headroom above ULP roundoff and is well below any plausible physical-discrimination scale on the surface-water flux densities (typical CLMU q_liq_0 / q_over / q_infl values fall in the 1e-6 to 1e-2 kg/(m^2s) range). Tests that yield exact algebraic zeros use abs: 1e-15 to bypass the relative-tolerance singularity at zero._

Parameters

NameUnitsDefaultDescription
q_liq_0kg/(m^2*s)0.0Liquid water reaching surface from rain or snowmelt
q_sevakg/(m^2*s)0.0Surface evaporation of liquid water
f_max10.4Maximum saturated fraction (Eq. 5.50, dimensionless)
z_vm2.0Water table depth (Eq. 5.50)
k_sat_1m/s1e-05Saturated hydraulic conductivity of top soil layer
B_115.0Clapp-Hornberger exponent for top soil layer (dimensionless)
ψ_sat_1m-0.05Saturated matric potential for top soil layer (typically negative)
θ_liq_1m^3/m^30.2Volumetric liquid water content of top soil layer
θ_sat_1m^3/m^30.4Porosity of top soil layer
θ_ice_1m^3/m^30.0Volumetric ice content of top soil layer
w_ice_1kg/m^20.0Ice content of top soil layer
w_liq_1kg/m^250.0Liquid water content of top soil layer
Δz_1m0.02Thickness of top soil layer
has_snow10.0Whether snow layers are present (1=yes, 0=no, dimensionless)
α13.0Scale-dependent parameter for frozen fraction (Eq. 5.51, dimensionless)
f_overm^-10.5Decay factor for saturated fraction (Oleson 2010 p. 126)
θ_impm^3/m^30.05Minimum effective porosity floor for relative-saturation denominator (Oleson 2010 p. 127)
ρ_liqkg/m^31000.0Density of liquid water (Oleson 2010 Table 1.4)
one_kgpm2kg/m^21.0Unit mass per area, used to unit-balance the 1e-10 floor in the f_frz_1 max() guard
zero_kgpm2skg/(m^2*s)0.0Zero flux sentinel, used to unit-balance the max() floors in q_over and q_infl

Observed

NameUnitsDefaultDescription
f_frz_11Impermeable fraction from frozen soil (Oleson Eq. 5.51, dimensionless)
f_sat1Fractional saturated area (Oleson Eq. 5.50, dimensionless)
s1Relative saturation of top soil layer (Oleson Eq. 5.53, dimensionless)
v1Infiltration variable v (Oleson Eq. 5.54, dimensionless)
q_infl_maxkg/(m^2*s)Maximum infiltration capacity (Oleson Eq. 5.52)
q_overkg/(m^2*s)Surface runoff (Oleson Eq. 5.49, SIMTOP)
q_inflkg/(m^2*s)Infiltration rate (Oleson Eqs. 5.56-5.57; snow-aware)

Observed expressions

$$ f_{frz\,1} = \frac{e^{\left(0.0 - α\right) \cdot \left(1.0 - \frac{w_{ice\,1}}{\mathrm{max}\left(w_{ice\,1} + w_{liq\,1}, 1 \times 10^{-10} \cdot one_{kgpm2}\right)}\right)} - e^{0.0 - α}}{1.0 - e^{0.0 - α}} $$

Impermeable fraction from frozen soil (Oleson Eq. 5.51, dimensionless)

$$ f_{sat} = \left(1.0 - f_{frz\,1}\right) \cdot f_{max} \cdot e^{\left(-0.5\right) \cdot f_{over} \cdot z_{v}} + f_{frz\,1} $$

Fractional saturated area (Oleson Eq. 5.50, dimensionless)

$$ s = \mathrm{max}\left(\frac{\frac{θ_{liq\,1}}{\mathrm{max}\left(θ_{imp}, θ_{sat\,1} - θ_{ice\,1}\right)} - f_{sat}}{\mathrm{max}\left(1.0 - f_{sat}, 0.01\right)}, 0.0\right) $$

Relative saturation of top soil layer (Oleson Eq. 5.53, dimensionless)

$$ v = \frac{B_{1} \cdot ψ_{sat\,1}}{0.5 \cdot Δz_{1}} $$

Infiltration variable v (Oleson Eq. 5.54, dimensionless)

$$ q_{infl\,max} = ρ_{liq} \cdot k_{sat\,1} \cdot \left(1.0 + v \cdot \left(s - 1.0\right)\right) $$

Maximum infiltration capacity (Oleson Eq. 5.52)

$$ q_{over} = f_{sat} \cdot q_{liq\,0} + \left(1.0 - f_{sat}\right) \cdot \mathrm{max}\left(zero_{kgpm2s}, q_{liq\,0} - q_{infl\,max}\right) $$

Surface runoff (Oleson Eq. 5.49, SIMTOP)

$$ q_{infl} = \mathrm{ifelse}\left(\mathrm{>}\left(has_{snow}, 0.5\right), q_{liq\,0} - q_{over}, \mathrm{max}\left(q_{liq\,0} - q_{over} - q_{seva}, zero_{kgpm2s}\right)\right) $$

Infiltration rate (Oleson Eqs. 5.56-5.57; snow-aware)

Raw .esm

Raw .esm JSON (this component)
{
  "reference": {
    "notes": "source_ref: UrbanCanopy.jl@9200edd4f15609f20339d3a7d104de8064544310:src/hydrology.jl:384\nUpstream `SurfaceRunoffInfiltration` is an MTK `@component` whose 14 inputs (q_liq_0, q_seva, f_max, z_v, k_sat_1, B_1, ψ_sat_1, θ_liq_1, θ_sat_1, θ_ice_1, w_ice_1, w_liq_1, Δz_1, has_snow) are `@parameters` (driven by a parent column/atmosphere/soil-hydraulics coupler) and whose seven outputs (f_frz_1, f_sat, s, v, q_infl_max, q_over, q_infl) are time-indexed `@variables` with seven algebraic equations and no derivatives. Identical structure preserved here.\n\nThe upstream component declares six `@constants` (α=3.0 [dimensionless], f_over=0.5 [m^-1], θ_imp=0.05 [m^3/m^3], ρ_liq=1000.0 [kg/m^3], one_kgpm2=1.0 [kg/m^2], zero_kgpm2s=0.0 [kg/(m^2*s)]). Per the convention of sibling files in this directory (snow_compaction.esm, groundwater_drainage.esm), `@constants` are promoted to parameters here with the upstream default values so the seven equations reference them by name without re-injecting magic numbers. The two `_kgpm2` / `_kgpm2s` constants exist upstream purely to make the unit-bearing `max()` floors well-typed under DynamicQuantities; they preserve unit-consistency of the equation AST and are kept here for round-trip fidelity.\n\nThe upstream component declares no defaults on the 14 input `@parameters`. The canonical Python runner (`earthsci_ast.simulation.simulate`) requires concrete numeric defaults to instantiate the system in isolation, so defaults are seeded from a mid-range CLMU pervious-road state representative of Oleson 2010 Tables 5.1-5.2: sandy-loam top layer (k_sat_1=1e-5 m/s, B_1=5.0, ψ_sat_1=-0.05 m, θ_sat_1=0.4 m^3/m^3, Δz_1=0.02 m), moderate water table (z_v=2.0 m), f_max=0.4 (mid-range maximum saturated fraction), zero precipitation/evaporation, and no snow. Each test below overrides the inputs that exercise its specific branch.\n\nObservability classification: this component is functionally observed-only — all seven outputs are given by single algebraic equations in terms of the parameters (the `(t)` time index has no role since there are no derivatives). Test scenarios use `time_span: [0, 1]` and sample at t=0 to mirror the convention used by the other observed-only ESM files in this directory (aquifer_water_balance.esm, impervious_water_balance.esm, snow_compaction.esm).\n\nEquation-ordering note (vs upstream comment ordering): the upstream Julia source orders the equations as (5.51, 5.50, 5.53, 5.54, 5.52, 5.49, 5.56-5.57). The same numerical order is preserved here verbatim because the AST is dependency-driven (each equation references only earlier outputs and the 14 inputs) and the canonical Python runner topologically sorts before solving — so ordering is informational, not load-bearing.\n\nTolerance rationale (model-level rel=1e-9): observed-only algebraic system (no ODE integration); the longest equation chain is O(10) IEEE-754 float64 operations including two `exp()` calls in f_frz_1 (each carrying ≈1 ULP of relative error) — worst-case roundoff is ~10*ULP ≈ 1e-15. 1e-9 leaves seven orders of magnitude of headroom above ULP roundoff and is well below any plausible physical-discrimination scale on the surface-water flux densities (typical CLMU q_liq_0 / q_over / q_infl values fall in the 1e-6 to 1e-2 kg/(m^2*s) range). Tests that yield exact algebraic zeros use `abs: 1e-15` to bypass the relative-tolerance singularity at zero."
  },
  "tolerance": {
    "rel": 1e-09
  },
  "variables": {
    "q_liq_0": {
      "type": "parameter",
      "units": "kg/(m^2*s)",
      "default": 0.0,
      "description": "Liquid water reaching surface from rain or snowmelt"
    },
    "q_seva": {
      "type": "parameter",
      "units": "kg/(m^2*s)",
      "default": 0.0,
      "description": "Surface evaporation of liquid water"
    },
    "f_max": {
      "type": "parameter",
      "units": "1",
      "default": 0.4,
      "description": "Maximum saturated fraction (Eq. 5.50, dimensionless)"
    },
    "z_v": {
      "type": "parameter",
      "units": "m",
      "default": 2.0,
      "description": "Water table depth (Eq. 5.50)"
    },
    "k_sat_1": {
      "type": "parameter",
      "units": "m/s",
      "default": 1e-05,
      "description": "Saturated hydraulic conductivity of top soil layer"
    },
    "B_1": {
      "type": "parameter",
      "units": "1",
      "default": 5.0,
      "description": "Clapp-Hornberger exponent for top soil layer (dimensionless)"
    },
    "ψ_sat_1": {
      "type": "parameter",
      "units": "m",
      "default": -0.05,
      "description": "Saturated matric potential for top soil layer (typically negative)"
    },
    "θ_liq_1": {
      "type": "parameter",
      "units": "m^3/m^3",
      "default": 0.2,
      "description": "Volumetric liquid water content of top soil layer"
    },
    "θ_sat_1": {
      "type": "parameter",
      "units": "m^3/m^3",
      "default": 0.4,
      "description": "Porosity of top soil layer"
    },
    "θ_ice_1": {
      "type": "parameter",
      "units": "m^3/m^3",
      "default": 0.0,
      "description": "Volumetric ice content of top soil layer"
    },
    "w_ice_1": {
      "type": "parameter",
      "units": "kg/m^2",
      "default": 0.0,
      "description": "Ice content of top soil layer"
    },
    "w_liq_1": {
      "type": "parameter",
      "units": "kg/m^2",
      "default": 50.0,
      "description": "Liquid water content of top soil layer"
    },
    "Δz_1": {
      "type": "parameter",
      "units": "m",
      "default": 0.02,
      "description": "Thickness of top soil layer"
    },
    "has_snow": {
      "type": "parameter",
      "units": "1",
      "default": 0.0,
      "description": "Whether snow layers are present (1=yes, 0=no, dimensionless)"
    },
    "α": {
      "type": "parameter",
      "units": "1",
      "default": 3.0,
      "description": "Scale-dependent parameter for frozen fraction (Eq. 5.51, dimensionless)"
    },
    "f_over": {
      "type": "parameter",
      "units": "m^-1",
      "default": 0.5,
      "description": "Decay factor for saturated fraction (Oleson 2010 p. 126)"
    },
    "θ_imp": {
      "type": "parameter",
      "units": "m^3/m^3",
      "default": 0.05,
      "description": "Minimum effective porosity floor for relative-saturation denominator (Oleson 2010 p. 127)"
    },
    "ρ_liq": {
      "type": "parameter",
      "units": "kg/m^3",
      "default": 1000.0,
      "description": "Density of liquid water (Oleson 2010 Table 1.4)"
    },
    "one_kgpm2": {
      "type": "parameter",
      "units": "kg/m^2",
      "default": 1.0,
      "description": "Unit mass per area, used to unit-balance the 1e-10 floor in the f_frz_1 max() guard"
    },
    "zero_kgpm2s": {
      "type": "parameter",
      "units": "kg/(m^2*s)",
      "default": 0.0,
      "description": "Zero flux sentinel, used to unit-balance the max() floors in q_over and q_infl"
    },
    "f_frz_1": {
      "type": "unknown",
      "units": "1",
      "description": "Impermeable fraction from frozen soil (Oleson Eq. 5.51, dimensionless)"
    },
    "f_sat": {
      "type": "unknown",
      "units": "1",
      "description": "Fractional saturated area (Oleson Eq. 5.50, dimensionless)"
    },
    "s": {
      "type": "unknown",
      "units": "1",
      "description": "Relative saturation of top soil layer (Oleson Eq. 5.53, dimensionless)"
    },
    "v": {
      "type": "unknown",
      "units": "1",
      "description": "Infiltration variable v (Oleson Eq. 5.54, dimensionless)"
    },
    "q_infl_max": {
      "type": "unknown",
      "units": "kg/(m^2*s)",
      "description": "Maximum infiltration capacity (Oleson Eq. 5.52)"
    },
    "q_over": {
      "type": "unknown",
      "units": "kg/(m^2*s)",
      "description": "Surface runoff (Oleson Eq. 5.49, SIMTOP)"
    },
    "q_infl": {
      "type": "unknown",
      "units": "kg/(m^2*s)",
      "description": "Infiltration rate (Oleson Eqs. 5.56-5.57; snow-aware)"
    }
  },
  "equations": [
    {
      "lhs": "f_frz_1",
      "rhs": {
        "op": "/",
        "args": [
          {
            "op": "-",
            "args": [
              {
                "op": "exp",
                "args": [
                  {
                    "op": "*",
                    "args": [
                      {
                        "op": "-",
                        "args": [
                          0.0,
                          "α"
                        ]
                      },
                      {
                        "op": "-",
                        "args": [
                          1.0,
                          {
                            "op": "/",
                            "args": [
                              "w_ice_1",
                              {
                                "op": "max",
                                "args": [
                                  {
                                    "op": "+",
                                    "args": [
                                      "w_ice_1",
                                      "w_liq_1"
                                    ]
                                  },
                                  {
                                    "op": "*",
                                    "args": [
                                      1e-10,
                                      "one_kgpm2"
                                    ]
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                "op": "exp",
                "args": [
                  {
                    "op": "-",
                    "args": [
                      0.0,
                      "α"
                    ]
                  }
                ]
              }
            ]
          },
          {
            "op": "-",
            "args": [
              1.0,
              {
                "op": "exp",
                "args": [
                  {
                    "op": "-",
                    "args": [
                      0.0,
                      "α"
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    },
    {
      "lhs": "f_sat",
      "rhs": {
        "op": "+",
        "args": [
          {
            "op": "*",
            "args": [
              {
                "op": "-",
                "args": [
                  1.0,
                  "f_frz_1"
                ]
              },
              "f_max",
              {
                "op": "exp",
                "args": [
                  {
                    "op": "*",
                    "args": [
                      -0.5,
                      "f_over",
                      "z_v"
                    ]
                  }
                ]
              }
            ]
          },
          "f_frz_1"
        ]
      }
    },
    {
      "lhs": "s",
      "rhs": {
        "op": "max",
        "args": [
          {
            "op": "/",
            "args": [
              {
                "op": "-",
                "args": [
                  {
                    "op": "/",
                    "args": [
                      "θ_liq_1",
                      {
                        "op": "max",
                        "args": [
                          "θ_imp",
                          {
                            "op": "-",
                            "args": [
                              "θ_sat_1",
                              "θ_ice_1"
                            ]
                          }
                        ]
                      }
                    ]
                  },
                  "f_sat"
                ]
              },
              {
                "op": "max",
                "args": [
                  {
                    "op": "-",
                    "args": [
                      1.0,
                      "f_sat"
                    ]
                  },
                  0.01
                ]
              }
            ]
          },
          0.0
        ]
      }
    },
    {
      "lhs": "v",
      "rhs": {
        "op": "/",
        "args": [
          {
            "op": "*",
            "args": [
              "B_1",
              "ψ_sat_1"
            ]
          },
          {
            "op": "*",
            "args": [
              0.5,
              "Δz_1"
            ]
          }
        ]
      }
    },
    {
      "lhs": "q_infl_max",
      "rhs": {
        "op": "*",
        "args": [
          "ρ_liq",
          "k_sat_1",
          {
            "op": "+",
            "args": [
              1.0,
              {
                "op": "*",
                "args": [
                  "v",
                  {
                    "op": "-",
                    "args": [
                      "s",
                      1.0
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    },
    {
      "lhs": "q_over",
      "rhs": {
        "op": "+",
        "args": [
          {
            "op": "*",
            "args": [
              "f_sat",
              "q_liq_0"
            ]
          },
          {
            "op": "*",
            "args": [
              {
                "op": "-",
                "args": [
                  1.0,
                  "f_sat"
                ]
              },
              {
                "op": "max",
                "args": [
                  "zero_kgpm2s",
                  {
                    "op": "-",
                    "args": [
                      "q_liq_0",
                      "q_infl_max"
                    ]
                  }
                ]
              }
            ]
          }
        ]
      }
    },
    {
      "lhs": "q_infl",
      "rhs": {
        "op": "ifelse",
        "args": [
          {
            "op": ">",
            "args": [
              "has_snow",
              0.5
            ]
          },
          {
            "op": "-",
            "args": [
              "q_liq_0",
              "q_over"
            ]
          },
          {
            "op": "max",
            "args": [
              {
                "op": "-",
                "args": [
                  {
                    "op": "-",
                    "args": [
                      "q_liq_0",
                      "q_over"
                    ]
                  },
                  "q_seva"
                ]
              },
              "zero_kgpm2s"
            ]
          }
        ]
      }
    }
  ],
  "tests": [
    {
      "id": "all_zero_inputs",
      "description": "Null state — every input zero except f_max=0 (drives saturation pathway off) and the mid-range soil defaults that do not zero out the denominators in s. All seven outputs are exactly zero by IEEE-754: f_frz_1=0 because w_ice_1=0 with finite w_liq_1; f_sat=0 because (1-f_frz_1)*0*exp(0)+0=0; s=0 because θ_liq_1=0; v=0 because ψ_sat_1=0; q_infl_max=0 because k_sat_1=0; q_over=0 because q_liq_0=0; q_infl=max(0-0-0, 0)=0. Exercises the additive/multiplicative-zero limits of all seven equations.",
      "parameter_overrides": {
        "q_liq_0": 0.0,
        "q_seva": 0.0,
        "f_max": 0.0,
        "z_v": 0.0,
        "k_sat_1": 0.0,
        "B_1": 0.0,
        "ψ_sat_1": 0.0,
        "θ_liq_1": 0.0,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        }
      ]
    },
    {
      "id": "dry_quiescent",
      "description": "Dry-soil quiescent state — no precipitation, no evaporation, no ice, moderate liquid storage. With q_liq_0=0 and q_seva=0, q_over and q_infl must both be exactly zero regardless of the saturated-fraction state. f_sat reaches the typical CLMU mid-range value (0.4·exp(-0.5·0.5·2) = 0.2426 at f_max=0.4, z_v=2 m), s settles at 0.34, q_infl_max takes its unsaturated value (0.175 kg/(m^2*s) — the upper-bound infiltration capacity in the absence of forcing).",
      "parameter_overrides": {
        "q_liq_0": 0.0,
        "q_seva": 0.0,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.2,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.2426122638850534
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.3398361550389345
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.17504096124026639
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        }
      ]
    },
    {
      "id": "moderate_rain_unsaturated",
      "description": "Typical rainfall event on an unsaturated pervious-road column: q_liq_0=1e-4 kg/(m^2*s) (~0.36 mm/hr) with no evaporation, no ice, no snow. Because q_liq_0 << q_infl_max (1e-4 vs 0.175), the infiltration-excess branch of q_over (the max() second term) is exactly zero; only the saturation-excess term contributes: q_over = f_sat·q_liq_0 = 0.2426·1e-4 = 2.43e-5. q_infl = q_liq_0 - q_over - q_seva = 7.57e-5 — the standard partition into saturation-area runoff and column infiltration.",
      "parameter_overrides": {
        "q_liq_0": 0.0001,
        "q_seva": 0.0,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.2,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.2426122638850534
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.3398361550389345
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.17504096124026639
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 2.426122638850534e-05
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 7.573877361149466e-05
        }
      ]
    },
    {
      "id": "fully_saturated_surface",
      "description": "Surface fully saturated (f_max=1, z_v=0 ⇒ f_sat = (1-0)·1·exp(0)+0 = 1). The Eq. 5.49 SIMTOP partition collapses to q_over = q_liq_0 (all rain becomes saturation-area runoff; the infiltration-excess term is multiplied by 1-f_sat=0). q_infl = q_liq_0 - q_over - q_seva = 0 — no water infiltrates through a fully saturated surface. Also exercises the max(1-f_sat, 0.01) floor in s: with 1-f_sat=0, the floor activates at 0.01, and combined with θ_liq_1=θ_sat_1 the s expression reaches exactly 0 (because θ_liq/θ_sat - f_sat = 1 - 1 = 0).",
      "parameter_overrides": {
        "q_liq_0": 0.001,
        "q_seva": 0.0,
        "f_max": 1.0,
        "z_v": 0.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.4,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 1.0
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.26
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.001
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        }
      ]
    },
    {
      "id": "fully_frozen_top_layer",
      "description": "Top soil layer completely frozen (w_ice_1=50, w_liq_1=0, θ_liq_1=0, θ_ice_1=θ_sat_1=0.4): Eq. 5.51 gives f_frz_1=1 (impermeable), which forces f_sat=1 via Eq. 5.50 ((1-1)·... + 1 = 1). All incident liquid becomes saturation-area runoff; no infiltration. Exercises both the w_ice_1/(w_ice_1+w_liq_1) ⇒ 1 limit of Eq. 5.51 and the f_frz_1·1 → f_sat = 1 propagation. Also triggers the θ_imp floor (max(0.05, θ_sat_1-θ_ice_1) = max(0.05, 0) = 0.05) — a divide-by-zero guard for the relative-saturation denominator.",
      "parameter_overrides": {
        "q_liq_0": 0.0002,
        "q_seva": 0.0,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.0,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.4,
        "w_ice_1": 50.0,
        "w_liq_1": 0.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 1.0
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 1.0
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.26
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.0002
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        }
      ]
    },
    {
      "id": "partial_ice_water_mix",
      "description": "Partially-frozen top layer (w_ice_1=20, w_liq_1=30 ⇒ ice fraction = 0.4): exercises the intermediate branch of Eq. 5.51 where f_frz_1 ∈ (0, 1). f_frz_1 = (exp(-3·(1-0.4))-exp(-3))/(1-exp(-3)) = 0.1216. f_sat then blends frozen and SIMTOP contributions. With θ_liq_1=0.1 and θ_sat_1-θ_ice_1=0.3, the saturation ratio θ_liq_1/0.3 ≈ 0.333 falls just below f_sat ≈ 0.335 ⇒ the max(..., 0) floor on s activates (the numerator is a tiny negative number; max() clamps it to 0).",
      "parameter_overrides": {
        "q_liq_0": 0.0002,
        "q_seva": 0.0,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.1,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.1,
        "w_ice_1": 20.0,
        "w_liq_1": 30.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.12156414210793089
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.3346834542689348
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.26
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 6.693669085378696e-05
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.00013306330914621305
        }
      ]
    },
    {
      "id": "snow_present_skip_evap",
      "description": "Snow layers present (has_snow=1): Eq. 5.57 branch of the q_infl ifelse selects q_infl = q_liq_0 - q_over (the surface-evaporation term q_seva is NOT subtracted — the snow layer mediates surface evaporation upstream of this component). With q_liq_0=5e-4 and q_seva=1e-4 (active but ignored), q_infl = 5e-4 - q_over = 3.79e-4, identical to the no-evap case at the same q_liq_0. This is the contrast with `evap_exceeds_throughfall` below.",
      "parameter_overrides": {
        "q_liq_0": 0.0005,
        "q_seva": 0.0001,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.2,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 1.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.2426122638850534
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.3398361550389345
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.17504096124026639
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.0001213061319425267
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.00037869386805747333
        }
      ]
    },
    {
      "id": "evap_exceeds_throughfall",
      "description": "No snow but evaporation exceeds throughfall (q_seva=2e-4 > q_liq_0-q_over): the max(..., zero_kgpm2s) floor in Eq. 5.56 clamps q_infl to exactly 0 — the surface cannot infiltrate water that has already evaporated. Specifically q_liq_0 - q_over - q_seva = 1e-4 - 2.43e-5 - 2e-4 = -1.24e-4 < 0 ⇒ max(...,0) = 0. Contrasts with snow_present_skip_evap above to isolate the q_seva branch.",
      "parameter_overrides": {
        "q_liq_0": 0.0001,
        "q_seva": 0.0002,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.2,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.2426122638850534
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.3398361550389345
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.17504096124026639
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 2.426122638850534e-05
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        }
      ]
    },
    {
      "id": "shallow_water_table",
      "description": "Shallow water table (z_v=0.1 m vs 2 m default): Eq. 5.50 exp(-0.5·f_over·z_v) → exp(-0.025) ≈ 0.9753, raising f_sat from 0.2426 to 0.3901 — a near-doubling of the saturated fraction in the SIMTOP partition. This is the typical CLMU-urban wet-zone state where a shallow water table elevates surface runoff for a fixed precipitation forcing.",
      "parameter_overrides": {
        "q_liq_0": 0.0005,
        "q_seva": 0.0,
        "f_max": 0.4,
        "z_v": 0.1,
        "k_sat_1": 1e-05,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.2,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.39012396481133305
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.1801612604021676
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.2149596848994581
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.00019506198240566654
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.0003049380175943335
        }
      ]
    },
    {
      "id": "infiltration_excess_runoff",
      "description": "Infiltration-excess (Hortonian) runoff regime: clay-like top layer (k_sat_1=1e-8 m/s) under intense rainfall (q_liq_0=5e-3 kg/(m^2*s) ≈ 18 mm/hr). q_infl_max collapses to 1.75e-4 ≪ q_liq_0, activating the second term of Eq. 5.49: q_over = f_sat·q_liq_0 + (1-f_sat)·(q_liq_0 - q_infl_max) = 0.2426·5e-3 + 0.7574·(5e-3 - 1.75e-4) = 4.87e-3. Nearly all rain becomes runoff; only 1.33e-4 infiltrates (close to q_infl_max). This is the canonical CLMU clay-soil storm scenario complementary to fully_saturated_surface (which exercised the saturation-excess limit).",
      "parameter_overrides": {
        "q_liq_0": 0.005,
        "q_seva": 0.0,
        "f_max": 0.4,
        "z_v": 2.0,
        "k_sat_1": 1e-08,
        "B_1": 5.0,
        "ψ_sat_1": -0.05,
        "θ_liq_1": 0.2,
        "θ_sat_1": 0.4,
        "θ_ice_1": 0.0,
        "w_ice_1": 0.0,
        "w_liq_1": 50.0,
        "Δz_1": 0.02,
        "has_snow": 0.0
      },
      "time_span": {
        "start": 0.0,
        "end": 1.0
      },
      "assertions": [
        {
          "variable": "f_frz_1",
          "time": 0.0,
          "expected": 0.0,
          "tolerance": {
            "abs": 1e-15
          }
        },
        {
          "variable": "f_sat",
          "time": 0.0,
          "expected": 0.2426122638850534
        },
        {
          "variable": "s",
          "time": 0.0,
          "expected": 0.3398361550389345
        },
        {
          "variable": "v",
          "time": 0.0,
          "expected": -25.0
        },
        {
          "variable": "q_infl_max",
          "time": 0.0,
          "expected": 0.0001750409612402664
        },
        {
          "variable": "q_over",
          "time": 0.0,
          "expected": 0.004867426122638851
        },
        {
          "variable": "q_infl",
          "time": 0.0,
          "expected": 0.00013257387736114942
        }
      ]
    }
  ]
}