Nodes#
Note
Engine: OpenSWMM 6 — refactored. This page documents the
openswmm.engine.Nodes class. Legacy SWMM 5 users access
nodes through the enum-driven getValue /
setValue API on openswmm.legacy.engine.Solver — see
Legacy SWMM 5 Solver.
Junctions, outfalls, storage units, and dividers — every point where
flow enters, leaves, or is stored in the network. The Nodes
class is the single entry point for reading and writing node state at
configure time AND during a running simulation.
Reference: openswmm_nodes.h.
Class signature#
class Nodes:
def __init__(self, solver: Solver) -> None: ...
solver— an activeSolver. The Nodes object holds a reference; its lifetime is tied to the solver’s.
A single Nodes instance covers every node in the model.
There is no per-node Python wrapper — work via integer indices or
string ids.
Key methods#
Identity & enumeration#
Method |
Returns |
|---|---|
|
Number of nodes in the model ( |
|
Integer index for a string id. |
|
String id for an integer index. |
|
|
Geometry & basic properties#
Method |
Returns |
|---|---|
|
Invert (bottom) elevation, model length units. |
|
Maximum depth above invert. |
|
Initial depth at simulation start. |
|
Surcharge depth above |
|
Surface ponded area when surcharged. |
|
Top-of-pipe elevation at the node. |
|
Volume at |
Each get_* has a matching set_* that requires the solver to be in
OPENED or later.
Hydraulic state (during the run)#
Method |
Returns |
|---|---|
|
Current water depth above invert. |
|
Total hydraulic head (invert + depth). |
|
Current volume in the node. |
|
Total inflow at this step. |
|
Total outflow at this step. |
|
External (lateral) inflow. |
|
Overflow (flooding) flow. |
|
Evaporation + seepage losses. |
|
Number of links connected. |
Forcing — overrides during a run#
Method |
Action |
|---|---|
|
Force the depth this step. |
|
Inject a lateral inflow this step. |
|
Force a head boundary (outfalls). |
These are one-shot — overwritten by the engine on the next step.
For sticky overrides that survive every step until cleared, use the
Forcing API (Advanced forcing).
Water quality#
Method |
Action |
|---|---|
|
Concentration of pollutant |
|
Inject a mass flux for pollutant |
Storage-node configuration#
Method |
Action |
|---|---|
|
Storage curve (depth → area) by index. |
|
|
|
Constant seepage out the bottom. |
|
Green-Ampt exfiltration parameters. |
Outfall-node configuration#
Method |
Action |
|---|---|
|
Free / fixed / tidal / time-series / normal. |
|
Constant stage (for |
|
Tidal curve index. |
|
Time-series index. |
|
Generic outfall parameter (varies by type). |
|
Flap-gate present? |
End-to-end example#
from openswmm.engine import Solver, Nodes, NodeType
with Solver("site_drainage.inp", "site_drainage.rpt", "site_drainage.out") as s:
nodes = Nodes(s)
print(f"Model has {nodes.count()} nodes")
# enumerate junctions vs. outfalls
for i in range(nodes.count()):
kind = NodeType(nodes.get_type(i)).name
print(f" {i:3d} {nodes.get_id(i):<12} {kind}")
# pick one and watch it
j1 = nodes.get_index("J1")
peak = 0.0
while s.step():
d = nodes.get_depth(j1)
if d > peak:
peak = d
t_peak = s.elapsed
print(f"J1 peak depth = {peak:.3f} at t={t_peak*24:.2f} h")
Common recipes#
Inject a constant lateral inflow into a junction#
One-shot per step (overwritten at the start of every step):
j1 = nodes.get_index("J1")
while s.step():
nodes.set_lateral_inflow(j1, 1.5) # cfs (or m³/s, per FLOW_UNITS)
Sticky (preserved by the engine across steps):
from openswmm.engine import Forcing, ForcingMode
forcing = Forcing(s)
j1 = nodes.get_index("J1")
forcing.node_lat_inflow(j1, 1.5, ForcingMode.REPLACE, persist=True)
while s.step():
pass
forcing.clear_all()
Pulse: ramp inflow up between hours 1-3#
j1 = nodes.get_index("J1")
while s.step():
h = s.elapsed * 24.0
if 1.0 <= h <= 3.0:
ramp = 1.0 - abs((h - 2.0) / 1.0) # triangular peak at t=2 h
nodes.set_lateral_inflow(j1, 5.0 * ramp)
else:
nodes.set_lateral_inflow(j1, 0.0)
Tide-driven outfall#
out1 = nodes.get_index("OUT1")
nodes.set_outfall_type(out1, OutfallType.FIXED)
while s.step():
h = s.elapsed * 24.0
# 12.42-hour M2 tide, mean stage 0, amplitude 1.5
stage = 1.5 * math.sin(2 * math.pi * h / 12.42)
nodes.set_head_boundary(out1, stage)
(For a real model, prefer set_outfall_tidal() with a tidal curve
loaded from the .inp.)
Walk every junction’s full state in one pass#
for i in range(nodes.count()):
if nodes.get_type(i) == NodeType.JUNCTION:
print(
f"{nodes.get_id(i):<12} "
f"invert={nodes.get_invert_elev(i):7.2f} "
f"max_d ={nodes.get_max_depth(i):5.2f} "
f"depth ={nodes.get_depth(i):5.2f}"
)
Convert a junction to a storage node mid-edit#
See Model editing (deletion + type conversion) — type conversion is owned by ModelEditor,
not Nodes.
Bulk arrays#
The bulk methods exist for the read-heavy quantities you most often
want vectorised. Each returns or accepts a contiguous
np.ndarray[float64] of shape (n_nodes,).
Method |
Returns / accepts |
|---|---|
|
All node depths. |
|
All node heads. |
|
Force depths for every node from |
|
Total inflow per node. |
|
Overflow (flooding) per node. |
|
Lateral inflow per node. |
|
Concentration of pollutant |
Memory-aliasing rule: the array returned by a get_*_bulk method
shares memory with an internal scratch buffer that the engine reuses
on the next call. Read-once-and-discard usage is safe; if you
keep the array (e.g. across a step), call .copy():
import numpy as np
history = []
while s.step():
history.append(nodes.get_depths_bulk().copy()) # detach from scratch
H = np.stack(history) # shape (T, n_nodes)
EngineState requirements & exceptions#
Method |
Required state |
Notes |
|---|---|---|
|
|
Identity is fixed once the model is parsed. |
|
|
Geometry; not state. |
|
|
Editing during a run is rarely meaningful; some setters refuse. |
|
|
Hydraulic state requires at least one step. |
|
|
One-shot per step. |
|
|
Requires a pollutant to be defined. |
|
same as scalar |
Same state rules as the per-element form. |
Common EngineError codes:
INVALID_INDEX— integer index out of range.INVALID_TYPE— calling a storage / outfall accessor on a node of the wrong type (e.g.set_outfall_stageon a junction).NOT_FOUND— string id not in the model.
Use Error handling, edge cases & debugging patterns for robust scripts.
See also#
Links — analogous class for conduits / pumps / orifices / weirs / outlets.
Advanced forcing — sticky cross-step forcings.
Control rules — programmatic / rule-based actions.
Model editing (deletion + type conversion) — adding, deleting, converting nodes.
Output reader (binary .out file) — read node time-series from a finished
.outfile.