Error handling, edge cases & debugging#

Note

Engine: OpenSWMM 6 — refactored.

This page is a cross-cutting reference for the EngineError hierarchy, the EngineState rules that govern when each method is callable, and the idioms we recommend for robust scripts.

For the underlying lifecycle see Concepts & engine lifecycle.


The exception model in one paragraph#

Every binding checks the C return code. A non-zero code raises an EngineError — but which EngineError depends on the underlying SWMM_ERR_*. Each subclass also inherits from a standard library exception so existing except IndexError: / except ValueError: handlers do the right thing without any engine-specific imports.

from openswmm.engine import Solver

with Solver("model.inp") as s:
    try:
        depth = s.nodes["DOES_NOT_EXIST"].depth
    except KeyError as e:                    # also an EngineError subclass
        print(f"missing node: {e}")
    try:
        depth = s.nodes[10_000].depth
    except IndexError as e:                  # likewise
        print(f"out of range: {e}")

The EngineError hierarchy#

The raise_for_code() dispatcher maps every documented SWMM_ERR_* to the corresponding subclass:

Subclass

Also inherits from

Raised when …

BadHandleError

RuntimeError

Engine handle is NULL or invalid.

BadIndexError

IndexError

Integer index is out of range.

BadParamError

ValueError

Argument fails range / shape / domain check.

LifecycleError

RuntimeError

Method called in the wrong EngineState.

HotStartError

RuntimeError

Hot start file is missing, corrupt, or schema-mismatched.

PluginError

RuntimeError

A loaded plugin returned a failure or refused to initialise.

FileError

IOError

Any of input / report / output file I/O failures.

ParseError

ValueError

Input file has a syntactically invalid token or section.

NumericalError

RuntimeError

Solver divergence, NaN propagation, instability.

CRSError

ValueError

Coordinate reference system string is missing or invalid.

DependencyError

RuntimeError

Object can’t be deleted / converted because other objects still reference it.

EngineError

Exception

Base class. Catches everything above; also raised directly for ErrorCode.NOMEM and ErrorCode.INTERNAL.

Every instance carries three attributes:

  • .code — the raw integer (SWMM_ERR_* value).

  • .code_enum — the same value as an ErrorCode enum member, or ErrorCode.INTERNAL if the code is unrecognised.

  • .message — human-readable description filled from swmm_error_message() when not given explicitly.

from openswmm.engine import EngineError, ErrorCode

try:
    s.nodes[-1].depth = 0.0
except EngineError as e:
    print(e.code, e.code_enum, e.message)
    # 8 ErrorCode.BADINDEX 'index out of range'

EngineState reference#

The Solver moves through these states in strict order:

State

Value

Meaning

CREATED

1

Engine handle allocated; no input parsed.

OPENED

2

.inp parsed; objects accessible for inspection / editing.

INITIALIZED

3

Initial conditions applied; arrays allocated.

STARTED

4

start() returned; ready for the first step().

RUNNING

5

Inside the routing loop, step() / stride() callable.

ENDED

6

end() called; cumulative statistics & mass balance available.

CLOSED

7

close() called; files flushed.

BUILDING

8

Programmatic construction in progress (no .inp involved).

Calling a method in the wrong state raises LifecycleError (which is also a RuntimeError):

from openswmm.engine import Solver, LifecycleError

s = Solver("model.inp")
try:
    s.step()                                 # not started yet
except LifecycleError as e:
    print(f"can't step in state {s.state.name}: {e}")


Common pitfalls#

Mutating during iteration#

Adding or deleting objects through solver.nodes / solver.links invalidates any wrapper objects currently held by Python code via a generation counter — accessing a stale wrapper raises a LifecycleError with a message naming the operation that invalidated it. Re-look up by id after a structural change.

Reading state too early#

Node.depth / Link.flow / etc. only have meaningful values once the engine is at EngineState.RUNNING (or later). Accessing them before start() raises LifecycleError.

Confusing KeyError and BadIndexError#

  • coll["NO_SUCH_ID"]KeyError (str didn’t match any object).

  • coll[10_000]BadIndexError (also catchable as IndexError).

  • coll[None]TypeError.

This split lets you write try / except KeyError: for the “check-if-name-exists” idiom without accidentally swallowing out-of-range integer accesses.


See also#