Running a simulation — Solver#
Note
Engine: OpenSWMM 6 — refactored. This is
openswmm.engine.Solver. The legacy SWMM 5 solver of the
same name lives at openswmm.legacy.engine.Solver — see
Legacy SWMM 5 Solver for its (different) API.
The Solver class is the entry point. It owns the SWMM engine
handle, parses the input file, drives the simulation forward in time,
and writes report and binary-output files. Every other domain class
(Nodes, Links, Forcing, …) takes a Solver in
its constructor.
Class signature#
class Solver:
def __init__(self, inp: str = "", rpt: str = "", out: str = "") -> None: ...
inp— path to the SWMM.inpinput file.rpt— path for the human-readable.rptreport. Empty string to skip.out— path for the binary.outresults file. Empty string to skip.
The C engine handle is allocated lazily on the first call to
open(); until then, only state, handle, and the lifecycle
helpers are valid.
Lifecycle methods#
Method |
What it does |
|---|---|
|
Allocate the engine handle. Implicit on |
|
Parse the |
|
Allocate state arrays; apply initial conditions. |
|
Start routing. |
|
Advance one routing step. Returns |
|
Stop routing; finalize cumulative outputs. |
|
Write the human-readable |
|
Flush |
|
Free the engine handle. Always call this. |
|
Context-manager wrapper for the full lifecycle. |
Inspection / time#
Property / method |
Returns |
|---|---|
|
Current |
|
Opaque engine handle (mostly for plugin authors). |
|
Elapsed simulation time in days. |
|
Simulation start time (decimal days since 1899-12-30). |
|
Simulation end time (same epoch). |
|
Current simulation time (same epoch). |
|
Current routing time step in seconds. |
Save / serialise#
Method |
Action |
|---|---|
|
Write the current model state back out as |
End-to-end example#
from openswmm.engine import Solver
with Solver("site_drainage.inp", "site_drainage.rpt", "site_drainage.out") as s:
print(f"Routing step: {s.get_routing_step():.1f}s")
print(f"Sim window: day {s.get_start_time():.4f} → day {s.get_end_time():.4f}")
steps = 0
while s.step():
steps += 1
if steps % 240 == 0: # every ~hour at 15s step
print(f" t = {s.elapsed*24:5.2f} h")
print(f"Done — {steps} routing steps.")
The context manager guarantees end() → report() → close() → destroy()
runs even if the loop body raises.
Manual lifecycle (when you need to inspect the parsed model before initialisation):
from openswmm.engine import Solver, Nodes
s = Solver("model.inp", "model.rpt", "model.out")
s.create()
s.open() # state == OPENED
nodes = Nodes(s)
print(f"Model has {nodes.count()} nodes")
for i in range(nodes.count()):
print(f" {nodes.get_id(i)} (type={nodes.get_type(i)})")
s.initialize()
s.start(save_results=True)
while s.step():
pass
s.end()
s.report()
s.close()
s.destroy() # release engine handle
Common recipes#
Report progress every wall-clock second#
import time
from openswmm.engine import Solver
with Solver("model.inp", "model.rpt", "model.out") as s:
last = time.monotonic()
total = s.get_end_time() - s.get_start_time()
while s.step():
now = time.monotonic()
if now - last >= 1.0:
pct = 100.0 * s.elapsed / total
print(f"{pct:5.1f}% ({s.elapsed:.4f} d / {total:.4f} d)")
last = now
Run multiple scenarios in parallel#
from concurrent.futures import ProcessPoolExecutor
from openswmm.engine import Solver
def run_one(inp_path):
rpt = inp_path.replace(".inp", ".rpt")
out = inp_path.replace(".inp", ".out")
with Solver(inp_path, rpt, out) as s:
while s.step():
pass
return out
inputs = ["scenario_a.inp", "scenario_b.inp", "scenario_c.inp"]
with ProcessPoolExecutor(max_workers=4) as pool:
for out_file in pool.map(run_one, inputs):
print("done:", out_file)
Each child process has its own engine handle; no global state to collide.
Stop simulation early on a custom condition#
from openswmm.engine import Solver, Nodes
with Solver("model.inp", "model.rpt", "model.out") as s:
nodes = Nodes(s)
flooded = nodes.get_index("J1")
while s.step():
if nodes.get_depth(flooded) > 5.0:
print(f"Flood threshold hit at t={s.elapsed:.4f} d")
break # context manager still runs end/report/close
Skip writing the binary .out#
# Pass empty string for `out`
with Solver("model.inp", "model.rpt", "") as s:
...
This is faster (no per-step output writes) and saves disk; you trade
away the ability to use OutputReader afterwards.
Skip writing the report too#
with Solver("model.inp", "", "") as s:
...
Useful in tight Monte-Carlo loops or as part of a CI smoke test.
Save the modified model back to disk#
After you’ve used ModelEditor or ModelBuilder to
mutate the model, persist the result:
s.model_write("modified.inp")
The output is a fully-valid SWMM .inp file (round-trippable
through any SWMM reader).
Bulk arrays#
The Solver itself does not expose a bulk-array surface — those live on
the domain classes (Nodes, Links, etc.). The Solver
does, however, expose scalar time accessors that you’ll often
combine with bulk reads:
import numpy as np
from openswmm.engine import Solver, Nodes
times, depths = [], []
with Solver("model.inp", "model.rpt", "") as s:
nodes = Nodes(s)
while s.step():
times.append(s.elapsed) # days since start
depths.append(nodes.get_depths_bulk().copy()) # (n_nodes,)
times = np.array(times) # shape (T,)
depths = np.stack(depths) # shape (T, n_nodes)
EngineState requirements & exceptions#
Method |
Required state |
Notes |
|---|---|---|
|
any (no-op if already created) |
Idempotent. |
|
|
Raises if the file fails to parse. |
|
|
Allocates per-element state arrays. |
|
|
|
|
|
Returns |
|
|
Idempotent; second call is a no-op. |
|
|
Writes the |
|
any (after |
Flushes |
|
any |
Frees the engine handle. |
Calling a method out of order raises EngineError. Common
codes you’ll see:
Code |
Name |
Meaning |
|---|---|---|
|
|
You called a method that needs |
|
|
You called |
|
|
You called |
|
|
Wrong object type for the operation (e.g. setting an outfall parameter on a junction). |
For the full list see ErrorCode.
See also#
Concepts & engine lifecycle — the broader engine-state and exception model.
Error handling, edge cases & debugging — programmatic error handling patterns.
Output reader (binary .out file) — post-process the
.outfile after the run.