Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions quantui/analytics.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
compute device (CPU grey, GPU green), so a user can spot regressions
or speedups visually as they run more calcs.

Older perf-log records that pre-date the M-GPU follow-up don't have
Older perf-log records that pre-date GPU support don't have
``gpu_used`` set — those are treated as "device unknown" and counted in
their own bucket rather than guessed CPU.

Expand All @@ -46,7 +46,7 @@
def _classify_device(record: dict) -> str:
"""Return ``"GPU"``, ``"CPU"``, or ``"Unknown"`` for one perf record.

Records written before the M-GPU follow-up (2026-05-25) don't have
Records written before GPU support (2026-05-25) don't have
``gpu_used`` at all — we don't backfill those as CPU because they
pre-date GPU support entirely, so calling them "CPU" would muddy any
speedup comparison. ``"Unknown"`` is the honest bucket.
Expand Down Expand Up @@ -353,7 +353,7 @@ def _timeline_html(records: list[dict], *, include_plotlyjs: bool) -> Optional[s


# ---------------------------------------------------------------------------
# Prediction-accuracy section (M-EST / EST.6, 2026-05-25)
# Prediction-accuracy section (2026-05-25)
# ---------------------------------------------------------------------------


Expand Down Expand Up @@ -555,7 +555,7 @@ def build_dashboard(out_path: Optional[Path] = None) -> Optional[Path]:
method_counts = _counts_by(records, "method")
calc_type_counts = _counts_by(records, "calc_type")

# M-EST / EST.6: prediction-accuracy data lives in its own log file.
# Prediction-accuracy data lives in its own log file.
# Best-effort read — older installs without the file produce an
# empty list and the section degrades to an empty-state message.
try:
Expand Down
179 changes: 113 additions & 66 deletions quantui/app.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion quantui/app_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def pop_preopt_trajectory(app: Any, ctx: Any) -> bool:
"""Populate Trajectory panel for the frequency-time DFT geometry
optimization trajectory.

POLISH.9 (2026-05-25): the wrapped operation is a full DFT geom-opt
The wrapped operation is a full DFT geom-opt
at the user's method/basis, not the classical LJ pre-opt that lives
in ``quantui/preopt.py``. The function name + ``preopt_trajectory.json``
filename stay (renaming the saved file would break history replay of
Expand Down
58 changes: 27 additions & 31 deletions quantui/app_builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def _ok(flag: bool, extra: str = "") -> str:
"Timing calibration: not yet run &mdash; use the Calibrate panel in History</div>"
)

# GPU offload indicator (M-GPU / GPU.2). Detecting GPUs imports gpu4pyscf +
# GPU offload indicator. Detecting GPUs imports gpu4pyscf +
# cupy and queries CUDA, which costs ~7 s — far too slow to block startup.
# So the status HTML is rendered via this closure with the GPU row as a
# "checking…" placeholder; the app re-renders it (via app._render_status_html)
Expand Down Expand Up @@ -259,7 +259,7 @@ def build_history_section(
tooltip="Open the full PySCF output log in the Output tab",
)

# M-EST / EST.4: 4-tier calibration selector. ToggleButtons works for
# 4-tier calibration selector. ToggleButtons works for
# 4 options; switch to a Dropdown if a 5th tier is ever added. Tier 3
# / tier 4 require PySCF (the geom-opt + freq dispatch); tier 1 / 2
# are SP-only and gated separately by the run button.
Expand Down Expand Up @@ -295,10 +295,9 @@ def build_history_section(
tooltip="Abandon the rest of the calibration (current step is also killed).",
layout=layout_fn(width="90px", display="none"),
)
# session 55 user request: replaced the hard 1800 s per-step timeout
# with a Skip button so the user can abandon ONE step that's running
# too long without losing the whole run. Distinct from Stop (which
# abandons everything remaining).
# Replaced the hard 1800 s per-step timeout with a Skip button so the
# user can abandon ONE step that's running too long without losing the
# whole run. Distinct from Stop (which abandons everything remaining).
app._cal_skip_btn = widgets.Button(
description="Skip step",
button_style="info",
Expand Down Expand Up @@ -391,7 +390,7 @@ def build_history_section(
if cal_last
else ""
)
# M-EST / EST.4: import tier sizes lazily so we can refer to all four
# Import tier sizes lazily so we can refer to all four
# in the panel blurb. ``benchmark_suite`` / ``benchmark_suite_long``
# are kept as positional args for back-compat but new code prefers
# the four named tiers.
Expand Down Expand Up @@ -431,10 +430,9 @@ def build_history_section(
app._cal_accordion = widgets.Accordion(children=[cal_panel], selected_index=None)
app._cal_accordion.set_title(0, "Calibrate time estimates")

# POLISH.3 (M-POLISH, 2026-05-25): the History tab is now purely
# the result-browser. Performance stats + Calibrate accordions
# moved to the System Settings tab — see below — so the user finds
# benchmarking + system state in one logical place.
# The History tab is now purely the result-browser. Performance stats
# + Calibrate accordions moved to the System Settings tab — see below —
# so the user finds benchmarking + system state in one logical place.
app.history_panel = widgets.VBox(
[
widgets.HTML(
Expand All @@ -455,7 +453,7 @@ def build_history_section(
]
)

# POLISH.3: now that the calibration + performance accordions exist
# Now that the calibration + performance accordions exist
# (created above in this function), append them to the System
# Settings tab. ``_status_tab_panel`` was built earlier in
# ``build_status_panel`` without these — extend its children tuple.
Expand Down Expand Up @@ -503,7 +501,7 @@ def build_shared_widgets(
app.mol_summary_compact = widgets.HTML(value="")
# Fixed heights reserve space so swapping content (backend/palette toggle)
# or streaming output never resizes the container — which would reflow the
# page and jump the scrollbar (BUG-SCROLL). The molecule viewer renders at
# page and jump the scrollbar. The molecule viewer renders at
# render_molecule_html's default 500px; the run log scrolls internally.
# overflow hidden (not auto): the 3D viewer is a fixed-size canvas, so it
# needs no scrollbar — clipping a few px of margin avoids an internal
Expand Down Expand Up @@ -570,7 +568,7 @@ def build_shared_widgets(
[app.viz_style_dd, app.viz_lighting_dd],
layout=layout_fn(gap="8px", margin="2px 0 0 0", align_items="center"),
)
# UXP.7: method / basis descriptor cards replace the old inline
# Method / basis descriptor cards replace the old inline
# ``notes_output`` educational-notes block. Two compact HTML cards
# (icon + one-line) sit to the right of the dropdowns; populated here with
# the defaults and refreshed by ``update_notes`` on every dropdown change.
Expand Down Expand Up @@ -644,7 +642,7 @@ def build_shared_widgets(
'<span style="font-size:11px;color:#94a3b8"> &mdash; fast MMFF/UFF '
"cleanup of a rough structure</span>"
)
# Interactive pre-opt (M-PREOPT PREOPT.2/.3): run the bonded-FF pre-opt on
# Interactive pre-opt: run the bonded-FF pre-opt on
# demand, watch it relax in-place, then keep or revert.
app.preopt_preview_btn = widgets.Button(
description="Preview",
Expand Down Expand Up @@ -696,7 +694,7 @@ def build_shared_widgets(

from quantui.config import SOLVENT_OPTIONS as _SOLVENT_OPTS

# POLISH.10: drop the default Checkbox description gutter + explicit width
# Drop the default Checkbox description gutter + explicit width
# (style description_width "initial" + indent=False) that produced the
# indent + horizontal scrollbar.
app.solvent_cb = widgets.Checkbox(
Expand Down Expand Up @@ -921,7 +919,7 @@ def build_shared_widgets(
layout=layout_fn(width="200px", height="36px"),
)
app.run_status = widgets.Label()
# M-PROGRESS A1: live elapsed-time chip next to the status text — a bg-thread
# Live elapsed-time chip next to the status text — a bg-thread
# ticker updates it every ~1 s while a calc runs, so even a fully silent
# native phase (first gradient / Hessian) visibly advances.
app._run_elapsed_lbl = widgets.HTML(value="")
Expand Down Expand Up @@ -990,7 +988,7 @@ def build_shared_widgets(
layout=layout_fn(width="130px"),
)
app.struct_export_status = widgets.Label()
# M-EXPORT / EXPORT.5: zip the entire result folder for emailing /
# Zip the entire result folder for emailing /
# attaching to a writeup. Disabled until ``_last_result_dir`` is set.
app._export_bundle_btn = widgets.Button(
description="Export bundle (.zip)",
Expand Down Expand Up @@ -1031,7 +1029,7 @@ def build_theme_selector(app: Any, *, layout_fn: Any) -> None:
def build_welcome_header(app: Any, *, layout_fn: Any = None) -> None:
"""Build the QuantUI welcome banner.

POLISH.1 third iteration (M-POLISH, 2026-05-25): the
Third iteration (2026-05-25): the
``<img src="data:image/svg+xml;base64,...">`` approach failed too
— Voilà's HTML sanitizer (stricter than JupyterLab's) strips
``data:`` URIs from ``<img src>`` attributes. The third iteration
Expand Down Expand Up @@ -1317,7 +1315,7 @@ def build_calc_setup(app: Any, *, layout_fn: Any) -> None:
widgets.HTML("&ensp;&ensp;"),
widgets.VBox([app.charge_si, app.mult_si]),
widgets.HTML("&ensp;"),
# UXP.7 descriptor cards — to the right of the inputs so
# Descriptor cards — to the right of the inputs so
# they don't displace charge/multiplicity.
app._descriptor_cards_box,
],
Expand Down Expand Up @@ -1404,7 +1402,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox:
layout=layout_fn(width="130px"),
tooltip="Export the current plot as HTML or PNG",
)
# M-EXPORT / EXPORT.4: per-panel "Copy data" button that exports
# Per-panel "Copy data" button that exports
# the underlying numerical data to CSV (saved to result_dir) and
# also attempts to copy to the system clipboard via the browser
# API. Status widget below is shared with the Save Plot path —
Expand Down Expand Up @@ -1447,7 +1445,6 @@ def _plot_export_row(prefix: str) -> widgets.HBox:
# Using `widgets.Output` here previously caused widget references inside
# `with output: display(widget)` to be deferred/asynchronous, leaving the
# accordion visibly empty even after _show_opt_trajectory logged success.
# See BUG-FRESH-TRAJ root-cause analysis in session 48.
app.traj_output = widgets.VBox(layout=layout_fn(margin="0"))
app.traj_accordion = widgets.Accordion(
children=[app.traj_output],
Expand Down Expand Up @@ -1551,7 +1548,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox:
style={"description_width": "120px"},
layout=layout_fn(width="300px", display="none"),
# continuous_update=False so dragging the slider only fires on
# release, not 30-60 times per second during the drag (BUG.9 fix).
# release, not 30-60 times per second during the drag.
# Combined with the atomic outputs swap in _set_html_output this
# eliminates the IR re-render storm that caused visible flicker.
continuous_update=False,
Expand Down Expand Up @@ -1640,7 +1637,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox:
orb_diagram_content,
layout=layout_fn(width="100%"),
)
# UXP.4: widen the preset buttons and add a "By index" mode that reveals a
# Widen the preset buttons and add a "By index" mode that reveals a
# free-entry 0-based MO index input, so any orbital (not just the HOMO/LUMO
# neighbourhood) can be rendered as an isosurface.
app._orb_toggle = widgets.ToggleButtons(
Expand Down Expand Up @@ -1694,7 +1691,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox:
),
layout=layout_fn(width="200px", margin="8px 0 4px 0"),
)
# M-EXPORT / EXPORT.5: copy the last-generated cube to the top-level
# Copy the last-generated cube to the top-level
# result dir under a friendly name (HOMO.cube / LUMO.cube / etc.).
# Disabled until the first isosurface generation populates
# ``app._last_cube_path``.
Expand All @@ -1711,7 +1708,7 @@ def _plot_export_row(prefix: str) -> widgets.HBox:
app._iso_export_status = widgets.HTML(
value="", layout=layout_fn(margin="0 0 0 8px")
)
# UXP.3: inline "calculating" spinner shown while a cube is being computed.
# Inline "calculating" spinner shown while a cube is being computed.
# Hidden until on_iso_generate reveals it; hidden again on completion.
app._iso_spinner = widgets.HTML(
value='<span class="quantui-spinner"></span>',
Expand Down Expand Up @@ -2083,8 +2080,7 @@ def build_output_tab(app: Any, *, layout_fn: Any) -> None:
button_style="danger",
layout=layout_fn(width="140px", display="none"),
)
# POLISH.8 (M-POLISH, 2026-05-25): the Log tab moved to be an
# Accordion inside the History tab — rationale in the roadmap. The
# The Log tab moved to be an Accordion inside the History tab. The
# explanatory text no longer needs to say "Use View log in the
# History tab" since the user IS in the History tab now.
app.log_tab_panel = widgets.VBox(
Expand Down Expand Up @@ -2117,7 +2113,7 @@ def build_output_tab(app: Any, *, layout_fn: Any) -> None:
layout=layout_fn(padding="8px 0"),
)

# POLISH.8: wrap the log panel in an Accordion + append to the
# Wrap the log panel in an Accordion + append to the
# History tab. ``history_panel`` was built in
# ``build_history_section`` earlier in the app-init sequence
# (see app.py: _build_history_section runs BEFORE _build_output_tab).
Expand Down Expand Up @@ -2222,8 +2218,8 @@ def build_help_section(app: Any, *, layout_fn: Any) -> None:
app.help_content_html = widgets.HTML()
app._render_help_topic()

# POLISH.2 (M-POLISH, 2026-05-25): the single-character "?" was
# visually noisy and hard to recognise as the global help toggle.
# The single-character "?" was visually noisy and hard to recognise
# as the global help toggle.
# Field-level "?" buttons (method_help_btn / basis_help_btn earlier
# in this file) keep the symbol — for inline-with-input help it's
# universally understood.
Expand Down
4 changes: 2 additions & 2 deletions quantui/app_exports.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def export_molecule_and_label(app: Any) -> tuple[Any, str, str]:


def on_iso_export_cube(app: Any, btn: Any) -> None:
"""Copy the last-generated cube file to the result folder (EXPORT.5).
"""Copy the last-generated cube file to the result folder.

Reads ``app._last_cube_path`` (set by the isosurface render path in
``app_visualization.py``) and copies it to
Expand Down Expand Up @@ -160,7 +160,7 @@ def on_iso_export_cube(app: Any, btn: Any) -> None:


def on_export_bundle(app: Any, btn: Any) -> None:
"""Zip the entire result folder for sharing (EXPORT.5)."""
"""Zip the entire result folder for sharing."""
from quantui.results_storage import export_result_bundle

result_dir = getattr(app, "_last_result_dir", None)
Expand Down
6 changes: 3 additions & 3 deletions quantui/app_formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def _result_extra_rows(get: Any) -> str:
``get(key, default=None)`` reads a field from either a result object
(``getattr``) or a saved ``result.json`` dict (``dict.get``). Used by BOTH
:func:`format_result` (live) and :func:`format_past_result` (history) so the
two cards can never drift again — the M-CLEAN regression where the compute
two cards can never drift again — a past regression had the compute
device / dipole / Mulliken rows existed only on the live card. Rows:
post-HF correlation breakdown (MP2 / CCSD / (T)), solvent, compute device
(always shown), dipole moment, Mulliken charges.
Expand Down Expand Up @@ -45,7 +45,7 @@ def _num(label: str, value: str) -> str:
if _solvent is not None:
rows += _num("Solvent (PCM)", str(_solvent))

# Compute device (M-GPU / GPU.2) — always shown; old saved results lack the
# Compute device — always shown; old saved results lack the
# field and safely read "CPU".
if bool(get("gpu_used", False)):
_name = get("gpu_name")
Expand Down Expand Up @@ -466,7 +466,7 @@ def format_past_result(data: dict[str, Any], result_dir: Optional[Path] = None)
ts = data.get("timestamp", "")

# Shared 'extra' rows (correlation breakdown / solvent / device / dipole /
# Mulliken) — same builder as the live card so the two never drift (M-CLEAN).
# Mulliken) — same builder as the live card so the two never drift.
_extra = _result_extra_rows(lambda k, d=None: data.get(k, d))

# Embed thumbnail if saved
Expand Down
16 changes: 8 additions & 8 deletions quantui/app_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@


class _LoadTimer:
"""Per-stage timing collector for a history-load operation (HIST.2).
"""Per-stage timing collector for a history-load operation.

Used as: open one ``_LoadTimer`` at the top of each loader, wrap each
interesting sub-stage in ``with timer.stage("name"):``, then call
``timer.emit(status=...)`` exactly once (from the loader's ``finally``
block). One ``history_load_timing`` event is appended to
``event_log.jsonl`` per load with the total elapsed time and a per-stage
breakdown. The data drives the HIST.2 latency-optimization pass — until
breakdown. The data drives the latency-optimization pass — until
we know which stage dominates, we don't know which to optimize.

Failures inside ``calc_log.log_event`` are swallowed: telemetry must
Expand Down Expand Up @@ -223,7 +223,7 @@ def mol_from_result_dir(result_dir: Path, data: dict[str, Any]) -> Any:


def _begin_history_load(app: Any, message: str, source_btns: tuple) -> None:
"""Show immediate feedback when a history-load action starts (HIST.1).
"""Show immediate feedback when a history-load action starts.

Lights the toolbar activity indicator and disables the source buttons so
a second click can't fire a parallel load. Both actions are best-effort —
Expand All @@ -242,7 +242,7 @@ def _begin_history_load(app: Any, message: str, source_btns: tuple) -> None:


def _end_history_load(app: Any, source_btns: tuple) -> None:
"""Restore UI state after a history-load action finishes (HIST.1).
"""Restore UI state after a history-load action finishes.

Mirrors :func:`_begin_history_load`. Called from the loader's ``finally``
block so the activity indicator + buttons are always restored, even when
Expand All @@ -269,11 +269,11 @@ def history_load_results(
"""Display a history result card in the Results tab and navigate there.

``source_btns`` is an optional tuple of button widgets to disable while
the load is in flight (HIST.1 immediate-loading-feedback contract). Tests
the load is in flight (immediate-loading-feedback contract). Tests
and callers that don't have a button reference can omit it.

Stage timings are emitted as a single ``history_load_timing`` event on
completion (HIST.2 — drives latency-optimization decisions).
completion (drives latency-optimization decisions).
"""
_begin_history_load(app, "Loading history result…", source_btns)
timer = _LoadTimer("history_load_results", result_dir)
Expand Down Expand Up @@ -330,11 +330,11 @@ def history_load_analysis(
"""Load analysis panels for a history result and navigate to Analysis tab.

``source_btns`` is an optional tuple of button widgets to disable while
the load is in flight (HIST.1 immediate-loading-feedback contract). Tests
the load is in flight (immediate-loading-feedback contract). Tests
and callers that don't have a button reference can omit it.

Stage timings are emitted as a single ``history_load_timing`` event on
completion (HIST.2 — drives latency-optimization decisions). Stages cover
completion (drives latency-optimization decisions). Stages cover
the four expected hotspots: pyscf.log read, context build, molecule
reconstruction, 3D viewer render, and the analysis-context registry walk.
"""
Expand Down
Loading
Loading