Programmatic model construction#
Note
Engine: OpenSWMM 6 — refactored. Documents
openswmm.engine.ModelBuilder.
The ModelBuilder constructs a complete SWMM model in Python,
without needing a .inp file. Use it for:
Generating models from a database or GeoJSON.
Synthetic models for testing and benchmarking.
Optimisation loops where the network topology is the decision variable.
Templates: build a base model in code, then
write()the resulting.inpfile.
Reference: openswmm_model.h.
Class signature#
class ModelBuilder:
def __init__(self) -> None: ...
The builder is standalone — it does not need a Solver. Once
populated and validated, call to_solver() to obtain a Solver
ready to run, or write(path)() to dump an .inp file.
Key methods#
Adding objects#
Method |
Returns |
|---|---|
|
Append a node with |
|
Append a link with |
|
Append a subcatchment. |
|
Append a rain gage. |
|
Pop the most recently added node / link (by id sanity check). |
Setting node properties#
Method |
Action |
|---|---|
|
Invert elevation. |
|
Maximum depth above invert. |
Setting link properties#
Method |
Action |
|---|---|
|
Connect link |
|
Conduit length. |
|
Manning’s n. |
|
Cross-section: |
Finalising#
Method |
Action |
|---|---|
|
Check the model for connectivity / consistency. Raises on issues. |
|
Lock the model — no more topology changes after this. |
|
Write the assembled model to a SWMM |
|
Opaque handle (mostly for plugin authors). |
|
Convert the builder into a runnable |
End-to-end example#
from openswmm.engine import (
ModelBuilder, NodeType, LinkType, XSectShape,
)
m = ModelBuilder()
# nodes
j1 = m.add_node("J1", NodeType.JUNCTION)
out1 = m.add_node("OUT1", NodeType.OUTFALL)
m.set_node_invert(j1, 100.0)
m.set_node_invert(out1, 99.0)
m.set_node_max_depth(j1, 5.0)
# link
c1 = m.add_link("C1", LinkType.CONDUIT)
m.set_link_nodes(c1, j1, out1)
m.set_link_length(c1, 300.0)
m.set_link_roughness(c1, 0.013)
m.set_link_xsect(c1, XSectShape.CIRCULAR, 1.0, 0.0, 0.0, 0.0)
# validate and run
m.validate()
m.finalize()
m.write("synthetic.inp") # optional: dump for inspection
solver = m.to_solver()
solver.start()
while solver.step():
pass
solver.end()
solver.report()
solver.close()
solver.destroy()
The to_solver() Solver behaves identically to one constructed
from a parsed .inp — every domain class, forcing, control rule,
and output reader works.
Common recipes#
Build a simple grid topology#
m = ModelBuilder()
# 4×4 grid of junctions, single outfall in the corner
grid = {}
for r in range(4):
for c in range(4):
nid = f"J_{r}_{c}"
grid[(r, c)] = m.add_node(nid, NodeType.JUNCTION)
m.set_node_invert(grid[(r, c)], 100.0 - 0.5 * (r + c))
m.set_node_max_depth(grid[(r, c)], 5.0)
out = m.add_node("OUT", NodeType.OUTFALL)
m.set_node_invert(out, 90.0)
# Conduits along every row and column
cid = 0
for r in range(4):
for c in range(3):
link = m.add_link(f"R_{r}_{c}", LinkType.CONDUIT)
m.set_link_nodes(link, grid[(r, c)], grid[(r, c + 1)])
m.set_link_length(link, 100.0)
m.set_link_roughness(link, 0.013)
m.set_link_xsect(link, XSectShape.CIRCULAR, 0.6, 0, 0, 0)
cid += 1
# Bottom-right corner connects to the outfall
final = m.add_link("FINAL", LinkType.CONDUIT)
m.set_link_nodes(final, grid[(3, 3)], out)
m.set_link_length(final, 50.0)
m.set_link_roughness(final, 0.013)
m.set_link_xsect(final, XSectShape.CIRCULAR, 1.5, 0, 0, 0)
m.validate()
m.finalize()
Pop a misconfigured node#
If you’ve appended an object and immediately realise it’s wrong, pop it before it’s referenced anywhere:
bad = m.add_node("typo_id", NodeType.JUNCTION)
m.pop_last_node("typo_id") # rolls back the last add
Build, dump, then handoff to a colleague#
m = ModelBuilder()
# ... build ...
m.validate()
m.finalize()
m.write("scenario_a.inp")
# Send scenario_a.inp anywhere — runs in any SWMM-compatible engine.
EngineState requirements & exceptions#
Method group |
Required state |
Notes |
|---|---|---|
|
pre- |
Order matters: links reference nodes by index. |
|
pre- |
After |
|
any |
Raises |
|
after |
n/a |
See also#
Model editing (deletion + type conversion) — modify an existing parsed model in place.
Running a simulation — Solver — run the model returned by
to_solver().Nodes, Links, Subcatchments — query / mutate state once the Solver is open.