Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Performance

Both engines are benchmarked against their predecessors: the drainage engine against SWMM 5.2.4 and the water distribution engine against EPANET, on published networks. This page carries the published figures, the method behind them, and the tools to reproduce or check them on your own machine.

Measured against SWMM 5.2.4

Release builds of both engines, the same machine (Apple M-series), best of three runs each, timed end to end: parse, solve, and write. SWMM runs in its own default surcharge closure. Where a model sets INERTIAL_DAMPING, SWMM is given the setting Hydra substitutes, so the comparison is between implementations and not between documented modelling deviations.

WorkloadHydra / SWMM runtime
Bellinge (published, 1,020 nodes), 7 h storm1.00
Bellinge, 24 h event1.24
SWMM test corpus, models running 0.2 to 1 s1.08
A 4,394-node combined system, 48 h dynamic wave0.66

The corpus is the predecessor’s own regression suite. Its many sub-0.2-second models are dominated by process start and parse and are excluded from the ratio, because a single aggregate over them measures process creation rather than either solver.

Accuracy rides the same runs. On the 48-hour system, 4,373 of 4,394 node depths agree within 5 cm. On Bellinge, node depths match SWMM’s own Preissmann-slot closure on 992 of 1,020 nodes. Every remaining difference across the corpus is either fixed or documented in the specification with its cause and a source citation; the specifications’ correspondence notes are the index of them.

Memory on the 4,394-node model (a 320 MB input): 613 MB peak during import, 194 MB for the rest of the run, against SWMM’s roughly 160 MB. SWMM reads the file from disk in passes; Hydra holds the model bytes in memory, which is also what lets the same engine run in a browser.

Water distribution, measured against EPANET

The same method: release builds of both engines, the same machine, best of three runs, timed end to end, over the published research networks bundled in tests/benchmarks/wds/.

NetworkHydra / EPANET runtime
Balerma, Exeter, Kentucky 8/9/10, NY Tunnels0.9 to 1.2 (sub-10 ms; process start dominates)
BWSN-2 (12,527 nodes)1.21
L-Town1.03
D-Town1.12
Micropolis1.29
Richmond2.00, or 0.85 with --tank-tolerance 0

Richmond’s ratio is a documented purchase, not a loss. Hydra integrates tank levels with a second-order predictor-corrector carrying a per-step error estimate, where EPANET takes one uncontrolled first-order step; on Richmond’s eighteen level-switched pumps that costs a corrector solve per step plus the error control’s retries. Setting the tolerance to zero (--tank-tolerance 0, or level_err_tol = 0 in the session API) restores EPANET’s own scheme exactly: 55 hydraulic solves to EPANET’s 54, and a faster run. The default keeps the error bound.

Accuracy rides the same runs: 95.4% of 2.6 million node pressures agree within 0.1 (in each model’s own pressure unit) across the set, and every residual is classified in the specifications’ correspondence notes. The survivors are three kinds, none a solver disagreement: isolated pockets behind closed links, whose head is physically undefined and where EPANET’s own value wanders between hours; runs both engines halt identically under Unbalanced Stop, where only the abandoned final iterate differs; and threshold events, where centimetre-scale tank drift between two different integrators flips a level-switched rule by one time step and the trajectories re-converge.

Reproducing the comparison

The tracked baseline harness times both engines when SWMM is available:

HYDRA_SWMM=/path/to/runswmm just perf-check

Without HYDRA_SWMM it times Hydra alone against the recorded baseline. The water networks are timed by just bench-report; comparing against EPANET means building EPANET’s runepanet from its repository and timing the same models with both, best-of-N, as above. The baseline (tests/benchmarks/uds/baseline.json) is tracked in the repository and gates every change on both runtime (25% band) and peak memory (15% band), so the published figures cannot quietly rot: a change that slows a benchmark model or grows its memory fails the check.

Timing methodology worth copying: always best-of-N, never single-shot (single runs on the 24-hour event scattered across 57 to 60 seconds where best-of-three gives 50.4), and read the report for errors rather than trusting the exit code, because SWMM exits zero after refusing a model.

Checking for regressions

The bundled networks are worth timing against each other. Comparing one Hydra build with another over a fixed set answers the question that matters during development: did this change make the solver slower? The arbitrariness that makes these networks useless as a public claim costs nothing here, because both runs meet the same models.

just bench-report

This builds the release CLI and runs scripts/benchmark.py, which times each of the networks in tests/benchmarks/wds/ and prints a Markdown table. Pass --runs N to change the sample count, or --hydra PATH to time a specific binary, which is how two builds are compared.

Note that it reads only the bundled directory, so it measures Hydra against Hydra. It is not a way to time your own model.

Building for maximum speed

The release profile already enables fat LTO and a single codegen unit. For the best local performance, build with native CPU features:

just release-native

This tunes the binary for the machine it is built on (-C target-cpu=native); such binaries are not portable to older CPUs.

Solver micro-benchmarks

For work on the solver itself, the criterion suite times the hydraulic solve step (warm and cold) in isolation:

just bench