Plotting#
Note
Engine: OpenSWMM 6 — refactored.
The bindings hand back numpy arrays with native dtypes, so plotting is mostly a one-liner. This page collects the recipes that matter most.
Time-series from the output file#
OutputReader.period_times is a datetime64[s] numpy array
and OutputReader.node_series() returns float32 values — both
plug directly into matplotlib:
import matplotlib.pyplot as plt
from openswmm.engine import OutputReader, OutNodeVar
with OutputReader("model.out") as out:
depth = out.node_series("J1", OutNodeVar.DEPTH)
head = out.node_series("J1", OutNodeVar.HEAD)
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(out.period_times, depth, label="depth")
ax.plot(out.period_times, head, label="head")
ax.set_ylabel("ft")
ax.legend()
fig.autofmt_xdate()
For multiple nodes on one plot:
fig, ax = plt.subplots()
for nid in ("J1", "J2", "OUT1"):
ax.plot(out.period_times,
out.node_series(nid, OutNodeVar.DEPTH),
label=nid)
ax.legend()
Bulk snapshots during a run#
When the engine is still open, each solver.<domain>.<bulk_prop>
returns a fresh numpy array. Accumulate snapshots inside the step loop
and stack them at the end:
import numpy as np
from openswmm.engine import Solver
history = []
with Solver("model.inp", "model.rpt") as s:
for elapsed in s.steps():
history.append(s.nodes.depths.copy()) # .copy() — see below
depths = np.stack(history) # shape (T, n_nodes)
Why .copy()? The array returned by solver.nodes.depths shares
the internal scratch buffer; the engine overwrites it on the next
call. For one-off reads inside a loop iteration you can skip the copy;
for accumulation across steps you cannot.
Spatial plots#
Solver.spatial exposes node coordinates and link geometry as
numpy arrays. For a quick scatter:
import matplotlib.pyplot as plt
coords = s.spatial.node_coords() # shape (n_nodes, 2)
plt.scatter(coords[:, 0], coords[:, 1])
For full network drawing, pair the node coordinates with the
link.from_node / link.to_node connectivity:
coords = s.spatial.node_coords()
fig, ax = plt.subplots()
for link in s.links:
a = coords[link.from_node.index]
b = coords[link.to_node.index]
ax.plot([a[0], b[0]], [a[1], b[1]], 'k-', lw=0.5)
ax.scatter(coords[:, 0], coords[:, 1], c="C0", s=8)
Aligning the engine-side bulk arrays with period_times#
The bulk arrays from Solver are sampled on the routing
step; OutputReader.period_times is on the report step.
They do not align one-to-one. If you need both axes on the same plot,
either:
Read the same variable from
OutputReader(report-step resolution, exact match withperiod_times), orTrack your own
current_datetimein the step loop and feed that as the time axis:
times, depths = [], []
with Solver("model.inp") as s:
for _ in s.steps():
times.append(s.current_datetime)
depths.append(s.nodes["J1"].depth)
ax.plot(times, depths)
See also#
Output reader (binary .out file) — every method that yields a plot-ready array.
SWMM DateTime and Python datetime interop — why
period_timesisdatetime64[s].Spatial (CRS, coordinates, geometry) — coordinates and link vertices.