Percent captured
Storms of 0.75 in and larger: share captured
Season table
Two bookkeepers and a clay floor
A stormwater credit for soil restoration has one job. It has to say how much of a one-inch storm a field of tilled, composted topsoil will swallow in March. Not in July. In March, when the trees are bare and the ground has been wet since Thanksgiving.
Four inches of good topsoil at 50 percent porosity holds two inches of pore space. Drain it to field capacity and you get 0.8 inch of room back. Dry it to the wilting point and you get 1.4. Under it sits compacted sandy clay that passes water at about a hundredth of an inch an hour, which is 0.24 inch a day. Those three numbers are the whole problem. Everything else is bookkeeping.
Figure 1. The same profile as three SWMM 5.2.4 setups see it.
The first bookkeeper keeps a clock
Modified Horton keeps one number: the water it has let in above the minimum rate. It forgives that debt on a schedule. Every dry time step, the debt is multiplied by a decay factor set by the drying time you typed.
With a seven-day drying time, the debt is 43 percent repaid after one day, 81 percent after three, and 98 percent after seven. That is a clock. It never asks whether the sun is out or whether the clay below can take the water. Make the clay impermeable and the clock keeps running. In the January test it still caught 78 percent of a one-inch storm after a week, with nowhere for the water to go. The only dial that ties it to the season is the monthly RECOVERY pattern. Scale that to evapotranspiration and the dormant-season capture of big storms falls from 77 to 47 percent.
The second bookkeeper owns a bucket with no lid
The LID setup puts the topsoil in the soil layer and the compacted clay in the storage layer. These books are real: topsoil moisture, storage depth, ET by month. The blind spot is in one line of code. The topsoil drains at its own unsaturated conductivity, capped only by the empty room left in the storage layer. The clay's ability to accept that water never enters the calculation.
//... limit soil perc by unused storage volume
maxRate = StorageExfil + StorageDrain + StorageEvap +
(storageThickness - storageDepth) * storageVoidFrac/Tstep;
SoilPerc = MIN(SoilPerc, maxRate);
Give that bucket 24 inches of sandy clay. Enter the void ratio from drainable porosity and the bucket has 2.6 inches of instant room; enter it from total porosity and it has 10. It empties at the seepage rate, 0.24 inch a day, which is faster than four inches of rain a month can fill it. So it never fills. Every storm, in both seasons, was captured in full. With the clay made impermeable, the growing season was still captured in full.
What the clay will actually take
Green-Ampt answers that from the clay's own numbers: Ksat 0.01 in/hr, suction 9.45 in, and 0.109 of drainable pore space. Make the most generous assumption, with water standing on the clay from the first minute of the storm.
Figure 2. The most the clay can take in during a storm, compared with the room the LID storage layer offers the moment the storm starts.
Six hours allows 0.39 inch. Twelve hours allows 0.58. The bucket offers two to twenty-six times that, and it offers it instantly.
The throttle that is already in the code
A third arrangement is already in 5.2.4, and it is the most honest of the three. Leave the storage layer out and put the clay's Ksat in the seepage field. SWMM then holds topsoil drainage to that seepage rate. The clay throttles drainage, ET does the drying, and the season appears without any tuning. This setup caught 79 percent of big storms in winter and 95 in summer. Make the clay five times tighter and winter drops to 54 percent while summer barely moves. That is how a credit ought to behave.
It is still not a perched water table. Field capacity acts as a floor. Nothing pulls water into the clay by suction, no water table rises inside the topsoil, and nothing drains sideways. If field data show the topsoil sitting wetter than field capacity all winter, this setup still gives back too much. A perched-zone option in SWMM would close that gap.
A second objection: soil is not pea gravel
The soil scientist who raised the question added a deeper point. Every model here treats the tilled layer as fixed granular media, like filter sand. Real topsoil gets its big pores from aggregates that roots, fungi and organic matter keep building. Saturate those aggregates often enough and they break down. In SWMM, the throttle setup saturates the topsoil for 47 hours in a winter season, and for 326 hours when the clay is five times tighter. The bucket setups never saturate it at all. No setup lets the pores wear out. The Soil structure tab has the numbers.
A fourth bookkeeper: the perched engine
The fix for the bucket is one rule: the clay decides how fast it drinks. swmm524-perched adds a SOIL2 layer and caps the flow into it at its Green-Ampt capacity, with the perched water depth added to the suction head. Water the clay refuses stays in the topsoil, which saturates from the bottom up. The engine reproduces EPA 5.2.4 byte for byte when the option is off. It captures 83 percent of big storms in winter and 97 in summer, and 54 percent in winter when the clay is five times tighter. The Perched engine tab has the details.
One broken knob, found on the way
Modified Horton has an optional cap called MaxInfil. In 5.2.4 the line that applies the cap says MAX where it means MIN. On the first wet step the running total jumps straight to the cap, and every step after that returns zero. With MaxInfil set to 0.8 inch, a four-inch rain infiltrated 0.002 inch. Anyone using that cap with this build is getting far too much runoff, not too little.
None of this is calibrated to a real site, and none of it stands in for a HYDRUS profile or a monitored plot. It shows, in the engine's own numbers, which knob moves which answer.
Soil is not pea gravel
Filter sand has only textural porosity: the gaps left by how the grains pack. That porosity barely changes. Soil has a second kind. Its large, connected macropores come from the packing of aggregates, crumbs of sand, silt and clay held together by organic matter, roots, fungi and soil fauna. Nimmo (2004) calls this structural porosity. It carries most of the infiltration, and the soil ecosystem has to keep rebuilding it.
Two consequences follow. Compaction destroys the macropores first, so the first increment of compaction cuts Ksat far more than the change in bulk density suggests. And when saturation lasts longer or comes more often, as it does in a perched zone over compacted subsoil, the aggregates break down and the structural porosity goes with them. Leij, Ghezzehei and Or (2002) model the pore-size distribution after tillage as something that changes with time, not a fixed property.
What his HYDRUS profiles show
He ran a full year of 1 m soil-moisture profiles for two cases. The first is an undisturbed soil at native bulk density with 5 % organic matter. The second is a topsoiled profile: 10 cm of uncompacted soil at 3 % organic matter over 90 cm compacted to a 10 % higher bulk density. In the topsoiled profile the top 10 cm returns to saturation again and again, with hard drying spells in the growing season, while the compaction below cuts percolation sharply. Those are the conditions that break aggregates down.
How often SWMM's topsoil saturates
The same question can be put to the four LID setups over the synthetic seasons: for how many hours in the 90-day window was the 4 in topsoil within 0.01 of saturation?
Hours with topsoil moisture at or above 0.49 (porosity 0.50) in the 90-day scoring window, read from the LID report file at the 1-minute wet step.
The storage-layer setups never let the topsoil saturate, because the bucket below swallows every storm. The throttle setup saturates it for 47 hours in winter, in spells of up to 15 hours. Make the clay five times tighter and that becomes 326 hours, 15 percent of the season, in spells of up to 40 hours. That is the perched regime his HYDRUS runs show.
What SWMM cannot hold
Every setup here keeps porosity, field capacity and Ksat at their day-one values for the whole run. Nothing in SWMM 5.2.4 lets a tilled layer lose structure after weeks of saturation. A perched-zone option with fixed van Genuchten parameters would get the water budget right and still credit the tilled layer with its day-one structure forever. A credit for shallow tillage has to answer two questions: whether the storage recovers between storms, and whether the pores that provide it last.
References. Nimmo, J.R. (2004). Porosity and pore-size distribution. In Encyclopedia of Soils in the Environment, v. 3, 295–303. Elsevier. Leij, F.J., Ghezzehei, T.A., and Or, D. (2002). Analytical models for soil pore-size distribution after tillage. Soil Science Society of America Journal 66, 1104–1114.
What changed in the engine
How the perched option handles the profile in each time step.
A bio-retention cell gets a SOIL2 line for the compacted layer, with the same fields as SOIL. Each time step, SWMM's own Green-Ampt routine works out the most water SOIL2 can take in. The perched water depth in the topsoil is added to the suction head, so a deeper perched zone pushes harder. Topsoil percolation can't exceed that capacity. Whatever the clay refuses stays in the topsoil, the topsoil saturates from the bottom up, and the next storm runs off. SOIL2 then drains to native soil at its own K(θ), capped by the STORAGE seepage rate. An optional STRUCT line lets the topsoil Ksat decay while the topsoil is saturated and recover once it drains to field capacity.
TOP SOIL 4 0.50 0.30 0.15 1.0 36 3.5 ; decompacted topsoil TOP SOIL2 24 0.43 0.321 0.221 0.01 50 9.45 ; compacted sandy clay TOP STORAGE 0 0 0.01 0 ; seepage below SOIL2 TOP STRUCT 0.2 0.02 0.1 ; optional, illustrative
The change is 310 lines in lid.c, lid.h and lidproc.c.
Whole season: storms of 0.75 in and larger captured
Same synthetic storm train and scoring windows as the Whole season tab. "Hours saturated" is the time the topsoil sat within 0.01 of saturation in the 90-day dormant window. θ2 is the average SOIL2 moisture over that window (porosity 0.43).
Controls
| Check | Result |
|---|---|
| No SOIL2 line, compared with EPA 5.2.4 on all 598 study inputs | 598 of 598 .out files byte-identical |
| Limiting case: SOIL2 conductivity 1000 in/hr, no suction. The interface no longer limits, so the result should equal the stock storage-layer bucket. | Test-storm runoff difference 0.000 in on all 64 recovery cases |
| STRUCT 0 0 1 compared with no STRUCT line | Byte-identical .out |
| Runoff continuity, perched runs | ≤ 0.003 % |
What the numbers say
The bucket setup captured every storm. The perched engine captures 83 % of the big storms in winter and 97 % in summer, and the difference comes out of the physics without any tuning. In winter the compacted layer averages 0.41 moisture against 0.43 porosity, so it sits nearly saturated and the interface runs at about the clay's Ksat. Make the clay five times tighter and winter capture falls to 54 %, with the topsoil saturated for 326 hours.
Once the clay stays wet, a stock LID with no storage layer and the clay's Ksat as the seepage rate gives nearly the same answer. The new engine matters when the subsoil has room to absorb water: after a dry spell, in summer, and at the start of a wet season.
Structure decay barely moves capture here (54 → 51 % with the tight clay), even though the structure factor falls to 0.11. The clay, not the topsoil, sets the infiltration rate. Also, only Ksat degrades in this version. The lost macropore volume is not modeled, so the effect is understated.
What this does not show
- There is no HYDRUS comparison yet. That needs the monthly HYDRUS summaries.
- It is not a Richards solver. Each layer is one moisture bucket, and the interface flux comes from Green-Ampt with a perched head.
- The STRUCT rates are illustrative, not calibrated.
- The recovery test starts the subsoil at its wilting point, which flatters the clay for short dry spells. The season runs are the fairer comparison.
MODIFIED_HORTON with MaxInfil > 0
src/solver/infil.c line 545, in modHorton_getInfil. The line is the same in the v5.2.4 tag and in EPA's develop branch as of February 2025.
// --- new cumulative infiltration minus seepage
infil->Fe += MAX((f - fmin), 0.0) * tstep;
if ( infil->Fmax > 0.0 ) infil->Fe = MAX(infil->Fe, infil->Fmax);
The line should use MIN. As written, the first wet step sets Fe equal to Fmax, and the next step stops at the saturated-condition test:
if ( infil->Fmax > 0.0 && infil->Fe >= infil->Fmax ) return 0.0;
Standard Horton (horton_getInfil) applies the cap correctly. To reproduce, run inputs/A_Hmax_Jan_D3_P1in.inp from the download.
What this does not show
- There are no observed data and no HYDRUS runs. This compares SWMM formulations with each other, plus one Green-Ampt bound.
- The rain is a synthetic storm train, not 15-minute gauge records.
- It covers one site and one soil pair. The topsoil conductivity slope is fixed at 36. The Horton f0 (3 in/hr) and drying time (7 days) are typical choices, not calibrated ones.
- The no-storage-layer LID setup is a unit-gradient throttle with field capacity as its floor. It is not a perched-water-table model.
- The perched engine (swmm524-perched) is a research build. Its structure-decay rates are illustrative, and it has not yet been compared with HYDRUS.
- Soil structure is static in every stock setup. Porosity, field capacity and Ksat never change during a run, so none of these numbers include the loss of macropores under repeated saturation.
- Initial conditions are a wet-up from dry for the recovery test and a 61-day spin-up for the season runs. A wetter antecedent subsoil would shift the results.
Controls that ran
- Null effect: a RECOVERY pattern of all 1.0 matched the run without one in 66 of 66 .out pairs, byte for byte.
- Repeat runs: 3 of 3 produced byte-identical .out files.
- Runoff integrated from the .out files matched the .rpt totals within 0.033 in (0.2 %).
- The worst runoff continuity error across all runs was 0.017 %.
- The LID report file stops writing rows whenever every LID flux, ET included, drops below MINFLOW. Layer states read from that file after such a gap are stale.
Site and soils
| Layer | Thick (in) | Porosity | FC | WP | Ksat (in/hr) | Suction (in) |
|---|---|---|---|---|---|---|
| Decompacted, amended topsoil | 4 | 0.50 | 0.30 | 0.15 | 1.0 | 3.5 |
| Compacted sandy clay (Rawls values, Ksat halved) | 24 | 0.430 | 0.321 | 0.221 | 0.01 | 9.45 |
The site is 1 acre, 100 % pervious, at 2 % slope, with 0.05 in of depression storage. PET in in/day, Jan–Dec: 0.02 0.03 0.06 0.10 0.14 0.17 0.18 0.16 0.12 0.07 0.04 0.02.
Files
soil-recovery-study.zip holds the Python scripts, an example .inp for each model, the full result tables, the control results, and a README. topsoil-recovery-doc.docx is the Doc essay as a Word file.
python3 run_recovery.py # recovery test python3 run_season.py # synthetic seasons python3 checks.py # controls + Green-Ampt bound # set SWMM_ENGINE to your runswmm (EPA 5.2.4)
About
Robert Dickinson · SWMM Enablement · October 2026. The engine is EPA SWMM 5.2.4, built from the v5.2.4 tag with _FORTIFY_SOURCE off; with it on, the 5.2.4 command-line build aborts while reading the project.
Update, 29 Oct 2026 (night): added the Perched engine tab and the swmm524-perched results. Claude wrote the engine change at Bob's direction and ran its controls.
Update, 29 Oct 2026 (evening): added the Soil structure tab and the saturation-time runs, from the questioner's follow-up note on structural porosity and his HYDRUS profiles.
AI use
Claude (Anthropic) did most of the work behind this page at Bob Dickinson's request. It compiled the engine, read the infiltration and LID source, designed and wrote the test scripts, ran about 600 simulations and the controls, and wrote this page and its Doc essay. Bob set the question and reviews the conclusions.