Skip to content

Lifecycle and ownership

A Simulation owns one isolated native SWMM project. Opening it, advancing it, and closing it are separate jobs. Its state tells you which job is available now, and which values you can read.

OPEN -- start() or first iterator advance --> RUNNING
RUNNING -- manual or iterator advance reaches model end --> COMPLETE
COMPLETE -- end() -------------------------------------------> ENDED
ENDED -- reset or non-metadata stable/persistent write ------> OPEN
OPEN or ENDED -- start() -------------------------------------> RUNNING
OPEN -- retryable State Load startup failure ----------------> OPEN
Ordinary start/routing/finalization failure -----------------> FAILED
Any closeable state -- close() -------------------------------> CLOSED
CLOSED -- open() ---------------------------------------------> OPEN

Input validation and invalid lifecycle requests reject the operation without poisoning a healthy Project Generation. A poisoning operational SolverError can move the owner to FAILED; preserve that original error and close the failed generation.

States

State Meaning Normal next operations
OPEN Input is parsed; static configuration is mutable. Configure, start, iterate, execute, or close.
RUNNING Routing is active. Advance, inspect, control, end, or close.
COMPLETE Manual or iterator advancement reached the end; finalization has not run. Copy final statistics, then end.
ENDED Finalization is complete and the project remains open. Report, restart, reconfigure, or close.
FAILED A poisoning start, routing, finalization, report, or flush failure established an unusable generation. Record the original error and close.
CLOSED Native state and files are released. Open a fresh project generation.

One advancement owner

Iteration owns advancement from its first next() until exhaustion or termination. Do not call start(), step(), stride(), or execute() during that interval.

If a loop may stop early, use step() or stride(). A break leaves that iterator as the advancement owner; call terminate(), then advance the same iterator once to observe StopIteration before switching workflows.

When step(), stride(), or the iterator reaches the model end, the model is COMPLETE. This is your chance to copy final statistics before end() makes statistics acquisition unavailable. Repeated next() stays exhausted until an existing lifecycle reset. Asking again does not create more simulation.

simulation.status acquires state, timing, progress, counters, and warnings together. Prefer it when values must describe the same solver instant.

Project generations

Calling open() after close() creates a new project generation. Collections and live object views remember which generation they belong to. Even if the new model uses the same node names, the old views do not transfer:

node = simulation.nodes["J1"]
simulation.close()
simulation.open("next.inp", "next.rpt")

# Reacquire from the new generation; `node` now raises StaleViewError.
node = simulation.nodes["J1"]

Snapshots and statistics already acquired are owned native value objects; numbers, tuples, and dictionaries already copied into Python are host-owned values. All remain usable after closing, while collections and Live Views do not.

Checkpoint boundaries

After a manual step() or stride(), a running or complete owner is at a quiescent checkpoint boundary. save_checkpoint() publishes its complete runtime/resource continuation state. Simulation.resume() and fork() return independent owners with fresh report and binary-output paths; load_checkpoint_state() performs Enhanced Hotstart into a compatible OPEN receiver. It imports classified warm physical/numerical state while keeping the receiver's dates, forcing, inputs, declarations, fresh accounting, and output history. It does not continue source RDII/input progress or promise the same next step as Checkpoint Resume. A preparation-dependent edit made after State Load is accepted but makes the pending warm state stale; repeat load_checkpoint_state() before calling start().

Iteration continues to own advancement, but save_checkpoint() and fork() are permitted between advances. The owner lock serializes either operation after an active next() finishes, so each captures a quiescent boundary. State Load and lifecycle-changing operations remain invalid while iteration owns advancement.

Durable checkpoints restore editable declarations, so a resumed owner can be ended, edited, prepared, and rerun. See Simulation checkpoints and forks.

Context managers

Use a context manager for interactive runs. It closes the project and ends an active run if the body raises, but it does not generate the detailed report:

with Simulation("model.inp", "model.rpt", "model.out") as simulation:
    for _ in simulation:
        pass
    simulation.end()
    simulation.report()

The native owner performs lifecycle guards, cadence and exhaustion tracking, path/checkpoint metadata retention, collision checks, and cleanup sequencing. Python retains Path, datetime, and timedelta presentation plus context-body exception precedence: a cleanup failure is raised when there is no body error, or attached as a note without replacing the body error.

See Choose a run workflow for the shortest lifecycle for each use case and Errors and recovery for state effects.