Resume from a checkpoint¶
Every result file aeolus1d run --out result.h5 writes is also a checkpoint.
aeolus1d run case.toml --resume result.h5 reads it back, seeds a freshly
built network from it, sets the clock to the file's t_final, and continues
to the case's t_end. This chart follows that path: what the file holds,
which function reads what, what is restored, where a file or a case is
refused, and what is not compared (that part matters most). The contracts
of every function below are in their docstrings (the standard is
Function contracts and flow charts).
What resume does not do. It does not re-read the case from the file: the case TOML is loaded and built exactly as for a fresh run, and the checkpoint only overwrites state in the built network. So the case and the checkpoint are two independent inputs, and the code compares them only by array shape and by id (table below).
What the file holds¶
write_hdf5 writes the final state under pipes/<id> and
junctions/junction_NN, the loop state under controllers/<id>, and, when
the run was traced, time_series/. t_final is written last, so an
interrupted flush leaves the clock behind the state, never ahead of it; the
time series commits the same way, with t created last. A fresh write opens
the file "w", which truncates it first and is not staged in a .partial
file: a failure part-way leaves a truncated result, and when --out is the
file being resumed from, the only checkpoint is gone with it. The reader
therefore never sees a half-advanced clock, but it can see a file that is not
a checkpoint at all (no pipes, no t_final).
flowchart TD
R["run: state at t_final"] --> W{"write_hdf5"}
W --> P["pipes/id: U (N,3), Y_*, T_solid, surface/*, filter/*, wall/*, coolant/*; also rho u p T for plots"]
W --> J["junctions/junction_NN: p T m E_internal, m_fuel, x_burnt, burn_progress, m_species, actuators, omega, housing_T, kind, id, n_ports"]
W --> C["controllers/id: kind and checkpoint_state()"]
W --> TS["time_series/ when traced: t and per-pipe, per-junction series"]
P --> F["root attrs: case_name, schema_version, then t_final LAST"]
J --> F
C --> F
TS --> F
Reading the file¶
read_resume_state is the only reader. It opens the file, finite-checks and
size-caps every array it will seed from, and returns a ResumeState. Every
refusal is a ResumeError that names the file; the CLI catches that one type,
logs resume failed: ... and exits 1, before the network is built.
flowchart TD
A["--resume prior.h5"] --> O{"h5py can open it?"}
O -- "missing, truncated, not HDF5" --> X1["ResumeError: cannot be opened as an HDF5 file"]
O --> G{"has a pipes group and a finite t_final?"}
G -- "no pipes group" --> X2["ResumeError"]
G -- "t_final missing, NaN or Inf" --> X3["ResumeError"]
G --> V{"_read_schema_version"}
V -- "newer than this install, or below the minimum" --> X4["ResumeError"]
V -- "attribute missing" --> N1["read as v1, a note"]
V -- "older, with a registered step" --> N2["a note per upgrade step"]
V --> PP{"_read_pipe_checkpoint for each pipe"}
PP -- "no U dataset" --> X5["ResumeError"]
PP -- "a seeding array is NaN or Inf, or above 50 million elements" --> X6["ResumeError naming the dataset"]
PP --> JJ{"_read_junction_checkpoint for each junction group"}
JJ -- "p, T, m or E_internal missing, or not finite" --> X7["ResumeError naming the group"]
JJ --> CC{"_read_controller_checkpoints"}
CC -- "a value is not finite" --> X8["ResumeError naming the controller"]
CC --> TT{"time_series with a t axis?"}
TT -- "group without t: an interrupted first flush" --> N3["skipped, a note: the trace starts at the resume time"]
TT -- "no group" --> N4["empty series: the trace starts at the resume time"]
TT -- yes --> RS["_read_prior_series: size-capped, not finite-checked"]
RS --> OUT["ResumeState"]
N3 --> OUT
N4 --> OUT
Only the seeding state is finite-checked. The time series is plot data: a
sample that went non-finite must not block a resume, so it is read as it is.
The saved rho, u, p and T are never read back for seeding: U is the
state and the rest is derived, so a file from before 2026-09-28 (no T)
resumes exactly like a new one. In a trace, a prior run without T gives
NaN for those samples after the merge.
Seeding the built network¶
_build_eager_network builds the network from the case (initial conditions
applied), then calls the three seed functions in order: pipes, junctions,
controllers (after the junctions, because a turbine actuator is restored by
both, to the same value), and finally sets clock.t to t_final for a case
that has a clock (cylinders and transient boundary conditions need one: the
crank angle is omega t, so a resumed engine continues at the right angle).
A mismatch never raises here: it becomes a note, a UserWarning, or nothing.
flowchart TD
S["ResumeState + a network built from the case"] --> PS{"seed_pipes_from_prior, per live pipe"}
PS -- "id not in the file" --> K1["kept initial state, a note"]
PS -- "cell count changed" --> K2["whole pipe kept, a note"]
PS --> U["U copied bit for bit; ghost cells untouched"]
U --> SP{"species by name and shape"}
SP -- "missing or other shape" --> K3["that species kept, a note"]
U --> OPT{"T_solid, surface, DPF, wall, coolant: shape matches?"}
OPT -- "no, or absent" --> K4["kept, NO note except wall and coolant when seeded"]
S --> JS{"seed_junctions_from_prior, per live junction"}
JS --> M{"matched by id, else by list position"}
M -- "no checkpoint" --> K5["kept initial state, a note"]
M --> KD{"kind and n_ports equal the live component's?"}
KD -- "no" --> W1["UserWarning and a note; initial state kept"]
KD --> JV["set_state(p, T), then m and E_internal exactly, fuel, burnt fraction, species inventory REPLACED, actuators, shaft omega, housing T"]
S --> CS{"seed_controllers_from_prior, per live controller"}
CS -- "file has no controllers group" --> K6["loop re-primes from the case's initial actuator value, a note"]
CS -- "id not in the file" --> K7["starts fresh, a note"]
CS -- "checkpoint written by another class" --> W2["UserWarning and a note; initial state kept"]
CS --> CR["restore_state: restored and not-restored keys listed in a note"]
U --> CK["clock.t = t_final"]
JV --> CK
CR --> CK
CK --> RUN["dispatch_advance from t_start = t_final to t_end"]
What must match, and what happens when it does not¶
| Between checkpoint and case | Checked? | On a mismatch |
|---|---|---|
| file is an aeolus1d result | yes | ResumeError, exit 1 |
| schema version | yes | newer or too old: ResumeError; older: notes |
t_final is finite |
yes | ResumeError |
case_name |
no (read, only informational) | resumes silently into a different case |
t_final below t_end |
no | t_final >= t_end takes no step and rewrites the file at the same state |
| pipe ids | by id | missing from file: kept, note; extra in file: ignored, no note |
| pipe cell count | yes | pipe kept at its initial state, note |
pipe length, area, gamma, R_gas, EOS, species list, closures |
no | silent: U is conservative, so a changed gamma changes the resumed p and T (a case at gamma 1.3 resumed from a 1.4 file gives p = 75 kPa for a saved 100 kPa) |
| species by name and shape | yes | that species kept, note |
T_solid, surface, DPF state shapes |
yes | kept, no note |
| wall and coolant shapes | yes | kept; a note only when seeded |
junction identity (kind, n_ports, id) |
yes, when the file has them | warning and note, initial state kept; old files skip the check and match by position |
| junction count and order | by id, else position | missing: kept, note |
| controller id and class | yes | missing: fresh, note; other class: warning, note |
--out equals --resume |
no | allowed; the manifest records the digest of the file that was READ as resume_sha256 (taken in _read_resume, before anything is written; until 2026-10-05 it was taken after the write and so named the NEW file); a failed write loses the checkpoint, because write_hdf5 truncates its target first |
What is not restored¶
The combustion sub-state of the predictive diesel model (the per-cycle Wiebe
rebuild, the knock integral, the adaptive timing retard), the Helmholtz
per-port neck velocity (relaxes within milliseconds), and the turbo
surge-damping memory. A file with no controllers group (written before
2026-09-29) resumes with each loop re-primed.
Functions¶
src/aeolus1d/cli/commands/run.py::_cmd_runsrc/aeolus1d/cli/commands/run.py::_read_resumesrc/aeolus1d/cli/commands/run.py::_build_eager_networksrc/aeolus1d/cli/commands/run.py::_build_tracersrc/aeolus1d/cli/commands/run.py::_advance_eagersrc/aeolus1d/io/writer.py::write_hdf5src/aeolus1d/io/writer.py::_emit_pipe_groupsrc/aeolus1d/io/writer.py::_emit_junction_groupsrc/aeolus1d/io/writer.py::_emit_controllers_groupsrc/aeolus1d/io/writer.py::_component_kind_and_idsrc/aeolus1d/io/snapshot.py::pipe_state_dictsrc/aeolus1d/io/snapshot.py::junction_state_dictsrc/aeolus1d/io/resume.py::read_resume_statesrc/aeolus1d/io/resume.py::_read_schema_versionsrc/aeolus1d/io/resume.py::_read_pipe_checkpointsrc/aeolus1d/io/resume.py::_read_junction_checkpointsrc/aeolus1d/io/resume.py::_read_controller_checkpointssrc/aeolus1d/io/resume.py::_read_prior_seriessrc/aeolus1d/io/resume.py::_checked_readsrc/aeolus1d/io/resume.py::seed_pipes_from_priorsrc/aeolus1d/io/resume.py::seed_junctions_from_priorsrc/aeolus1d/io/resume.py::seed_controllers_from_priorsrc/aeolus1d/io/tracer.py::Tracer.preload_from_priorsrc/aeolus1d/io/tracer.py::Tracer._pad_to_live_keys