The 1981 EXTRAN III Workbench: From the Printed Manual to WebAssembly in Your Browser
EXTRAN—the Extended Transport block of the EPA Storm Water Management Model (SWMM) Version III—was developed at Camp Dresser & McKee (CDM) by Larry A. Roesner, Robert P. Shubinski, and John A. Aldrich under EPA Cooperative Agreement No. CR805664 (Final Report period February 1979 – August 1981). Its 1981 user’s manual (Addendum I EXTRAN) printed the complete DEC-20 FORTRAN-10 source code as an appendix on pages 167–228, dated 20 May 1981.
1. Executive Summary: History You Can Run
That 1981 line-printer appendix is the only surviving copy of EXTRAN III’s source code, preserved only as raw OCR text of the scanned manual. The EXTRAN III Workbench reconstructs that listing line by line, compiles it to WebAssembly (wasm32-wasi), and runs it directly in your browser alongside a WebAssembly build of modern EPA SWMM 5.2.4. Nothing is uploaded to a server; the engines, the 80-column card decks, and your .inp models execute locally in background threads using an in-memory file system.
2. Rebuilding the 1981 Engine from OCR to WebAssembly
Anatomy of the 15 Program Units
The OCR text of pages 167–228 was split into the original 15 subroutines and block data units, preserving the exact line numbering from the 1981 compiler listing:
| Program Unit | Manual Pages | Lines | OCR Repairs (Sure / Likely / Guess) | Role in the 1981 Engine |
|---|---|---|---|---|
EXTRAN | pp. 167–168 | 89 | 25 / 0 / 0 | Main stand-alone driver, SWMM interface tape setup, and DEC-20 file OPEN dialogs. |
TRANSX | pp. 169–177 | 480 | 230 / 10 / 0 | Executive driver performing the modified Euler solution of the motion and continuity equations. |
BLKDAT | pp. 178–179 | 64 | 40 / 1 / 0 | BLOCK DATA tables (ANORM, HRNORM, TWNORM) for 5 closed conduit shapes. |
BOUND | pp. 180–185 | 285 | 121 / 6 / 0 | Computes link flows QP(LINK) for tidal boundaries, storage nodes, orifices, weirs, pumps, and outfalls. |
CURVE | pp. 186–187 | 86 | 26 / 0 / 0 | Sets up X/Y scales and draws curves into the line-printer plot grid. |
DEPTHX | pp. 188–189 | 107 | 40 / 3 / 0 | Computes critical depth (YC) and normal depth (YNORM) for closed conduits and trapezoids. |
HEAD | pp. 190–192 | 135 | 52 / 6 / 0 | Converts nodal depths to pipe depths, assigns surface area AS, and computes area, velocity, and hydraulic radius. |
HYDRAD | pp. 193–194 | 70 | 20 / 2 / 0 | Interpolates geometry tables or computes exact properties for rectangular and trapezoidal cross-sections. |
INDATA | pp. 195–211 | 925 | 420 / 62 / 3 | Reads and echoes Card Groups 1–22, initializes conditions, and converts node-conduit links to internal numbering. |
INFLOW | pp. 212–214 | 142 | 77 / 3 / 0 | Interpolates input hydrograph ordinates at each time step from interface tape (N21) and/or Card Group 22. |
OUTPUT | pp. 215–219 | 235 | 97 / 28 / 0 | Prints the end-of-run continuity balance, HGL/flow time histories, summary tables, and triggers printer plots. |
PINE | pp. 220–221 | 68 | 22 / 2 / 0 | Rasterizes line segments between (X1,Y1) and (X2,Y2) onto the character plot grid. |
PPLOT | pp. 222–223 | 81 | 41 / 4 / 0 | Maintains the 41×101 character buffer A(51,101) and prints formatted ASCII plots. |
SCALE | pp. 224–225 | 88 | 22 / 0 / 0 | Scans arrays for min/max bounds and selects clean axis step intervals (2, 4, 5, 8, 10). |
TIDCF | pp. 226–228 | 143 | 56 / 6 / 0 | Computes 7 Fourier coefficients (A1–A7) for diurnal tidal stage boundary conditions (NTIDE = 4). |
Resolving OCR Ambiguities by Running the 1981 Examples
Cross-checking COMMON block declarations across all 15 subroutines and against SWMM 4.4h (EXTBLK) resolved almost all OCR syntax damage mechanically. Where OCR ambiguities affected program logic, running the manual’s seven example problems settled the reading. For example, on INDATA lines 487 and 541 (weir and pump connectivity loops), the OCR text read DO 873 NK=1,8 and DO 1180 NK=1,8, even though the loop bodies tested and assigned NCHAN(J,KK). Compiled with NK, Example 4 missed the 1981 printed junction summary in 14 out of 77 values; compiled with KK, Example 4 and Example 7 matched the 1981 printed summaries (75 exact, 2 OCR digit confusions), proving the printed page had KK.
The 27 Compiler Port Edits (P1–P5)
P1– DEC-20 File I/O (13 lines inEXTRAN): Replaced 5-character word arrays (INTEGER IFNAM(3)) and interactive terminal prompts (DEVICE='DSK', DIALOG=IFNAM) withCHARACTER*256, command-line argument retrieval (CALL GETARG), and standard FortranOPENstatements.P2– Implied-DO Sublists (10 lines inINDATA,INFLOW,TRANSX): Unwrapped inner parentheses inside implied-DO lists such as((JUN(J),Y(J),JCHECK(J)),J=1,NJ)to(JUN(J),Y(J),JCHECK(J),J=1,NJ).P3–DATAStatement Type & Count Rules (2 lines inINDATA): Converted character literals initializing aREALarray (DATA OTYPE/'SIDE','SUMP'/) to Hollerith constants (4HSIDE,4HSUMP), and commented out the unusedDATA ENOTE/6HERROR ,6HERRORS/on an undimensioned scalar.P4– Subroutine Name Mismatch (1 line inHEADline 33): UpdatedCALL DEPTH(...)toCALL DEPTHX(...)to match the printed subroutine nameSUBROUTINE DEPTHX.P5– Common Block Alignment (1 line inHYDRADline 15): UncommentedC REAL LENinHYDRADsoCOMMON/PIPE/has an identical byte layout across all compilation units.
36-Bit DEC-20 Words vs. Modern WebAssembly
The WebAssembly engine produces output that is byte-for-byte identical to native desktop builds across all seven example decks while preserving DEC-20 static storage semantics. Minor last-digit differences against the 1981 paper tables arise because CDM’s 1981 runs executed on a DECsystem-20 using a 36-bit single-precision word with a 27-bit mantissa (between IEEE 754 24-bit float and 53-bit double), and f2c evaluates intrinsics (SQRT, SIN, COS, EXP) in double precision.
3. The 10 Bugs Found in 1981 EXTRAN III
The workbench ships with two complete WebAssembly builds: As printed (preserving the exact numerical behavior—bugs included—that produced CDM’s 1981 manual printouts) and Bugs fixed (modifying the 10 specific defects below).
Part A: Two Bugs Active in CDM’s 1981 Printed Manual Runs
DTEMP in Trapezoid Normal Depth
DEPTHX line 95 · p. 189
C**** DEPTHX, listing lines 89–99, manual page 189 — trapezoid normal depth
665 QNORMO=0.
SROOTS=SQRT(1.+STHETA(N)**2.)+SQRT(1.+SPHI(N)**2.)
DO 680 I=2,26
YI=0.04*FLOAT(I-1)*DEEP(N)
AREA=YI*(WIDE(N)+YI/2.*(STHETA(N)+SPHI(N)))
- HRAD=AREA/(WIDE(N)+DTEMP*SROOTS)
+ HRAD=AREA/(WIDE(N)+YI*SROOTS)
Mechanism & Impact: DTEMP is never assigned anywhere in the program, so DEC-20 static storage initializes it to 0.0. The wetted perimeter is evaluated as the bottom width WIDE(N) alone, overestimating hydraulic radius and normal flow and returning a normal depth that is far too shallow. Someone pencilled “Replace DTEMP by YI” in the margin of the scanned 1981 manual. In Example 1, free outfall 10208 peaks at 0.53 ft as printed, but 2.51 ft when fixed (matching SWMM 5.2.4’s 2.53 ft). Across the SWMM5 corpus, this fix cuts median peak-depth error from 0.48 ft to 0.11 ft across 82 models.
0.677 Instead of 2/3 in Trapezoid Manning Equation
DEPTHX lines 96–97 · p. 189
QNORM=SQRT(32.2*(ZP(N,1)-ZP(N,2))/(LEN(N)*ROUGH(N)))*AREA*HRAD**0.
- 2677
+ 26667
Mechanism & Impact: Raises HRAD to 0.677 instead of 0.6667 (whereas closed conduits on line 58 use 0.6667), overestimating normal flow by ~0.7%–1.7%. Fixing this line alone causes Example 1 to differ from the 1981 printout in 99 places instead of 13, confirming the 1981 printout ran with 0.677.
Part B: Five Additional Bugs Found During Code Transcription
400 QNORMO=0.
DO 600 I=2,26
AREA=AFULL(N)*ANORM(I,NTYPE)
HRAD=RFULL(N)*HRNORM(I,NTYPE)
- IF(NTYPE.EQ. 2) HRAD=WIDE(N)+2.*(I-1)/25.*DEEP(N)
+ IF(NTYPE.EQ. 2) HRAD=AREA/(WIDE(N)+2.*(I-1)/25.*DEEP(N))
Mechanism & Impact: Sets HRAD for rectangular conduits (NTYPE = 2) to the wetted perimeter b + 2y instead of AREA / (b + 2y) during normal-depth calculation.
C******** CRITICAL SECTION UPSTREAM' SURFACE AREA DOWNSTREAM
200 HEAD1=YC+ZP(N,1)
YNL=YC
YMID=0.5*(YNL+YNH)
IF (YMID.LE.0.0) YMID=0.0
CALL HYDRAD(N,NKLASS(N),YNL,RNL,ANL,BNL)
- CALL HYDRAD(N,NKLASS(N),YMID,RMID,AMID,RMID)
+ CALL HYDRAD(N,NKLASS(N),YMID,RMID,AMID,BMID)
CALL HYDRAD(N,NKLASS(N),YNH,RNH,ANH,BNH)
Mechanism & Impact: Passes RMID twice to HYDRAD when the upstream end of a conduit is at critical depth, overwriting the midpoint hydraulic radius with the surface width and leaving BMID with a stale value from a previous pipe.
C**** CHECK CONVERGENCE OF THE FLOW ERROR IN SURCHARGED AREAS
IF(ABS(ERROR)-TOL) 565,565,561
561 IT=IT+1
- IF(IT-30)562,562,564
+ ITLIM=ITMAX
+ IF(ITLIM.LE.0) ITLIM=30
+ IF(IT-ITLIM)562,562,564
Mechanism & Impact: Card Group 3 reads ITMAX into COMMON/SURCHG/SURTOL,ITMAX, but line 340 hardcoded 30, ignoring user input.
C******** COMPUTE WATER VOLUME LEFT IN STORAGE
IF(NSTORE.EQ.0) GO TO 801
DO 800 I=1,NSTORE
J=JSTORE(I)
- 800 VLEFT=VLEFT+Y(J)*ASTORE(J)
+ 800 VLEFT=VLEFT+Y(J)*ASTORE(I)
Mechanism & Impact: Indexes ASTORE(20) by the internal junction number J (up to 187) instead of the storage index I (up to 20), reading out-of-bounds memory when computing end-of-run continuity. In Example 5, fixing this changes reported remaining volume from 8,299 ft³ to 8,379 ft³.
C****** SET PUMP RATE
885 QOUT = PRATE(I,1)
IF(YDEP(J1).GT.VRATE(I,1)) QOUT = PRATE(I,2)
- IF(YDEP(J2).GT.VRATE(I,2)) QOUT = PRATE(I,3)
+ IF(YDEP(J1).GT.VRATE(I,2)) QOUT = PRATE(I,3)
888 QP(LINK) = QOUT
Mechanism & Impact: Tests the downstream receiving node depth YDEP(J2) instead of the upstream wet-well depth YDEP(J1) when triggering the third pump stage PRATE(I,3).
Part C: Three Major Defects Discovered via the 259-Model SWMM5 Corpus
875 JSKIP(J1)=0
IF(YDEP(J1)-0.001) 882,882,885
882 QINJ=0.
DO 883 K=1,8
N=NCHAN(J1,K)
IF(N.GT.NC) GO TO 884
IF(NJUNC(N,2).EQ.J1) QINJ=QINJ+QP(N)
883 CONTINUE
- 884 QOUT=QINJ
+ 884 QOUT=AMIN1(QINJ,PRATE(I,1))
GO TO 888
Mechanism & Impact: When an in-line pump’s inlet node J1 is dry (YDEP(J1) <= 0.001 ft), EXTRAN sets QOUT = QINJ without capping it at PRATE(I,1). Pumping out 100% of inflow keeps J1 dry on the next step, locking the pump in the uncapped dry branch forever (e.g., passing 70.65 cfs through a 5 cfs pump in the extran6 models). Capping at AMIN1(QINJ, PRATE(I,1)) drops median peak-flow error from ~22% to 6–9% across 11 pump models.
C******** FULL PIPE
160 WIDTH = 0.
AREA=AFULL(N)
HRAD=RFULL(N)
RETURN
C******** RECTANGULAR SECTION (SPECIAL CASE)
- 180 WIDTH=WIDE(N)
+ 180 IF(DEPTH.GE.DEEP(N)) GO TO 160
+ WIDTH=WIDE(N)
AREA=WIDTH*DEPTH
HRAD=AREA/(WIDTH+2.*DEPTH)
Mechanism & Impact: Unlike tabulated shapes (NKLASS = 1, 3, 4, 5), the rectangular special case (NKLASS = 2) had no full-depth check. When a closed box culvert surcharged, AREA and HRAD kept growing above the crown and WIDTH stayed at WIDE(N) instead of dropping to zero. Adding IF(DEPTH.GE.DEEP(N)) GO TO 160 reduces median peak-flow differences in the 13 extran1 shape test models from 340%–4,200% down to 2%–18%.
C**** OUTPUT, listing lines 60–63, manual page 216
WRITE(N6,5009) VLEFT
5009 FORMAT(' VOLUME LEFT IN SYSTEM =',5X,F12.0,' CU FT',/)
- PCTERR = ((SUMQIN-SUMOUT-VLEFT)/SUMQIN)*100.
+ PCTERR = ((SUMQIN+VINIT-SUMOUT-VLEFT)/(SUMQIN+VINIT))*100.
WRITE(N6,5006) PCTERR
Mechanism & Impact: Any water initially in the system at t = 0 (from Card Groups 19–20 or submerged tidal outfalls) is counted in VLEFT or SUMOUT at the end of the run, but was omitted from the initial mass balance. Including VINIT improves extran8a‘s reported continuity error from −44.2% to +0.3%.
4. How the SWMM5-to-EXTRAN III Converter Works
- Stage 1 – Run EPA SWMM 5.2.4 in WebAssembly: Runs your
.inpmodel in the browser at the model’s routing time step (or reads your uploaded.rptand.outfiles). - Stage 2 – Extract Lateral Inflows to Card Group 22: Extracts SWMM5 node lateral inflows onto 80-column hydrograph cards (shifted back by one routing step to match SWMM step-inflow evaluation) so both engines route the exact same water volume.
- Stage 3 – Translate Geometry & Structures: Maps conduits to EXTRAN’s 6 shape classes (converting custom shapes to equivalent rectangles/trapezoids with matching full depth and area), maps orifices, weirs, pumps, and storage units to Card Groups 10–13, and enforces Courant stability on
Δt.
5. Interactive Tour of the Workbench’s 8 Tabs
- Run: Execute any of the 7 manual examples with an animated HGL profile plot, multi-node/link time-series charts, one-click GIF recorder, and the As printed / Bugs fixed toggle.
- SWMM5 vs EXTRAN III: Upload any SWMM5/SWMM6
.inpmodel or pick from 10 built-in sample models to run side-by-side peak scatter plots and hydrograph comparisons. - Card deck: Edit 80-column card decks with a live column ruler, Card Group gutter labels (
CG 1–CG 22), and a cursor field inspector. - Printed report: Read Unit 21 output on green-bar fanfold line-printer paper with ASCII printer plots and section jump links.
- Against 1981: Automated cell-by-cell comparison against the 1981 manual’s printed tables (Exact, Last digit, OCR, or Mismatch).
- Source listing: Browse all 2,998 FORTRAN lines across the 15 subroutines with interactive badges for all 1,425 OCR repairs, 27 port edits, and 10 bugs.
- Docs: Inspect unified diffs, physical explanations, and corpus impact stats for all 10 bugs, with a one-click “Run it both ways” diff runner.
- About: Complete documentation of provenance, DEC-20 36-bit floating-point behavior, and conversion methodology.