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

version

int

SWMM version that produced the file.

flow_units

FlowUnits

Enum, not bare int.

start_datetime

datetime

Simulation start moment.

report_step

timedelta

Reporting interval.

period_count

int

Number of reporting periods written.

period_times

numpy.ndarray[datetime64[s]]

One moment per period. Cached after first access.

pollutant_count

int

node_count / link_count / subcatchment_count

int

node_ids / link_ids / subcatchment_ids

list[str]

Ordered by integer index.

error_code

int

SWMM error code in the footer; 0 is a clean run.


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#