Surface Runoff Model

Overview

This module implements a surface runoff model based on the Saint-Venant equation system for surface water movement, coupled with a Heaviside step function boundary condition for surface-subsurface water flow interaction.

The Saint-Venant equations describe the mass conservation and momentum conservation of surface water flow along a soil surface slope. The Heaviside boundary condition enables automatic switching between infiltration and evaporation conditions at the soil surface based on the soil water pressure head.

The model includes three key processes:

  1. Surface water flow along the soil surface (Saint-Venant equations)
  2. Friction effects via Manning's roughness equation
  3. Automatic surface-subsurface boundary condition switching (Heaviside function)

Reference: Wang, Z., Timlin, D., Kouznetsov, M., Fleisher, D., Li, S., Tully, K., & Reddy, V. (2020). Coupled model of surface runoff and surface-subsurface water movement. Advances in Water Resources, 137, 103499. https://doi.org/10.1016/j.advwatres.2019.103499

EnvironmentalTransport.SurfaceRunoffFunction
SurfaceRunoff(; name) -> ModelingToolkitBase.System

Create a surface runoff model based on the Saint-Venant equation system for surface water movement, following the approach of Wang et al. (2020).

The model describes mass conservation and momentum conservation of surface water flow along a soil surface slope. The Saint-Venant equations are:

\[\frac{\partial \tilde{h}}{\partial t} = -\frac{\partial q}{\partial l} + (P - I)\]

\[\frac{\partial q}{\partial t} = -\frac{\partial}{\partial l}\left(\frac{q^2}{\tilde{h}} + \frac{g \tilde{h}^2}{2}\right) + g \tilde{h} (S_0 - S_f)\]

where $\tilde{h}$ is the flow depth (ponded water height), $q$ is the surface runoff flux per unit width, $P$ is precipitation flux, $I$ is infiltration flux, $S_0$ is the surface slope, and $S_f$ is the friction slope computed from Manning's equation.

This component represents the equations at a single surface node. The spatial derivative terms ($\partial q / \partial l$ and the momentum flux divergence) are represented as input parameters that should be provided by a spatial discretization scheme or coupled model.

Reference: Wang, Z., Timlin, D., Kouznetsov, M., Fleisher, D., Li, S., Tully, K., & Reddy, V. (2020). Coupled model of surface runoff and surface-subsurface water movement. Advances in Water Resources, 137, 103499. https://doi.org/10.1016/j.advwatres.2019.103499

source
EnvironmentalTransport.HeavisideBoundaryConditionFunction
HeavisideBoundaryCondition(
;
    name
) -> ModelingToolkitBase.System

Create a smoothed Heaviside boundary condition component for coupling surface and subsurface water flow, following Wang et al. (2020).

The Heaviside step function determines whether the soil surface is in a ponding state (h ≥ 0) or unsaturated state (h < 0), enabling automatic switching between infiltration and evaporation boundary conditions:

\[\eta_\omega(h) = \frac{1}{\pi} \arctan\left(\frac{h}{\omega}\right) + \frac{1}{2}\]

\[\delta_\omega(h) = \frac{1}{\pi} \frac{\omega}{\omega^2 + h^2}\]

These smooth approximations converge to the Heaviside step function and Dirac delta function respectively as $\omega \to 0^+$.

The boundary condition equation is (Eq. 3):

\[\frac{\partial(\eta(h) \cdot h)}{\partial t} - (P - I) = 0\]

Reference: Wang, Z., Timlin, D., Kouznetsov, M., Fleisher, D., Li, S., Tully, K., & Reddy, V. (2020). Coupled model of surface runoff and surface-subsurface water movement. Advances in Water Resources, 137, 103499. https://doi.org/10.1016/j.advwatres.2019.103499

source
EnvironmentalTransport.SaintVenantPDEFunction
SaintVenantPDE(
    L_domain,
    T_end;
    P_val,
    I_val,
    S_0_val,
    n_manning_val,
    g_val,
    h_min_val,
    h_init_val,
    q_init_val,
    name
) -> ModelingToolkitBase.PDESystem

Create a PDESystem for the Saint-Venant equations (Eq. 1 from Wang et al., 2020) suitable for spatial discretization with MethodOfLines.jl.

The system implements the full 1D Saint-Venant equations with proper SI units:

  • Mass conservation (Eq. 1a): $\partial \tilde{h}/\partial t = -\partial q/\partial l + (P - I)$
  • Momentum conservation (Eq. 1b): $\partial q/\partial t = -\partial F/\partial l + g \tilde{h}(S_0 - S_f)$
  • Momentum flux (auxiliary): $F = q^2/\tilde{h} + g \tilde{h}^2/2$

where $S_f$ is Manning's friction slope computed with non-dimensionalized fractional exponents: $S_f = ((n/n_{ref})(q/q_{ref}))^2 / (\tilde{h}/h_{ref})^{10/3}$.

Arguments

  • L_domain: Length of the spatial domain (m)
  • T_end: Duration of the simulation (s)

Keyword Arguments

  • P_val: Precipitation rate (m/s), default 70 mm/hr
  • I_val: Infiltration rate (m/s), default 0.0
  • S_0_val: Surface slope (dimensionless), default 0.01
  • n_manning_val: Manning roughness coefficient (m^(-1/3)·s), default 0.03
  • g_val: Gravitational acceleration (m/s²), default 9.81
  • h_min_val: Minimum flow depth to prevent singularity (m), default 1e-5
  • h_init_val: Initial and boundary flow depth (m), default 1e-3
  • q_init_val: Initial and boundary flux (m²/s), default 0.0
  • name: System name, default :SaintVenantPDE

Reference: Wang, Z., Timlin, D., Kouznetsov, M., Fleisher, D., Li, S., Tully, K., & Reddy, V. (2020). Coupled model of surface runoff and surface-subsurface water movement. Advances in Water Resources, 137, 103499. https://doi.org/10.1016/j.advwatres.2019.103499

source

Soil Properties (Table 1)

The following table lists the soil physics properties and parameters for soil hydraulic models used in the numerical examples of Wang et al. (2020). Values are shown in the original paper units with SI equivalents.

ParameterSoil ASoil BSI Unit
Residual Water Content ($\theta_r$)0.020.02m$^3$ m$^{-3}$
Saturated Water Content ($\theta_s$)0.330.52m$^3$ m$^{-3}$
van Genuchten Parameter ($\alpha$)0.04 (cm$^{-1}$) = 4.0 (m$^{-1}$)0.03 (cm$^{-1}$) = 3.0 (m$^{-1}$)m$^{-1}$
van Genuchten Parameter ($n$)2.01.1dimensionless
Saturated Hydraulic Conductivity ($k_s$)62.4 cm day$^{-1}$ = 7.22$\times 10^{-6}$ m s$^{-1}$1.0 cm day$^{-1}$ = 1.16$\times 10^{-7}$ m s$^{-1}$m s$^{-1}$
Soil Bulk Density ($\rho_b$)1.51 g cm$^{-3}$ = 1510 kg m$^{-3}$1.2 g cm$^{-3}$ = 1200 kg m$^{-3}$kg m$^{-3}$
Mass Fraction of Soil Organic Matter0.040.04kg kg$^{-1}$
Mass Fraction of Sand0.950.25kg kg$^{-1}$
Mass Fraction of Silt0.040.15kg kg$^{-1}$

Implementation

SurfaceRunoff Component

The SurfaceRunoff component implements the Saint-Venant equation system (Eq. 1 from Wang et al., 2020) at a single surface computing node.

State Variables

using DataFrames, ModelingToolkit, Symbolics, DynamicQuantities
using EnvironmentalTransport

sys = SurfaceRunoff()
vars = unknowns(sys)
DataFrame(
    :Name => [string(Symbolics.tosymbol(v, escape = false)) for v in vars],
    :Units => [string(dimension(ModelingToolkit.get_unit(v))) for v in vars],
    :Description => [ModelingToolkit.getdescription(v) for v in vars]
)
3×3 DataFrame
RowNameUnitsDescription
StringStringString
1mFlow depth / ponded water height (Eq. 1)
2qm² s⁻¹Surface runoff flux per unit width (Eq. 1)
3S_fFriction slope from Manning's equation (Eq. 1)

Parameters

params = parameters(sys)
DataFrame(
    :Name => [string(Symbolics.tosymbol(p, escape = false)) for p in params],
    :Units => [try string(dimension(ModelingToolkit.get_unit(p))) catch; "dimensionless" end for p in params],
    :Description => [ModelingToolkit.getdescription(p) for p in params]
)
11×3 DataFrame
RowNameUnitsDescription
StringStringString
1dqdlm s⁻¹Spatial derivative of runoff flux ∂q/∂l (Eq. 1)
2I_infilm s⁻¹Infiltration flux density (Eq. 1)
3Pm s⁻¹Precipitation/irrigation flux density (Eq. 1)
4dFdlm² s⁻²Spatial derivative of momentum flux ∂/∂l(q²/h̃ + g·h̃²/2) (Eq. 1)
5S_0Surface slope (dimensionless)
6h̃_0mMinimum flow depth to prevent singularity (Eq. 1)
7gm s⁻²Gravitational acceleration
8n_mannm⁻¹ᐟ³ sManning roughness coefficient
9q_refm² s⁻¹Reference flux for non-dimensionalization
10n_refm⁻¹ᐟ³ sReference Manning coefficient for non-dimensionalization
11h_refmReference flow depth for non-dimensionalization

Equations

eqs = equations(sys)

\[ \begin{align} \frac{\mathrm{d} ~ \mathtt{\tilde{h}}\left( t \right)}{\mathrm{d}t} &= - \mathtt{I\_infil} + P - \mathtt{dqdl} \\ \frac{\mathrm{d} ~ q\left( t \right)}{\mathrm{d}t} &= - \mathtt{dFdl} + \left( \mathtt{S\_0} - \mathtt{S\_f}\left( t \right) \right) ~ g ~ max\left( \mathtt{\tilde{h}}\left( t \right), \mathtt{\tilde{h}\_0} \right) \\ \mathtt{S\_f}\left( t \right) &= \frac{\left( \frac{\mathtt{n\_mann} ~ q\left( t \right)}{\mathtt{n\_ref} ~ \mathtt{q\_ref}} \right)^{2}}{\left( \frac{max\left( \mathtt{\tilde{h}}\left( t \right), \mathtt{\tilde{h}\_0} \right)}{\mathtt{h\_ref}} \right)^{3.3333}} \end{align} \]

HeavisideBoundaryCondition Component

The HeavisideBoundaryCondition component implements the smoothed Heaviside step function (Eq. 5) and the surface boundary condition (Eq. 3) for coupling surface and subsurface water flow.

State Variables

hbc = HeavisideBoundaryCondition()
vars_hbc = unknowns(hbc)
DataFrame(
    :Name => [string(Symbolics.tosymbol(v, escape = false)) for v in vars_hbc],
    :Units => [try string(dimension(ModelingToolkit.get_unit(v))) catch; "dimensionless" end for v in vars_hbc],
    :Description => [ModelingToolkit.getdescription(v) for v in vars_hbc]
)
3×3 DataFrame
RowNameUnitsDescription
StringStringString
1hmSoil water pressure head at surface (Eq. 3)
2η_ωSmoothed Heaviside step function (Eq. 5) (dimensionless)
3δ_ωm⁻¹Smoothed Dirac delta function (Eq. 5)

Parameters

params_hbc = parameters(hbc)
DataFrame(
    :Name => [string(Symbolics.tosymbol(p, escape = false)) for p in params_hbc],
    :Units => [try string(dimension(ModelingToolkit.get_unit(p))) catch; "dimensionless" end for p in params_hbc],
    :Description => [ModelingToolkit.getdescription(p) for p in params_hbc]
)
5×3 DataFrame
RowNameUnitsDescription
StringStringString
1one_over_pi1/π constant (dimensionless)
2ωmSmoothing parameter for Heaviside approximation (Eq. 5)
3one_half1/2 constant (dimensionless)
4I_infilm s⁻¹Infiltration flux density (Eq. 3)
5Pm s⁻¹Precipitation/irrigation flux density (Eq. 3)

Equations

eqs_hbc = equations(hbc)

\[ \begin{align} \frac{\mathrm{d} ~ h\left( t \right)}{\mathrm{d}t} &= \frac{ - \mathtt{I\_infil} + P}{\mathtt{\eta\_\omega}\left( t \right) + h\left( t \right) ~ \mathtt{\delta\_\omega}\left( t \right)} \\ \mathtt{\eta\_\omega}\left( t \right) &= \mathtt{one\_half} + \mathtt{one\_over\_pi} ~ \arctan\left( \frac{h\left( t \right)}{\omega} \right) \\ \mathtt{\delta\_\omega}\left( t \right) &= \frac{\mathtt{one\_over\_pi} ~ \omega}{\left( h\left( t \right) \right)^{2} + \omega^{2}} \end{align} \]

Analysis

Smoothed Heaviside Step Function (Eq. 5)

The smoothed Heaviside function $\eta_\omega(h)$ transitions from 0 to 1 around $h = 0$, with the sharpness controlled by the parameter $\omega$. Smaller values of $\omega$ produce a sharper transition, approaching the true Heaviside step function.

using OrdinaryDiffEqDefault
using Plots

# Evaluate η_ω for different ω values
h_range = range(-0.01, 0.01, length=500)

p = plot(xlabel="Soil water pressure head h (m)",
         ylabel="η_ω(h)",
         title="Smoothed Heaviside Step Function (Eq. 5)")

for ω_val in [1e-2, 1e-3, 1e-4]
    η_vals = [(1/π) * atan(h / ω_val) + 0.5 for h in h_range]
    plot!(p, h_range, η_vals, label="ω = $ω_val m", linewidth=2)
end

p
Example block output

Smoothed Dirac Delta Function (Eq. 5)

The smoothed Dirac delta function $\delta_\omega(h)$ is the derivative of the smoothed Heaviside function. It peaks at $h = 0$ with amplitude $1/(\pi\omega)$.

p2 = plot(xlabel="Soil water pressure head h (m)",
          ylabel="δ_ω(h) (1/m)",
          title="Smoothed Dirac Delta Function (Eq. 5)")

for ω_val in [1e-2, 1e-3, 1e-4]
    δ_vals = [(1/π) * ω_val / (ω_val^2 + h^2) for h in h_range]
    plot!(p2, h_range, δ_vals, label="ω = $ω_val m", linewidth=2)
end

p2
Example block output

Boundary Condition Transition: Unsaturated to Ponding

This example demonstrates the automatic switching behavior of the Heaviside boundary condition (Eq. 3). Starting from an unsaturated state ($h < 0$), precipitation drives the soil water pressure head toward positive values, transitioning from unsaturated to ponded conditions.

hbc = HeavisideBoundaryCondition()
chbc = mtkcompile(hbc)

# Scenario: Start unsaturated, apply precipitation with partial infiltration
prob = ODEProblem(chbc,
    merge(
        Dict(chbc.h => -0.05),
        Dict(chbc.P => 1e-5, chbc.I_infil => 0.0, chbc.ω => 1e-3)
    ),
    (0.0, 20000.0))
sol = solve(prob)

p3 = plot(sol.t, sol[chbc.h] .* 100,
     xlabel="Time (s)", ylabel="Pressure head h (cm)",
     title="Boundary Condition Transition (Eq. 3)",
     label="h(t)", linewidth=2, legend=:bottomright)
hline!(p3, [0.0], linestyle=:dash, color=:red, label="h = 0 (ponding threshold)")

p3
Example block output

Manning's Friction Effect on Surface Runoff

This example shows how Manning's roughness coefficient affects the friction slope and thus the momentum balance in the Saint-Venant equations (Eq. 1). Higher roughness produces greater friction, reducing flow velocity. Note that this single-node model demonstrates the local momentum response; in a full spatial simulation, the spatial flux terms would transport momentum downstream.

sys_sr = SurfaceRunoff()
csys = mtkcompile(sys_sr)

# Compare different Manning coefficients with steady precipitation
# Using a short time window to observe the initial momentum response
p4 = plot(xlabel="Time (s)", ylabel="Runoff flux q (m²/s)",
          title="Effect of Manning's Roughness on Runoff (Eq. 1)",
          legend=:topleft)

for (n_val, label) in [(0.01, "n=0.01 (smooth)"),
                        (0.03, "n=0.03 (bare soil)"),
                        (0.10, "n=0.10 (dense grass)")]
    prob_sr = ODEProblem(csys,
        merge(
            Dict(csys.h̃ => 1e-3, csys.q => 0.0),
            Dict(
                csys.P => 2e-5,
                csys.I_infil => 1e-5,
                csys.S_0 => 0.01,
                csys.n_mann => n_val,
                csys.h̃_0 => 1e-5,
                csys.dqdl => 0.0,
                csys.dFdl => 0.0,
            )
        ),
        (0.0, 5.0))
    sol_sr = solve(prob_sr)
    plot!(p4, sol_sr.t, sol_sr[csys.q], label=label, linewidth=2)
end

p4
Example block output

Water Depth Accumulation Under Different Precipitation-Infiltration Regimes

This example demonstrates the mass conservation equation (Eq. 1a) under different net water input scenarios, showing how the balance between precipitation and infiltration controls ponded water depth.

p5 = plot(xlabel="Time (s)", ylabel="Flow depth h̃ (m)",
          title="Water Depth vs. Net Water Input (Eq. 1a)",
          legend=:topleft)

for (P_val, I_val, label) in [
    (3e-5, 1e-5, "P=3e-5, I=1e-5 (net gain)"),
    (2e-5, 2e-5, "P=2e-5, I=2e-5 (balanced)"),
    (1e-5, 2e-5, "P=1e-5, I=2e-5 (net loss)")]
    prob_acc = ODEProblem(csys,
        merge(
            Dict(csys.h̃ => 0.005, csys.q => 0.0),
            Dict(
                csys.P => P_val,
                csys.I_infil => I_val,
                csys.S_0 => 0.0,
                csys.n_mann => 0.03,
                csys.h̃_0 => 1e-5,
                csys.dqdl => 0.0,
                csys.dFdl => 0.0,
            )
        ),
        (0.0, 200.0))
    sol_acc = solve(prob_acc)
    plot!(p5, sol_acc.t, sol_acc[csys.h̃], label=label, linewidth=2)
end

p5
Example block output

PDE Spatial Solution with MethodOfLines.jl

The full Saint-Venant equations (Eq. 1 from Wang et al., 2020) can be solved as a PDE system using MethodOfLines.jl for spatial discretization via the method of lines (finite differences).

The SaintVenantPDE function creates a PDESystem with proper SI units that can be directly discretized with MethodOfLines.jl. All variables and parameters carry SI unit annotations, and unit consistency is verified during system construction.

using ModelingToolkit: t
using DomainSets
using MethodOfLines

# Create the Saint-Venant PDE system with 70 mm/hr rainfall on a 0.5 m domain
pde = SaintVenantPDE(0.5, 60.0;
    P_val = 70.0 / 1000 / 3600,  # 70 mm/hr in m/s
    S_0_val = 0.01,
    n_manning_val = 0.03,
    h_init_val = 1e-3,
    q_init_val = 0.0)

# Discretize using method of lines (finite differences)
l = pde.ivs[2]
dl = 0.1
discretization = MOLFiniteDifference([l => dl], t, approx_order = 2)
prob = discretize(pde, discretization; checks = false)

# Solve the discretized ODE system
sol = solve(prob)

h_tilde = pde.dvs[1]
q_flux = pde.dvs[2]
disc_l = sol[l]
h_vals = sol[h_tilde]

# Plot spatial profiles of water depth at different times
p6 = plot(xlabel="Distance along slope l (m)",
          ylabel="Flow depth h̃ (m)",
          title="Saint-Venant PDE: Water Depth Evolution (Eq. 1)",
          legend=:topleft)

for (i, ti) in enumerate(sol.t)
    label_str = "t = $(round(ti, digits=1)) s"
    plot!(p6, disc_l, h_vals[i, :], label=label_str, linewidth=2)
end

p6
Example block output
println("ODEProblem created with $(length(prob.u0)) unknowns")
println("Time span: $(prob.tspan)")
println("Solution time steps: $(length(sol.t))")
ODEProblem created with 8 unknowns
Time span: (0.0, 60.0)
Solution time steps: 28

Limitations

The current implementation provides:

  • Single-node ODE components (SurfaceRunoff, HeavisideBoundaryCondition) suitable for coupling with the EarthSciML framework
  • PDE spatial solution (SaintVenantPDE) via MethodOfLines.jl discretization of the Saint-Venant equations (Eq. 1) with full SI unit support

The following features from Wang et al. (2020) are not yet implemented:

  • Richards equation for subsurface flow (Eq. 2) — requires a separate subsurface flow component
  • Coupled surface-subsurface simulations (Figs. 3-6) — requires both surface and subsurface models
  • Ridge-furrow water harvesting application (Figs. 7-8) — requires the full coupled model with complex topography