Conversation
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Critical fix: translateShape() used simple +1 offset which misaligned LinkData::XsectShape enums with XSectBatch::XSectShape enums beyond the first 8 shapes. IRREGULAR(18) mapped to 19 (SEMIELLIPTICAL) instead of 22, CUSTOM(19) to 20 (BASKETHANDLE) instead of 23, etc. Replaced with explicit switch mapping for all 22 shape types. Also simplified CUSTOM pre-computation in PostParseResolver to let buildCustomTables() handle all geometry (removed redundant inline area/perimeter integration that used wrong width scaling). Result: Continuity -42% → -26%, flooding 397 → 192 acre-ft. Runtime: 428s (down from 475s due to correct batch kernels).
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
EXTRAN's dQ/dH surcharge formulation is discontinuous and not a smooth fixed-point map G(y). Anderson acceleration mixing can produce depths below the crown floor that setNodeDepth enforces, leading to non-physical results. This guard skips AA mixing when a node is surcharged under EXTRAN, falling back to the standard Picard iterate. AA remains active for SLOT and DYNAMIC_SLOT methods which produce smooth G(y) maps.
Skip Anderson acceleration for surcharged nodes under EXTRAN
Only strip conduit/orifice half-areas at a STORAGE node when the storage curve provides area > MIN_SURFAREA. For degenerate storage nodes (zero or near-zero curve), keep the legacy pipe-half contribution so the Picard denominator stays bounded away from MIN_SURFAREA. Fixes: DynamicWave.cpp::updateNodeFlows (conduit path) Fixes: SWMMEngine.cpp non-conduit callback (orifice path) Tests: 5 new StorageHalfAreaGuard unit tests
… regimes
Anderson acceleration (AA) requires a smooth fixed-point operator G
between consecutive Picard iterates. Three surcharge formulations
violate this assumption:
EXTRAN: discontinuous dQ/dH formulation at crown
DYNAMIC_SLOT: per-iterate geometry rewrite (Sharior et al. 2023)
SLOT: C⁰ kink at the slot cutoff (~0.985·yFull)
The existing code only skipped AA for EXTRAN surcharged nodes (from
pr/aa-extran-guard). This extends the guard to all three regimes via
a per-node aa_skip_ flag vector, scatter-computed each Picard iteration
after geometry is known:
- EXTRAN: skip when xnode_.is_surcharged
- DPS: skip end-nodes of conduits with active slot area (As > 0)
- SLOT: skip end-nodes of conduits with depth_mid/yFull in [0.98, 1.02]
Free-surface nodes (95%+ of network) retain full AA speedup.
No physics is altered; only the numerical accelerator is gated.
Implementation:
- DynamicWave.hpp: add aa_skip_ (vector<uint8_t>), computeAASkipFlags()
declaration, aaSkipFlags() const accessor
- DynamicWave.cpp: allocate aa_skip_ in init(), implement
computeAASkipFlags() with O(n_conduits) scatter, call after
computeLinkGeometry() in execute(), guard AA block in
updateNodeDepths() with !aa_skip_[ui]
Tests: 6 new AASkipFlagTest cases in test_routing.cpp
- FreeSurfaceNoSkip, ExtranSurchargedSkips, DPSActiveSkipsEndNodes,
SlotNearKinkSkipsEndNodes, SlotFarFromKinkNoSkip, AADisabledNoFlags
All 61 routing tests pass.
…it-halves fix(issue-2): conditionally zero storage-node conduit half-areas
…leration-scope fix(issue-3): extend Anderson acceleration skip guard to DPS and SLOT…
…I portability Curve unit conversion fixes (all curve types now apply x/y UCF at runtime): - HydStructures: pump curve x-axis UCF for PUMP1(vol), PUMP2-4(depth), PUMP3/5(head); RATING curve x-axis ucf_len and y-axis /ucf_flow in computeOutletFlows - Node: STORAGE curve depth*ucf_len input and area/ucf_area output in getSurfArea/getVolume - Outfall: FIXED/TIDAL/TIMESERIES stage divided by ucf_len after table lookup - Divider: DIVERSION curve x-axis multiplied by ucf_flow before lookup - PostParseResolver: pump_startup/shutoff divided by ucf_len to convert display→ft PUMP5 support: - TableData: add CURVE_PUMP5=11 enum value - TablesHandler: add "PUMP5" -> CURVE_PUMP5 parse entry - HydStructures init: extend mapping range to tt<=11 (PUMP5 -> curve_type=5) InpWriter round-trip fix: - Add CURVE_TYPE_LABEL map; replace hardcoded "STORAGE" with per-type label (previously all curves written as STORAGE, breaking pump simulation on re-read) GeoPackage writer fix: - pump_startup/shutoff multiplied by ucf_len before write to reverse PostParseResolver's ft conversion and restore display units Controls UCF fix: - getVariableValue: all node/link quantities multiplied by UCF before threshold comparison (depth*ucf_len, flow*ucf_flow, volume*ucf_vol) to match legacy - computePIDSetting: pass current_setting and is_pump for correct PID behaviour Windows/MSVC portability: - LinkData/NodeData: vector<bool> -> vector<uint8_t> (avoids proxy-ref UB on MSVC) - test_routing: add _USE_MATH_DEFINES guard before <cmath> for M_PI on MSVC - test_gap_fixes: replace hardcoded /tmp/ paths with std::filesystem::temp_directory_path()
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
|
Fantastic work @cbuahin I am getting my Ais to check all of this |
added 8 commits
April 23, 2026 21:30
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
…stence Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Adds Darwin-tests, Linux-tests, and Windows-tests configure/build presets that inherit from each platform's debug preset and enable the vcpkg tests feature plus OPENSWMM_BUILD_UNIT_TESTS and OPENSWMM_BUILD_REGRESSION_TESTS. This makes `cmake --preset <platform>-tests` a one-line local repro of the CI test build, which previously required setting VCPKG_MANIFEST_FEATURES=tests manually (otherwise find_package(GTest) fails because gtest is gated behind the optional tests feature in vcpkg.json).
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
Signed-off-by: cbuahin <caleb.buahin@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
OpenSWMM Engine v6.0.0-alpha.1
Summary
This pull request delivers the OpenSWMM Engine v6.0.0-alpha.1 — a ground-up modernization of the EPA SWMM computational engine. The work spans 12 implementation phases covering architecture, algorithms, API design, plugin infrastructure, Python bindings, testing, CI/CD, and documentation.
The legacy EPA SWMM 5.x solver is preserved unmodified in
src/legacy/as a regression baseline. The new engine is a complete, parallel implementation that produces as baseline, near identical results using the same algorithms, constants, and convergence criteria with significantly improved performance. The data structures implement pave the way for a more community driven development effort through the implementation of the SWMM code in a modular manner. The API and python bindings allow for a fully end to end modeling workflow fully done by code.What Changed
New Engine Architecture (146 C++ source files)
SimulationContext.SWMM_Enginehandle encapsulates all simulation state, enabling multiple independent simulations per process.CREATED → OPENED → BUILDING → INITIALIZED → STARTED → RUNNING → ENDED → CLOSED) with compile-time state guards on all API calls.-fno-fast-mathand-fexcess-precision=standardfor numerical fidelity.Comprehensive C API (19 domain-split headers, ~250+ functions)
The monolithic legacy interface is replaced by a domain-organized C89-compatible API:
openswmm_engine.hopenswmm_model.h.inpserialization, optionsopenswmm_nodes.hopenswmm_links.hopenswmm_subcatchments.hopenswmm_gages.hopenswmm_pollutants.hopenswmm_tables.hopenswmm_inflows.hopenswmm_controls.hopenswmm_infrastructure.hopenswmm_spatial.hopenswmm_quality.hopenswmm_massbalance.hopenswmm_callbacks.hopenswmm_hotstart.hopenswmm_statistics.hModels can be built entirely via the C API without an
.inpfile.Process Formulation Enhancements
The following physics-based improvements are being designed for backward-compatible integration. Each addresses a known simplification in the current SWMM formulation.
Spatially Explicit Overland Flow & Groundwater — SWMM currently has one-way feedback between hydrology and hydraulics: flooded nodes pond over a user-specified area and are reintroduced locally. In reality, surcharge flows traverse the terrain and may re-enter the network at a downstream node. The new module couples a 2D overland flow grid with the 1D pipe network, enabling spatially explicit green infrastructure placement, terrain-routed surcharge, and lateral groundwater flow exchanges, etc.
Dynamic Preissmann Slot — Standard SWMM uses a fixed-width slot for pressurized conduit transitions, which can cause numerical instability at the open-channel/pressure interface. A dynamic slot formulation smooths the transition with a geometry-dependent slot width, improving stability for rapidly filling/draining conduits.
Spatially Explicit Inlets — Current inlets are attributes of conduits, limiting bypass routing and preventing backflow or series-inlet configurations. The redesign promotes inlets to mode-switching junction nodes that actively capture street flow when the gutter spread exceeds a threshold and revert to passive junctions otherwise.
LID as Storage Nodes — Current LID units lack hydraulic feedback: no backpressure from the drainage network, no bidirectional flow, and no control-structure integration. The redesign maps LID layers (surface, media, gravel) onto an extended storage node using a reduced-physics kinematic Richards ODE — a simplified 1D formulation that captures gravity-driven percolation and capillary redistribution between discrete layers without the computational cost of a full 3D variably-saturated solver. This provides physically meaningful moisture dynamics while remaining compatible with SWMM's routing timestep, and enables elevation-mapped underdrain links with full network coupling.
Physics-Based Initial Abstraction Recovery — SWMM's seasonal RTK calibration for RDII confounds infrastructure leakage fraction with soil moisture state, requiring 12 monthly parameter sets. The new formulation tracks initial abstraction capacity as an exponential decay/recovery process. During dry periods, available capacity recovers with additive time-based and temperature-dependent rate components:
The base rate$k_0$ captures gravity drainage and capillary redistribution that occur regardless of temperature, while $k_T \cdot e^{\theta(T-T_{ref})}$ captures thermally-driven evapotranspiration. During storms, capacity is depleted: $IA_{avail}(t+\Delta t) = IA_{avail}(t) \cdot e^{-k_{dep},\Delta P}$ . Recovery is suppressed when $T < T_{freeze}$ , producing emergent seasonal variation (high winter RDII, low summer RDII) from a single RTK set per sewershed — no monthly parameter tables required.
Plugin SDK (5 headers)
IOutputPlugin/IReportPlugin— Abstract C++ interfaces for writing time-series results and summary reports to custom formats (HDF5, NetCDF, CSV, etc.).IPluginComponentInfo— Metadata and factory interface with reverse-DNS identifiers.PluginState— Lifecycle state machine:LOADED → INITIALIZED → VALIDATED → PREPARED → UPDATING → FINALIZED → CLOSED.SimulationSnapshot— Read-only deep-copy of simulation state passed to plugins on a dedicated I/O thread.[PLUGINS]input file section for registering shared library plugins with initialization arguments.Computational Algorithms (Numerical Parity with Legacy SWMM)
All SWMM computational modules ported to SoA/batch/SIMD-friendly design with near identical numerical behavior as an initial starting point :
xsect.ckinwave.cdwflow.c,dynwave.crouting.c,flowrout.crunoff.c,subcatch.cinfil.cclimate.csnow.cgwater.clid.c,lidproc.cqualrout.ctreatmnt.c,mathexpr.ccontrols.clink.cinflow.crdii.cculvert.cinlet.c,street.cforcmain.cexfil.ctransect.c.inpwriter for all 37 SWMM sectionsExplicit Timestep Control
dt_next = min(dt_output, dt_cfl, dt_controls, dt_rdii)— simulation stops exactly at output boundaries with no interpolation.Hot Start API
OPENSWMM_HS_V1with CRC32, object UUID map for missing-object detection.swmm_hotstart_save/open/apply/modify/closewith per-object depth/flow overrides.Input System Enhancements
USER_CSVformat with column selection:FILE "rain.csv:EAST_GAGE".[OPTIONS]keys stored in a string map accessible to plugins viaswmm_options_get_ext().CRSoption (EPSG code or PROJ string) for spatial data.[USER_FLAGS]and[USER_FLAG_VALUES]sections for BOOLEAN/INTEGER/REAL/STRING metadata on any object.SectionRegistrywith custom function pointer readers.Python Bindings (11 Cython modules)
_solver.pyx—Solverlifecycle context manager with iterative stepping._model.pyx—ModelBuilderfor programmatic model construction._nodes.pyx/_links.pyx/_subcatchments.pyx/_gages.pyx— Domain access with numpy bulk arrays._hotstart.pyx— Hot start save/open/apply with context manager._massbalance.pyx— Continuity error queries._enums.py—NodeType,LinkType,XSectShape,FlowUnits,RouteModelint enums.Legacy Isolation
src/legacy/engine/andsrc/legacy/output/— zero modifications to legacy code.openswmm_legacy_engine,openswmm_legacy_output.<openswmm/legacy/engine/openswmm_solver.h>.Testing
tests/unit/legacy/{engine,output}andtests/unit/{engine,output}.RunLegacy+RunNew+CompareOutputswith per-object tolerance (depth ±0.001 ft, flow ±0.001 cfs).CI/CD Pipelines (4 workflows rewritten)
unit_testing.yml— 4-platform matrix (Linux x64, macOS x64, macOS ARM64, Windows x64), engine build+test+package, Python wheel build+test. Fixed critical bug:-DBUILD_TESTS=ON→-DOPENSWMM_BUILD_TESTS=ON.regression_testing.yml— Multi-platform regression with vcpkg + Google Test (removed Boost.Test/NuGet dependency).documentation.yml— Doxygen build + GitHub Pages deployment; updated toactions/checkout@v4.deployment.yml— Tag-triggered release builds + cibuildwheel wheels + GitHub Release creation.Documentation
@brief,@details,@param,@returns,@see,@note,@warning.@author Caleb Buahin,@copyright HydroCouple 2026,@license MITon all 146 new engine source files.[OPTIONS]CRS and extension options;[RAINGAGES]CSV column syntax.Stats
src/engine/)src/legacy/)Key Design Decisions
__restrict__+#pragma ivdepfor complex loops; explicitsimd::intrinsics for simple array operations.Breaking Changes
OpenSWMMCoretoopenswmm; library output isopenswmm.engine.OPENSWMM_*prefix (legacyOPENSWMMCORE_*aliases preserved).Remaining Work (tracked in MASTER_IMPLEMENTATION_PLAN.md)