quantui 0.5.2__tar.gz → 0.6.1__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.
- {quantui-0.5.2 → quantui-0.6.1}/CHANGELOG.md +98 -1
- {quantui-0.5.2/quantui.egg-info → quantui-0.6.1}/PKG-INFO +1 -1
- {quantui-0.5.2 → quantui-0.6.1}/pyproject.toml +1 -1
- {quantui-0.5.2 → quantui-0.6.1}/quantui/__init__.py +1 -1
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app.py +122 -4
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_analysis.py +104 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_builders.py +231 -1
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_exports.py +124 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_formatters.py +260 -23
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_runflow.py +16 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_visualization.py +579 -106
- {quantui-0.5.2 → quantui-0.6.1}/quantui/orbital_visualization.py +456 -47
- {quantui-0.5.2 → quantui-0.6.1}/quantui/reorganization_energy.py +93 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/results_storage.py +67 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/user_settings.py +20 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/viz_assets.py +21 -6
- {quantui-0.5.2 → quantui-0.6.1}/quantui/viz_backend_router.py +34 -8
- {quantui-0.5.2 → quantui-0.6.1/quantui.egg-info}/PKG-INFO +1 -1
- {quantui-0.5.2 → quantui-0.6.1}/LICENSE +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/MANIFEST.in +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/README.md +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/SECURITY.md +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/analytics.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/app_history.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/ase_bridge.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/benchmarks.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/c_stderr.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/cactus.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/calc_log.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/calculator.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/cancellation.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/cli.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/comparison.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/config.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/data/js/3Dmol-min.js +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/data/js/3Dmol-min.js.LICENSE.txt +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/data/library/library.sqlite +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/data/manifests/bulk_qm9.json +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/data/manifests/curated.json +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/data/manifests/presets.json +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/descriptor_cards.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/freq_calc.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/freq_ir_workers.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/gpu_offload.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/help_content.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/ir_plot.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/issue_tracker.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/live_log.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/log_utils.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/molecule.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/molecule_library.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/nmr_calc.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/optimizer.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/pes_scan.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/preopt.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/progress.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/pubchem.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/security.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/session_calc.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/structure_providers.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/tddft_calc.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/theme.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/utils.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/vib_cache.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui/visualization_py3dmol.py +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui.egg-info/SOURCES.txt +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui.egg-info/dependency_links.txt +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui.egg-info/entry_points.txt +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui.egg-info/requires.txt +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/quantui.egg-info/top_level.txt +0 -0
- {quantui-0.5.2 → quantui-0.6.1}/setup.cfg +0 -0
|
@@ -7,6 +7,101 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.1] - 2026-08-05
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Reorganization-energy results now survive History.** Reloading one from
|
|
15
|
+
History showed the calculation's headline numbers as missing — λ, the
|
|
16
|
+
four-point energies and the per-channel breakdown were never written to disk,
|
|
17
|
+
so there was nothing to redisplay. Both the live and History result cards now
|
|
18
|
+
render from the same saved data, so anything visible during a run is visible
|
|
19
|
+
after reloading it.
|
|
20
|
+
**Results saved before this cannot be recovered** — λ requires two geometry
|
|
21
|
+
optimizations and four SCF energies. Those results now say so on the card and
|
|
22
|
+
tell you to re-run, rather than appearing silently incomplete.
|
|
23
|
+
- **3-D backgrounds follow the theme** in the new geometry viewers, matching the
|
|
24
|
+
fix applied to the other viewers in 0.6.0.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **A Geometries panel** for reorganization energy, on the Analysis tab — which
|
|
29
|
+
previously showed nothing at all for this calculation type. Step through the
|
|
30
|
+
distinct geometries behind the four energies, or overlay two of them with
|
|
31
|
+
arrows marking how each atom moved. The arrows can be scaled ×1–×10 for small
|
|
32
|
+
relaxations; the structures always show true computed positions.
|
|
33
|
+
- **Geometry relaxation on the result card** — RMSD and the largest single-atom
|
|
34
|
+
shift between the neutral and ion geometries, which is what λ physically
|
|
35
|
+
measures.
|
|
36
|
+
- **Compare λ across saved results.** Selecting several reorganization-energy
|
|
37
|
+
runs in the Compare tab now shows a λ table with a column per channel, for
|
|
38
|
+
screening candidates by how much they reorganize.
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- The NCShare GPU diagnostic notebook now reports the **CPU affinity mask**
|
|
43
|
+
alongside the core count, and states plainly when CPU timings cannot be
|
|
44
|
+
trusted. A job can be granted 6 cores while still *seeing* 192; whether that
|
|
45
|
+
matters depends on how the scheduler constrains it, and the core count alone
|
|
46
|
+
cannot distinguish the two cases.
|
|
47
|
+
- `apptainer/README.md` records the first verified H200 run — driver, compute
|
|
48
|
+
capability, partition, and the measured CPU/GPU crossover.
|
|
49
|
+
|
|
50
|
+
## [0.6.0] - 2026-08-04
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **Save orbital isosurfaces as images.** A **Save PNG** button sits under the
|
|
55
|
+
viewer and captures exactly what you are looking at, camera and all. Options
|
|
56
|
+
for the filename, the DPI written into the file (72–600; this sets the
|
|
57
|
+
*print* size, not the pixel count), and a transparent background for dropping
|
|
58
|
+
a figure onto a slide — the transparency applies only to the saved file, so
|
|
59
|
+
the viewer on screen is unchanged.
|
|
60
|
+
- **Tunable isosurface resolution** — Coarse/Medium/Fine/Very fine grids,
|
|
61
|
+
remembered between launches.
|
|
62
|
+
- **Live isosurface controls**: isovalue, opacity and a colour-scheme dropdown
|
|
63
|
+
(GaussView orange/blue, Avogadro/Jmol red/blue, journal yellow/blue, and
|
|
64
|
+
more). All three update the existing viewer without recomputing anything, so
|
|
65
|
+
the orientation you rotated into is preserved.
|
|
66
|
+
- **The isovalue is explained in chemical terms.** Beside the slider, QuantUI
|
|
67
|
+
reports what fraction of the orbital's density the surface actually encloses
|
|
68
|
+
— "encloses 98.4% of the density" — because an amplitude threshold is rarely
|
|
69
|
+
the question anyone has.
|
|
70
|
+
- **A Cancel button** for isosurface generation. The underlying PySCF call
|
|
71
|
+
cannot be interrupted, so the computation finishes in the background, but its
|
|
72
|
+
result is discarded and the controls come back immediately.
|
|
73
|
+
- **The molecule is shown before you generate anything**, so the panel is never
|
|
74
|
+
empty — and you can orient the structure first, since that view carries into
|
|
75
|
+
the isosurface.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- **Orbital isosurfaces are py3Dmol only.** The Plotly renderer is no longer
|
|
80
|
+
selectable for them: it downsamples the volume by construction, and it cannot
|
|
81
|
+
carry the new image export. Plotly remains available for the molecule viewers.
|
|
82
|
+
- **Exported animations now match what you saw.** Vibrational modes and
|
|
83
|
+
optimization trajectories both export from py3Dmol — the renderer that drew
|
|
84
|
+
them on screen — instead of being rebuilt in Plotly. Exported files are also
|
|
85
|
+
now complete HTML documents declaring UTF-8, so labels no longer risk
|
|
86
|
+
mojibake when opened from disk.
|
|
87
|
+
- **For geometry optimizations, the Orbital Isosurface panel opens by default**
|
|
88
|
+
on the Analysis tab.
|
|
89
|
+
- **Isosurface viewers are framed** like the other 3-D viewers, and generating a
|
|
90
|
+
new one dims the current one in place rather than collapsing the panel.
|
|
91
|
+
|
|
92
|
+
### Fixed
|
|
93
|
+
|
|
94
|
+
- **3-D backgrounds follow the theme.** The isosurface, vibrational and
|
|
95
|
+
trajectory viewers, and the Analysis-tab molecule viewer, kept their old
|
|
96
|
+
background when switching Light/Dark until something happened to redraw them.
|
|
97
|
+
- **The page no longer jumps** when generating an isosurface.
|
|
98
|
+
- **Isosurface changes now apply correctly.** Adjusting the isovalue, opacity or
|
|
99
|
+
colours previously layered a new surface on top of the old one, so raising the
|
|
100
|
+
isovalue appeared to do nothing, lowering opacity did nothing, and switching
|
|
101
|
+
palettes made the surface progressively brighter.
|
|
102
|
+
- Very large orbital grids are dramatically lighter in the browser — the cube
|
|
103
|
+
data was being embedded three times per render.
|
|
104
|
+
|
|
10
105
|
## [0.5.2] - 2026-08-03
|
|
11
106
|
|
|
12
107
|
### Fixed
|
|
@@ -508,7 +603,9 @@ Initial public scaffolding of the QuantUI package: `quantui` package with
|
|
|
508
603
|
`calculator.py`, basic notebook launcher, Apptainer container definition,
|
|
509
604
|
MIT license, and project metadata.
|
|
510
605
|
|
|
511
|
-
[Unreleased]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.
|
|
606
|
+
[Unreleased]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.6.1...HEAD
|
|
607
|
+
[0.6.1]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.6.0...v0.6.1
|
|
608
|
+
[0.6.0]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.5.2...v0.6.0
|
|
512
609
|
[0.5.2]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.5.1...v0.5.2
|
|
513
610
|
[0.5.1]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.5.0...v0.5.1
|
|
514
611
|
[0.5.0]: https://github.com/The-Schultz-Lab/QuantUI/compare/v0.4.1...v0.5.0
|
|
@@ -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.
|
|
10
|
+
__version__ = "0.6.1"
|
|
11
11
|
|
|
12
12
|
import logging
|
|
13
13
|
from typing import Any
|
|
@@ -45,6 +45,9 @@ from quantui.app_analysis import (
|
|
|
45
45
|
from quantui.app_analysis import (
|
|
46
46
|
deactivate_all_ana_panels as _ana_deactivate_all_ana_panels,
|
|
47
47
|
)
|
|
48
|
+
from quantui.app_analysis import (
|
|
49
|
+
on_reorg_view_changed as _ana_on_reorg_view_changed,
|
|
50
|
+
)
|
|
48
51
|
from quantui.app_analysis import (
|
|
49
52
|
pop_energies as _ana_pop_energies,
|
|
50
53
|
)
|
|
@@ -69,6 +72,9 @@ from quantui.app_analysis import (
|
|
|
69
72
|
from quantui.app_analysis import (
|
|
70
73
|
pop_preopt_trajectory as _ana_pop_preopt_trajectory,
|
|
71
74
|
)
|
|
75
|
+
from quantui.app_analysis import (
|
|
76
|
+
pop_reorg_geometries as _ana_pop_reorg_geometries,
|
|
77
|
+
)
|
|
72
78
|
from quantui.app_analysis import (
|
|
73
79
|
pop_uv_vis as _ana_pop_uv_vis,
|
|
74
80
|
)
|
|
@@ -147,6 +153,9 @@ from quantui.app_exports import (
|
|
|
147
153
|
from quantui.app_exports import (
|
|
148
154
|
on_iso_export_cube as _exp_on_iso_export_cube,
|
|
149
155
|
)
|
|
156
|
+
from quantui.app_exports import (
|
|
157
|
+
on_orb_png_captured as _exp_on_orb_png_captured,
|
|
158
|
+
)
|
|
150
159
|
from quantui.app_formatters import (
|
|
151
160
|
format_freq_result as _fmt_freq_result,
|
|
152
161
|
)
|
|
@@ -333,6 +342,12 @@ from quantui.app_visualization import (
|
|
|
333
342
|
from quantui.app_visualization import (
|
|
334
343
|
on_ir_mode_changed as _viz_on_ir_mode_changed,
|
|
335
344
|
)
|
|
345
|
+
from quantui.app_visualization import (
|
|
346
|
+
on_iso_appearance_changed as _viz_on_iso_appearance_changed,
|
|
347
|
+
)
|
|
348
|
+
from quantui.app_visualization import (
|
|
349
|
+
on_iso_cancel as _viz_on_iso_cancel,
|
|
350
|
+
)
|
|
336
351
|
from quantui.app_visualization import (
|
|
337
352
|
on_iso_generate as _viz_on_iso_generate,
|
|
338
353
|
)
|
|
@@ -360,6 +375,9 @@ from quantui.app_visualization import (
|
|
|
360
375
|
from quantui.app_visualization import (
|
|
361
376
|
render_vib_mode as _viz_render_vib_mode,
|
|
362
377
|
)
|
|
378
|
+
from quantui.app_visualization import (
|
|
379
|
+
rerender_3d_scenes_for_theme as _viz_rerender_3d_scenes_for_theme,
|
|
380
|
+
)
|
|
363
381
|
from quantui.app_visualization import (
|
|
364
382
|
show_ir_spectrum as _viz_show_ir_spectrum,
|
|
365
383
|
)
|
|
@@ -1618,6 +1636,7 @@ class QuantUIApp:
|
|
|
1618
1636
|
("IR Spectrum", "_ir_accordion", "Frequency"),
|
|
1619
1637
|
("PES Scan", "_pes_scan_accordion", "PES Scan"),
|
|
1620
1638
|
("Isosurface", "_iso_accordion", "Single Point (Linux/WSL only)"),
|
|
1639
|
+
("Geometries", "_reorg_geom_accordion", "Reorganization Energy"),
|
|
1621
1640
|
("UV-Vis", "_tddft_accordion", "UV-Vis (TD-DFT)"),
|
|
1622
1641
|
("NMR", "_nmr_accordion", "NMR Shielding"),
|
|
1623
1642
|
]
|
|
@@ -1628,9 +1647,31 @@ class QuantUIApp:
|
|
|
1628
1647
|
("Isosurface", "_pop_isosurface", False),
|
|
1629
1648
|
],
|
|
1630
1649
|
"geometry_opt": [
|
|
1631
|
-
|
|
1650
|
+
# ORDER MATTERS: the FIRST entry whose populator returns True and
|
|
1651
|
+
# carries auto_select=True becomes the default panel (see the rules
|
|
1652
|
+
# above). Isosurface therefore leads (requested 2026-08-04); an
|
|
1653
|
+
# earlier attempt put it last, which did nothing because Trajectory
|
|
1654
|
+
# had already claimed the selection.
|
|
1655
|
+
#
|
|
1656
|
+
# Trajectory keeps auto_select=True as the fallback: when a result
|
|
1657
|
+
# has no orbital data, _pop_isosurface returns False, Isosurface
|
|
1658
|
+
# never activates, and Trajectory becomes the default instead.
|
|
1659
|
+
# ORDER IS LOAD-BEARING TWICE OVER.
|
|
1660
|
+
#
|
|
1661
|
+
# 1. Execution: _pop_energies calls show_orbital_diagram, which is
|
|
1662
|
+
# what populates _last_orb_mo_coeff / _mol_atom / _mol_basis —
|
|
1663
|
+
# the very state _pop_isosurface checks. Energies MUST run
|
|
1664
|
+
# first, or Isosurface reports "required data is missing" on a
|
|
1665
|
+
# result that has it. (Putting Isosurface first did exactly
|
|
1666
|
+
# that, 2026-08-04.)
|
|
1667
|
+
# 2. Selection: the FIRST entry with auto_select=True that returns
|
|
1668
|
+
# True becomes the default panel. Energies is False, so
|
|
1669
|
+
# Isosurface is the first candidate and opens by default —
|
|
1670
|
+
# which is the request — while Trajectory keeps True as the
|
|
1671
|
+
# fallback for results with no orbital data.
|
|
1632
1672
|
("Energies", "_pop_energies", False),
|
|
1633
|
-
("Isosurface", "_pop_isosurface",
|
|
1673
|
+
("Isosurface", "_pop_isosurface", True),
|
|
1674
|
+
("Trajectory", "_pop_geo_trajectory", True),
|
|
1634
1675
|
],
|
|
1635
1676
|
"frequency": [
|
|
1636
1677
|
("Vibrational", "_pop_vibrational", True),
|
|
@@ -1644,6 +1685,16 @@ class QuantUIApp:
|
|
|
1644
1685
|
"nmr": [
|
|
1645
1686
|
("NMR", "_pop_nmr_shielding", True),
|
|
1646
1687
|
],
|
|
1688
|
+
# reorganization_energy had NO entry at all until 2026-08-05, so the
|
|
1689
|
+
# Analysis tab populated nothing for these runs — not a missing panel,
|
|
1690
|
+
# no panels. Order matters twice over: the FIRST auto_select=True that
|
|
1691
|
+
# returns True wins, AND _pop_energies loads the orbital state
|
|
1692
|
+
# _pop_isosurface checks, so Energies must precede Isosurface.
|
|
1693
|
+
"reorganization_energy": [
|
|
1694
|
+
("Energies", "_pop_energies", False),
|
|
1695
|
+
("Geometries", "_pop_reorg_geometries", True),
|
|
1696
|
+
("Isosurface", "_pop_isosurface", False),
|
|
1697
|
+
],
|
|
1647
1698
|
"pes_scan": [
|
|
1648
1699
|
("PES Scan", "_pop_pes_plot", True),
|
|
1649
1700
|
("Trajectory", "_pop_pes_trajectory", False),
|
|
@@ -1659,6 +1710,9 @@ class QuantUIApp:
|
|
|
1659
1710
|
def _pop_energies(self, ctx: _AnalysisContext) -> bool:
|
|
1660
1711
|
return _ana_pop_energies(self, ctx)
|
|
1661
1712
|
|
|
1713
|
+
def _pop_reorg_geometries(self, ctx: _AnalysisContext) -> bool:
|
|
1714
|
+
return _ana_pop_reorg_geometries(self, ctx)
|
|
1715
|
+
|
|
1662
1716
|
def _pop_isosurface(self, ctx: _AnalysisContext) -> bool:
|
|
1663
1717
|
return _ana_pop_isosurface(self, ctx)
|
|
1664
1718
|
|
|
@@ -1979,6 +2033,35 @@ class QuantUIApp:
|
|
|
1979
2033
|
)
|
|
1980
2034
|
# Cube + bundle exports
|
|
1981
2035
|
self._iso_export_cube_btn.on_click(self._on_iso_export_cube)
|
|
2036
|
+
self._iso_cancel_btn.on_click(self._safe_cb(self._on_iso_cancel))
|
|
2037
|
+
# Reorg geometry views (REORG.3): both redraw from data already in
|
|
2038
|
+
# memory, so they respond directly rather than behind an Apply button.
|
|
2039
|
+
for _w in (
|
|
2040
|
+
self._reorg_view_toggle,
|
|
2041
|
+
self._reorg_overlay_pair,
|
|
2042
|
+
self._reorg_exaggerate,
|
|
2043
|
+
):
|
|
2044
|
+
_w.observe(self._safe_cb(self._on_reorg_view_changed), names="value")
|
|
2045
|
+
# PNG capture arrives from the browser, so there is no button to bind
|
|
2046
|
+
# here — the viewer's own Save-PNG button posts into this Textarea and
|
|
2047
|
+
# ipywidgets syncs it back, firing this observer (ORBX.1).
|
|
2048
|
+
self._orb_png_inbox.observe(
|
|
2049
|
+
self._safe_cb(self._on_orb_png_captured), names="value"
|
|
2050
|
+
)
|
|
2051
|
+
# Persist the grid choice so it survives a relaunch (ORBX.2).
|
|
2052
|
+
self._iso_resolution_dd.observe(
|
|
2053
|
+
self._safe_cb(self._on_iso_resolution_changed), names="value"
|
|
2054
|
+
)
|
|
2055
|
+
# Appearance controls redraw from the cube on disk — no cubegen — so
|
|
2056
|
+
# they can respond directly rather than behind an Apply button.
|
|
2057
|
+
for _w in (
|
|
2058
|
+
self._iso_isovalue_slider,
|
|
2059
|
+
self._iso_opacity_slider,
|
|
2060
|
+
self._iso_colors_dd,
|
|
2061
|
+
# NOT _iso_png_transparent: it is an export-only option, applied at
|
|
2062
|
+
# capture time. Observing it here would change the live viewer.
|
|
2063
|
+
):
|
|
2064
|
+
_w.observe(self._safe_cb(self._on_iso_appearance_changed), names="value")
|
|
1982
2065
|
self._export_bundle_btn.on_click(self._on_export_bundle)
|
|
1983
2066
|
|
|
1984
2067
|
# ── Files tab ────────────────────────────────────────────────────────
|
|
@@ -2746,8 +2829,18 @@ class QuantUIApp:
|
|
|
2746
2829
|
_last_pes = getattr(self, "_last_pes_result", None)
|
|
2747
2830
|
if _last_pes is not None:
|
|
2748
2831
|
self._show_pes_scan_result(_last_pes)
|
|
2749
|
-
# Re-render 3D molecule
|
|
2750
|
-
|
|
2832
|
+
# Re-render BOTH 3D molecule viewers so scene_bgcolor updates
|
|
2833
|
+
# immediately. This used to call _refresh_calc_mol_viewer directly,
|
|
2834
|
+
# which covered the Calculate tab only — the Analysis-tab viewer kept
|
|
2835
|
+
# its old background until something else happened to redraw it.
|
|
2836
|
+
# _rerender_3d_views already handled both; it just was not reached from
|
|
2837
|
+
# here. Reported 2026-08-04.
|
|
2838
|
+
self._rerender_3d_views()
|
|
2839
|
+
# ...and the isosurface / vibrational viewers, which bake the same
|
|
2840
|
+
# colour into their generated HTML. Without this they keep the old
|
|
2841
|
+
# background until the user regenerates — reported 2026-08-04. Both
|
|
2842
|
+
# re-render from cached inputs; neither re-runs a calculation.
|
|
2843
|
+
_viz_rerender_3d_scenes_for_theme(self)
|
|
2751
2844
|
|
|
2752
2845
|
def _initialize_viz_state_from_preference(self) -> None:
|
|
2753
2846
|
"""Align _viz_backend and the three preference widgets with the
|
|
@@ -3313,6 +3406,31 @@ class QuantUIApp:
|
|
|
3313
3406
|
def _on_export_pdb(self, btn) -> None:
|
|
3314
3407
|
_exp_on_export_pdb(self, btn)
|
|
3315
3408
|
|
|
3409
|
+
def _on_reorg_view_changed(self, change) -> None:
|
|
3410
|
+
_ana_on_reorg_view_changed(self, change)
|
|
3411
|
+
|
|
3412
|
+
def _on_iso_cancel(self, btn) -> None:
|
|
3413
|
+
_viz_on_iso_cancel(self, btn)
|
|
3414
|
+
|
|
3415
|
+
def _on_iso_appearance_changed(self, change) -> None:
|
|
3416
|
+
_viz_on_iso_appearance_changed(self, change)
|
|
3417
|
+
|
|
3418
|
+
def _on_orb_png_captured(self, change) -> None:
|
|
3419
|
+
_exp_on_orb_png_captured(self, change)
|
|
3420
|
+
|
|
3421
|
+
def _on_iso_resolution_changed(self, change) -> None:
|
|
3422
|
+
"""Persist the isosurface grid choice.
|
|
3423
|
+
|
|
3424
|
+
Saved on change rather than at generate time so the preference sticks
|
|
3425
|
+
even if the user picks a grid and then closes the app without running
|
|
3426
|
+
anything.
|
|
3427
|
+
"""
|
|
3428
|
+
new_val = (change or {}).get("new")
|
|
3429
|
+
if not new_val or new_val == self._user_settings.viz.iso_resolution:
|
|
3430
|
+
return
|
|
3431
|
+
self._user_settings.viz.iso_resolution = str(new_val)
|
|
3432
|
+
self._user_settings.save()
|
|
3433
|
+
|
|
3316
3434
|
def _on_iso_export_cube(self, btn) -> None:
|
|
3317
3435
|
_exp_on_iso_export_cube(self, btn)
|
|
3318
3436
|
|
|
@@ -253,6 +253,110 @@ def pop_energies(app: Any, ctx: Any) -> bool:
|
|
|
253
253
|
return bool(app._show_orbital_diagram(result))
|
|
254
254
|
|
|
255
255
|
|
|
256
|
+
def pop_reorg_geometries(app: Any, ctx: Any) -> bool:
|
|
257
|
+
"""Populate the Geometries panel for a reorganization-energy result.
|
|
258
|
+
|
|
259
|
+
Works from a live result OR a saved one by reading the same channel payload
|
|
260
|
+
both now carry — the shape REORG.1 introduced. That is deliberate: the
|
|
261
|
+
original bug was two paths reading different things, so this one never had
|
|
262
|
+
the chance to grow a second reader.
|
|
263
|
+
|
|
264
|
+
Returns False when the payload is absent, which is exactly the pre-REORG.1
|
|
265
|
+
case; the panel then stays unavailable rather than showing an empty viewer,
|
|
266
|
+
and the results card explains why.
|
|
267
|
+
"""
|
|
268
|
+
channels, neutral = _reorg_payload(app, ctx)
|
|
269
|
+
if not channels or not neutral:
|
|
270
|
+
_set_panel_unavailable_message(
|
|
271
|
+
app,
|
|
272
|
+
"Geometries",
|
|
273
|
+
(
|
|
274
|
+
"Not available for this result: the per-channel geometries were "
|
|
275
|
+
"not saved. Re-run the calculation to enable the comparison."
|
|
276
|
+
),
|
|
277
|
+
)
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
from quantui.reorganization_energy import reorg_geometries
|
|
281
|
+
|
|
282
|
+
geoms = reorg_geometries(channels, neutral)
|
|
283
|
+
if len(geoms) < 2:
|
|
284
|
+
return False
|
|
285
|
+
app._reorg_geometries = geoms
|
|
286
|
+
app._reorg_overlay_pair.options = [
|
|
287
|
+
(f"{geoms[0]['label'].split(' — ')[0]} vs {g['label'].split(' — ')[0]}", i)
|
|
288
|
+
for i in range(1, len(geoms))
|
|
289
|
+
for g in [geoms[i]]
|
|
290
|
+
]
|
|
291
|
+
if app._reorg_overlay_pair.options:
|
|
292
|
+
app._reorg_overlay_pair.value = app._reorg_overlay_pair.options[0][1]
|
|
293
|
+
render_reorg_geometries(app)
|
|
294
|
+
return True
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _reorg_payload(app: Any, ctx: Any) -> tuple[list, dict]:
|
|
298
|
+
"""Channel list + neutral geometry, from a live result or a saved one."""
|
|
299
|
+
live = getattr(ctx, "live_result", None)
|
|
300
|
+
if live is not None and getattr(live, "channels", None):
|
|
301
|
+
from quantui.results_storage import _reorg_channels_payload
|
|
302
|
+
|
|
303
|
+
payload = _reorg_channels_payload(live) or []
|
|
304
|
+
neutral = payload[0].get("neutral_geometry") if payload else None
|
|
305
|
+
return payload, neutral or {}
|
|
306
|
+
|
|
307
|
+
result_dir = getattr(ctx, "result_dir", None)
|
|
308
|
+
if result_dir is not None:
|
|
309
|
+
try:
|
|
310
|
+
from quantui import load_result
|
|
311
|
+
|
|
312
|
+
data = load_result(result_dir)
|
|
313
|
+
payload = data.get("reorg_channels") or []
|
|
314
|
+
neutral = payload[0].get("neutral_geometry") if payload else None
|
|
315
|
+
return payload, neutral or {}
|
|
316
|
+
except Exception: # noqa: BLE001 — a missing panel, never a crash
|
|
317
|
+
return [], {}
|
|
318
|
+
return [], {}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def render_reorg_geometries(app: Any) -> None:
|
|
322
|
+
"""Draw the current view (stepper or overlay) into the Geometries panel."""
|
|
323
|
+
geoms = getattr(app, "_reorg_geometries", None)
|
|
324
|
+
if not geoms:
|
|
325
|
+
return
|
|
326
|
+
from quantui.app_visualization import (
|
|
327
|
+
build_reorg_geometry_viewer_html,
|
|
328
|
+
build_reorg_overlay_html,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
bg = app._plotly_theme_colors()["scene_bgcolor"]
|
|
332
|
+
try:
|
|
333
|
+
if app._reorg_view_toggle.value == "overlay":
|
|
334
|
+
idx = int(app._reorg_overlay_pair.value or 1)
|
|
335
|
+
html = build_reorg_overlay_html(
|
|
336
|
+
geoms[0],
|
|
337
|
+
geoms[idx],
|
|
338
|
+
bgcolor=bg,
|
|
339
|
+
exaggerate=float(getattr(app._reorg_exaggerate, "value", 1.0)),
|
|
340
|
+
)
|
|
341
|
+
else:
|
|
342
|
+
html = build_reorg_geometry_viewer_html(geoms, bgcolor=bg)
|
|
343
|
+
app._set_html_output(app._reorg_geom_output, html)
|
|
344
|
+
except Exception as exc: # noqa: BLE001
|
|
345
|
+
app._set_html_output(
|
|
346
|
+
app._reorg_geom_output,
|
|
347
|
+
f'<p style="color:#b91c1c;padding:8px">Geometry view failed: {exc}</p>',
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def on_reorg_view_changed(app: Any, change: Any = None) -> None:
|
|
352
|
+
"""Toggle between stepper and overlay; the pair picker only applies to one."""
|
|
353
|
+
is_overlay = app._reorg_view_toggle.value == "overlay"
|
|
354
|
+
app._reorg_overlay_pair.layout.display = "" if is_overlay else "none"
|
|
355
|
+
# Only meaningful for the overlay — the stepper shows true positions.
|
|
356
|
+
app._reorg_exaggerate.layout.display = "" if is_overlay else "none"
|
|
357
|
+
render_reorg_geometries(app)
|
|
358
|
+
|
|
359
|
+
|
|
256
360
|
def pop_isosurface(app: Any, ctx: Any) -> bool:
|
|
257
361
|
"""Populate Isosurface availability from orbital state.
|
|
258
362
|
|