Offline flow tracing#

FlowTracer estimates upstream or downstream flow paths and travel times from completed hydraulic output or supplied averages. It owns its topology, works independently of a live solver, and closes through a with block. All dimensional inputs and results use SI units, even when the source output uses US units.

Analytical example#

from openswmm.engine import (
    FlowTracer, TraceNode, TraceLink, TraceNodeAverage, TraceLinkAverage,
    TraceDirection, TraceTerminal, NodeType,
)

nodes = [TraceNode("S"), TraceNode("O", NodeType.OUTFALL)]
links = [TraceLink("SO", from_node=0, to_node=1, length_m=30)]
with FlowTracer(nodes, links) as trace:
    trace.set_averages(
        [TraceNodeAverage(lateral_in_m3s=1), TraceNodeAverage()],
        [TraceLinkAverage(net_flow_m3s=1, absolute_flow_m3s=1,
                          absolute_velocity_mps=1)],
    )
    downstream = trace.estimate("S")
    assert downstream.nodes[1].time_s == 30
    assert downstream.summary.terminal[TraceTerminal.OUTFALL] == 1
    upstream = trace.estimate("O", TraceDirection.UPSTREAM)

Results are immutable snapshots in topology order and remain valid after the handle closes. trace.node_ids, trace.link_ids, and trace.topology describe that order. trace.averages returns node/link snapshots that can be restored with set_averages(..., info=trace.info). Native numerical defaults are available through FlowTracer.default_options() and can be adjusted with dataclasses.replace before passing them as options=.

Completed output and caching#

For a real model, supply every node and link with matching IDs, node/link types, physical link lengths, endpoints, and appropriate TraceNodeFlags. The reader matches output records by ID, regardless of their row order.

with FlowTracer(nodes, links) as trace:
    trace.prepare("completed.out")
    result = trace.estimate("S")

An optional HDF5 cache requires a verified content fingerprint. For example, compute SHA-256 from the completed output bytes and pass its hexadecimal digest as fingerprint= alongside cache_path="completed.trace.h5". Recompute the digest whenever the output changes; a path or timestamp is not a content fingerprint. Caching reports TraceStatus.NO_HDF5 when the engine was built without HDF5; uncached tracing remains available.

Interpreting results and errors#

TraceResult contains nodes, links, and summary. Passage ratios may exceed one where flow recirculates. Times are estimates under frozen average flows, not particle arrival times. Unknown times remain NaN; time_coverage and TraceFlags distinguish partial timing, reversal, unreachable elements, and trapped circulation. Index summary.terminal with TraceTerminal for terminal accounting. terminal_kind is None for results without a terminal classification.

prepare and estimate accept progress(fraction, stage). Returning true cancels and raises TraceError with code == TraceStatus.CANCELLED. A callback exception is re-raised after the native operation stops. Long native operations release the GIL, but each handle permits only one operation at a time: concurrent or callback access to that handle raises RuntimeError. Use separate handles for independent analyses. Native tracing failures raise TraceError with a TraceStatus code; closed handles raise RuntimeError.