Silent-phase log heartbeat (M-PROGRESS Phase D) + launcher banner, pre-opt suppression, geo-opt seed geometry - #33
Merged
Conversation
The launcher terminal opened with two bare lines of text. It is the first
thing a user sees on every run, and for students it is often the only
terminal they ever look at, so it may as well identify itself.
Adds launchers/_banner.sh — a sourced helper printing the same figlet
wordmark as the in-app run header, plus a mode line. Shared rather than
pasted into each launcher: three copies of ASCII art would drift.
- Colour only when stdout is a TTY, so piping or redirecting stays clean.
- Every call is `|| true` and guarded by a file-exists check: all three
launchers run under `set -eu`, and decoration must never be what aborts
a launch.
- The art is duplicated from log_utils rather than printed by Python
because the banner runs before the conda env is activated — there is no
interpreter to ask yet.
- Windows .bat launchers deliberately excluded: batch escaping of the
backslashes and pipes in the art is error-prone and unverifiable from
this dev environment.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
User report: when a pre-optimization changes essentially nothing, the animation pane and the Keep/Revert buttons still appear. The animation shows a molecule sitting still, and the buttons ask the user to choose between two effectively identical geometries — which reads as "something happened, now judge it" when the honest answer is "your geometry was already fine". Below _PREOPT_NEGLIGIBLE_RMSD_A (0.05 A RMS displacement) the preview now reports the number and stops: no animation, no Keep/Revert, and _preopt_relaxed_mol is left unset so there is nothing to accept. The status line says what happens next — the calculation uses the geometry as-is. The threshold is deliberately conservative. Bond lengths are ~1.0-1.5 A, so 0.05 A RMS is a few percent and invisible at viewer scale, while perceptible motion starts around 0.1 A. Showing a real-but-small change is much less bad than hiding one. The old 1e-3 A test only distinguished mathematically-zero from nonzero, which is not the question the user is asking. The meaningful-change path explicitly restores both panes, so a negligible preview followed by a real one does not leave Keep/Revert invisible. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Frequency and UV-Vis could already start from a previously optimised geometry; Geometry Opt could not. The motivating workflow is standard: optimise at a cheap level of theory, then feed that geometry in as the starting point for a more expensive one. Adds the same filtered-dropdown pattern used by the other two (the _refresh_seed_options helper was already shared), plus consumption in _do_run: when a seed is selected the optimisation starts from that result's final geometry instead of the current molecule, and the choice is recorded in the run log alongside formula and atom count. One deliberate difference from the Frequency/UV-Vis handlers: this one does NOT disable _freq_preopt_cb. That checkbox means "optimise before the calculation", which is meaningless when the optimisation IS the calculation. Note this makes three near-duplicate seed widget groups (geo, freq, tddft) differing only in their notes. Consolidating them into one reusable group is worth doing — only one calc type is visible at a time — but that refactor touches two working paths, so it is left as an M-UX2 item rather than bundled into a feature commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measured motivation: an aspirin (21 atoms) B3LYP/6-31G* UV-Vis run printed
nothing for 120 s after "converged SCF energy" while the TD-DFT solve ran.
The status label advanced the whole time — Phase A covers that — but the
output log, which is what a user actually watches, looked frozen.
_LogCapture already sees every write, so it is the natural place to know when
output last happened. A watchdog thread appends "… still working — <stage> ·
<elapsed>" whenever the stream has been quiet for _HEARTBEAT_AFTER_S.
Sized from the measurement rather than guessed: 25 s gives ~4 lines across
that 120 s gap — enough to prove liveness, few enough to stay quiet. Gaps grow
steeply with system size and aspirin is a small case, so the interval errs
short.
Three deliberate details:
- The beat writes directly to the widget, NOT through write(): that path
checks cancellation, which would raise _CalcCancelled on the watchdog
thread where nothing can catch it, and would reset the very timer being
measured.
- It appends to the widget only, not to the capture buffer. The buffer
becomes the result directory's pyscf.log, which should stay a faithful
record of PySCF's output rather than being padded with UI chrome.
- Any real write resets the timer, so a steadily-printing run never shows a
heartbeat at all.
Stopped in _do_run's finally alongside the elapsed ticker, so it cannot
outlive a run and write into a finished log.
This is only viable now that M-LOGSCROLL shipped: before route C, a line
appended every 25 s would have yanked the user's scroll position back to the
bottom on a timer.
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.
One milestone package and three UX requests, one commit each.
contracts, not how any of it looks or feels. A Voilà pass is worth doing before
merge; see "What to check live" at the bottom.
M-PROGRESS Phase D (PROG.D1) — silent-phase heartbeat
Measured motivation. An aspirin (21 atoms) B3LYP/6-31G* UV-Vis run printed
nothing for 120 s after
converged SCF energywhile the TD-DFT solve ran. Thestatus label advanced the whole time — Phase A covers that — but the output log,
which is what a user actually watches, looked frozen. Phase A solved this for the
status label; this is the same problem for the log.
_LogCapturealready sees every write, so it is the natural place to know whenoutput last happened. A watchdog appends
… still working — <stage> · <elapsed>after_HEARTBEAT_AFTER_Sof silence.25 s, sized from the measurement rather than guessed: ~4 lines across that
120 s gap — enough to prove liveness, few enough to stay quiet. Gaps grow steeply
with system size and aspirin is a small case, so the interval errs short.
Three decisions worth reviewing:
write(). That pathchecks cancellation, so a heartbeat routed through it would raise
_CalcCancelledon the watchdog thread, where nothing can catch it — thebeat would vanish silently. It would also reset the very timer being measured.
getvalue()becomes theresult directory's
pyscf.log, which should stay a faithful record of whatPySCF emitted; padding it with UI chrome would make a long silent run look
chatty after the fact. Live view gets the beat, the archive does not.
Started next to
self._active_login_do_runand stopped in the samefinallyas the elapsed ticker, so it cannot outlive a run and write into a finished log.
This was only viable because M-LOGSCROLL shipped first. Before route C, a line
appended every 25 s would have yanked the user's scroll position to the bottom on
a timer — the roadmap's sequencing warning was real, not theoretical.
D2 (TD-DFT root progress) and D3 (specific progress for NMR/Hessian/post-HF) stay
open, but Phase D's exit criterion is met by D1 alone: no silent gap can now
exceed ~25 s.
UXP2.2 — QuantUI wordmark in the shell launchers
The launcher terminal opened with two bare lines of text. It is the first thing a
user sees on every run, and for students often the only terminal they ever look at.
launchers/_banner.shis sourced bylaunch-native.sh,launch-native.commandand
launch-native-jupyter.sh— shared rather than pasted, since three copies ofASCII art would drift.
log_utilsrather than printed by Python because thebanner runs before the conda env is activated — there is no interpreter yet.
|| truebehind a file-exists check: all three launchers run underset -eu, and decoration must never be what aborts a launch..batlaunchers deliberately excluded — batch escaping of thebackslashes and pipes in the art is error-prone and unverifiable from this dev
environment.
UXP2.3 — no pre-opt preview when the geometry barely moves
Below 0.05 Å RMS displacement the preview reports the number and stops: no
animation, no Keep/Revert, and
_preopt_relaxed_molleft unset so there isnothing to accept. The status line says what happens next — the calculation uses
the geometry as-is.
percent and invisible at viewer scale; perceptible motion starts near 0.1 Å.
Deliberately conservative — showing a real-but-small change is far less bad than
hiding one. Easy to tune if it silences previews you wanted.
1e-3 Åtest only chose wording; it distinguished mathematically-zerofrom nonzero, which is not the question a user is asking.
preview followed by a real one cannot leave Keep/Revert invisible (tested).
UXP2.4 — seed geometry for Geometry Opt
Frequency and UV-Vis already supported this; Geometry Opt did not. Same filtered
dropdown (the
_refresh_seed_optionshelper was already shared), plus consumptionin
_do_run— when a seed is selected the optimisation starts from that result'sfinal geometry, logged with source, formula and atom count.
One deliberate asymmetry:
on_geo_seed_changeddoes not disable_freq_preopt_cbthe way the other two handlers do. That checkbox means "optimisebefore the calculation", which is meaningless when the optimisation is the
calculation. A test guards it so a future "consistency" pass doesn't quietly add it.
Known debt, recorded not hidden: this makes three near-duplicate seed
widget groups differing only in note text and that checkbox behaviour. Only one
calc type is visible at a time, so they could collapse into one — but that refactor
touches two working paths, so it is filed as M-UX2 UXP2.5 rather than bundled into
a feature commit.
Testing
tests/test_log_heartbeat.py(12) — fires during silence, repeats, carries thestage and elapsed, stays silent while output flows, stops cleanly, is idempotent,
tolerates a missing status label, keeps out of the capture buffer, and is not
suppressed by a pending cancellation.
tests/test_geo_seed_geometry.py(10) — including a regression guard that therefresh button targets the geo dropdown (an early revision bound it to the
Frequency button) and that each calc type keeps its own dropdown.
tests/test_preopt_preview.py— 5 new cases covering the threshold boundary inboth directions and pane restoration.
pre-commitclean.What to check live
… still workingduring the TD-DFT solve, at a rate that reassures rather thannags.
that really relaxes should still animate with Keep/Revert.
optimisations, and the run log records the seed.
🤖 Generated with Claude Code