diff --git a/quantui/app.py b/quantui/app.py index cc3b772..4683a21 100644 --- a/quantui/app.py +++ b/quantui/app.py @@ -829,7 +829,7 @@ def _layout(**kwargs: Any) -> widgets.Layout: ) _RE_CONV = re.compile(r"converged SCF energy\s*=\s*([\-\d\.]+)") _RE_Q_STATUS = re.compile(r"\[QuantUI_STATUS\]\s*(.+)") -# TD-DFT root convergence (M-PROGRESS D2). PySCF's Davidson solver prints +# TD-DFT root convergence. PySCF's Davidson solver prints # "root %d converged |r|= ... e= max|de|= ..." at # verbose=5 (DEBUG) — see tddft_calc.py's td.verbose. This is the only # per-root progress signal the solve emits; without it the heartbeat's @@ -838,7 +838,7 @@ def _layout(**kwargs: Any) -> widgets.Layout: _RE_TD_ROOT = re.compile( r"root\s+(\d+)\s+converged\s+\|r\|=\s*[\d.eE+\-]+\s+e=\s*([\d.eE+\-]+)" ) -# M-PROGRESS D3 — per-phase progress for long silent kernels (mirrors D2). +# Per-phase progress for long silent kernels (NMR, Hessian, post-HF, TD-DFT). _RE_CCSD_CYCLE = re.compile( r"cycle\s*=\s*(\d+)\s+E_corr\(CCSD\)\s*=\s*[\-\d\.Ee+\-]+\s+dE\s*=\s*([\-\d\.Ee+\-]+)" ) @@ -852,7 +852,7 @@ def _layout(**kwargs: Any) -> widgets.Layout: # message is used as a per-stage timing key — see _LogCapture._stage_key. _RE_STAGE_NUMBERS = re.compile(r"\d+(?:[./]\d+)*") -# ── Silent-phase heartbeat (M-PROGRESS Phase D) ────────────────────────────── +# ── Silent-phase heartbeat ─────────────────────────────────────────────────── # # Seconds of stream silence before the log says it is still alive. # @@ -906,19 +906,18 @@ def __init__( # Completion fraction (0..1) reported by calc modules via # log_utils.emit_progress; read by the elapsed ticker. None = unknown. self._fraction: Optional[float] = None - # Silent-phase heartbeat (M-PROGRESS Phase D). Long kernels — the TD-DFT - # excited-state solve most of all — print nothing for minutes, so the - # log looks hung even though the status label is advancing. A watchdog - # appends a "still working" line when the stream has gone quiet. + # Silent-phase heartbeat. Long kernels — the TD-DFT excited-state solve + # most of all — print nothing for minutes, so the log looks hung even + # though the status label is advancing. A watchdog appends a "still + # working" line when the stream has gone quiet. self._last_write_t = time.monotonic() self._hb_started_t = self._last_write_t self._hb_stop = threading.Event() self._hb_thread: Optional[threading.Thread] = None - # Per-stage wall times (M-PROGRESS Phase C, deferred from B3). - # Every calc type already announces its phases through - # log_utils.emit_status, and every one of those announcements passes - # through this object — so stage boundaries can be timed here without - # threading a timer through optimizer/freq/tddft/nmr one by one. + # Per-stage wall times. Every calc type announces its phases through + # log_utils.emit_status, and every announcement passes through this + # object — stage boundaries can be timed here without threading a timer + # through optimizer/freq/tddft/nmr one by one. self._stage_times: dict[str, float] = {} self._stage_name: Optional[str] = None self._stage_started_t = self._last_write_t @@ -2410,9 +2409,9 @@ def _wire_callbacks(self) -> None: self._safe_cb(self._on_calc_type_changed), names="value" ) # Geometry Opt / Frequency / UV-Vis (TD-DFT) share one seed-geometry - # dropdown + refresh button (UXP2.5, M-UX2) — only one observer/click - # binding is needed, not three, since `_geo_seed_dd`, `_freq_seed_dd` - # and `_tddft_seed_dd` are the same underlying widget. + # dropdown + refresh button — only one observer/click binding is needed, + # not three, since `_geo_seed_dd`, `_freq_seed_dd` and `_tddft_seed_dd` + # are the same underlying widget. self._seed_dd.observe(self._safe_cb(self._on_seed_changed), names="value") self._seed_refresh_btn.on_click(lambda _btn: self._refresh_seed_options()) self._scan_type_dd.observe( @@ -4036,7 +4035,7 @@ def _on_seed_changed(self, change) -> None: # Geometry Opt / Frequency / UV-Vis (TD-DFT) used to each have their own # seed-refresh + seed-changed methods; the widget they operate on is now - # one shared dropdown (UXP2.5, M-UX2), so these are aliases of the two + # one shared dropdown, so these are aliases of the two methods above rather # methods above rather than separate implementations. Kept under their # original names because app_runflow.py's per-calc-type branches and the # existing per-calc-type tests still call them by these names. @@ -4931,7 +4930,7 @@ def _set_molecule( # # This used to refresh only the Frequency/UV-Vis dropdowns, because # Geometry Opt's seed dropdown didn't exist yet when this was written. - # Now that all three calc types share one dropdown (UXP2.5), a single + # Now that all three calc types share one dropdown, a single call here # call here also fixes a real gap: switching molecules while already # on the Geometry Opt panel used to leave its seed list showing the # PREVIOUS molecule's matches until the user switched calc types away @@ -5378,9 +5377,9 @@ def _run_required_final_single_point(target_mol, reason: str): # Expose this run's log to the elapsed ticker so it can read the # completion fraction calc modules report via emit_progress. self._active_log = log - # Watch for silent stretches (M-PROGRESS Phase D). Stopped in the - # `finally` alongside the elapsed ticker, so it cannot outlive the run - # and keep writing into a finished log. + # Watch for silent stretches in the output log. Stopped in the `finally` + # alongside the elapsed ticker, so it cannot outlive the run and keep + # writing into a finished log. log.start_heartbeat() # --- Checkpoint for this run (M-CHECKPOINT) --- diff --git a/quantui/app_builders.py b/quantui/app_builders.py index 47ad96c..0ca2d22 100644 --- a/quantui/app_builders.py +++ b/quantui/app_builders.py @@ -1131,9 +1131,8 @@ def build_shared_widgets( # molecule (e.g. optimise cheaply, then refine at a higher level of # theory). Only one of these three calc types is ever visible at a time, # so ONE widget group is built here and referenced under all three - # historical per-calc-type attribute names below (UXP2.5, M-UX2, - # 2026-07-31) — this used to be three near-identical widget groups. - # Real object count is one; the aliases exist so app.py, app_runflow.py, + # historical per-calc-type attribute names below — this used to be three + # near-identical widget groups. Real object count is one; the aliases exist so app.py, app_runflow.py, # and the existing per-calc-type tests keep working unchanged. app._seed_dd = widgets.Dropdown( options=[("(use current molecule)", "")], @@ -2651,7 +2650,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox: app._nmr_output = app._nmr_summary # Mulliken Populations — table + Plotly bar chart + 3D overlay toggles - # (always-mounted; content swapped when activated, DEC-009). + # (always-mounted; content swapped when activated). app._mulliken_summary = widgets.HTML(value="", layout=layout_fn(width="100%")) app._mulliken_table = widgets.HTML(value="", layout=layout_fn(width="100%")) # min_height matches plot_mulliken_charges height so the Output does not @@ -2889,7 +2888,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox: ), ] ) - # DEC-009: the panel stays visible regardless of backend — only its + # The panel stays visible regardless of backend — only its # content swaps (MEAS.6). Shown when the resolved backend can't support # native clicking (plotlymol, or py3Dmol simply unavailable). app._measure_fallback_msg = widgets.HTML( diff --git a/quantui/app_measurement.py b/quantui/app_measurement.py index c22d427..58baed6 100644 --- a/quantui/app_measurement.py +++ b/quantui/app_measurement.py @@ -384,7 +384,7 @@ def reset_picks(app: Any) -> None: def update_panel_for_backend(app: Any, backend: Any) -> None: """Swap the measurement panel between controls and an explanation. - DEC-009: the panel itself stays visible either way — only its *content* + The panel itself stays visible either way — only its *content* switches, never a silent no-op when the resolved backend can't support clicking. """ diff --git a/quantui/app_runflow.py b/quantui/app_runflow.py index 35185ff..55e0aca 100644 --- a/quantui/app_runflow.py +++ b/quantui/app_runflow.py @@ -404,7 +404,7 @@ def on_calc_type_changed(app: Any, change: Any, *, layout_fn: Any) -> None: elif ct in ("Frequency", "UV-Vis (TD-DFT)"): app._freq_preopt_cb.layout.display = "" # The seed dropdown is shared across all three seed-consuming calc - # types (UXP2.5), so it can carry a value in from whichever of these + # types, so it can carry a value in from whichever of these # two was active before. Re-evaluate .disabled here rather than trust # whatever it was left at — otherwise switching Frequency (seeded) -> # UV-Vis carries a stale disabled=True even if UV-Vis's own seed slot @@ -930,7 +930,7 @@ def refresh_seed_options(app: Any) -> None: Used by Geometry Opt, Frequency, UV-Vis (TD-DFT), and NMR Shielding — only one of which is ever visible at a time, so there is exactly one - dropdown to refresh (UXP2.5, M-UX2). Superseded the three near-identical + dropdown to refresh. Superseded the three near-identical ``refresh_{geo,freq,tddft}_seed_options`` wrappers that used to exist here. """ include_freq = getattr(app, "calc_type_dd", None) @@ -943,7 +943,7 @@ def on_seed_changed(app: Any, change: Any) -> None: redundant. Superseded the three near-identical ``on_{geo,freq,tddft}_seed_changed`` - handlers (UXP2.5, M-UX2) — the dropdown is now one shared widget, so one + handlers — the dropdown is now one shared widget, so one handler suffices, made calc-type-aware where the three used to differ: - **Frequency / UV-Vis (TD-DFT):** a selected seed is already an optimised diff --git a/quantui/calc_log.py b/quantui/calc_log.py index e1a1238..13b850e 100644 --- a/quantui/calc_log.py +++ b/quantui/calc_log.py @@ -477,9 +477,9 @@ def log_calculation( ``quantui.analytics.build_dashboard`` compute GPU-vs-CPU speedups across runs of the same (method, basis, formula) tuple. - ``source`` / ``warm`` / ``import_s`` / ``stages`` (added 2026-08-05, - M-PROGRESS Phase C) describe **how the timing was measured**, which - turned out to matter more than the cost model itself: + ``source`` / ``warm`` / ``import_s`` / ``stages`` (added 2026-08-05) + describe **how the timing was measured**, which turned out to matter more + than the cost model itself: * ``source`` — ``"app"`` for a run the user launched in the UI, ``"calibration"`` for one the benchmark harness measured in a fresh @@ -554,8 +554,8 @@ def log_calculation( _HESSIAN_MULTIPLIER_HF_DFT: float = 2.0 _HESSIAN_MULTIPLIER_POST_HF: float = 6.0 _POST_HF_METHODS: frozenset = frozenset({"MP2", "CCSD", "CCSD(T)"}) -# Minimum tagged frequency records carrying ``stages`` before the learned -# ratios replace the fixed Hessian / IR multipliers (PROG.C6). +# Minimum tagged frequency records carrying ``stages`` before learned stage +# ratios replace the fixed Hessian / IR multipliers. _MIN_FREQ_STAGE_SAMPLES: int = 2 @@ -708,8 +708,8 @@ def _estimate_frequency_cost( return None scf_anchor_s = float(sp_est["seconds"]) - # PROG.C6 — replace fixed multipliers with medians from tagged stage - # records when enough ``source="app"`` frequency runs have accumulated. + # Replace fixed multipliers with medians from tagged stage records when + # enough ``source="app"`` frequency runs have accumulated. learned = _learned_frequency_stage_params( records, method=method, basis=basis, source=source ) @@ -868,7 +868,7 @@ def estimate_time_from_records( label downgraded one notch — better an approximate estimate from cross-device data than no estimate at all. - **Provenance filtering** (2026-08-05, M-PROGRESS Phase C): ``source`` + **Provenance filtering** (2026-08-05): ``source`` partitions the pool the partitions the pool the same way ``gpu_used`` does, and for the same reason — the two populations measure different things. A calibration record times a fresh subprocess, so it includes PySCF's import cost; diff --git a/quantui/engines/pyfock_engine.py b/quantui/engines/pyfock_engine.py index 03eb240..13495a2 100644 --- a/quantui/engines/pyfock_engine.py +++ b/quantui/engines/pyfock_engine.py @@ -15,7 +15,7 @@ _PYFOCK_SPEC = importlib.util.find_spec("pyfock") _AVAILABLE = _PYFOCK_SPEC is not None -# Phase-1 subset — see QUANTUM-ENGINE-CONTRACT.md and DEC-020. +# Phase-1 subset — see QUANTUM-ENGINE-CONTRACT.md. _PYFOCK_METHODS = ("PBE", "B3LYP", "PBE0") _PYFOCK_BASES = ("def2-SVP", "def2-TZVP", "LANL2DZ") diff --git a/quantui/estimator_eval.py b/quantui/estimator_eval.py index 20be53d..a1c28fc 100644 --- a/quantui/estimator_eval.py +++ b/quantui/estimator_eval.py @@ -1,9 +1,8 @@ """ Offline evaluation of QuantUI's runtime estimator. -M-PROGRESS Phase C asked for the pre-run time estimate to be "overhauled" -until it is trustworthy. That is only actionable if a change to the model -can be *scored*, so this module replays the recorded performance history +The pre-run time estimate is only actionable if a change to the model can be +*scored*, so this module replays the recorded performance history through :func:`quantui.calc_log.estimate_time_from_records` and reports how well the predictor would have done. diff --git a/quantui/freq_calc.py b/quantui/freq_calc.py index 28fd6a3..5a59140 100644 --- a/quantui/freq_calc.py +++ b/quantui/freq_calc.py @@ -496,8 +496,8 @@ def _status(msg: str) -> None: try: hess_obj = mf.Hessian() # verbose=6 surfaces per-atom integral contractions — the only - # in-kernel progress signal during an analytical Hessian build - # (M-PROGRESS D3). _LogCapture.write greps for them. + # in-kernel progress signal during an analytical Hessian build. + # _LogCapture.write greps for per-atom contraction lines. hess_obj.verbose = 6 hess_obj.stdout = stream diff --git a/quantui/help_content.py b/quantui/help_content.py index cd33f5f..f0aa26f 100644 --- a/quantui/help_content.py +++ b/quantui/help_content.py @@ -48,6 +48,44 @@ "apptainer run quantui.sif

" "

Each dropdown in the Calculate tab has a ? button for " "context-sensitive help on that specific option.

" + "

While a calculation runs, see Live run progress in the " + "Help browser for what the status line and log messages mean.

" + ), + }, + "run_progress": { + "title": "Live run progress — what the status line means", + "body": ( + "

Long quantum-chemistry steps can run for minutes with little or " + "no new text in the output log. QuantUI keeps you oriented with a " + "live status line next to the Run button and, when needed, " + "occasional still working lines in the log.

" + "

Status chip — shows the current stage, elapsed time, and " + "(when history allows) an approximate time remaining. Examples you " + "may see:

" + "" + "

Log heartbeat — if PySCF prints nothing for ~25 seconds, " + "the live log may show a grey … still working — <stage> · " + "<elapsed> line. This proves the run is alive; those lines " + "are not copied into the saved pyscf.log in your " + "results folder.

" + "

When in doubt: as long as the status chip or heartbeat is " + "advancing, the calculation is progressing. Use Cancel if you " + "need to stop — cancellation is checked between SCF cycles and " + "optimizer steps.

" ), }, "calc_type": { @@ -182,7 +220,7 @@ "ferrocene) or paste your own coordinates in the XYZ Input " "tab rather than relying on an online name search, which often " "returns a disconnected salt form for coordination compounds.

" - # UXP2.1: the two Pople notations are a recurring source of + # The two Pople notations are a recurring source of # confusion — a reader who only knows 6-31G(d) can conclude the # 6-31G* in the dropdown is a different set they can't select. "

Reading the names: two notations, " diff --git a/quantui/log_utils.py b/quantui/log_utils.py index 8c316b9..2399db9 100644 --- a/quantui/log_utils.py +++ b/quantui/log_utils.py @@ -406,7 +406,6 @@ def _extract_warnings( (> ``_DEGENERACY_GAP_THRESHOLD_EV``), such lines are dropped as transient: they describe a pre-convergence density, not the result. A genuinely (near-)degenerate converged state has a small gap and keeps its warning. - (M-UX2 UXP2.6.) """ drop_degeneracy = ( converged_gap_ev is not None and converged_gap_ev > _DEGENERACY_GAP_THRESHOLD_EV diff --git a/quantui/nmr_calc.py b/quantui/nmr_calc.py index ff9c8a6..68444e3 100644 --- a/quantui/nmr_calc.py +++ b/quantui/nmr_calc.py @@ -411,7 +411,7 @@ def _run_nmr_calc_body( else: nmr_obj = _pyscf_nmr.RKS(mf) if mol.spin == 0 else _pyscf_nmr.UKS(mf) # verbose=4 prints per-atom shielding blocks — parsed by _LogCapture - # for live GIAO progress (M-PROGRESS D3). + # for live GIAO progress. _LogCapture.write greps for per-atom lines. nmr_obj.verbose = 4 nmr_obj.stdout = stream tensors = nmr_obj.kernel() diff --git a/quantui/session_calc.py b/quantui/session_calc.py index f6b1d6b..143ff26 100644 --- a/quantui/session_calc.py +++ b/quantui/session_calc.py @@ -580,7 +580,7 @@ def _run_session_calc_body( emit_status(stream, "Running MP2 correlation…") _mp2 = _mp.MP2(mf) # verbose=5 surfaces integral-transform / kernel milestones for - # the live status label (M-PROGRESS D3). + # the live status label during the correlation step. _mp2.verbose = 5 _mp2.stdout = stream _e_corr, _ = _mp2.kernel() diff --git a/quantui/tddft_calc.py b/quantui/tddft_calc.py index 13363a3..57c940b 100644 --- a/quantui/tddft_calc.py +++ b/quantui/tddft_calc.py @@ -290,8 +290,8 @@ def _run_tddft_calc_body( td.nstates = nstates # verbose=5 (DEBUG) is what surfaces PySCF's per-root "root %d # converged" lines during the Davidson solve — the only progress - # signal available while it runs (M-PROGRESS D2). _LogCapture.write - # in app.py greps for them to update the live status label. + # signal available while it runs. _LogCapture.write in app.py greps + # for per-root convergence lines to update the live status label. td.verbose = 5 td.stdout = stream td.kernel() diff --git a/quantui/theme.py b/quantui/theme.py index e0e6076..00a5558 100644 --- a/quantui/theme.py +++ b/quantui/theme.py @@ -1,4 +1,4 @@ -"""Theme colour tokens and preset palettes (M-THEME). +"""Theme colour tokens and preset palettes. QuantUI themes are **preset palettes** backed by CSS custom properties. Each palette defines independent light/dark (or tinted) values — the whole-page diff --git a/quantui/user_settings.py b/quantui/user_settings.py index 46bc32b..09d7cdc 100644 --- a/quantui/user_settings.py +++ b/quantui/user_settings.py @@ -80,7 +80,7 @@ class VizSettings: @dataclass class ThemeSettings: - """Visual theme preferences (M-THEME THEME.6).""" + """Visual theme preferences (preset palette selection).""" palette: str = "Dark" diff --git a/tests/test_help_content.py b/tests/test_help_content.py index 82ae210..63b9633 100644 --- a/tests/test_help_content.py +++ b/tests/test_help_content.py @@ -82,3 +82,11 @@ def test_method_panel_mentions_rhf_uhf(self): def test_basis_set_panel_mentions_sto3g(self): panel = help_panel("basis_set") assert "STO-3G" in panel.value + + def test_run_progress_panel_covers_status_and_heartbeat(self): + panel = help_panel("run_progress") + assert "status" in panel.value.lower() + assert ( + "heartbeat" in panel.value.lower() or "still working" in panel.value.lower() + ) + assert "TD-DFT" in panel.value