EPA hotstarts and chained runs
An EPA hotstart file captures selected SWMM dynamic state in the interoperable
.hsf format so a compatible model can start warm instead of cold. Use one for
warm-up followed by forecasting or state transfer to another SWMM-compatible
run.
An EPA hotstart is not a results file and does not preserve a Python
Simulation owner, report/output history, persistent forcing policy, or the
fuller swmmrs runtime/resource continuation boundary. Start a new compatible owner, configure the
hotstart with use_hotstart(), and let start() load it. For fuller swmmrs
continuation, use Simulation checkpoints and forks.
Its restoration matrix
compares EPA Hotstart, Enhanced Hotstart, and Checkpoint Resume subsystem by
subsystem. That guide also covers Enhanced Hotstart through
load_checkpoint_state(), which transfers more classified physical/numerical
state while retaining the receiver's dates, forcing, inputs, and fresh
accounting. EPA .hsf behavior is unchanged by that separate operation.
Warm up, save a hotstart, then continue
Write a hotstart while the producer is RUNNING or COMPLETE. The method
waits until the file is complete and usable before returning.
from datetime import timedelta
from pathlib import Path
from swmmrs import Simulation
hotstart = Path("warmup.hsf")
# Produce a warm-up hotstart. A scratch output is sufficient here.
with Simulation("model.inp", "warmup.rpt") as warmup:
warmup.start(save_results=False)
warmup.stride(timedelta(days=30))
warmup.save_hotstart(hotstart)
warmup.end()
# Continue from the hotstart in an independent model owner.
with Simulation("model.inp", "forecast.rpt", "forecast.out") as forecast:
forecast.use_hotstart(hotstart)
forecast.start(save_results=True)
while forecast.stride(timedelta(minutes=15)) is not None:
pass
forecast.end()
forecast.report()
stride() may return None if the producer reaches its configured end time;
that leaves it COMPLETE, where save_hotstart() remains valid. A hotstart is
most useful before the end time, when it represents a warm state that a later
run will extend.
Lifecycle rules
| Operation | Valid states | Effect |
|---|---|---|
use_hotstart(path) |
OPEN, ENDED |
Configures an EPA hotstart to load before every later start() in this Project Generation. A write from ENDED returns it to OPEN. |
use_hotstart(None) |
OPEN, ENDED |
Clears the configured hotstart; the next start is cold. |
save_hotstart(path) |
RUNNING, COMPLETE |
Writes current state in EPA hotstart format. |
start() |
OPEN, ENDED |
Loads the configured hotstart, if any, then initializes the run. |
close() or a fresh open(...) |
Any closeable state | Clears the generation's configured hotstart input. |
The input configuration persists across repeated starts of one project generation. Clear it deliberately when comparing a cold run with a continued run:
simulation.use_hotstart(hotstart)
simulation.start(save_results=False) # Starts from the EPA hotstart.
# ... run and end ...
simulation.use_hotstart(None)
simulation.start(save_results=False) # Starts cold.
Branch scenarios safely
Produce the hotstart once, then create one fresh Simulation per branch.
Each branch gets independent native state and artifacts while sharing the
read-only hotstart.
from pathlib import Path
from swmmrs import Simulation
def run_branch(name: str, hotstart: Path, target_setting: float) -> Path:
directory = Path("branches")
directory.mkdir(exist_ok=True)
report_path = directory / f"{name}.rpt"
output_path = directory / f"{name}.out"
with Simulation("model.inp", report_path, output_path) as simulation:
simulation.use_hotstart(hotstart)
simulation.start(save_results=True)
simulation.links["GATE"].target_setting = target_setting
while simulation.step() is not None:
pass
simulation.end()
simulation.report()
return output_path
A branch can run concurrently with other branches; give every owner unique
report and output paths. Do not share one Simulation or write the same
hotstart from multiple owners. The hotstart file is safe to reuse only once
its producer's save_hotstart() call has returned.
Compatibility and artifact ownership
Treat a hotstart and its input file as a matched pair. Malformed or truncated
files and detectable object-count or flow-unit mismatches are rejected when
start() loads them. Object identities, ordering, subtypes, and complete
configuration compatibility are not verified; a mismatched model can silently
receive state for the wrong object. The caller must preserve the matched input
and hotstart pair. A detected loading failure raises SolverError and moves the
generation to FAILED; record the error and close it.
use_hotstart() and save_hotstart() reject byte paths and paths that collide
with the owner's input, report, or output artifact. A hotstart output also
cannot be the same path as the owner's configured hotstart input. Use a
different file for a new hotstart, then configure that file for a later owner.
Avoid reusing a hotstart path that the input model itself schedules through
its [FILES] hotstart directives. Keep host-managed hotstarts in a dedicated
directory with an explicit scenario or timestamp in the filename.
Common patterns and gotchas
- Warm-up then forecast: finish a dry-weather or long-term stabilization period, save a hotstart, then start forecast owners from it.
- Scenario tree: create one baseline hotstart, branch with independent
forcings or
target_settingvalues, and compare host-collected results. - Resume after a planned boundary: save after a routing callback, close the producer, and hand the hotstart to a later process or scheduled job.
- Not crash recovery: a hotstart only exists after
save_hotstart()has completed. It does not preserve unsaved Python records, report output, or an in-progress call. - No hotstart after
end(): save before finalizing. If the run is complete, save inCOMPLETE, then callend(). - Hotstart does not replace output: use
save_results=Trueand an explicitoutput_pathwhen the continued run also needs a binary results artifact.
For the ordinary state machine and error model, see Run a model. For fuller runtime/resource continuation, see Simulation checkpoints and forks. For live forcing and branch-control patterns, see Runtime forcings.