Checkpoints are Fortran unformatted (stream) binary files that capture
everything needed to resume a run exactly. The on-disk format is the
unified format (currently version 6): the solution is gathered to rank 0
and written as a single file over the GLOBAL point count, so a checkpoint
is independent of the rank count it was written under. 1D's record list
is UNCHANGED since v5 (Q2d bumped only the shared header's version
number): a freshly WRITTEN 1D checkpoint differs from its v5 form only
in the stamped header version. That does NOT make existing v5 files
readable — read_ckpt_header enforces strict version equality, so v5
files remain rejected. Contents:
t_comp compensation scalar, the halo width h
(validated against the current scheme's halo width on read), and the
boundary halo/ghost cells left(neq,h)/right(neq,h) — see below.Why the halo/ghost cells: for "prescribed" BCs (dirichlet, inflow,
supersonic_inlet), apply_bcs seeds the boundary ghost cells once at
apply_initial_condition and never rewrites them afterwards (no_write
in boundary_conditions.f90). But those cells are NOT actually constant
across a run: the explicit steppers' whole-array stage arithmetic (see
time_integration.f90) spans the halo too, so a mathematically-null
update still perturbs the ghost value by ~1 ulp on the very first step,
and that perturbed value is a stable fixed point for the rest of the run.
Pre-v5, checkpoints never persisted this settled ghost, so a restart
reloaded the stale IC-time value instead, feeding a 1-ulp-different ghost
into the boundary reconstruction stencil on the first post-restart step —
a permanent, silent divergence from an uninterrupted run. v5 closes this
gap by gathering/scattering the two edge ranks' halo columns alongside
the interior array (see gather_edge_ghosts_to_root/
scatter_edge_ghosts_from_root in solution_gather.F90), and also
persists t_comp (previously silently reset to 0 on restart, a much
smaller — sub-ulp-in-t — but real source of restart non-reproducibility
on adaptive-dt runs). See doc/superpowers/2026-07-08-f2-restart-diagnosis.md for the full
root-cause analysis.
MPI contract:
- write_checkpoint gathers every rank's interior slab to rank 0 via
gather_solution_to_root and writes the single global file only on
my_rank() == 0. Write success is agreed across ranks with
agree_outcome (checkpoint_io's collective wrapper over par_lor).
- read_checkpoint opens the global file READ-ONLY on every rank
(concurrent reads do not race), reads the global arrays with iostat=
on every read, validates, then copies out this rank's slab using
decomp % i_first_global. Restart success is agreed with
conclude_read (one collective par_lor) so all ranks proceed or
fail together (no restart deadlock).
- All ranks must have consistent BDF2 allocation state (the same scheme
applied uniformly across ranks); write_checkpoint additionally enforces
this defensively via a collective OR (par_lor) before the collective
BDF2 gather, so the file flag and the gather stay consistent on all ranks.
- The v5 edge-ghost gather/scatter is a fixed collective position reached
by every rank unconditionally on both write and read (mirroring the
BDF2 gather's positioning), so it cannot desync ranks the way a
rank-divergent early return would.
Version compatibility: this is the unified v6 format (magic 42, version 6,
dim=1, extents=[n_pt_global]) — 1D's on-disk record list is unchanged from
v5 (see the module-level v5 (F2) notes above); Q2d only advanced the
shared header's version number, so a freshly WRITTEN 1D checkpoint differs
from its v5 form only in that stamped version. This does NOT make
existing v5 files readable: read_ckpt_header's version check is strict
equality, so v5 — like the legacy v2/v3/v4 formats before it — is
rejected cleanly with a clear error (owner-authorized break; no
user-generated checkpoints exist). Everything else — the global gather,
the BDF2 record, the pointer-file mechanics, and the MPI contract above —
is unchanged.
File naming: <base>_N.bin where N is the (width-agnostic) iteration
number. A companion text file latest_checkpoint (written by rank 0)
records the path of the most recent checkpoint so that restart drivers
need not track the iteration themselves.
Typical usage in the driver: @code use checkpoint, only: write_checkpoint, read_checkpoint ! --- writing --- call write_checkpoint(state, cfg%checkpoint_file, t, iter) ! --- reading --- call read_checkpoint(state, cfg%restart_file, t, iter) @endcode
Coordinate fingerprint of the global node grid: (sum x, sum x^2). Cheap, sensitive to the node distribution, and bit-reproducible because the grid is rebuilt deterministically from the same grid_file.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| real(kind=wp), | intent(in) | :: | x(:) |
Write a checkpoint file for the current solver state.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(solver_state_t), | intent(in) | :: | state |
Current solver state (must have ub allocated). |
||
| character(len=*), | intent(in) | :: | base |
Base name (e.g. 'checkpoint'); file = base_NNNNNN.bin. |
||
| real(kind=wp), | intent(in) | :: | t |
Current simulation time [s]. |
||
| integer, | intent(in) | :: | iter |
Current iteration number. |
||
| logical, | intent(out), | optional | :: | is_ok | ||
| character(len=*), | intent(out), | optional | :: | message | ||
| character(len=*), | intent(out), | optional | :: | written_file |
Path of the file actually written (e.g. |
|
| real(kind=wp), | intent(in), | optional | :: | t_comp |
Optional Kahan compensation term for |
Read a checkpoint file and restore solver state.
| Type | Intent | Optional | Attributes | Name | ||
|---|---|---|---|---|---|---|
| type(solver_state_t), | intent(inout) | :: | state |
Solver state with ub already allocated (from setup_solver). |
||
| character(len=*), | intent(in) | :: | fname |
Path to the checkpoint file (or 'latest_checkpoint'). |
||
| real(kind=wp), | intent(out) | :: | t |
Restored simulation time [s]. |
||
| integer, | intent(out) | :: | iter |
Restored iteration count. |
||
| logical, | intent(out), | optional | :: | is_ok | ||
| character(len=*), | intent(out), | optional | :: | message | ||
| real(kind=wp), | intent(out), | optional | :: | t_comp |
Optional: restored Kahan compensation term for |