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
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Auto detect text files and perform LF normalization
* text=auto

# Shell scripts must always use LF — CRLF breaks bash on Linux/WSL
# Shell scripts must always use LF — CRLF breaks bash on Linux/WSL/macOS
*.sh text eol=lf
*.command text eol=lf
apptainer/*.def text eol=lf
137 changes: 133 additions & 4 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,14 @@ with transparent and extensible open tooling).
```
QuantUI/
├── quantui/ ← Main Python package (imports as `quantui`)
│ ├── app.py ← QuantUIApp class — all widgets, callbacks, state
│ ├── app.py ← QuantUIApp — orchestration + run dispatch
│ ├── app_analysis.py ← Analysis-tab panel registry + _pop_* methods
│ ├── app_builders.py ← _build_* widget construction
│ ├── app_runflow.py ← _do_run + run/orchestration UI handlers
│ ├── app_visualization.py ← Trajectory / vib / IR / orbital / PES rendering
│ ├── app_history.py ← History tab loaders, replay context
│ ├── app_formatters.py ← Result-card and text formatters
│ ├── app_exports.py ← Export handlers (XYZ/MOL/PDB/script)
│ ├── molecule.py ← Molecule dataclass + XYZ/SMILES parsing
│ ├── session_calc.py ← In-session PySCF runner (run_in_session)
│ ├── optimizer.py ← QM geometry optimization (ASE-BFGS + PySCF)
Expand All @@ -46,6 +53,9 @@ QuantUI/
│ ├── calc_log.py ← Performance + event logging (JSONL)
│ ├── pubchem.py ← PubChem molecule search
│ ├── visualization_py3dmol.py ← 3D molecular viewer (py3Dmol / plotlyMol)
│ ├── viz_backend_router.py ← Capability-aware backend router (pure function)
│ ├── user_settings.py ← Persistent user preferences (~/.quantui/settings.json)
│ ├── vib_cache.py ← On-disk cache of rendered vib-mode HTML
│ ├── ase_bridge.py ← ASE structure I/O + molecule library
│ ├── preopt.py ← ASE force-field pre-optimisation (fast, no PySCF)
│ ├── progress.py ← StepProgress widget
Expand All @@ -61,7 +71,7 @@ QuantUI/
├── notebooks/
│ ├── molecule_computations.ipynb ← Student-facing Voilà app (thin launcher)
│ └── tutorials/ ← 01–05 step-by-step tutorial notebooks
├── tests/ ← pytest suite (~875 tests)
├── tests/ ← pytest suite (~1000 tests; 97 PySCF-gated skip on Windows)
├── .github/
│ └── copilot-instructions.md ← This file
├── apptainer/
Expand All @@ -70,10 +80,21 @@ QuantUI/
├── local-setup/ ← Conda environment YAMLs
├── launch-app.bat ← Windows double-click launcher (Voilà app mode)
├── launch-dev.bat ← Windows double-click launcher (JupyterLab mode)
├── CHANGELOG.md ← Release history (Keep a Changelog format)
├── pyproject.toml ← Package config (name: quantui, imports as quantui)
└── pytest.ini ← pytest configuration
```

**Modular UI note.** `QuantUIApp` lives in `app.py`, but most of its logic is
implemented as module-level functions in `app_builders.py`, `app_analysis.py`,
`app_runflow.py`, `app_visualization.py`, `app_history.py`, `app_formatters.py`,
and `app_exports.py`. `app.py` keeps thin instance-method wrappers that pass
`self` through (e.g. `def _pop_energies(self, ctx): return _ana_pop_energies(self, ctx)`).
When working on UI changes, prefer editing the relevant companion module
rather than growing `app.py`. The conservative refactor close (DEC-014)
intentionally leaves `_do_run` and a small molecule/theme/pubchem callback
cluster in `app.py`.

---

## Architecture
Expand Down Expand Up @@ -208,6 +229,113 @@ to build the context from disk.

---

## Visualization Backend Router

3D rendering is dispatched through a pure-function router in
`quantui/viz_backend_router.py`. **Do not call py3Dmol or plotlymol3d
directly from `app_visualization.py` — always go through the router.**

### `VizTask` keys

`MOLECULE_PREVIEW`, `STRUCTURE_VIEW_RESULTS`, `ANALYSIS_STRUCTURE_VIEW`,
`HISTORY_STRUCTURE_REPLAY`, `TRAJECTORY_FRAME`, `TRAJECTORY_EXPORT`,
`VIB_INTERACTIVE`, `VIB_EXPORT`, `ORBITAL_ISOSURFACE`.

### Policy

- Each task has a `(primary, fallback)` tuple.
- User preference (`VizPreference.AUTO | PY3DMOL | PLOTLYMOL`) is respected
for tasks that support both backends.
- Single-backend tasks **ignore preference** (e.g. `TRAJECTORY_FRAME` is
py3Dmol-only — Plotly causes flicker; `ORBITAL_ISOSURFACE` is py3Dmol-only).
- `BackendAvailability.from_environment()` probes imports at startup; the
router falls back gracefully when one backend is missing.

### Usage

```python
from quantui.viz_backend_router import VizTask, select_backend
decision = app._resolve_backend(VizTask.STRUCTURE_VIEW_RESULTS)
# decision.chosen is "py3dmol", "plotlymol", or None (no renderer)
# decision.reason carries a human-readable explanation
```

`Decision` is a frozen dataclass — never mutate it.

### Calculate ↔ Analysis toggle sync

The Calculate and Analysis tabs each have a "Default 3D backend" ToggleButton.
They are kept in sync via `_set_viz_preference(...)`, gated by
`_viz_sync_in_progress` to prevent observer echo loops. The preference is
persisted to `~/.quantui/settings.json` (`viz.default_backend`).

---

## Lifecycle Telemetry

Every render-dispatch site in `app_visualization.py` is wrapped in the
`_viz_render_event(app, task, backend, **extras)` context manager. It emits
three event types to `event_log.jsonl`:

| Event | When |
| --- | --- |
| `viz_render_start` | Entry into the context |
| `viz_render_done` | Successful exit, with `elapsed_ms` |
| `viz_render_error` | Exception in the body, with `elapsed_ms` + `error` |

**All four render dispatch sites are wrapped:** static structure view,
trajectory frame, vib mode, and the vib sync cache-hit fast path. Use this
helper for any new render call so telemetry stays uniform.

---

## Persistent User Settings

`quantui/user_settings.py` provides `UserSettings.load()` /
`UserSettings.save()`, persisted to `~/.quantui/settings.json` (override with
`QUANTUI_SETTINGS_PATH` for testing).

Current schema (`_schema_version = 1`):

| Section | Field | Default | Purpose |
| --- | --- | --- | --- |
| `viz` | `default_backend` | `"auto"` | One of `auto / py3dmol / plotlymol` |
| `viz` | `vib_framerate_fps` | `10` | Clamped `[1, 120]`; included in vib cache key |

**Robustness rules:** atomic writes (`.tmp` + rename); missing file, malformed
JSON, unknown schema version, missing sections, or invalid values all fall
back to defaults with a single warning log — startup never crashes on bad
settings. Schema growth is additive; bump `_schema_version` only for
breaking changes.

---

## Vib-Animation Disk Cache

`quantui/vib_cache.py` stores rendered py3Dmol HTML for each vibrational mode
under `<result_dir>/vib_frames/`:

```
<result_dir>/
└── vib_frames/
├── index.json ← manifest (schema v1)
├── mode_001.html
└── ...
```

**Cache key:** `(result_dir, mode_number, n_frames, amplitude, renderer, fps)`.
Any parameter change → stale → cache miss → re-render.

**Sync cache-hit fast path:** `_swap_vib_output()` atomically swaps in cached
HTML without a transient "Rendering…" placeholder, eliminating flash on
mode switches.

**Staleness guard:** `_vib_render_token` is incremented on every render
request; background renders that finish after a newer token has been issued
bail out instead of overwriting newer output.

---

## Analysis Tab — 8 Panels

All 8 panels are **always in the DOM** (`layout.display=""`, `selected_index=None`).
Expand Down Expand Up @@ -527,8 +655,8 @@ Test files in `tests/`:
**PySCF-gated tests** use `@pytest.mark.skipif(not _PYSCF_AVAILABLE, ...)`.
On Windows, these become skips — not failures.

**Baseline (WSL, 2026-05-01; `python -m pytest tests/ -q --no-cov`):**
860 passed, 15 skipped (875 collected).
**Baseline (Windows `quantui-win`, 2026-05-22; `python -m pytest tests/ -q --no-cov`):**
1004 passed, 97 skipped (the 97 skips are PySCF-gated Linux-only tests).

---

Expand All @@ -553,6 +681,7 @@ Install all runtime + dev extras: `pip install -e ".[pyscf,ase,app,notebook,dev]
| --- | --- | --- |
| `QUANTUI_RESULTS_DIR` | `./results` | Where calculation results are saved |
| `QUANTUI_LOG_DIR` | `~/.quantui/logs` | Where perf_log and event_log live |
| `QUANTUI_SETTINGS_PATH` | `~/.quantui/settings.json` | User-preferences file (`user_settings.py`); override for tests |

---

Expand Down
198 changes: 198 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Changelog

All notable changes to QuantUI are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.0] - 2026-05-22

First substantial release after `v0.1.0`. The codebase moved from a single
monolithic `app.py` to a modular package, added six PySCF-backed calculation
types end-to-end, introduced a results-persistence layer with history replay,
and shipped a complete visualization stack (3D viewer with selectable backend,
trajectory animation, IR/UV-Vis/PES plots, orbital isosurfaces, vibrational
mode animation with caching). UI runs as a Voilà app suitable for classroom
deployment.

### Added

#### Calculations

- **Geometry optimization** (`optimizer.py`) — ASE-BFGS driver around a custom
PySCF calculator; per-step trajectory persisted.
- **Vibrational frequency analysis** (`freq_calc.py`) — Hessian via
`pyscf.hessian`, ZPVE, thermochemistry (H/S/G at 298 K), IR intensities via
`pyscf.prop.infrared` or a numerical-derivative fallback for compatibility
across PySCF versions.
- **TD-DFT UV-Vis** (`tddft_calc.py`) — excitation energies, oscillator
strengths, wavelengths; full spectrum plot in the Analysis tab.
- **NMR shielding** (`nmr_calc.py`) — GIAO shielding via `pyscf.nmr` (core
preferred over `pyscf-properties` to dodge a known upstream bug); ¹H/¹³C
chemical shifts relative to TMS.
- **1D PES scan** (`pes_scan.py`) — bond / angle / dihedral; energy profile +
per-step geometry animation.
- **PCM implicit solvent** — Water, Ethanol, THF, DMSO, Acetonitrile via a
single checkbox in the Calculate tab.
- **MP2** post-HF method support.

#### Analysis & visualization

- **Analysis tab with 8 always-in-DOM panels** (Energies, Trajectory,
Vibrational, IR Spectrum, PES Scan, Isosurface, UV-Vis, NMR) wired through
a `_PANEL_REGISTRY` so live runs and history replay share one code path.
- **IR spectrum chart** (`ir_plot.py`) — stick plot + Lorentzian-broadened
curve; broadening toggle and FWHM slider.
- **UV-Vis spectrum plot** — Plotly chart with wavelength/energy axes.
- **Orbital visualization** (`orbital_visualization.py`) — energy-level
diagram (matplotlib → Plotly HTML) and cube-file isosurface viewer with
HOMO-1/HOMO/LUMO/LUMO+1 toggle.
- **Trajectory animation** — atomic Output-children swap to avoid
Voilà-deferred-display blank frames; py3Dmol-only render path with
prev/next arrow navigation.
- **Vibrational mode animation** — py3Dmol multi-frame XYZ renderer with
amplitude scaling, prev/next mode nav, and dropdown skipping near-zero
modes.
- **3D visualization backend router** (`viz_backend_router.py`) — pure
function that picks py3Dmol or plotlymol3d per `VizTask` based on user
preference and runtime availability; immutable `Decision` carries chosen
backend, fallback, and reason.
- **Lifecycle telemetry** — `_viz_render_event` context manager emits
`viz_render_start` / `viz_render_done` / `viz_render_error` JSONL events
with backend, task, `elapsed_ms`, and extras at every render dispatch.
- **Side-by-side Compare tab** — pick any two saved calculations and view a
diff table.

#### Persistence & logging

- **Results storage** (`results_storage.py`) — every run is saved to a
timestamped directory containing `result.json` (schema v2, additive-only),
`pyscf.log`, optional `trajectory.json` / `orbitals.npz` / `thumbnail.png`.
- **History tab** — browse and replay saved calculations after a kernel
restart; replay path is identical to live-run analysis activation.
- **Performance log** (`calc_log.py`) — `perf_log.jsonl` per converged run +
`event_log.jsonl` for startup/calc/error events; 7-day auto-prune.
- **Time estimator** — 4-strategy priority chain (N_basis-normalised → cross-method
electron-count) populates "Estimated time" before each run.
- **Benchmark suite** (`benchmarks.py`) — one-click calibration suite to
populate the time-estimator history with real machine data.
- **Issue tracker** (`issue_tracker.py`) — in-app bug-report UI writing to a
local `issues.db`.
- **Persistent user settings** (`user_settings.py`) — stored at
`~/.quantui/settings.json` (override via `QUANTUI_SETTINGS_PATH`). Schema
is section-based for additive growth, with atomic writes and graceful
fallback to defaults on corruption.
- **Vibrational-animation disk cache** (`vib_cache.py`) — per-result-dir
`vib_frames/` of pre-rendered py3Dmol HTML keyed by
`(mode, n_frames, amplitude, renderer, fps)`. Mode switches on repeat
visits and history replay are instant.
- **Vib FPS user preference** — `viz.vib_framerate_fps` exposed as an
IntSlider in the Status tab (clamped 1–120, default 10); included in the
vib cache key so changing FPS invalidates cleanly.

#### UI

- **Modular UI package** — `app.py` (orchestration) plus `app_analysis.py`,
`app_builders.py`, `app_exports.py`, `app_formatters.py`, `app_history.py`,
`app_runflow.py`, `app_visualization.py`.
- **Seven-tab layout** — Calculate, Results, Analysis, History, Compare, Log,
Status — with a floating Help overlay (not a tab).
- **Light / Dark theme selector** — dark by default on startup.
- **Status tab** — environment info, performance-history accordion (two-step
reset), default-3D-backend toggle, vib-FPS slider.
- **Files tab + activity indicator** for browsing saved results.
- **Plot export UI** — save IR, UV-Vis, PES, orbital diagram plots as HTML.
- **Scroll guard** for the run output area to keep long PySCF logs from
jumping the page.
- **Welcome header**, completion banner, structured log header/footer.
- **Compare-tab Copy-path button** (replaced a broken Open-folder action).
- **Result directory label + log accordion** showing inline `pyscf.log`.
- **Structure exports** — XYZ, MOL/SDF, PDB, plus a standalone runnable `.py`
script export.

#### Tooling & dev

- **Test suite grew from a handful to 1004 passed / 97 skipped** (Windows
`quantui-win` env baseline; the 97 skips are PySCF-gated Linux-only tests).
- New analysis-history end-to-end tests for every calc type
(`test_sp_analysis_history.py`, `test_geo_opt_analysis_history.py`,
`test_freq_analysis_history.py`, `test_tddft_analysis_history.py`,
`test_nmr_analysis_history.py`, `test_pes_scan_analysis_history.py`).
- `test_code_quality.py` enforces:
- No `include_plotlyjs="cdn"` anywhere (fails silently in offline Voilà).
- No bare `except: pass` blocks.
- `test_viz_backend_router.py` + `test_viz_backend_sync.py` — full
task × preference × availability matrix and Calculate/Analysis toggle sync.
- `test_vib_cache.py`, `test_vib_py3dmol_render.py`,
`test_viz_render_telemetry.py` — vib animation + telemetry coverage.
- `_layout(...)` helper sanitises `widgets.Layout` kwargs to eliminate a
4808 → 13 traitlets warning regression.
- `_safe_cb` wrapper around every `.observe()` callback so exceptions surface
in the Log tab instead of disappearing into the Voilà kernel console.
- Kernel `io_loop` is cached at startup; thread-spawned callbacks are queued
onto the main thread to avoid `RuntimeError: no current event loop`.
- Native launchers: `launch-native.bat` (Windows / WSL) and
`launch-native.command` (macOS / Linux) — double-clickable, port `8867`,
stamp-based editable-install skip, browser auto-open. README documents
pinning each to the Start menu / Dock as a real app.
- Native JupyterLab launcher (`launch-native-jupyter.bat`) and Apptainer
launcher improvements.

#### Docs

- `.github/copilot-instructions.md` — canonical AI-assistant context (now
the single source of truth for any AI assistant working on this repo).
- `CLAUDE.md` — Claude-specific session/workflow context (git-ignored).
- Site favicons (ICO + SVG) for the GitHub Pages docs site.

### Changed

- **Visualization is py3Dmol-first.** `plotlymol3d` remains an optional
fallback for non-trajectory tasks; trajectory rendering is hard-wired to
py3Dmol to avoid Plotly/RequireJS flicker.
- **Plotly figures are rendered via `plotly.io.to_html(..., include_plotlyjs="require")`**
inside `widgets.HTML`, not `display(fig)`, so threaded renders work and
offline Voilà loads correctly.
- **`pyscf` is now an optional extra** (`pip install quantui[pyscf]`); the
package imports cleanly on Windows with PySCF unavailable.
- Repo renamed from `QuantUI-local` to `QuantUI`.

### Fixed

- **Trajectory accordion blank on first expand** — switched `traj_output`
from `Output` to `VBox` and use atomic children-swap so deferred
widget-display is no longer a blank-frame risk.
- **Vib mode races on rapid switching** — render-token guard (`_vib_render_token`)
causes stale background renders to bail rather than overwriting newer
output.
- **Camera state lost on mode switch** — JS hook caches the active
`$3Dmol.GLViewer` state across atomic HTML swaps; reset only on a
genuinely new frequency result.
- **PySCF API drift** — robust handling for v2 NMR / thermo API and the
`pyscf.prop.infrared` rename; both `Infrared.kernel()` and the older
IR API are supported.
- **Result-dir name collisions** — timestamps now include microseconds; same
formula + method + basis no longer overwrite each other.
- **IR x-axis** — corrected wavenumber axis on the IR Plotly figure.
- **Plotly figures invisible after accordion show** — figures are re-rendered
on accordion expand to handle RequireJS / display-deferral edge cases.

### Removed

- `visualization.py` (PlotlyMol fallback) — replaced by the router-backed
`visualization_py3dmol.py` path.
- All SLURM-era infrastructure already removed during the downstream port:
`job_manager.py`, `storage.py`, `slurm_errors.py`, SLURM config templates.

## [0.1.0] - 2026

Initial public scaffolding of the QuantUI package: `quantui` package with
`molecule.py`, `pubchem.py`, `config.py`, `visualization_py3dmol.py`,
`calculator.py`, basic notebook launcher, Apptainer container definition,
MIT license, and project metadata.

[Unreleased]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.1.0...v0.2.0
[0.1.0]: https://github.com/The-Schultz-Lab/QuantUI/releases/tag/v0.1.0
Loading
Loading