Repository navigation
Keep the .ymf current during the run, not only after a clean close - #86
Merged
Merged
Conversation
The archive of record was assembled at close: the per-step grid metadata went into the HDF5 file as it was produced, and the document describing it was built from that only when close() ran. A run that was killed, hit a scheduler timeout or died on one rank therefore left an archive with the data in it and nothing describing the data -- recoverable, but only by a separate pass, which is what scripts/gatherArchives.py exists for. Master already holds this step's grids while allGatherIncremental runs, so the running document costs no extra read and no extra parse: appending is the same work the close-time assembly does once, done once per step. The file is written to <name>.ymf.writing and renamed, so a reader sees either the previous step's document or this one, never a half-written file. A failed write is logged and swallowed. The authoritative document is still written at close from the HDF5, and a full disk at step 300 should not destroy the 300 steps already in the archive. It is not free, and the cost is measured rather than asserted: a current document is a whole document, so writing is O(steps) each step -- 0.6 ms at 10 steps, 6.8 ms at 100, 35 ms at 500, which is 8.6 s over a 500-step run and minutes over a 5,000-step one. Rather than guess a cadence policy, the cost is made visible: it is accumulated and reported at close, and a single write over half a second says so once and names the switch. Set writeIncrementalYmf = False to restore writing only at close. Nine tests. The one that matters runs the steps and never calls close(), then reads the document; its control does the same with the feature off and asserts there is no document at all, so the first is not a tautology. The structural one compares the running document against the one written at close: the first is appended in memory from master's grids, the second is re-assembled from the YAML in the HDF5, and they have to agree -- if they ever diverge, the archive of record depends on whether the run finished, which is the property this removes. Both layouts, one to three ranks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review of the first version: rewriting the whole document to record a step
is O(steps) per step, so O(steps^2) over a run, and many proteus runs have
very large numbers of steps. That requirement is inherited from XML, where
a document has to be closed to be well formed. YAML has no such rule, so a
step can be appended and flushed.
Measured on a one-collection archive, per-step write and cumulative:
steps rewrite append
10 0.6 ms 0.10 ms
100 6.8 ms 0.11 ms
500 35.0 ms 0.09 ms
5000 ~350 ms 0.11 ms
5000 steps: ~15 min (extrapolated) vs 0.49 s
Flat per step, as it should be: the write no longer depends on what the
archive already holds.
This needs ymf's append_step and its stream-aware read_ymf (cekees/ymf#18),
which must merge first -- proteus CI installs ymf from @main. There the
document grows as a stream: a header, then one document per step naming its
collection. Naming rather than indexing matters because proteus creates
collections lazily, one per archived FE space, so a model that first
archives at step 40 adds one then.
close() still writes the canonical single document from the HDF5, so a run
that finishes leaves exactly what it left before; only an interrupted run
is in the live shape, and read_ymf takes both.
The cost accounting and the slow-write warning the first version added are
gone with the cost they measured.
Two tests pin the mechanism rather than the outcome: that the mid-run file
really is a stream, one record per step with each one completed, and that
close collapses it so nothing downstream needs to know the live shape
exists.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The import sat outside the guard, so a proteus newer than its ymf raised out of the middle of a timestep instead of losing its live document -- the one failure mode the writer was built to absorb. CI found it the honest way. Three legs of run 39894170430 resolved ymf@main at 03:18, five minutes before cekees/ymf#18 merged at 03:23; the macOS legs resolved it at 04:25 and passed. The provenance probe this workflow prints made that a two-minute diagnosis rather than a hunt, but the skew it exposed is real for anyone installing the two repositories separately. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
C1.1. The archive of record was assembled at close: per-step grid metadata went into the HDF5 as it was produced, and the document describing it was built from that only when
close()ran. A run that was killed, hit a scheduler timeout or died on one rank left an archive with the data in it and nothing describing the data — recoverable, but only by a separate pass, which is whatscripts/gatherArchives.pyexists for.Each step is appended, not rewritten
The first version of this rewrote the whole document each step. On review that is the wrong shape: O(steps) per step, O(steps²) over a run, and many proteus runs have very large numbers of steps. The requirement that a document be rewritten to stay well formed is inherited from XML; YAML has no such rule.
Flat per step — the write no longer depends on what the archive already holds.
How it fits together
allGatherIncrementalruns, so a record costs no extra HDF5 read and no extra YAML parse;<name>.ymf.writingand renamed; the records after it need no such care, because each ends with the document-end marker that tells a reader it is complete;close()still writes the canonical single document, so a finished run leaves exactly what it leaves today. Only an interrupted run is in the live shape, andread_ymftakes both;writeIncrementalYmf = Falserestores writing only at close.Tests (11 new; file goes 33 → 44)
test_an_interrupted_run_still_leaves_a_readable_document— the point. Steps are synced,close()never happens, the document is complete and correct.test_what_it_looked_like_before— the control, same run with the feature off: asserts there is no.ymfat all, so the first is not a tautology.test_the_running_document_agrees_with_the_one_written_at_close— the appended stream is folded by the reader; the closing document is re-assembled from YAML in the HDF5. Different code, different sources, same result required. If they diverged, the archive of record would depend on whether the run finished, which is what this removes.test_the_running_document_is_appended_not_rewritten— pins the mechanism: one---and one...per step, mid-run.test_close_leaves_a_single_document— and the live shape does not leak into finished archives.mpiexec, and no.writingleft behind.The rank script gained
SKIP_CLOSEandNO_INCREMENTALso an interrupted run is simulated faithfully rather than approximated.Testing
310 passed, 10 skipped, 1 failed across all six archive files plus
test_adr_problem.pyandtest_pro_cli.py. The failure istest_an_l_shaped_region_converges_at_second_order, in the known pre-existing set verified on a clean tree.Follow-on
This removes
gatherArchives's reason to exist. The script is still shipped (setup.py:1136) and tested, so retiring it is a separate call —ERDC_ISSUE_TRIAGE.mdhas it as a Tier C close candidate.🤖 Generated with Claude Code