Two Tables, One Pipe

A companion to “Exact and user-defined cross-section geometry for SWMM” by Corinne Wiesner-Friedman (HydroCouple, August 2026). Every number here is recomputed from the shape constants her article links to; her measured Bellinge results are quoted, not reproduced.
Pick a shape and a stored fraction. The engine holds up to four independently digitised descriptions of the same pipe. Ask each one “how deep is the water?” and they answer differently. The band is the disagreement, drawn at true scale — her Figure 2, live.
depth-of-area table (Y) area table inverted (A) width table integrated (W) the disagreement, true scale

Depth each description assigns to the same stored fraction

What you are looking at

A LEGACY run reads area from depth through one table (A_xxx, or the inverted Y_xxx for the four shapes that ship no area table), depth from area through another (Y_xxx), top width through a third (W_xxx), and — for Gothic, Catenary, Semi-elliptical and Semi-circular — hydraulic radius through a fourth, the section-factor table, via R = (S/A)3/2. Width is the derivative of area, so at most one of these can be right about any real pipe.

The band is Corinne's Figure 2: a 2 m gothic sewer holding 2% of its capacity sits at 90 mm by its depth-of-area table and at 145 mm by its own width table. She reports 146 mm; the 1 mm is integration step. At 0.5% the gap is a factor of three.

Tables: xsect_tables.hpp at commit 7ff727be (a verbatim translation of EPA xsect.dat 5.2.1), read through the lookup()/invLookup() logic of xsect.c. Full-section constants from xsect_setParams().
The LD-1 diagnostic. Differentiate the area description and compare it with the width table: (dA/dy − W) / W. It needs no outside reference — the two tables simply have to agree with each other, and they do not. Under linear resample this reproduces the article's table digit for digit.

Relative disagreement between dA/dy and W

linear resampleengine lookup()±10% lines

All eight tabulated closed shapes

Two notes on reading that table (hers): the 2% depth column is a local slope, so it is sensitive to where the table's own nodes fall — y/D = 0.02 is exactly a node of Circular's 51-point table, and the slope jumps from −34% just below it to +22% just above. The last two columns are dense sweeps and are not node-sensitive.

A third note (this app's): the four worst rows are the four shapes with no area table. Their area comes from inverting the depth-of-area table — a second digitisation of the same pipe — which is exactly why it need not agree with the first.

Her table: “The area and width tables describe different pipes”, HydroCouple, Aug 2026. Register entry LD-1: docs/dev/legacy_defects.md.
Draw the pipe as it is. An XPOLYGON boundary is a closed chain of straight segments and DXF-bulge arcs. Area, perimeter, top width and first moment come from one Green's-theorem contour integral, so T = dA/dy holds by construction — check it at any depth below. Then export the [CURVES] and [XSECTIONS] lines.
boundarywetted areacritical heights (where A(y) is not analytic)

Section curves from the one boundary

A / AfullT / wmaxR / Rfulllegacy table A (when a matching shape exists)

Identity check |dA/dy − T| / T

Export


        

Syntax from Appendix D of the OpenSWMM v6 manual at the same commit: Link POLYGON Scale Open 0 0 Barrels Bcurve; Barrels must be supplied because Bcurve is read from the token after it. A clockwise chain is reoriented automatically with every bulge sign flipped; a self-crossing chain is rejected; |bulge| > 1 is rejected (a half circle is the largest single arc). This app's own validity test is stricter than a chord check: it walks the arcs as 48-piece polylines and refuses touching and overlap as well as crossing, so a boundary that passes here is simple in the plain geometric sense — whether the engine at this commit accepts exactly the same set is a claim I have not verified. The invert is located automatically as the boundary's lowest point, arc interiors included. Runs only on openswmm.engine at or after feature/xsect-geometry; EPA SWMM 5.2.4 will stop with ERROR 205 on the keyword.

Why the standard check cannot see this. Dynamic wave turns net inflow into head change with top width and reports stored volume with area. Those are the two tables that disagree. Below: her measured Bellinge results as reported, then a one-cell caricature of the mechanism you can run yourself.

Reported in the article — not recomputed here

Bellinge: 1015 conduits, 48 h dry weather, one binary, one changed option — XSECT_GEOMETRY LEGACY versus EXACT.

LEGACYEXACT
FV, 2 h window−2.253%−0.134%~17× better
FV, filling window−4.453%−0.022%~200× better
DYNWAVE continuity−0.073%+0.085%no meaningful change
What moved under DYNWAVE
Of 189 conduits carrying > 1 L/s, 80% diverge by more than 1% of their own peak at some instant, 45% by more than 10%, 33% by more than 25%. Median 7.0% at the worst instant; 1.2% averaged over 48 h (upper decile 14%). Node depths: median 5.8%; one node differs by 755 mm at one moment.
What did not move
Peak flows agree to a median of 0.20%; the largest change in any node's peak depth is 2 mm. Exact geometry shifts timing and shape under an almost unchanged peak.
Cost of the shipped build
DYNWAVE 40.0 s → 63.1 s (~1.58×); FV 2 h 319.9 s → 536.2 s (~1.68×). Part of that is -ffp-contract=off, kept for bit-parity with EPA 5.2.4; the split is unmeasured. Gothic compiles 5.6× faster than its legacy path; Circular ~1.15× slower.
Her own caveat
953 of Bellinge's 1031 sections are circular and the other 78 are shapes EXACT does not compile. Restricting EXACT to every shape except circular gives output byte-for-byte identical to LEGACY. Bellinge cannot show what exact geometry is worth on the shapes it helps most.

One-cell caricature: run it

Depth: width-table head update vs consistent head update

T from the width table (as LEGACY does)T = dA/dy of the same area table (consistent)inflow (right axis)

Continuity residual, % of total inflow

width-table cellconsistent cell

Why the residual is a state function

In the cell, head advances by dy = (Qin − Qout) dt / (T·L) and stored volume is reported as V = A(y)·L. The continuity residual after any stretch of time is therefore

∫(Qin − Qout) dt − ΔV  =  L · ∫y₀y₁ (T − dA/dy) dy  =  L · [Φ(y₁) − Φ(y₀)]

It depends on where the depth started and ended, not on the path between. A dry-weather run that finishes near where it began returns a residual near zero however far the depth wandered in between — while every intermediate head, and every outflow computed from it, carried the width error. The curve below is Φ for the selected shape; the residual you just plotted is the difference of two of its values. This is a caricature of one node-and-conduit, not of SWMM's network solver, but the mechanism is the same one her measurement shows: the check confirms the bookkeeping is self-consistent; it was never an instrument for asking whether the geometry is right.

Φ(y) = ∫₀ʸ (T − dA/dy) dy, normalised by Afull
Finite volume has no such blind spot: its state is area, depth is recovered by inverting the same geometry every substep, and FvKernels.hpp requires depthOfArea ∘ areaOfDepth to be the identity — two independently digitised tables cannot satisfy that, one boundary can.
The side tab. Her article has a shape of its own — twelve moves in a fixed order, each doing one job. Tick them off against a draft, or fill the template and get the piece back in her markup and CSS, or as Markdown for WordPress and LinkedIn. Attribution: structure and stylesheet tokens from hydrocouple.org (Caleb Buahin's site); the piece is Corinne Wiesner-Friedman's.

The twelve moves, in her order

    Fill the template

    Preview, in her stylesheet

    Rendered with the article page's own tokens and type (Sora, Inter, JetBrains Mono) so you can see the piece as it would sit on hydrocouple.org. The preview stays light in both themes — that is her site's palette, quoted.

    Her markup skeleton

    Her CSS tokens (from css/site.css)

    The checks, run here. Every number this app shows rests on the ported lookup(), the parsed shape tables and the Green's-theorem boundary evaluator. This tab runs the same test file the build ran in Node — against the copy of the code that is executing in your browser right now — so a passing count is evidence about this page, not about a build on someone else's machine.

    Embedded verification suite

    Not run yet in this session.
    Groups: her table reproduced under linear resampling · the gothic 2 % / 0.5 % depths · the ported lookup() against closed-form circle geometry · the LD-2 register number · the Appendix D four-arc circle (area, perimeter, width, hydraulic radius, T = dA/dy) · degenerate boundaries rejected (bow-tie, vertex-on-edge, repeated vertex, zero-length chord, fold-back, inward arc crossings) · legitimate concave and inward-arc shapes accepted · the one-cell continuity caricature · SI ↔ US round trip on this page's own controls. Sources and pinned commits are behind the Sources button in the header.