Skip to content

Checkpoint restart and warm-start chains

Two different things share the word "checkpoint"

This project uses checkpoint restart for two purposes that look identical at the file level (a checkpoint.dump, restored via argv[2]) but mean something different physically:

Segment — restarting the same condition purely because SLURM's walltime cut off a single job before the simulation finished. Nothing about the physics changes; the run is mathematically meant to continue exactly as if it had never been interrupted. omega_b_prev is never set. This is what Your first sweep and How to sweep one parameter use.

Warm-start — seeding a different condition (a different omega_b, theta_max, etc.) from another condition's already-developed flow field, to skip that condition's own cold-start transient. omega_b_prev (and the other *_prev fields) are set to whatever condition actually produced the checkpoint, and a smooth-step ramp carries the forcing from the old condition to the new one.

Conflating these two — assuming a same-condition segment restart is "basically free" the way it is for pure wall-clock continuation — is exactly where this gets subtle, below.

How the restart ramp actually works

BioReactor.c's restart path:

if (params.t_checkpoint > 0.0) {
    restart_file = argv[2];
    // Smooth-step interpolation starts AT the checkpoint and runs
    // N_RAMP_CYCLES forward. alpha goes 0→1 over
    // [t_checkpoint, t_checkpoint + N_RAMP_CYCLES*T_per_st].
    t_ramp_start = params.t_checkpoint;
    ...
}

This branch fires on any restart where t_checkpoint > 0 — it has no condition checking whether omega_b (or anything else) actually changed from the segment that wrote the checkpoint. A 3-period forcing ramp is re-triggered every single time a segment restarts, whether it's a genuine warm-start into a new condition or just a same-condition continuation split across two SLURM jobs for wall-time reasons alone.

For warm-starts, that's exactly the intended behavior — you want the forcing to ramp smoothly from the old condition to the new one. For a same-condition segment restart, it's an open question whether this introduces a spurious transient that a single, uninterrupted run at the same total duration would never see.

Why this matters for postprocessing

postprocess.py's quasi-steady-state (QSS) window is t > t_ramp, where t_ramp is computed once from the very first ramp (3 × T_per_nd, measured from t=0). It has no knowledge of later ramps that occur at each subsequent segment boundary in a multi-segment chain — those all fall well inside what the QSS window considers "already settled," so if a restart-ramp transient exists, nothing currently excludes it from KPIs like tau_100_max that are explicitly a max over the QSS window (and therefore maximally sensitive to a brief spike, however small).

tau_98 across a real 2-segment smoke-test chain: no visible discontinuity at the t≈13.2 restart boundary.

At fidelity 3 over a few rocking cycles, the same-condition restart in Your first sweep shows no visible discontinuity — which is what "checkpointing is basically free" would predict. Whether that holds at production fidelity, over the many-period durations tau_100_max is actually computed over, is exactly what the isolating experiment below is checking — a clean restart at fidelity 3 over one period doesn't rule out a small ramp transient getting captured by a max statistic over a much longer QSS window.

Testing the hypothesis

The isolating experiment: run one condition at a fidelity that already has a verified-clean single-shot baseline (fidelity 9), deliberately split into the same same-condition segment structure a real multi-segment chain uses, and compare against that baseline.

The first attempt at this used a corrupted baseline — its result doesn't count

17.5 RPM was the original choice, compared against L9 run 44133566 as the "trusted single-shot baseline." That baseline wasn't trusted enough — 44133566 turned out to have been overwritten by an abandoned cross-condition warm-start pilot (t_checkpoint=18.85, omega_b_prev=2.356194, seeded from 22.5 RPM), not a genuine cold start. The −2.4%/−17.1% numbers from that comparison are retracted, not reported here. See Validating against Kim et al. (2024) for the full story of how this was found. Every condition used anywhere else on this site was individually re-verified (t_checkpoint=None, raw data starting at t=0) specifically because this happened — reusing an existing "baseline" without checking its actual params.json first is exactly the mistake to avoid.

Resolved: not a real effect

Redone on 30.0 RPM, whose L9 baseline (488db14b) was verified clean before use this time: t_checkpoint=None, omega_b_prev=None, shear_stress.dat starting at t=0.

Metric Clean single-shot baseline Same-fidelity, 3-segment chain Difference
tau_100_max 0.13659 0.13245 −3.0%
tau_mean_max 0.0010078 0.0009818 −2.6%

Both small — neither remotely close to the −17.1% tau_mean_max gap the retracted (corrupted-baseline) 17.5 RPM experiment showed. That result was almost certainly an artifact of comparing against a cross-condition warm-started run, not evidence of genuine restart contamination. On a valid comparison, same-condition checkpoint restart segmenting does not meaningfully perturb either metric — both differences here are consistent with ordinary run-to-run/restart numerics, not a systematic effect.

That rules checkpointing out as the explanation for the L9-vs-L10 tau_100_max sign flip (see Validating against Kim et al. (2024)) more firmly than before. experiments/l9_l10_checkpoint_isolation_test_30rpm/ has the full manifest and raw results.

A follow-up, cheaper experiment (experiments/l9_l10_short_window_test_30rpm/) then tested the mesh-fidelity change directly: two cold-start runs at the same condition, one at fidelity 9 and one at fidelity 10, each truncated to a single short window just past the ramp (t=[6.0, 8.5], no chaining, no checkpoint restart of any kind). tau_100_max more than doubled (+112%) between fidelity 9 and fidelity 10 in that short window alone. That is real, direct evidence — not an untested guess — that the mesh-fidelity change itself is a fast, resolution-intrinsic driver of the sign flip; see Validating against Kim et al. (2024) for the full numbers.

n_mix_cycles vs n_transition_cycles

Related but separate: fresh runs (segment 0) use n_mix_cycles (typically 80) rocking cycles before oxygen injection, to let the flow field develop from rest. Restart segments use the much shorter n_transition_cycles (typically 10) instead, because the flow is already developed — that assumption is exactly what makes chained sweeps 70–90% cheaper than running every condition cold. It's also exactly the assumption that same-condition restart-ramp contamination would undermine if it turns out to be real.