TASK-054: warn when the configured timestep exceeds the stability limit - #94
Merged
Merged
Conversation
`stable_timestep()` has existed since Stage 5 and no live run reached it.
A 64x64 lid-driven cavity at the shipped `timestep: 0.008` is 2.05x its own
stability limit and diverges at step 17, with nothing said beforehand.
`build_simulation_state` now computes the limit for the mesh it is about to
run on and warns when the configured timestep exceeds it, naming both numbers
and their ratio. It lives in `simulation_run.py` rather than `bootstrap.py`
so `run`, `record` and `resume` all get it -- `record` is the path a long
unattended run uses.
Non-fatal by design: `stable_timestep`'s 0.25 safety factor is conservative
and 32x32 at ratio 1.02 demonstrably runs to completion.
`_characteristic_velocity` reads only what the configuration prescribes --
`simulation.velocity` and the `velocity.*` entries of each boundary face's
`field_values`. The `velocity.` prefix filter is load-bearing and was
untested until mutation testing found it: replacing it with an unconditional
`True` left all eleven tests passing, while letting a declared scalar's wall
value (`temperature: 300.0`) be read as a speed of 300, shrinking the limit
by two orders of magnitude. That is the same scalar/velocity conflation
TASK-052 fixed one layer down, reappearing in new code reading the same
mapping. `test_a_declared_scalars_wall_value_is_not_read_as_a_speed` closes
it and fails under that mutation.
Verified by hand on all three subcommands at 64x64:
configured numerics.timestep 0.008 exceeds this mesh's own stability
limit 0.0039062 (2.05x)
and silent at the shipped 16x16 (0.51x).
Stage 9 stays `opened` rather than closing at 7-of-7: TASK-055 is drafted to
carry Criterion 3's remaining half (`BoundaryFaceConfig.velocity` is still
validated and then ignored) and Criterion 6's documentation grep. It carries
an open design question -- wire the field or reject it -- that wants a
maintainer's answer before implementation.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merge Gate criterion 2, run as a grep rather than remembered, on the
branch that was already green.
**Dates.** TASK-054's commit landed 2026-09-13T12:08 UTC, and the entry
dated itself the 12th -- the local wall clock at drafting time, which is
exactly the failure `check_dates.py`'s own docstring now warns about
("date a change by its UTC commit time, not by the wall clock you are
looking at"). The rule was written yesterday after CI rejected the
opposite error; this is the same mistake in the other direction. TASK-054's
status, TASK-055's drafted date, and the test-count date move to the 13th.
The maintainer's warn-rather-than-reject call stays on the 12th: that
decision really was taken then.
**Two claims this branch made false and left standing.**
`stable_timestep` being engine code no live run reaches was stated in two
places outside TASK-054's own entry -- Stage 5's Criterion 12 verdict and
Stage 10's Design Question Two -- both in the present tense, both now
wrong. Amended rather than deleted: the Stage 5 verdict keeps what it said
on 2026-08-29 and records that the one gap it chose to note rather than
file has since been closed. Stage 10's question now says what TASK-054
deliberately did not do (derive the timestep) instead of restating a gap
that is gone.
**And one error of my own.** TASK-054's Purpose said Stage 5's verdict
"noted and filed" the gap. The verdict says the opposite in as many words
-- noted *rather than* filed as a violation -- and the distinction is the
whole point of the surrounding paragraph, which argues that a gap recorded
against no criterion is how it survives. Corrected to what the source
actually says, with the reasoning Stage 5 gave, and with why the outcome
was still a user's run blowing up with nothing said.
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.
Closes Stage 9's third finding (F3):
stable_timestep()has existed sinceStage 5 and no live run reached it.
A 64x64 lid-driven cavity at the shipped
timestep: 0.008is 2.05x its ownstability limit and diverges at step 17, with nothing said beforehand:
What this does
build_simulation_statenow computes the limit for the mesh it is about torun on and warns when the configured timestep exceeds it, naming both numbers
and their ratio.
It lives in
simulation_run.pyrather thanbootstrap.pysorun,recordand
resumeall get it --recordis the path a long unattended run uses.Non-fatal by design:
stable_timestep's 0.25 safety factor is conservative,and 32x32 at ratio 1.02 demonstrably runs to completion. A gate would reject
runs that work.
The fifth test exists because mutation testing found the fourth missing
_characteristic_velocityreads only what the configuration prescribes --simulation.velocityand thevelocity.*entries of each boundary face'sfield_values. That prefix filter is load-bearing and was untested:replacing
name.startswith("velocity.")with an unconditionalTrueleftall eleven tests passing, while letting a declared scalar's wall value
(
temperature: 300.0) be read as a speed of 300 -- shrinking the limit bytwo orders of magnitude and firing this warning on configurations that are
perfectly stable.
That is the same scalar/velocity conflation TASK-052 fixed one layer down,
reappearing in new code that reads the same mapping.
test_a_declared_scalars_wall_value_is_not_read_as_a_speedcloses it andfails under that mutation.
Verification
By hand on all three subcommands against a real 64x64 configuration:
Silent at the shipped 16x16 (0.51x).
make cigreen locally: 1219 passed,20 skipped, all 17 structural checks.
Stage 9 stays open
Not closed at 7-of-7. TASK-055 is drafted to carry Criterion 3's remaining
half --
BoundaryFaceConfig.velocityis still validated for mutualexclusivity and zero net flux, then read by no engine code -- and Criterion
6's documentation grep. It carries an open design question (wire the field,
or reject it) that wants a maintainer's answer before implementation rather
than the easier reading picked silently.
🤖 Generated with Claude Code