quantui 0.7.0__tar.gz → 0.8.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {quantui-0.7.0 → quantui-0.8.2}/CHANGELOG.md +183 -0
  2. {quantui-0.7.0/quantui.egg-info → quantui-0.8.2}/PKG-INFO +87 -9
  3. {quantui-0.7.0 → quantui-0.8.2}/README.md +80 -6
  4. {quantui-0.7.0 → quantui-0.8.2}/pyproject.toml +20 -4
  5. {quantui-0.7.0 → quantui-0.8.2}/quantui/__init__.py +1 -1
  6. {quantui-0.7.0 → quantui-0.8.2}/quantui/analytics.py +28 -16
  7. {quantui-0.7.0 → quantui-0.8.2}/quantui/app.py +1433 -236
  8. {quantui-0.7.0 → quantui-0.8.2}/quantui/app_analysis.py +381 -40
  9. {quantui-0.7.0 → quantui-0.8.2}/quantui/app_builders.py +986 -116
  10. quantui-0.8.2/quantui/app_exports.py +916 -0
  11. {quantui-0.7.0 → quantui-0.8.2}/quantui/app_formatters.py +244 -151
  12. {quantui-0.7.0 → quantui-0.8.2}/quantui/app_history.py +15 -2
  13. quantui-0.8.2/quantui/app_launcher.py +375 -0
  14. quantui-0.8.2/quantui/app_measurement.py +425 -0
  15. quantui-0.8.2/quantui/app_pes_pick.py +134 -0
  16. {quantui-0.7.0 → quantui-0.8.2}/quantui/app_runflow.py +925 -103
  17. quantui-0.8.2/quantui/app_slurm.py +667 -0
  18. {quantui-0.7.0 → quantui-0.8.2}/quantui/app_visualization.py +568 -57
  19. quantui-0.8.2/quantui/app_xyz_input.py +252 -0
  20. quantui-0.8.2/quantui/backends/__init__.py +37 -0
  21. quantui-0.8.2/quantui/backends/base.py +111 -0
  22. quantui-0.8.2/quantui/backends/cluster_config.py +159 -0
  23. quantui-0.8.2/quantui/backends/cluster_security.py +149 -0
  24. quantui-0.8.2/quantui/backends/dispatch.py +193 -0
  25. quantui-0.8.2/quantui/backends/local.py +46 -0
  26. quantui-0.8.2/quantui/backends/registry.py +206 -0
  27. quantui-0.8.2/quantui/backends/slurm.py +642 -0
  28. quantui-0.8.2/quantui/backends/slurm_errors.py +205 -0
  29. quantui-0.8.2/quantui/backends/slurm_ingest.py +242 -0
  30. quantui-0.8.2/quantui/backends/slurm_utils.py +184 -0
  31. quantui-0.8.2/quantui/backends/worker.py +479 -0
  32. quantui-0.8.2/quantui/backends/worker_payload.py +287 -0
  33. {quantui-0.7.0 → quantui-0.8.2}/quantui/benchmarks.py +25 -4
  34. {quantui-0.7.0 → quantui-0.8.2}/quantui/calc_log.py +160 -18
  35. {quantui-0.7.0 → quantui-0.8.2}/quantui/calculator.py +8 -0
  36. {quantui-0.7.0 → quantui-0.8.2}/quantui/checkpoint.py +93 -9
  37. {quantui-0.7.0 → quantui-0.8.2}/quantui/cli.py +68 -3
  38. {quantui-0.7.0 → quantui-0.8.2}/quantui/config.py +1 -0
  39. quantui-0.8.2/quantui/connectivity.py +294 -0
  40. {quantui-0.7.0 → quantui-0.8.2}/quantui/data/library/library.sqlite +0 -0
  41. quantui-0.8.2/quantui/data/manifests/inorganic.json +1412 -0
  42. quantui-0.8.2/quantui/density_fitting.py +89 -0
  43. {quantui-0.7.0 → quantui-0.8.2}/quantui/descriptor_cards.py +23 -10
  44. {quantui-0.7.0 → quantui-0.8.2}/quantui/estimator_eval.py +12 -8
  45. {quantui-0.7.0 → quantui-0.8.2}/quantui/freq_calc.py +220 -15
  46. {quantui-0.7.0 → quantui-0.8.2}/quantui/freq_ir_workers.py +50 -20
  47. quantui-0.8.2/quantui/freq_raman_workers.py +103 -0
  48. {quantui-0.7.0 → quantui-0.8.2}/quantui/help_content.py +240 -14
  49. quantui-0.8.2/quantui/inorganic_guards.py +146 -0
  50. {quantui-0.7.0 → quantui-0.8.2}/quantui/ir_plot.py +2 -1
  51. {quantui-0.7.0 → quantui-0.8.2}/quantui/live_log.py +1 -1
  52. {quantui-0.7.0 → quantui-0.8.2}/quantui/log_utils.py +43 -3
  53. quantui-0.8.2/quantui/measurement.py +80 -0
  54. {quantui-0.7.0 → quantui-0.8.2}/quantui/molecule.py +14 -27
  55. quantui-0.8.2/quantui/mulliken_plot.py +58 -0
  56. {quantui-0.7.0 → quantui-0.8.2}/quantui/nmr_calc.py +17 -0
  57. quantui-0.8.2/quantui/nmr_plot.py +118 -0
  58. {quantui-0.7.0 → quantui-0.8.2}/quantui/optimizer.py +75 -1
  59. {quantui-0.7.0 → quantui-0.8.2}/quantui/orbital_visualization.py +215 -26
  60. {quantui-0.7.0 → quantui-0.8.2}/quantui/pes_scan.py +15 -1
  61. quantui-0.8.2/quantui/pes_scan_ui.py +384 -0
  62. quantui-0.8.2/quantui/populations_overlay.py +337 -0
  63. {quantui-0.7.0 → quantui-0.8.2}/quantui/preopt.py +222 -11
  64. {quantui-0.7.0 → quantui-0.8.2}/quantui/progress.py +8 -5
  65. {quantui-0.7.0 → quantui-0.8.2}/quantui/pubchem.py +30 -4
  66. quantui-0.8.2/quantui/raman_calc.py +403 -0
  67. quantui-0.8.2/quantui/raman_plot.py +110 -0
  68. {quantui-0.7.0 → quantui-0.8.2}/quantui/reorganization_energy.py +76 -1
  69. {quantui-0.7.0 → quantui-0.8.2}/quantui/results_storage.py +93 -3
  70. {quantui-0.7.0 → quantui-0.8.2}/quantui/session_calc.py +76 -10
  71. quantui-0.8.2/quantui/spin_presets.py +276 -0
  72. {quantui-0.7.0 → quantui-0.8.2}/quantui/tddft_calc.py +17 -1
  73. quantui-0.8.2/quantui/theme.py +742 -0
  74. {quantui-0.7.0 → quantui-0.8.2}/quantui/user_settings.py +114 -1
  75. {quantui-0.7.0 → quantui-0.8.2}/quantui/vib_cache.py +9 -2
  76. {quantui-0.7.0 → quantui-0.8.2}/quantui/visualization_py3dmol.py +240 -43
  77. quantui-0.8.2/quantui/xyz_input.py +211 -0
  78. {quantui-0.7.0 → quantui-0.8.2/quantui.egg-info}/PKG-INFO +87 -9
  79. {quantui-0.7.0 → quantui-0.8.2}/quantui.egg-info/SOURCES.txt +32 -0
  80. {quantui-0.7.0 → quantui-0.8.2}/quantui.egg-info/requires.txt +7 -2
  81. quantui-0.7.0/quantui/app_exports.py +0 -318
  82. quantui-0.7.0/quantui/theme.py +0 -125
  83. {quantui-0.7.0 → quantui-0.8.2}/LICENSE +0 -0
  84. {quantui-0.7.0 → quantui-0.8.2}/MANIFEST.in +0 -0
  85. {quantui-0.7.0 → quantui-0.8.2}/SECURITY.md +0 -0
  86. {quantui-0.7.0 → quantui-0.8.2}/quantui/ase_bridge.py +0 -0
  87. {quantui-0.7.0 → quantui-0.8.2}/quantui/c_stderr.py +0 -0
  88. {quantui-0.7.0 → quantui-0.8.2}/quantui/cactus.py +0 -0
  89. {quantui-0.7.0 → quantui-0.8.2}/quantui/cancellation.py +0 -0
  90. {quantui-0.7.0 → quantui-0.8.2}/quantui/comparison.py +0 -0
  91. {quantui-0.7.0 → quantui-0.8.2}/quantui/data/js/3Dmol-min.js +0 -0
  92. {quantui-0.7.0 → quantui-0.8.2}/quantui/data/js/3Dmol-min.js.LICENSE.txt +0 -0
  93. {quantui-0.7.0 → quantui-0.8.2}/quantui/data/manifests/bulk_qm9.json +0 -0
  94. {quantui-0.7.0 → quantui-0.8.2}/quantui/data/manifests/curated.json +0 -0
  95. {quantui-0.7.0 → quantui-0.8.2}/quantui/data/manifests/presets.json +0 -0
  96. {quantui-0.7.0 → quantui-0.8.2}/quantui/gpu_offload.py +0 -0
  97. {quantui-0.7.0 → quantui-0.8.2}/quantui/issue_tracker.py +0 -0
  98. {quantui-0.7.0 → quantui-0.8.2}/quantui/molecule_library.py +0 -0
  99. {quantui-0.7.0 → quantui-0.8.2}/quantui/security.py +0 -0
  100. {quantui-0.7.0 → quantui-0.8.2}/quantui/structure_providers.py +0 -0
  101. {quantui-0.7.0 → quantui-0.8.2}/quantui/utils.py +0 -0
  102. {quantui-0.7.0 → quantui-0.8.2}/quantui/viz_assets.py +0 -0
  103. {quantui-0.7.0 → quantui-0.8.2}/quantui/viz_backend_router.py +0 -0
  104. {quantui-0.7.0 → quantui-0.8.2}/quantui.egg-info/dependency_links.txt +0 -0
  105. {quantui-0.7.0 → quantui-0.8.2}/quantui.egg-info/entry_points.txt +0 -0
  106. {quantui-0.7.0 → quantui-0.8.2}/quantui.egg-info/top_level.txt +0 -0
  107. {quantui-0.7.0 → quantui-0.8.2}/setup.cfg +0 -0
@@ -7,6 +7,189 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.2] - 2026-08-30
11
+
12
+ ### Added
13
+
14
+ - **SLURM batch execution (M-CLUSTER2)** — optional cluster backend when
15
+ `QUANTUI_ENABLE_SLURM=1` and `sbatch` is on PATH: submit from the same UI,
16
+ monitor on the **Cluster Jobs** tab, hybrid `squeue`/`sacct` polling, submit
17
+ cooldown, and stale-registry cleanup. All Calculate-tab calc types supported
18
+ on the batch worker path.
19
+ - **`QUANTUI_ENABLE_SLURM` site gate** — SLURM batch stays hidden and
20
+ undispatchable until an operator sets this environment variable. Student CPU
21
+ images and JupyterHub profiles can omit it (default off); instructors enable
22
+ cluster testing with `QUANTUI_ENABLE_SLURM=1` without any extra student
23
+ command.
24
+ - **`quantui setup` and `quantui run app`** — writes `~/QuantUI.ipynb` for
25
+ NCShare/JupyterLab (Render with Voilà). `run app` detects Apptainer +
26
+ Jupyter and exits with Voilà instructions instead of opening an unreachable
27
+ second port.
28
+ - **Parallel IR intensity displacements (CPU)** — Settings checkbox
29
+ (`compute.freq_parallel`) plus `QUANTUI_FREQ_PARALLEL` env override fans out
30
+ the 6N finite-difference SCF loop on hosts with ≥4 cores and ≥2 atoms. The
31
+ **CPU teaching SIF** sets `QUANTUI_FREQ_PARALLEL=1` by default; the checkbox
32
+ reflects the deployment value and locks while the env var is set.
33
+ - **Imaginary-mode perturbation** for Frequency reruns seeded from History.
34
+ - **Stage-aware frequency cost estimate** and per-phase run status labels (NMR,
35
+ Hessian, post-HF).
36
+ - **Preset theme palettes** replacing the invert filter (M-THEME THEME.6).
37
+ - **Quantum engine registry** (`quantui/engines/`) and
38
+ `ComputeSettings.quantum_engine` field (M-PYFOCK PYF.1 groundwork).
39
+ - **Static Raman spectrum** on the Analysis tab (M-SPECTRA2).
40
+
41
+ ### Fixed
42
+
43
+ - **Mulliken 3D viewer** could render blank after geometry persistence and lazy
44
+ panel activation (#103).
45
+ - **`apptainer/quantui-gpu.def` never installed the `xtb` extra**, so GFN-FF
46
+ metal-capable classical pre-optimization (DEC-018) was unavailable in every
47
+ GPU/HPC image — the app correctly reported the honest-failure message
48
+ ("RDKit could not perceive bonds ... and the GFN-FF (xtb) metal backend is
49
+ not installed"), but the underlying cause was a container gap, not a
50
+ missing local install. Fixed by pinning the image's Python to 3.11 via the
51
+ deadsnakes PPA (Ubuntu 24.04's own repos ship 3.12 only, and `xtb`'s PyPI
52
+ wheels stop at `cp311`) and adding `xtb` to the pip extras. `quantui.def`
53
+ (the CPU teaching image) gained the equivalent `xtb-python` conda-forge
54
+ package, which it had also been missing despite `local-setup/environment.yml`
55
+ already carrying it for local conda installs.
56
+
57
+ ## [0.8.1] - 2026-08-26
58
+
59
+ ### Added
60
+
61
+ - **PNG export on the geometry-optimization trajectory, vibrational mode, and
62
+ molecule viewers** — the same camera-accurate Save-PNG capture already
63
+ available on the orbital isosurface and reorganization-energy geometry
64
+ viewers now covers every 3-D viewer in the app. The molecule viewer (shown
65
+ on the Calculate, Results, and Analysis tabs) captures only on the py3Dmol
66
+ backend, since Plotly already provides its own "download plot as PNG"
67
+ button; each of its three tabs gets an independent capture button so a
68
+ save from one tab can never be mislabeled with another tab's molecule or
69
+ overwrite another tab's file.
70
+
71
+ ## [0.8.0] - 2026-08-25
72
+
73
+ ### Added
74
+
75
+ - **Transition-metal and coordination-complex support.** QuantUI now
76
+ recognizes and handles metal centers throughout the pipeline instead of
77
+ erroring or silently mishandling them: a pre-run guard catches
78
+ basis-coverage and charge/multiplicity problems before a run starts (with a
79
+ one-click "Switch to def2-SVP" fix when that alone resolves it); a GFN-FF
80
+ (xtb) pre-optimization backend actually relaxes coordination complexes,
81
+ since RDKit's organic valence model can't; the 3D viewer falls back to
82
+ py3Dmol instead of showing a "Visualization failed" box; a fetched
83
+ structure that resolves to a disconnected salt (e.g. cisplatin as separate
84
+ NH₃/HCl/Pt fragments) is flagged before you compute the wrong geometry;
85
+ coordination bonds are drawn dashed since 3Dmol.js's own bond perception
86
+ draws none to a metal; and an oxidation-state → spin-state multiplicity
87
+ suggestion engine proposes — never auto-sets — physically reasonable
88
+ multiplicities, naming the strong- vs weak-field ligand ambiguity where one
89
+ exists.
90
+ - **14 bundled inorganic examples** (up from 3), GFN-FF-validated, spanning
91
+ octahedral (aqua and cyanide, high- and low-spin), tetrahedral, and
92
+ square-planar geometries — a teaching spread across spin states and
93
+ charges.
94
+ - **Density fitting (RI)** as an opt-in SCF speedup — a large win for TD-DFT
95
+ and larger systems, roughly neutral for small single points, so it defaults
96
+ off. Shown on/off in the run-header provenance banner and flagged on the
97
+ result card ("⚡ RI") whenever it was actually applied.
98
+ - **Mulliken Populations Analysis panel.** Per-atom Mulliken charges as a
99
+ table and bar chart after Single Point and Geometry Optimization runs, with
100
+ its own 3D viewer below the energy-level diagram: atoms colored by charge,
101
+ a center-of-mass dipole arrow, a color-vividness slider, and a
102
+ magnitude-only fallback when an older History result has ‖μ‖ but no saved
103
+ vector.
104
+ - **NMR stick plot** with a ¹H/¹³C nucleus toggle, mirroring the existing
105
+ IR/UV-Vis panels; UV-Vis gains λ min/max axis-bound inputs.
106
+ - **Click-to-measure atom selection** on the Analysis-tab viewer — click 2, 3,
107
+ or 4 atoms to read a bond length, angle, or dihedral, GaussView-style
108
+ (py3Dmol only for now; the Plotly viewer shows an explanatory message
109
+ instead of a silent no-op).
110
+ - **Interactive XYZ input.** An atom-row builder and a cleanup-coordinates
111
+ accept/reject preview on the Calculate tab. Loading no longer enforces
112
+ charge/multiplicity parity at parse time — an odd-electron geometry loads
113
+ with a note instead of an error — and charge/multiplicity now stay owned by
114
+ Calculation Setup.
115
+ - **Suggest charge & multiplicity from geometry** — a Calculation Setup
116
+ button proposes a neutral charge and a parity-compatible multiplicity from
117
+ the loaded structure, with an explicit Apply step rather than a silent
118
+ auto-fill.
119
+ - **Export provenance.** Cube, PNG, and XYZ exports now carry method, basis,
120
+ and resolution (plus charge/multiplicity for XYZ) as embedded metadata, not
121
+ just filenames. Reorganization Energy gained its own XYZ export — one file
122
+ per distinct geometry — and a generalized PNG-capture bridge shared with
123
+ the isosurface viewer.
124
+ - **Reorganization Energy is checkpointed.** The run most likely to be
125
+ interrupted — two geometry optimizations plus four SCF energies under one
126
+ Calculate-tab run — can now resume mid-leg instead of restarting from
127
+ scratch.
128
+ - Orbital isosurfaces gained **smoothed meshes** (no more visible
129
+ marching-cubes facets) and a **wireframe finish toggle**.
130
+ - TD-DFT's live status label now surfaces **per-root convergence**
131
+ ("TD-DFT root 3 converged @ 16.663 eV") during the excited-state solve,
132
+ instead of only a generic heartbeat.
133
+ - A representative example gallery in the README and GitHub Pages site, and
134
+ new documentation for inorganic/coordination-complex support.
135
+
136
+ ### Fixed
137
+
138
+ - **Heavy-element runs on an ECP basis were silently wrong.** Cisplatin and
139
+ similar complexes on LANL2DZ or def2 were built with the basis set but no
140
+ effective core potential attached, so PySCF ran all-electron against a
141
+ valence-only basis — converging to a nonphysical energy (positive HOMO)
142
+ with garbage gradients, which made a geometry optimization look like it was
143
+ diverging. ECPs are now attached consistently across every calculation
144
+ type.
145
+ - **Frequency time estimates for app runs were systematically inflated.** The
146
+ estimator's fallback cost model wasn't told which pool a run came from, so
147
+ small-molecule Frequency estimates drew on the calibration pool's
148
+ fresh-subprocess import overhead instead of the app-run pool.
149
+ - **Density fitting's "⚡ RI" badge could silently vanish.** TD-DFT, NMR,
150
+ Frequency, and Geometry Optimization results discarded the density-fitting
151
+ flag instead of storing it, and results were never persisting it to disk
152
+ for *any* calculation type — so even a live single point's badge
153
+ disappeared on History replay.
154
+ - **A warm-start rejection could abort an otherwise-good calculation.** A
155
+ GPU-migrated mean-field rejecting a host density, or an incompatible
156
+ chkfile density, now logs a warning and retries from a scratch guess
157
+ instead of failing the whole run.
158
+ - **Resuming a resumed-and-interrupted-again optimization under-reported its
159
+ progress** — the BFGS step counter wasn't seeded from the checkpoint, so it
160
+ restarted counting at 0 instead of continuing from what had already been
161
+ banked.
162
+ - The resume list's Restore/Discard buttons now log an unexpected exception
163
+ instead of letting it escape into ipywidgets' click dispatch, matching
164
+ every other action button.
165
+ - **Converged-but-near-degenerate metal runs no longer show a stale "HOMO ==
166
+ LUMO" warning** (observed on ferrocene) — the check now looks at the
167
+ converged gap, not PySCF's initial-guess density, which is always
168
+ near-degenerate for a bare d-manifold starting guess.
169
+ - Click-to-measure's highlight halo no longer sits buried inside larger atoms
170
+ — it was a fixed radius; ball-and-stick atoms render at a radius scaled
171
+ from each element's VDW radius.
172
+ - Analysis-tab polish: the welcome logo's SVG fill token, accordions
173
+ expanding automatically when they have data, scroll-to-top on tab entry,
174
+ and a shared seed-geometry dropdown for NMR Shielding.
175
+ - **Older History results without saved Mulliken data now say so
176
+ explicitly**, with a magnitude-only note for dipoles recorded before the
177
+ full vector was saved, instead of a blank or misleading panel.
178
+ - The CPU Apptainer build crashing on a floating `miniforge3:latest` base
179
+ image — libmamba choking on a fuzzy version pin. See
180
+ `apptainer/quantui.def`.
181
+
182
+ ### Changed
183
+
184
+ - Three separate calc-type label→key mappings, and three separate
185
+ implementations of the Hill-formula algorithm, had drifted apart; each is
186
+ now a single shared helper.
187
+ - `python -m quantui.estimator_eval`'s replay is now linear time instead of
188
+ quadratic.
189
+ - ~380 scattered hex color literals across the chrome files are now one
190
+ theme token map.
191
+ - Real type checking restored in CI.
192
+
10
193
  ## [0.7.0] - 2026-08-06
11
194
 
12
195
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantui
3
- Version: 0.7.0
3
+ Version: 0.8.2
4
4
  Summary: An open-source frontend for DFT and post-HF quantum chemistry with PySCF
5
5
  Author-email: Jonathan Schultz <nccu-schultz-lab@users.noreply.github.com>
6
6
  License: MIT License
@@ -55,13 +55,17 @@ Requires-Dist: numpy<3,>=1.24.0
55
55
  Requires-Dist: requests<3,>=2.28.0
56
56
  Requires-Dist: py3Dmol<3,>=2.0.0
57
57
  Requires-Dist: matplotlib<4,>=3.7.0
58
- Requires-Dist: plotly<7,>=5.0.0
58
+ Requires-Dist: plotly<8,>=5.0.0
59
59
  Requires-Dist: plotlymol<1,>=0.2.1
60
+ Requires-Dist: kaleido<2,>=0.2.1
60
61
  Provides-Extra: pyscf
61
62
  Requires-Dist: pyscf<3,>=2.13.0; extra == "pyscf"
62
63
  Requires-Dist: pyscf-properties; extra == "pyscf"
63
64
  Provides-Extra: ase
64
65
  Requires-Dist: ase<4,>=3.22.0; extra == "ase"
66
+ Provides-Extra: xtb
67
+ Requires-Dist: xtb>=22.1; extra == "xtb"
68
+ Requires-Dist: ase<4,>=3.22.0; extra == "xtb"
65
69
  Provides-Extra: app
66
70
  Requires-Dist: voila<0.6,>=0.5.0; extra == "app"
67
71
  Requires-Dist: ipykernel<8,>=6.0.0; extra == "app"
@@ -81,7 +85,7 @@ Requires-Dist: pytest>=7.0.0; extra == "dev"
81
85
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
82
86
  Requires-Dist: pytest-mock>=3.10.0; extra == "dev"
83
87
  Requires-Dist: pytest-xdist>=3.0.0; extra == "dev"
84
- Requires-Dist: mypy>=1.0.0; extra == "dev"
88
+ Requires-Dist: mypy<2.4,>=1.10; extra == "dev"
85
89
  Requires-Dist: types-requests>=2.28.0; extra == "dev"
86
90
  Requires-Dist: black~=26.5.1; python_version >= "3.10" and extra == "dev"
87
91
  Requires-Dist: black~=25.11.0; python_version < "3.10" and extra == "dev"
@@ -109,13 +113,49 @@ research and classroom use.
109
113
 
110
114
  ---
111
115
 
116
+ ## Example results
117
+
118
+ Real output from QuantUI, straight from the app:
119
+
120
+ <table>
121
+ <tr>
122
+ <td width="50%" align="center" valign="top">
123
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/cisplatin_lumo.png" alt="Cisplatin LUMO isosurface" width="400"><br>
124
+ <b>Cisplatin LUMO</b><br>
125
+ <sub>Molecular-orbital isosurface · B3LYP/LANL2DZ (ECP on Pt &amp; Cl)</sub>
126
+ </td>
127
+ <td width="50%" align="center" valign="top">
128
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/aspartame_orbital_diagram.png" alt="Aspartame orbital energy-level diagram" width="300"><br>
129
+ <b>Aspartame orbital energies</b><br>
130
+ <sub>Occupied/virtual levels · HOMO–LUMO gap 6.09 eV · B3LYP/6-31G*</sub>
131
+ </td>
132
+ </tr>
133
+ <tr>
134
+ <td width="50%" align="center" valign="top">
135
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/benzene_ir_spectrum.png" alt="Benzene IR spectrum" width="400"><br>
136
+ <b>Benzene IR spectrum</b><br>
137
+ <sub>Analytical Hessian · ωB97X-D/6-31G · the four IR-active bands</sub>
138
+ </td>
139
+ <td width="50%" align="center" valign="top">
140
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/cisplatin_optimization.png" alt="Cisplatin geometry-optimization energy convergence" width="400"><br>
141
+ <b>Geometry optimization</b><br>
142
+ <sub>Cisplatin relaxing over 21 BFGS steps · B3LYP/LANL2DZ</sub>
143
+ </td>
144
+ </tr>
145
+ </table>
146
+
147
+ ▶ **[Live interactive gallery →](https://the-schultz-lab.github.io/QuantUI/#examples)** — rotate the 3D structures, and watch the optimization trajectory and a vibrational mode animate.
148
+
149
+ ---
150
+
112
151
  ## What it does
113
152
 
114
- - **Molecule input** — paste XYZ coordinates, browse an indexed three-tier
115
- bundled library (20 presets + 156 curated molecules + ~1,900 QM9 structures,
116
- searchable by name/formula), or run a structure search by name, SMILES,
117
- InChI, PubChem CID, InChIKey, or CAS number (PubChem → NCI CACTUS → offline
118
- bundled-library fallback; SMILES/InChI resolve locally with no network)
153
+ - **Molecule input** — paste XYZ coordinates, browse an indexed bundled library
154
+ (organic presets + curated molecules + ~1,900 QM9 structures + **14
155
+ ready-to-run coordination complexes**, searchable by name/formula), or run a
156
+ structure search by name, SMILES, InChI, PubChem CID, InChIKey, or CAS number
157
+ (PubChem → NCI CACTUS → offline bundled-library fallback; SMILES/InChI resolve
158
+ locally with no network)
119
159
  - **Offline-first** — runs with no internet: the bundled molecule library and
120
160
  the 3D viewer's JavaScript (3Dmol.js) are vendored, so structure lookup and
121
161
  every 3D view work in an air-gapped classroom. (Network is used only for the
@@ -139,6 +179,18 @@ research and classroom use.
139
179
  animation; vibrational frequency analysis with animated normal modes,
140
180
  user-tunable playback FPS, and a per-result-directory disk cache so mode
141
181
  switches on repeat visits and history replay are instant
182
+ - **Inorganic / coordination complexes** — first-class support for
183
+ transition-metal chemistry the organic pipeline can't handle: 14 bundled
184
+ metal complexes (octahedral / tetrahedral / square-planar; aqua, ammine,
185
+ cyanide, carbonyl, halide, oxo) with correct charge and spin; a pre-run guard
186
+ that catches a metal on an incompatible basis (nudges to def2-SVP / LANL2DZ)
187
+ and an impossible charge/multiplicity before the calculation starts; a
188
+ **spin-state helper** that suggests a multiplicity from a metal centre's
189
+ oxidation state and geometry (both high- and low-spin where the ligand field
190
+ decides — you pick); optional **GFN-FF (xtb) pre-optimization** that relaxes a
191
+ metal complex the classical organic force field can't; a warning when a name
192
+ search returns a disconnected salt instead of the coordinated complex; and a
193
+ viewer that draws the coordination bonds so the metal is never a lone dot
142
194
  - **Results persistence** — every calculation is saved automatically to a
143
195
  timestamped directory; a built-in browser lets you reload past results
144
196
  after a kernel restart; the full `pyscf.log` is shown inline
@@ -255,6 +307,26 @@ and result cards will display the compute device.
255
307
  Whenever gpu4pyscf can't offload a particular call, QuantUI falls back
256
308
  to CPU automatically and the result card reflects which device ran.
257
309
 
310
+ ### Optional: GFN-FF metal pre-optimization (xtb)
311
+
312
+ The classical (MMFF/UFF) pre-optimizer relies on RDKit's organic valence
313
+ model, which can't handle a transition metal. Install
314
+ [xtb](https://github.com/grimme-lab/xtb) to enable **GFN-FF**, a general
315
+ force field that relaxes coordination complexes across the whole periodic
316
+ table. Fully optional — without it, metal pre-opt simply reports that it
317
+ isn't available and points you to the DFT geometry optimization.
318
+
319
+ ```bash
320
+ # Linux (PyPI wheels bundle the compiled library):
321
+ pip install quantui[xtb]
322
+
323
+ # Windows / macOS (no PyPI wheel — use conda-forge):
324
+ conda install -c conda-forge xtb-python
325
+ ```
326
+
327
+ QuantUI detects xtb automatically and routes metal pre-optimizations through
328
+ GFN-FF; organic molecules still use the faster RDKit force field.
329
+
258
330
  ---
259
331
 
260
332
  ## Quick start
@@ -456,7 +528,13 @@ Five step-by-step notebooks in [`notebooks/tutorials/`](https://github.com/The-S
456
528
  ### Basis sets
457
529
 
458
530
  STO-3G (fast, good for learning) → 3-21G → 6-31G / 6-31G\* / 6-31G\*\* →
459
- cc-pVDZ / cc-pVTZ → def2-SVP / def2-TZVP
531
+ cc-pVDZ / cc-pVTZ → def2-SVP / def2-TZVP → LANL2DZ
532
+
533
+ For **transition metals and heavy elements**, use **def2-SVP** / **def2-TZVP**
534
+ or **LANL2DZ** — these carry the effective core potentials that cover metals,
535
+ whereas the Pople (`6-31G…`) and Dunning (`cc-pV…`) sets do not. QuantUI's
536
+ pre-run guard flags a metal on an incompatible basis and offers a one-click
537
+ switch to def2-SVP before the calculation starts.
460
538
 
461
539
  ---
462
540
 
@@ -18,13 +18,49 @@ research and classroom use.
18
18
 
19
19
  ---
20
20
 
21
+ ## Example results
22
+
23
+ Real output from QuantUI, straight from the app:
24
+
25
+ <table>
26
+ <tr>
27
+ <td width="50%" align="center" valign="top">
28
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/cisplatin_lumo.png" alt="Cisplatin LUMO isosurface" width="400"><br>
29
+ <b>Cisplatin LUMO</b><br>
30
+ <sub>Molecular-orbital isosurface · B3LYP/LANL2DZ (ECP on Pt &amp; Cl)</sub>
31
+ </td>
32
+ <td width="50%" align="center" valign="top">
33
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/aspartame_orbital_diagram.png" alt="Aspartame orbital energy-level diagram" width="300"><br>
34
+ <b>Aspartame orbital energies</b><br>
35
+ <sub>Occupied/virtual levels · HOMO–LUMO gap 6.09 eV · B3LYP/6-31G*</sub>
36
+ </td>
37
+ </tr>
38
+ <tr>
39
+ <td width="50%" align="center" valign="top">
40
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/benzene_ir_spectrum.png" alt="Benzene IR spectrum" width="400"><br>
41
+ <b>Benzene IR spectrum</b><br>
42
+ <sub>Analytical Hessian · ωB97X-D/6-31G · the four IR-active bands</sub>
43
+ </td>
44
+ <td width="50%" align="center" valign="top">
45
+ <img src="https://raw.githubusercontent.com/The-Schultz-Lab/QuantUI/main/docs/images/cisplatin_optimization.png" alt="Cisplatin geometry-optimization energy convergence" width="400"><br>
46
+ <b>Geometry optimization</b><br>
47
+ <sub>Cisplatin relaxing over 21 BFGS steps · B3LYP/LANL2DZ</sub>
48
+ </td>
49
+ </tr>
50
+ </table>
51
+
52
+ ▶ **[Live interactive gallery →](https://the-schultz-lab.github.io/QuantUI/#examples)** — rotate the 3D structures, and watch the optimization trajectory and a vibrational mode animate.
53
+
54
+ ---
55
+
21
56
  ## What it does
22
57
 
23
- - **Molecule input** — paste XYZ coordinates, browse an indexed three-tier
24
- bundled library (20 presets + 156 curated molecules + ~1,900 QM9 structures,
25
- searchable by name/formula), or run a structure search by name, SMILES,
26
- InChI, PubChem CID, InChIKey, or CAS number (PubChem → NCI CACTUS → offline
27
- bundled-library fallback; SMILES/InChI resolve locally with no network)
58
+ - **Molecule input** — paste XYZ coordinates, browse an indexed bundled library
59
+ (organic presets + curated molecules + ~1,900 QM9 structures + **14
60
+ ready-to-run coordination complexes**, searchable by name/formula), or run a
61
+ structure search by name, SMILES, InChI, PubChem CID, InChIKey, or CAS number
62
+ (PubChem → NCI CACTUS → offline bundled-library fallback; SMILES/InChI resolve
63
+ locally with no network)
28
64
  - **Offline-first** — runs with no internet: the bundled molecule library and
29
65
  the 3D viewer's JavaScript (3Dmol.js) are vendored, so structure lookup and
30
66
  every 3D view work in an air-gapped classroom. (Network is used only for the
@@ -48,6 +84,18 @@ research and classroom use.
48
84
  animation; vibrational frequency analysis with animated normal modes,
49
85
  user-tunable playback FPS, and a per-result-directory disk cache so mode
50
86
  switches on repeat visits and history replay are instant
87
+ - **Inorganic / coordination complexes** — first-class support for
88
+ transition-metal chemistry the organic pipeline can't handle: 14 bundled
89
+ metal complexes (octahedral / tetrahedral / square-planar; aqua, ammine,
90
+ cyanide, carbonyl, halide, oxo) with correct charge and spin; a pre-run guard
91
+ that catches a metal on an incompatible basis (nudges to def2-SVP / LANL2DZ)
92
+ and an impossible charge/multiplicity before the calculation starts; a
93
+ **spin-state helper** that suggests a multiplicity from a metal centre's
94
+ oxidation state and geometry (both high- and low-spin where the ligand field
95
+ decides — you pick); optional **GFN-FF (xtb) pre-optimization** that relaxes a
96
+ metal complex the classical organic force field can't; a warning when a name
97
+ search returns a disconnected salt instead of the coordinated complex; and a
98
+ viewer that draws the coordination bonds so the metal is never a lone dot
51
99
  - **Results persistence** — every calculation is saved automatically to a
52
100
  timestamped directory; a built-in browser lets you reload past results
53
101
  after a kernel restart; the full `pyscf.log` is shown inline
@@ -164,6 +212,26 @@ and result cards will display the compute device.
164
212
  Whenever gpu4pyscf can't offload a particular call, QuantUI falls back
165
213
  to CPU automatically and the result card reflects which device ran.
166
214
 
215
+ ### Optional: GFN-FF metal pre-optimization (xtb)
216
+
217
+ The classical (MMFF/UFF) pre-optimizer relies on RDKit's organic valence
218
+ model, which can't handle a transition metal. Install
219
+ [xtb](https://github.com/grimme-lab/xtb) to enable **GFN-FF**, a general
220
+ force field that relaxes coordination complexes across the whole periodic
221
+ table. Fully optional — without it, metal pre-opt simply reports that it
222
+ isn't available and points you to the DFT geometry optimization.
223
+
224
+ ```bash
225
+ # Linux (PyPI wheels bundle the compiled library):
226
+ pip install quantui[xtb]
227
+
228
+ # Windows / macOS (no PyPI wheel — use conda-forge):
229
+ conda install -c conda-forge xtb-python
230
+ ```
231
+
232
+ QuantUI detects xtb automatically and routes metal pre-optimizations through
233
+ GFN-FF; organic molecules still use the faster RDKit force field.
234
+
167
235
  ---
168
236
 
169
237
  ## Quick start
@@ -365,7 +433,13 @@ Five step-by-step notebooks in [`notebooks/tutorials/`](https://github.com/The-S
365
433
  ### Basis sets
366
434
 
367
435
  STO-3G (fast, good for learning) → 3-21G → 6-31G / 6-31G\* / 6-31G\*\* →
368
- cc-pVDZ / cc-pVTZ → def2-SVP / def2-TZVP
436
+ cc-pVDZ / cc-pVTZ → def2-SVP / def2-TZVP → LANL2DZ
437
+
438
+ For **transition metals and heavy elements**, use **def2-SVP** / **def2-TZVP**
439
+ or **LANL2DZ** — these carry the effective core potentials that cover metals,
440
+ whereas the Pople (`6-31G…`) and Dunning (`cc-pV…`) sets do not. QuantUI's
441
+ pre-run guard flags a metal on an incompatible basis and offers a one-click
442
+ switch to def2-SVP before the calculation starts.
369
443
 
370
444
  ---
371
445
 
@@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta"
8
8
 
9
9
  [project]
10
10
  name = "quantui"
11
- version = "0.7.0"
11
+ version = "0.8.2"
12
12
  description = "An open-source frontend for DFT and post-HF quantum chemistry with PySCF"
13
13
  readme = "README.md"
14
14
  requires-python = ">=3.9"
@@ -62,8 +62,9 @@ dependencies = [
62
62
  "requests>=2.28.0,<3",
63
63
  "py3Dmol>=2.0.0,<3",
64
64
  "matplotlib>=3.7.0,<4",
65
- "plotly>=5.0.0,<7",
65
+ "plotly>=5.0.0,<8",
66
66
  "plotlymol>=0.2.1,<1",
67
+ "kaleido>=0.2.1,<2",
67
68
  ]
68
69
 
69
70
  [project.urls]
@@ -82,7 +83,7 @@ Issues = "https://github.com/The-Schultz-Lab/QuantUI/issues"
82
83
  quantui = "quantui.cli:main"
83
84
 
84
85
  [tool.setuptools]
85
- packages = ["quantui"]
86
+ packages = ["quantui", "quantui.backends"]
86
87
 
87
88
  [tool.setuptools.package-data]
88
89
  # Bundled molecule library (M-STRUCT): the indexed SQLite store + the
@@ -118,6 +119,17 @@ ase = [
118
119
  "ase>=3.22.0,<4",
119
120
  ]
120
121
 
122
+ # GFN-FF metal-capable classical pre-optimization via xtb (Grimme's general
123
+ # force field). RDKit's organic valence model can't touch a transition metal;
124
+ # GFN-FF covers the whole periodic table. quantui.preopt uses it when present
125
+ # and falls back gracefully otherwise. xtb publishes **Linux pip wheels only** —
126
+ # on Windows/macOS install it from conda-forge (see local-setup/environment.yml).
127
+ # Depends on ase (the optimizer driving the relaxation).
128
+ xtb = [
129
+ "xtb>=22.1",
130
+ "ase>=3.22.0,<4",
131
+ ]
132
+
121
133
  # Voilà app server — hides notebook code; students see only the widget UI.
122
134
  # Run with: voila notebooks/molecule_computations.ipynb
123
135
  app = [
@@ -164,7 +176,11 @@ dev = [
164
176
  "pytest-cov>=4.0.0",
165
177
  "pytest-mock>=3.10.0",
166
178
  "pytest-xdist>=3.0.0", # parallel test execution (-n=auto in addopts)
167
- "mypy>=1.0.0",
179
+ "mypy>=1.10,<2.4", # pinned, not an open floor — same reasoning as
180
+ # black/ruff below: it must agree with .pre-commit-config.yaml's rev,
181
+ # which is what CI enforces (M-TYPECHECK TYPE.2). A newer mypy silently
182
+ # disagrees: 2.x drops support for python_version = "3.9" (this repo's
183
+ # floor) and reports a different error set than the pinned check.
168
184
  "types-requests>=2.28.0",
169
185
  # Formatter/linter versions are pinned to a compatible range, NOT an open
170
186
  # floor: they must agree with the revs in .pre-commit-config.yaml, which is
@@ -7,7 +7,7 @@ Calculations run locally in the Jupyter session — no cluster or SLURM required
7
7
  PySCF requires Linux/macOS/WSL. Windows users should use the Apptainer container.
8
8
  """
9
9
 
10
- __version__ = "0.7.0"
10
+ __version__ = "0.8.2"
11
11
 
12
12
  import logging
13
13
  from typing import Any
@@ -34,7 +34,7 @@ import statistics
34
34
  from collections import defaultdict
35
35
  from datetime import datetime, timezone
36
36
  from pathlib import Path
37
- from typing import Optional
37
+ from typing import Optional, cast
38
38
 
39
39
  from quantui.calc_log import _log_dir, get_perf_history, get_prediction_history
40
40
 
@@ -280,11 +280,17 @@ def _bar_chart_html(
280
280
  margin=dict(l=40, r=20, t=10, b=40),
281
281
  plot_bgcolor="#ffffff",
282
282
  )
283
- return pio.to_html(
284
- fig,
285
- include_plotlyjs="inline" if include_plotlyjs else False,
286
- full_html=False,
287
- config={"displayModeBar": False},
283
+ # plotly has no bundled type stubs; --ignore-missing-imports leaves
284
+ # pio.to_html untyped. It genuinely returns str (verified: this
285
+ # function's other returns are explicit None for the no-data case).
286
+ return cast(
287
+ str,
288
+ pio.to_html(
289
+ fig,
290
+ include_plotlyjs="inline" if include_plotlyjs else False,
291
+ full_html=False,
292
+ config={"displayModeBar": False},
293
+ ),
288
294
  )
289
295
 
290
296
 
@@ -344,11 +350,14 @@ def _timeline_html(records: list[dict], *, include_plotlyjs: bool) -> Optional[s
344
350
  plot_bgcolor="#ffffff",
345
351
  legend=dict(orientation="h", x=0, y=1.05),
346
352
  )
347
- return pio.to_html(
348
- fig,
349
- include_plotlyjs="inline" if include_plotlyjs else False,
350
- full_html=False,
351
- config={"displayModeBar": False},
353
+ return cast(
354
+ str,
355
+ pio.to_html(
356
+ fig,
357
+ include_plotlyjs="inline" if include_plotlyjs else False,
358
+ full_html=False,
359
+ config={"displayModeBar": False},
360
+ ),
352
361
  )
353
362
 
354
363
 
@@ -452,11 +461,14 @@ def _prediction_scatter_html(
452
461
  plot_bgcolor="#ffffff",
453
462
  legend=dict(orientation="h", x=0, y=1.05),
454
463
  )
455
- return pio.to_html(
456
- fig,
457
- include_plotlyjs="inline" if include_plotlyjs else False,
458
- full_html=False,
459
- config={"displayModeBar": False},
464
+ return cast(
465
+ str,
466
+ pio.to_html(
467
+ fig,
468
+ include_plotlyjs="inline" if include_plotlyjs else False,
469
+ full_html=False,
470
+ config={"displayModeBar": False},
471
+ ),
460
472
  )
461
473
 
462
474