Output reader (binary .out file)#
Note
Engine: OpenSWMM 6 — refactored.
The OutputReader reads a SWMM binary .out file
independently of any running engine. It needs only the file path
and supports the context-manager protocol.
Reference: openswmm_output.h.
Quickstart#
from pathlib import Path
from openswmm.engine import OutputReader, OutNodeVar, OutLinkVar
with OutputReader(Path("model.out")) as out:
# Metadata is typed.
print(out.start_datetime) # datetime
print(out.report_step) # timedelta
print(out.period_count)
print(out.flow_units) # FlowUnits enum
# Enum-typed variable selection; int|str object selector.
depths = out.node_series("J1", OutNodeVar.DEPTH)
flows = out.link_series(0, OutLinkVar.FLOW)
# Time axis as datetime64[s] — plug straight into matplotlib.
ax.plot(out.period_times, depths)
Lifecycle#
out = OutputReader("model.out")
# ... use ...
out.close()
# Or, more typically:
with OutputReader("model.out") as out:
...
The reader does not require an active Solver. It can be
opened on any historic .out file.
Path argument accepts str, pathlib.Path, or any
os.PathLike. Open failures raise FileError (which is
also an IOError).
Metadata properties#
Property |
Type |
Meaning |
|---|---|---|
|
|
SWMM version that produced the file. |
|
|
Enum, not bare int. |
|
Simulation start moment. |
|
|
Reporting interval. |
|
|
|
Number of reporting periods written. |
|
|
One moment per period. Cached after first access. |
|
|
|
|
|
|
|
|
Ordered by integer index. |
|
|
SWMM error code in the footer; |
Reading results#
Per-period (all objects at one period)#
depths_at_t5 = out.node_result(5, OutNodeVar.DEPTH) # → np.ndarray
flows_at_t5 = out.link_result(5, OutLinkVar.FLOW)
runoff_at_t5 = out.subcatchment_result(5, OutSubcatchVar.RUNOFF)
sys_runoff = out.system_result(5, OutSystemVar.RUNOFF) # → float
The var argument is the enum, not a bare integer. IntEnum
implements __int__, so passing an int still works but loses the
type-check benefit.
Time series (one object across periods)#
# By string id:
depths = out.node_series("J1", OutNodeVar.DEPTH)
# By integer index:
depths = out.node_series(0, OutNodeVar.DEPTH)
# Slice by period range (defaults to the full run):
snip = out.node_series(0, OutNodeVar.DEPTH, start=10, end=20)
Same shape for link_series, subcatchment_series, and
system_series (which takes no object selector — it’s the
system-wide series).
Unknown ids raise KeyError; out-of-range periods raise
IndexError.
All-attribute dict#
When you want every attribute for one object at one period, the
*_attributes methods return a dictionary keyed by the appropriate
enum:
attrs = out.node_attributes("J1", period=10)
# Base attributes are keyed by OutNodeVar; pollutant slots by int.
print(attrs[OutNodeVar.DEPTH], attrs[OutNodeVar.HEAD])
Pollutant concentration slots live past OutNodeVar.POLLUT_BASE and
are keyed by their integer index (because the enum doesn’t enumerate
individual pollutants).
Node summary statistics#
The C API aggregates flooding/overflow stats per node from the .out file (independently of the engine-side stats):
stats = out.node_stats("J1")
print(stats.max_depth)
print(stats.max_overflow)
print(stats.vol_flooded) # ft³ (US) / m³ (SI)
print(stats.time_flooded) # seconds
These values are aggregated over report-step samples, so for runs
with a finer routing step the engine-side Node.stats is more
precise.
Plotting recipe#
period_times is a datetime64[s] numpy array that matplotlib
understands without further conversion:
import matplotlib.pyplot as plt
from openswmm.engine import OutputReader, OutNodeVar
with OutputReader("model.out") as out:
depths = out.node_series("J1", OutNodeVar.DEPTH)
fig, ax = plt.subplots()
ax.plot(out.period_times, depths)
ax.set_ylabel("Depth")
ax.set_xlabel("Time")
fig.autofmt_xdate()
plt.show()
See also#
Running a simulation — Solver —
Solver.outis the runtime equivalent for the same data while the engine is still open.Nodes, Links — engine-side wrappers cover the same variables for the active run.
SWMM DateTime and Python datetime interop — SWMM DateTime ↔ Python
datetimerules.