![]() |
OpenSWMM Engine
6.0.0-alpha.4
Data-oriented, plugin-extensible SWMM Engine (6.0.0-alpha.4)
|
OpenSWMM Engine v6 provides a comprehensive C API for building, running, and querying SWMM models entirely through code, without requiring an input file. This is useful for embedding SWMM in larger simulation frameworks, coupling with other models, or building custom user interfaces.
The new engine uses an opaque handle (SWMM_Engine) that encapsulates all simulation state. This reentrant design allows multiple independent simulations to run within the same process. The engine progresses through a well-defined lifecycle:
When a model is constructed programmatically (no input file), the engine instead begins in a BUILDING state and swmm_finalize_model() transitions it directly to INITIALIZED:
All API functions return an integer error code (SWMM_OK on success) and are organized by domain into separate headers; the master header openswmm_engine.h pulls in all of them:
| Header | Domain |
|---|---|
openswmm_engine.h | Engine lifecycle, error codes, state machine, timing, master include |
openswmm_model.h | Model building, validation, serialization, options, user flags, [PLUGINS]/[FILES] access |
openswmm_nodes.h | Junction, outfall, storage, and divider nodes |
openswmm_links.h | Conduit, pump, orifice, weir, and outlet links |
openswmm_subcatchments.h | Subcatchments and infiltration |
openswmm_gages.h | Rain gages |
openswmm_pollutants.h | Pollutants and their properties |
openswmm_tables.h | Time series, curves, and patterns |
openswmm_inflows.h | External inflows, DWF, and RDII |
openswmm_controls.h | Control rules |
openswmm_infrastructure.h | Transects, streets, inlets, LID controls |
openswmm_spatial.h | CRS, coordinates, polylines, polygons |
openswmm_quality.h | Landuse, buildup/washoff, treatment |
openswmm_initial_quality.h | [INITIAL_QUALITY] per-element initial concentrations |
openswmm_reactions.h | Multi-species reaction system, expression validation, .rxn text |
openswmm_process_components.h | [PROCESS_COMPONENTS] registrations and config paths |
openswmm_water_age.h | [WATER_AGE_SOURCES] source ages and node overrides |
openswmm_heat.h | Heat-transport fluxes, solar/cloud forcing, [HEAT_SOURCES] |
openswmm_infil2d.h | 2-D surface infiltration cells, defaults, and totals |
openswmm_massbalance.h | Continuity errors and flux totals |
openswmm_statistics.h | Post-run node/link/pump statistics |
openswmm_forcing.h | Runtime forcing overrides |
openswmm_climate.h | Evaporation, temperature, snowmelt, adjustments |
openswmm_edit.h | Object rename, delete, convert, impact analysis |
openswmm_datetime.h | SWMM DateTime encode/decode utilities |
openswmm_xsect.h | Cross-section geometry computations |
openswmm_output.h | Binary output (.out) file reading |
openswmm_geopackage.h | GeoPackage container queries |
openswmm_callbacks.h | Progress, warning, and step callback typedefs |
openswmm_hotstart.h | Hot start file operations |
openswmm_2d.h | 2-D overland flow mesh (when built with OPENSWMM_HAS_2D) |
Every engine instance moves through the deterministic lifecycle of Figure 13-1. The file-based path enters at swmm_engine_open(); the programmatic path enters at swmm_engine_new() (BUILDING) and joins it at swmm_finalize_model().
stateDiagram-v2
direction LR
[*] --> CREATED : swmm_engine_create
CREATED --> OPENED : swmm_engine_open - parse inp, load plugins
[*] --> BUILDING : swmm_engine_new
BUILDING --> OPENED : swmm_finalize_model
OPENED --> INITIALIZED : swmm_engine_initialize
INITIALIZED --> STARTED : swmm_engine_start
STARTED --> RUNNING : first swmm_engine_step
RUNNING --> RUNNING : swmm_engine_step
RUNNING --> ENDED : swmm_engine_end
ENDED --> ENDED : swmm_engine_report
ENDED --> CLOSED : swmm_engine_close
CLOSED --> [*] : swmm_engine_delete
*Figure 13-1 Engine lifecycle states (SWMM_EngineState in openswmm_engine.h)*
The typical workflow for building and running a model programmatically (no input file):
To run an existing input file instead, use swmm_engine_create() followed by swmm_engine_open() and swmm_engine_initialize():
The final NULL argument to swmm_engine_open() selects the built-in .inp reader; passing the path of an input-plugin shared library reads the model through that plugin instead (see Section 13.8). The single-call helpers swmm_engine_run() and swmm_engine_run_with_callback() chain the entire lifecycle for batch runs.
The callback system allows applications to receive notifications during simulation execution:
Register callbacks before calling swmm_engine_initialize(). See openswmm_callbacks.h for details and examples.
The hot start API enables saving and restoring simulation state for warm-start scenarios:
swmm_hotstart_apply() must be called after swmm_engine_initialize() but before swmm_engine_start(). Objects present in the file but missing from the model (or vice versa) generate warnings rather than errors; they can be enumerated with swmm_hotstart_warning_count() / swmm_hotstart_warning(). Saving dispatches through any registered state-IO plugins, so alternative hot-start file formats can be provided by plugins (see Section 13.8).
OpenSWMM 6.0 ships a first-class Python package (openswmm) that provides Pythonic, type-annotated access to the full engine feature set. Install from PyPI:
openswmm.engine)All simulation functionality lives in the openswmm.engine sub-package.
| Class | Purpose |
|---|---|
Solver | Engine lifecycle — open, start, step, end, report |
Nodes | Query and set node attributes and results |
Links | Query and set link attributes and results |
Subcatchments | Query and set subcatchment attributes and results |
Gages | Query rain-gage attributes and recorded rainfall |
HotStart | Save and restore simulation state |
MassBalance | Retrieve continuity error and mass-balance statistics |
ModelBuilder | Construct a SWMM model programmatically without an input file |
Hot start files allow a long-term simulation to be split into segments, each beginning from the hydraulic state left by the previous run.
ModelBuilder constructs a complete SWMM network without an input file. The finished model is passed directly to Solver.
The complete class and method documentation, including all attributes, enumerations, and error types, is published in the Python Bindings API Reference.
See the python/ directory in the source tree for the Cython source (.pyx), type stubs (.pyi), and test suite.
To use the OpenSWMM Engine C API in your own project, link against openswmm_engine using CMake:
All public headers are installed under include/openswmm/engine/.
OpenSWMM Engine v6 introduces user-defined flags (inspired by InfoWorks ICM custom attributes) that allow metadata to be attached to any model object—nodes, links, or subcatchments. Flags are defined with a name, data type, and optional description and then assigned values per object.
Two new sections are recognised in the input file:
Supported types are BOOLEAN (YES/NO/TRUE/FALSE/1/0), INTEGER, REAL, and STRING.
Flag values can be read or written at runtime through the C API:
See openswmm_model.h for the complete set of flag functions.
The plugin SDK enables third-party shared libraries to replace or supplement the engine's built-in file I/O — the model reader/writer, the binary output (.out) file, the text-based status report, and the hot-start (state) file. Four abstract C++ interfaces are provided:
| Interface | Header | Purpose |
|---|---|---|
IInputPlugin | IInputPlugin.hpp | Reads model data into the engine (and writes it back out) in alternative container formats |
IOutputPlugin | IOutputPlugin.hpp | Writes time-series results at each output time step |
IReportPlugin | IReportPlugin.hpp | Writes summary statistics at simulation end |
IStateIOPlugin | IStateIOPlugin.hpp | Reads and writes simulation state (hot-start) files |
All interfaces share a common lifecycle that mirrors the engine's own state machine (enumerated by PluginState in PluginState.hpp):
Each plugin is compiled as a shared library (.so / .dylib / .dll) that exports a single C factory function:
The IPluginComponentInfo class provides metadata (id in reverse-DNS notation, caption, description, version, vendor, license), capability queries (has_input(), has_output(), has_report(), has_state_io() — a single plugin may support several roles), optional registration/licensing hooks, and factory methods for creating instances of each interface. Plugins also advertise the file formats they handle through file_filters(), one entry per (role, glob-pattern) pair; hosts such as the GUI use these to build file-picker dialogs without hard-coding format lists.
Each engine instance owns a PluginFactory that automatically scans the engine library directory and its plugins/ and components/ subdirectories for shared libraries exporting openswmm_plugin_info. Discovered libraries are registered in a component registry keyed by id:version; the built-in default input, output, report, and state-IO plugins are registered alongside them (flagged as built-ins). Hosts that only need to enumerate available formats can call the framework-free discovery facade in PluginDiscovery.hpp:
Plugins are loaded from the [PLUGINS] input-file section:
Each line begins with the plugin to load — a shared-library path, a plugin id, or an id:version pair (ids are resolved against the auto-discovery registry) — followed by initialisation arguments that are forwarded verbatim to the plugin's initialize() method.
Figure 13-2 traces the full resolution path from engine open to an initialized plugin.
flowchart TD
A[swmm_engine_open] --> B[PluginFactory scans engine lib dir plus plugins and components subdirectories]
B --> C[Libraries exporting openswmm_plugin_info registered by id and version]
C --> D[Built-in input, output, report and state-IO plugins registered as built-ins]
D --> E[Parse PLUGINS section entries]
E --> F{Entry form}
F -- shared-library path --> G[Load library directly]
F -- plugin id --> H[Resolve newest version in registry]
F -- id colon version --> I[Resolve exact version in registry]
G --> J[Instantiate plugin]
H --> J
I --> J
J --> K[Forward remaining arguments to initialize]
K --> L[Plugin receives host callbacks during the run]
Figure 13-2 Plugin discovery, resolution and loading workflow (rendered diagram)
The [PLUGINS] section can also be inspected and edited through the C API without re-parsing the input file: swmm_plugins_count(), swmm_plugin_get(), swmm_plugin_set(), and swmm_plugin_remove() (declared in openswmm_model.h) read and mutate the in-memory plugin list, which is re-serialised on the next model write.
An IInputPlugin reads a model file into the engine's simulation context and can also write the current model back out. The reader is selected per-open through the final argument of swmm_engine_open() (NULL selects the built-in .inp reader). To save the in-memory model through a specific writer plugin — for example a GeoPackage container instead of .inp — use:
The plugin id is resolved with the same path / id / id:version logic as the [PLUGINS] section, and the resolved plugin must advertise input capability.
An IStateIOPlugin persists and restores simulation state (hot-start). On save, swmm_hotstart_save() dispatches through the registered state-IO plugins in order — the first plugin whose write_state() succeeds wins. On read, the engine calls each plugin's can_read() (a cheap extension/magic-number sniff) to pick the plugin that recognises the file. A built-in DefaultStateIOPlugin is always registered as a fallback, so the native binary hot-start format keeps working when no external plugin claims the file. Missing-object mismatches between the state file and the model are reported through the plugin's warnings() rather than as hard errors.
At every output time step the engine passes a read-only SimulationSnapshot to update(). The snapshot exposes per-object results:
See the headers in include/openswmm/plugin_sdk/ for full details. The engine-side loader, lifecycle dispatcher, and the built-in default plugins live in src/engine/plugins/ (PluginFactory.cpp, DefaultInputPlugin.cpp, DefaultOutputPlugin.cpp, DefaultReportPlugin.cpp, DefaultStateIOPlugin.cpp).
A new rain-file format, USER_CSV, allows rain gage data to be read from multi-column series files. A single file can serve multiple rain gages by specifying a column name after the file path:
The syntax "filename.csv:COLUMN_NAME" tells the engine to open filename.csv and read the column whose header matches COLUMN_NAME (case-insensitive). An empty column name selects the file's first data column. The file is expected to have a header row whose first column holds a full date-time; the remaining columns hold values.
Three file layouts are recognised, detected automatically from the file's contents (the extension does not matter):
DateTime,EAST_GAGE,WEST_GAGE.IDs: row naming the columns, a parameter row, and a units row. Date-times use the 12-hour MM/DD/YYYY hh:mm:ss AM/PM form.Date-times in CSV/TSV files may be ISO-8601 (YYYY-MM-DD HH:MM[:SS]) or US (MM/DD/YYYY HH:MM[:SS]), with an optional trailing AM/PM token for 12-hour clocks.
The same "path:COLUMN_NAME" reference works for named time series: [TIMESERIES] TS_E FILE "rain.csv:EAST_GAGE" binds the series TS_E to that column, so inflows, outfall stages, and other objects that reference a time series by name can depend on one column of a shared file.
Each multi-column file is parsed once per model open (or per swmm_gage_reload_rain_files() call), no matter how many rain gages and time series reference it, and there is no limit on the number of columns or the width of a row. A referenced file that cannot be opened, or that yields no readable rows, fails the model open with an explicit error rather than silently supplying zero values.
The RainFileFormat enumeration now includes:
| Value | Constant | Description |
|---|---|---|
| 0 | NWS_15 | NWS 15-minute data |
| 1 | NWS_HOURLY | NWS hourly data |
| 2 | DSI_3240 | NCDC DSI 3240 hourly |
| 3 | DSI_3260 | NCDC DSI 3260 15-minute |
| 4 | HLY_PRCP | HLY_PRCP format |
| 5 | STAN_PRCP | Standard SWMM rain file |
| 6 | USER_CSV | User-supplied multi-column CSV (new in v6) |
The [OPTIONS] section now tolerates extension option keys that are not part of the standard SWMM vocabulary. Any key the parser does not recognise is stored in an extension-options map as a string key-value pair rather than producing a fatal error (a non-fatal warning is issued). This mechanism allows plugins and coupled models to pass configuration through the familiar [OPTIONS] section.
Extension option keys are upper-cased for storage. Plugins (or any code using the C API) can retrieve and set these values at runtime:
Note that the CRS key is a standard option new to v6 and is stored separately in SimulationOptions::crs. All other unrecognised keys go into the extension map.