Legacy SWMM 5 Solver#

Note

Engine: SWMM 5.x — legacy. This page documents the openswmm.legacy.engine.Solver class. For the modern reentrant OpenSWMM 6 Solver see Running a simulation — Solver.

The legacy Solver is a thin Cython wrapper around the original EPA SWMM 5.x C solver. The class is preserved verbatim so that scripts written against the EPA SWMM 5.x C API or earlier openswmm releases continue to run unchanged.

If you are starting fresh, prefer openswmm.engine.Solver. If you have existing legacy code, this page documents what’s available and Migrating from SWMM 5 to v6 shows how to migrate.


Class signature#

from openswmm.legacy.engine import Solver

class Solver:
    def __init__(
        self,
        inp_file: str,
        rpt_file: str = "",
        out_file: str = "",
        swmm_progress_callback: Callable[[float], None] = None,
        stride_step: int = 0,
    ) -> None: ...
  • inp_file / rpt_file / out_file — same role as the v6 Solver: input, human-readable report, binary output.

  • swmm_progress_callback — called periodically with the simulation fraction (0.0 → 1.0). Frequency controlled via progress_callbacks_per_second.

  • stride_step — number of routing steps to advance per step() call (0 = single step).

The class supports the context-manager and iterator protocols.


Lifecycle#

The legacy lifecycle is simpler than v6 (no explicit create):

Method

Action

Solver.open()

Parse the .inp.

Solver.initialize()

Allocate state arrays.

Solver.start()

Start routing.

Solver.step(steps=1)()

Advance steps routing steps.

Solver.end()

Stop routing.

Solver.report()

Write the human-readable .rpt.

Solver.close()

Close output files.

Solver.finalize()

Free engine memory.

Solver.execute()

Convenience: open/initialize/start/step-to-end/end/report/close.

Solver.__enter__() / __exit__

Context-manager wrapper.

Solver.__iter__() / __next__

Iterator over (time, mass_bal_err) tuples.

State machine#

The legacy solver uses SolverState (different from v6’s EngineState):

from openswmm.legacy.engine import SolverState

if solver.solver_state == SolverState.RUNNING:
    ...

End-to-end example#

from openswmm.legacy.engine import Solver

# Context-manager style — easiest:
with Solver("model.inp", "model.rpt", "model.out") as s:
    for time, mb_err in s:               # iterator stops at end of sim
        if int(time * 24) % 1 == 0:      # every hour
            print(f"t={time*24:.2f} h  mb_err={mb_err:+.4f}%")

The iterator yields (elapsed_days, continuity_error_percent) tuples and stops naturally at the simulation end.

Manual lifecycle (when you need to inspect parsed objects between open and initialize):

from openswmm.legacy.engine import Solver, SWMMObjects

s = Solver("model.inp", "model.rpt", "model.out")
s.open()
print("nodes:", s.get_object_count(SWMMObjects.NODE))
print("links:", s.get_object_count(SWMMObjects.LINK))

s.initialize()
s.start()
while True:
    time, _ = s.step()
    if time >= s.end_datetime.timestamp():
        break
s.end()
s.report()
s.close()
s.finalize()        # free engine memory

Reading object state — getValue / setValue#

The legacy API is enum-driven: every property read or write goes through get_value() / set_value() keyed by an SWMMObjects (object kind) and an attribute enum specific to that kind.

from openswmm.legacy.engine import (
    Solver, SWMMObjects, SWMMNodeProperties, SWMMLinkProperties,
)

with Solver("model.inp", "model.rpt", "model.out") as s:
    # Resolve names to integer indices once
    j1 = s.get_object_index(SWMMObjects.NODE, "J1")
    c1 = s.get_object_index(SWMMObjects.LINK, "C1")

    for _ in s:
        d = s.get_value(SWMMObjects.NODE, SWMMNodeProperties.DEPTH, j1)
        q = s.get_value(SWMMObjects.LINK, SWMMLinkProperties.FLOW, c1)
        ...

The corresponding attribute enums are:

Attribute enum

Object kind

SWMMRainGageProperties

Rain gages

SWMMSubcatchmentProperties

Subcatchments

SWMMNodeProperties

Nodes

SWMMLinkProperties

Links

SWMMSystemProperties

System-wide values

For the value-oriented type tags:

Type enum

Used by

SWMMNodeTypes

Identify node kind from get_value(NODE, ..., NODE_TYPE).

SWMMLinkTypes

Identify link kind.

SWMMFlowUnits

Decode the FLOW_UNITS system value.


Common recipes#

Inject a lateral inflow at a node#

from openswmm.legacy.engine import (
    Solver, SWMMObjects, SWMMNodeProperties,
)

with Solver("model.inp", "model.rpt", "model.out") as s:
    j1 = s.get_object_index(SWMMObjects.NODE, "J1")
    for _ in s:
        s.set_value(
            SWMMObjects.NODE, SWMMNodeProperties.LATERAL_INFLOW,
            j1, 1.5,
        )

Override a rain-gage rainfall reading#

from openswmm.legacy.engine import (
    Solver, SWMMObjects, SWMMRainGageProperties,
)

with Solver("model.inp", "model.rpt", "model.out") as s:
    rg1 = s.get_object_index(SWMMObjects.RAIN_GAGE, "RG1")
    for time, _ in s:
        # custom rainfall hyetograph injected each step
        r = compute_rainfall(time)
        s.set_value(
            SWMMObjects.RAIN_GAGE, SWMMRainGageProperties.RAINFALL,
            rg1, r,
        )

Continuity / mass-balance error#

runoff_err, flow_err, qual_err = s.get_mass_balance_error()
print(f"runoff: {runoff_err:+.4f}%   routing: {flow_err:+.4f}%")

Hot start — save / load#

s.save_hotstart("scenario_a.hsf")
# ... later, in a new Solver:
s.use_hotstart("scenario_a.hsf")

Progress callback#

def on_progress(fraction):
    print(f"  {fraction*100:5.1f}%")

with Solver("model.inp", "model.rpt", "model.out",
            swmm_progress_callback=on_progress) as s:
    s.progress_callbacks_per_second = 2     # twice a second
    for _ in s:
        pass

Per-step callbacks (start, before-step, after-step, end)#

from openswmm.legacy.engine import CallbackType

def post_step(solver):
    print("step completed at t =", solver.current_datetime)

with Solver("model.inp", "model.rpt", "model.out") as s:
    s.add_callback(CallbackType.POST_STEP, post_step)
    for _ in s:
        pass


Exceptions#

The legacy solver raises SWMMSolverException on engine errors. The error code → message mapping is exposed through SWMMAPIErrors and the helper get_error_message().

from openswmm.legacy.engine import (
    Solver, SWMMSolverException, get_error_message,
)

try:
    with Solver("nonexistent.inp", "out.rpt", "out.out") as s:
        for _ in s:
            pass
except SWMMSolverException as e:
    print("legacy solver failed:", e)

See also#