Interactive visualization + robust offline operation#18
Merged
Conversation
Introduce a py3Dmol-based renderer and route selection for orbital isosurfaces. - Add render_orbital_isosurface_py3dmol(...) in quantui/orbital_visualization.py to load the full-resolution .cube, create ± isosurfaces in-browser, and return self-contained HTML via py3Dmol._make_html(). - Update quantui/app_visualization.py to resolve the visualization backend for ORBITAL_ISOSURFACE, save the cube to disk, and dispatch rendering to either the new py3Dmol path or the existing Plotly fallback (wrapped in a viz render event). - Update quantui/viz_backend_router.py policy to prefer PY3DMOL for VizTask.ORBITAL_ISOSURFACE with Plotly as fallback. - Tests updated: added py3Dmol-only tests for the new renderer, added a test ensuring the py3Dmol path is used and the cube file is saved, and adjusted existing isosurface test to pin the Plotly fallback. This change enables full-resolution, in-browser isosurfacing via py3Dmol while retaining Plotly as a universal fallback.
Bundle vendored 3Dmol.js and related provenance/license files and include them in the wheel (pyproject.toml). Add quantui.viz_assets to provide an offline-safe bootstrap and helper make_view/standalone_html utilities. Update app and visualization modules to inject the offline 3Dmol loader on display, use make_view for all py3Dmol views, and embed the loader into exported standalone HTML. Add tests for offline viz and other small refactors. The bootstrap is guarded with a try/except and logged on failure so app rendering is not blocked.
Add pytest-xdist as a dev dependency and enable parallel test execution (-n=auto) to speed up the common pytest loop. Tweak addopts to be quieter by default (-q) and remove default coverage instrumentation (CI still runs coverage explicitly). Document how to run coverage or run tests serially for debugging. In tests/test_viz_render_telemetry.py, import time and add a wait loop to ensure the daemon render thread finishes (viz_render_done or viz_render_error) before asserting, preventing intermittent races under heavy parallel test load.
Surface where resolved structures actually came from (PubChem / CACTUS / SMILES / Library / Library (offline)) and show a no-network note for offline fallback. Introduce resolve_structure_with_message returning (xyz, message, source, is_offline) and keep student_friendly_resolve as a 2-tuple wrapper; update app code to use the provenance and label loaded molecules accordingly. Light and reliably end the toolbar activity indicator during the search/resolve lifecycle, and persist user backend choices when toggles are used (sync Settings/Calculate/Analysis). Also clarify frequency-calculation status text to indicate "finite-difference displacement SCFs done (6 per atom)". Add tests covering provenance, activity balancing, and backend-persistence.
Replace the old ASE Lennard-Jones pre-optimizer with a bonded RDKit force-field (MMFF94, fallback UFF) preopt implementation that preserves connectivity and never degrades input geometries (non-destructive fallback if RDKit/bond perception/params are unavailable). Factor result-card "extra" rows into a shared _result_extra_rows builder and persist matching fields (solvent, gpu_used/gpu_name, dipole, Mulliken, atom_symbols) to saved results with JSON-safe coercion helpers so live/history cards remain in parity (M-CLEAN). Add apptainer build-time check for offline 3Dmol bootstrap. Update tests to reflect RDKit-based preopt, add formatter parity tests, isolate settings in viz tests, and add deterministic vib-loading lifecycle tests.
Make py3Dmol work reliably offline by switching to a per-view vendored JS data: URI rather than a startup-time page bootstrap. Changes in quantui/viz_assets.py embed the packaged 3Dmol bundle as a base64 data: URI returned by _js_data_uri(), make_view now uses that URI by default, standalone_html becomes a pass-through, and offline bootstrap logic and sentinel constants were removed. quantui/app.py no longer injects a one-time bootstrap (avoids breaking Voilà/RequireJS startup). Launchers (mac/linux/win) and the native apptainer build check were made resilient to offline environments: editable pip reinstalls run with a short timeout/no retries and are non-fatal so launches don't hang when there's no network. Tests updated to assert views load the vendored data: URI and not the CDN, and a new scripts/test_offline_startup.py probe was added to simulate no-network startup and log any attempted network calls. Minor logging and error-handling improvements added where the vendored bundle may be missing.
Ensure .dev_install_stamp is touched even when editable pip reinstall fails to avoid repeated retries/delays on offline launches. Updated launch-native-jupyter.sh and launch-native.command to touch the stamp in the failure branch and added explanatory comments. Expanded the WSL command and comments in launch-native.bat and make the failure branch echo the message and touch the stamp (so a one-time attempted reinstall won't cause every subsequent offline launch to retry pip). Note: re-run `pip install -e .` manually when online if you add real dependencies.
Update docs and packaging to describe the new offline-first features and release bump to v0.3.0. Changes include: README additions describing a bundled three-tier molecule library, vendored 3Dmol.js, expanded structure-search fallback (PubChem → CACTUS → offline), added CCSD/CCSD(T) to capability matrix, new module references (cactus, structure_providers, molecule_library, viz_assets), and updated test counts. apptainer/README.md documents RDKit inclusion, offline behavior, longer build time, and an offline 3Dmol.js check; quantui.def label bumped to Version "0.3.0". docs/CLI.md log example and docs/index.html badges/features updated to reflect v0.3.0, offline capability, and test count.
Implement cooperative calculation cancellation and prevent clearing live output mid-run. Introduces _CalcCancelled and a cancel_check in _LogCapture so running calculations can be stopped at the next output line; the cancel exception inherits BaseException to bypass broad Exception handlers. Adds a Cancel widget, wiring and UI state (_cancel_event, _calc_running) in QuantUIApp, passes cancel_check into _LogCapture, and handles _CalcCancelled in _do_run to show a cancelled message and log the event. Also disables the Clear button during runs and adds a guard in on_clear_log to avoid wiping in-progress output. Includes unit tests (tests/test_cancel_and_clear_guard.py) for the capture cancellation, cancel handler, and clear-while-running guard.
Introduce an interactive "Preview" flow for the classical bonded-FF pre-optimization so users can watch a fast MMFF94/UFF relaxation, then keep or revert the relaxed geometry. Key changes: - UI: add Preview/Keep/Revert buttons and preview box; wire callbacks and hide stale previews on load (quantui/app.py, quantui/app_builders.py). - Runflow: implement preview handlers and background worker to run a frame-capturing FF relax, marshal results to the main thread, and surface success/failure states (quantui/app_runflow.py). - Preopt backend: extend FF relax to optionally capture per-iteration frames, add preoptimize_with_trajectory returning (molecule, rmsd, frames), enforce a frame cap and non-destructive fallbacks when RDKit is unavailable (quantui/preopt.py). - Visualization: build_preopt_preview_html to render offline-safe animated views from captured frames (quantui/app_visualization.py). - Docs/tests: add help text explaining the preview flow and a test suite covering trajectory capture, renderer output, and preview/accept/revert handlers (quantui/help_content.py, tests/test_preopt_preview.py). Notes: preview is non-destructive on failure, handles missing RDKit gracefully, and disables auto pre-opt when a previewed geometry is accepted.
Introduce client-side interactive controls for the classical pre-optimization preview. The viewer now loads all frames with addModelsAsFrames and a small JS stepper (prev/next, play/pause, scrub slider, and input↔relaxed flip) drives viewer.setFrame for instant offline frame navigation; falls back to animate() if a viewer id can't be found. Adjust viewer/layout heights and update the preopt status text and help copy to reference the new controls. Add tests covering multi-frame interactive stepper behavior and add .claudeignore to .gitignore.
Replace the old per-iteration Minimize(maxIts=1) snapshotting with a more robust preview pipeline: capture fresh minimizations at increasing iteration budgets, limit work with a wall-clock time budget, and select displayed frames at (approximately) even RMSD spacing. Introduce _PREVIEW_FRAMES, _PREVIEW_TIME_BUDGET_S, _preview_iter_grid and _select_even_rmsd_frames and update _rdkit_ff_relax to return the final geometry plus the selected frames. Tweak playback JS to replay from the input geometry when starting at the end and to stop on the relaxed frame rather than looping. Add tests (including an RDKit embed helper) to verify final-frame consistency and that motion is distributed across frames and that the even-RMSD selector behaves correctly.
Clear lingering run_status when loading/reverting molecules and avoid reraise during cancel cleanup. - quantui/app.py: When setting a new molecule or reverting a preview, clear run_status.value unless a calculation is running (guarded on _calc_running) so a previous "Pre-optimized geometry accepted." doesn't persist or wipe a live "Pre-optimizing…" status. In the cancel handler, clear the cancel event before writing the cancellation footer to the shared log to prevent _CalcCancelled from being re-raised during the cleanup write. - quantui/app_runflow.py: Ensure on_preopt_reset also drops stale run_status when no calculation is running. - tests: Add tests to verify the cancel cleanup write does not re-raise and to cover clearing/retaining run_status in the various molecule load/reset scenarios. These changes prevent a stuck "Cancelling…" state and stale acceptance messages after switching molecules or reverting previews.
Introduce a single persistent py3Dmol viewer for vibrational animations that holds all modes and switches frames client-side (window.__quantuiVibSetMode) so the camera is preserved across mode changes. Adds embedded JS (_VIB_VIEWER_JS) and helpers (build_vib_viewer_html, _vib_single_viewer_supported, _render_vib_single_viewer, _vib_bridge_set_mode) to build offline-safe HTML, detect support, and swap the viewer; falls back to the legacy per-mode renderer on failure. Wire the new client-side bridge into the UI by adding a hidden Output widget in the results accordion and update show_vib_animation/on_vib_mode_changed to prefer the single-viewer path. Tests added to validate the single-viewer behaviour and displacement requirements; small test formatting tweak for textwrap.dedent.
Move slow startup work off the synchronous construction path to improve perceived startup time. GPU detection now runs on a daemon thread (named quantui-gpu-detect) and the Status panel is rendered via a closure that accepts a gpu_state so the UI can show a "checking…" placeholder and be re-rendered when detection completes. History and Compare population are deferred onto the kernel I/O loop (or run inline if no loop) and show loading placeholders until populated. Also hardens result-label formatting in runflow to use safe .get() defaults for formula/method/basis to avoid KeyErrors. Adds tests (tests/test_startup_deferral.py) that assert placeholders, scheduling of deferred callbacks, and status rendering behavior.
Remove the silent classical pre-optimization checkbox and make MMFF/UFF pre-optimizations an explicit Preview → Keep/Revert workflow. Updates include: probing preopt availability in app.py (no automatic invocation), replacing the checkbox with a descriptive label and Preview button in app_builders.py, stopping automatic pre-opt in the run path in app.py/app_runflow.py, updating help text to describe the Preview flow, and adapting tests to the new behaviour. This avoids invisible geometry changes and the confusing dual path of preview+silent re-opt.
Stop accumulating animation loops by calling stopAnimate() before each animate() in the vibrational viewer JS. Add a client-side window.__quantuiVibSetFps(fps) function to update the animation interval and restart the loop without rebuilding (camera preserved). Expose a Python bridge _vib_bridge_set_fps that sends the FPS change to the frontend and wire app.py to call it for the single-viewer path. Update tests to check for the new stopAnimate and fps-change hooks and tidy a dedent call in an unrelated test.
Add a helper _to_numpy_array to safely convert arrays (including CuPy GPU arrays) to NumPy on the host and use it throughout session_calc. Use the helper for early extraction of mf.mo_energy / mf.mo_occ (including UHF handling), Mulliken charges and dipole moment. Fall back to mf.to_cpu() when population analysis is not implemented on the GPU object. Removes a duplicate helper and addresses a silent bug where GPU-offloaded CuPy arrays caused MO, charge and dipole fields to be lost.
Prepare and document the 0.4.0 release: update package version in pyproject.toml and quantui.__init__.py, update the Apptainer image label, and add a detailed 0.4.0 entry to CHANGELOG.md. The changelog highlights interactive MO isosurfaces (py3Dmol), an interactive classical pre-optimization preview workflow, single persistent 3D viewers with preserved camera state, offline-first 3D rendering, faster startup, a cooperative Cancel button, and several bug fixes (GPU result extraction, vibrational animation, cancel/stale status handling, and provenance persistence).
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.
This pull request delivers a major release focused on interactive visualization and robust offline operation. It introduces molecular-orbital isosurface viewing, an interactive geometry pre-optimization preview, and a complete overhaul of 3D rendering to work fully offline by vendoring 3Dmol.js. The molecule input workflow is expanded with a large, searchable offline library and improved structure search fallback. The documentation, container build, and launch scripts are updated to reflect these new features and to ensure seamless operation in air-gapped environments.
Major new features:
CHANGELOG.md,README.md,docs/index.html) [1] [2] [3] [4]CHANGELOG.md)Offline-first and library enhancements:
CHANGELOG.md,README.md,apptainer/README.md,apptainer/quantui.def) [1] [2] [3] [4] [5]README.md,docs/index.html) [1] [2] [3]apptainer/README.md,apptainer/quantui.def) [1] [2] [3]Calculation and method improvements:
README.md,apptainer/README.md) [1] [2] [3]CHANGELOG.md)Build, test, and launch process updates:
README.md,docs/index.html) [1] [2]launch-native-jupyter.sh,launch-native.bat) [1] [2]Documentation and versioning:
CHANGELOG.md,README.md,docs/index.html,apptainer/README.md,apptainer/quantui.def,docs/CLI.md) [1] [2] [3] [4] [5] [6] [7]This release is a significant step forward for interactive quantum chemistry education and research, especially in restricted or offline environments.