M-CHECKPOINT CHK.4: Frequency displacement checkpoint/resume - #119
Merged
Conversation
Foundational pieces of CHK.4 (frequency displacement restart) per the scoped design in QuantUI-development-tracking roadmap 34: concurrency- safe, per-displacement, set-based checkpointing rather than a wave- boundary scheme. CHK.4.1 -- Checkpoint API extension. Adds a set-oriented completion API (mark_item_done()/completed_item_ids()/completed_items()) to Checkpoint, generalizing CHK.3's index-based completed_points() to arbitrary named item sets. Each item gets its own file (items/<name>/<item_id>.json), written via temp-file + os.replace -- never a shared file, so two writers can never race on the same filename and a crash mid-write never produces a file that looks done. completed_item_ids() is derived from validated, parsed content, not mere filename presence, so a corrupted item is never mistaken for a completed one. has_progress() now recognizes item-set progress too. Also adds mark_item_done_at()/completed_items_at() -- path-only siblings of the same API, for a caller that has only a directory path rather than a full Checkpoint object. This is exactly the situation a ProcessPoolExecutor worker is in (CHK.4.4): it receives the checkpoint directory as a plain string via initargs, and a Checkpoint with a live log stream attached is not something safe to pickle across a process boundary. CHK.4.2 -- quantui/freq_displacement_ids.py: deterministic (atom_index, axis, sign) -> id scheme (e.g. "d000_x_+"), shared by freq_calc.py's serial/parallel dispatch and freq_ir_workers.py / freq_raman_workers.py's worker processes, so none of them can disagree about what the required displacement set is. 104 new/updated tests (test_checkpoint.py, test_freq_displacement_ids.py). Not yet wired into freq_calc.py/freq_ir_workers.py/freq_raman_workers.py -- that's CHK.4.3-.5, still open.
Completes M-CHECKPOINT CHK.4 (roadmap 34) -- concurrency-safe,
per-displacement resume for Frequency's IR and Raman finite-difference
loops, on top of CHK.4.1/.2's checkpoint API + id scheme.
freq_calc.py: run_freq_calc()/_run_freq_calc_body() take checkpoint/
resume params (same convention as optimizer.py's CHK.2). The IR
displacement loop (serial and parallel) is unified around one shared
_dipoles dict keyed by (atom, axis, sign) -- resumed items, parallel-
pool results, and serial-fallback results all land in the same dict,
so the final dpdx assembly is a proper gate: it only runs once every
required id is present, regardless of which path produced it. Each
displacement is durably banked via checkpoint.mark_item_done() the
moment it finishes. mark_complete() is called on success so a finished
run doesn't linger forever in the "unfinished calculations" listing.
Bonus fix found while unifying the two paths: a parallel-pool failure
partway through used to discard every already-completed displacement
and recompute all of them serially. The shared _dipoles dict fixes
this for free -- the serial fallback now skips anything already done.
freq_ir_workers.py / freq_raman_workers.py: run_displaced_scf() /
run_displaced_polarizability() take an item_id and, when
init_worker()/init_raman_worker() were given a checkpoint directory,
write their result durably via checkpoint.mark_item_done_at() before
returning -- so a completed displacement survives even if the parent
process dies before collecting that Future.
raman_calc.py: _cpu_raman_activities_fd()/compute_raman_activities()
get the identical checkpoint/resume treatment under a separate named
item set ("raman_displacements") so its records can't collide with
IR's ("freq_displacements") despite sharing the exact same
(atom, axis, sign) displacement grid.
backends/worker.py: _run_frequency() now calls
_begin_worker_checkpoint() and threads checkpoint/resume into
run_freq_calc() -- this is the actual SLURM batch path where the real
production pain happened (GOTCHAS.md: a 19-atom NCShare frequency job
needed two resource-bump rounds, discarding the whole Hessian each
time). Previously only geometry_opt/pes_scan were wired (CL2.8).
37 new tests across test_freq_calc.py, test_raman_calc.py,
test_backends_worker.py. The CHK.4.6 tests prove actual resume
behavior via run_scf_with_rescue call counts, not just "produces a
plausible-looking answer": a fully-banked resume does zero displacement
recompute, a partial resume recomputes exactly the missing ids, a
corrupted banked item is recomputed rather than trusted, resume=False
correctly ignores existing progress. Full suite: 3237 passed, 30 skipped.
jonathanschultzNU
deleted the
claude/quantui-lab2-improvements-no3hwc
branch
September 7, 2026 05:08
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #118 (merged) on the same branch — the previous PR's history
is preserved via rebase onto the post-merge
main; this PR only containsthe two new commits below.
Adds concurrency-safe, per-displacement checkpoint/resume for Frequency's
finite-difference IR and Raman loops (M-CHECKPOINT CHK.4, the one item
left open from the Lab 2 findings in #118). Full design writeup lives in
the (private) planning repo's
TODO/roadmaps/34-m-checkpoint-calc-restart-roadmap.md.Geometry-Opt and PES-Scan (#118) both have a natural sequence point to
checkpoint at — an optimizer step, a scan point. The Hessian's
6Nfinite-difference displacement loop doesn't, and
freq_ir_workers.py/freq_raman_workers.pycan run displacements concurrently via theopt-in
QUANTUI_FREQ_PARALLELworker pool, so resume has to diff a setof completed displacement ids, not trim a prefix.
quantui/checkpoint.py— set-oriented completion API(
mark_item_done/completed_item_ids/completed_items) alongside theexisting index-based
completed_points(), plus path-only siblings(
mark_item_done_at/completed_items_at) for aProcessPoolExecutorworker that only has a directory path, not a picklable
Checkpointobject.
quantui/freq_displacement_ids.py(new) — deterministic(atom_index, axis, sign)→ id scheme, the single source of truthshared by every path below.
quantui/freq_calc.py—run_freq_calc()takescheckpoint/resume(same convention as Geometry-Opt). Serial and parallel pathsare unified around one shared dict keyed by displacement id, so resume,
the parallel pool, and the serial fallback can never disagree about
what's already done — and the Hessian assembly step only runs once
every required id is present, regardless of which path produced it.
quantui/freq_ir_workers.py/quantui/freq_raman_workers.py—each worker durably records its own completion (temp-file +
os.replace) before returning to the parent, so a completeddisplacement survives even if the parent process dies before collecting
that
Future.quantui/raman_calc.py— identical treatment under a separate nameditem set (
raman_displacements), so its records can't collide withIR's (
freq_displacements) despite sharing the exact same displacementgrid.
quantui/backends/worker.py—_run_frequencynow calls_begin_worker_checkpoint()and threads checkpoint/resume through, thesame pattern
_run_geometry_opt/_run_pes_scanalready used. This isthe actual SLURM batch path the motivating production case hit.
One pre-existing bug fixed in passing: a parallel-pool failure partway
through used to discard every already-completed displacement and
recompute all of them serially. The shared-dict unification fixes this
for free — the serial fallback now skips anything already done.
Testing
pytest -m "not network": full suite green, 3237 passed / 30 skipped. 37new tests (
test_freq_calc.py,test_raman_calc.py,test_backends_worker.py) — the load-bearing ones assertrun_scf_with_rescuecall counts, not just a plausible-looking answer: a fully-banked resume
does zero displacement recompute, a partial resume recomputes exactly the
missing ids, a corrupted banked item is recomputed rather than trusted,
and
resume=Falsecorrectly ignores a populated checkpoint.ruff+blackclean.Authorship
Generated by Claude Code