quantui 0.5.1__py3-none-any.whl
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/__init__.py +311 -0
- quantui/analytics.py +609 -0
- quantui/app.py +5650 -0
- quantui/app_analysis.py +662 -0
- quantui/app_builders.py +2465 -0
- quantui/app_exports.py +194 -0
- quantui/app_formatters.py +493 -0
- quantui/app_history.py +624 -0
- quantui/app_runflow.py +1544 -0
- quantui/app_visualization.py +2620 -0
- quantui/ase_bridge.py +236 -0
- quantui/benchmarks.py +1543 -0
- quantui/c_stderr.py +124 -0
- quantui/cactus.py +88 -0
- quantui/calc_log.py +1116 -0
- quantui/calculator.py +204 -0
- quantui/cancellation.py +88 -0
- quantui/cli.py +288 -0
- quantui/comparison.py +306 -0
- quantui/config.py +725 -0
- quantui/data/js/3Dmol-min.js +2 -0
- quantui/data/js/3Dmol-min.js.LICENSE.txt +5 -0
- quantui/data/library/library.sqlite +0 -0
- quantui/data/manifests/bulk_qm9.json +1 -0
- quantui/data/manifests/curated.json +15482 -0
- quantui/data/manifests/presets.json +816 -0
- quantui/descriptor_cards.py +186 -0
- quantui/freq_calc.py +712 -0
- quantui/freq_ir_workers.py +229 -0
- quantui/gpu_offload.py +278 -0
- quantui/help_content.py +474 -0
- quantui/ir_plot.py +130 -0
- quantui/issue_tracker.py +170 -0
- quantui/live_log.py +387 -0
- quantui/log_utils.py +492 -0
- quantui/molecule.py +577 -0
- quantui/molecule_library.py +433 -0
- quantui/nmr_calc.py +437 -0
- quantui/optimizer.py +670 -0
- quantui/orbital_visualization.py +1102 -0
- quantui/pes_scan.py +420 -0
- quantui/preopt.py +355 -0
- quantui/progress.py +111 -0
- quantui/pubchem.py +1157 -0
- quantui/reorganization_energy.py +435 -0
- quantui/results_storage.py +902 -0
- quantui/security.py +14 -0
- quantui/session_calc.py +622 -0
- quantui/structure_providers.py +277 -0
- quantui/tddft_calc.py +307 -0
- quantui/user_settings.py +238 -0
- quantui/utils.py +287 -0
- quantui/vib_cache.py +247 -0
- quantui/visualization_py3dmol.py +593 -0
- quantui/viz_assets.py +101 -0
- quantui/viz_backend_router.py +243 -0
- quantui-0.5.1.dist-info/METADATA +533 -0
- quantui-0.5.1.dist-info/RECORD +62 -0
- quantui-0.5.1.dist-info/WHEEL +5 -0
- quantui-0.5.1.dist-info/entry_points.txt +2 -0
- quantui-0.5.1.dist-info/licenses/LICENSE +21 -0
- quantui-0.5.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""
|
|
2
|
+
3D visualization backend router for QuantUI.
|
|
3
|
+
|
|
4
|
+
Resolves which 3D rendering backend (py3Dmol or plotlymol3d) to use for a given
|
|
5
|
+
render task, taking into account user preference and installed-package
|
|
6
|
+
availability. Pure function — no I/O, no widget state, no app reference.
|
|
7
|
+
|
|
8
|
+
The routing policy mirrors a capability-based routing policy table.
|
|
9
|
+
|
|
10
|
+
Typical usage
|
|
11
|
+
-------------
|
|
12
|
+
>>> from quantui.viz_backend_router import (
|
|
13
|
+
... BackendAvailability, VizPreference, VizTask, select_backend,
|
|
14
|
+
... )
|
|
15
|
+
>>> avail = BackendAvailability.from_environment()
|
|
16
|
+
>>> decision = select_backend(
|
|
17
|
+
... task=VizTask.TRAJECTORY_FRAME,
|
|
18
|
+
... preference=VizPreference.AUTO,
|
|
19
|
+
... availability=avail,
|
|
20
|
+
... )
|
|
21
|
+
>>> if decision.chosen == "py3dmol":
|
|
22
|
+
... ... # render via py3Dmol
|
|
23
|
+
... elif decision.chosen == "plotlymol":
|
|
24
|
+
... ... # render via plotlymol3d
|
|
25
|
+
... else:
|
|
26
|
+
... ... # no renderer available
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import sys
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
from enum import Enum
|
|
34
|
+
|
|
35
|
+
# StrEnum landed in the stdlib in Python 3.11. Provide a behaviour-preserving
|
|
36
|
+
# shim on 3.10 (and 3.9, which pyproject still claims to support) so the
|
|
37
|
+
# router can use the cleaner inherited class. The shim matches StrEnum's key
|
|
38
|
+
# observable behaviours: members compare equal to plain strings, and both
|
|
39
|
+
# ``str(member)`` and ``f"{member}"`` return the underlying value rather than
|
|
40
|
+
# the dotted enum name. ``__str__ = str.__str__`` works because each member is
|
|
41
|
+
# already a real ``str`` instance whose content is the declared value.
|
|
42
|
+
if sys.version_info >= (3, 11):
|
|
43
|
+
from enum import StrEnum
|
|
44
|
+
else:
|
|
45
|
+
|
|
46
|
+
class StrEnum(str, Enum): # type: ignore[no-redef]
|
|
47
|
+
__str__ = str.__str__
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class VizTask(StrEnum):
|
|
51
|
+
"""Internal routing key for each visualization context."""
|
|
52
|
+
|
|
53
|
+
MOLECULE_PREVIEW = "molecule_preview"
|
|
54
|
+
STRUCTURE_VIEW_RESULTS = "structure_view_results"
|
|
55
|
+
ANALYSIS_STRUCTURE_VIEW = "analysis_structure_view"
|
|
56
|
+
HISTORY_STRUCTURE_REPLAY = "history_structure_replay"
|
|
57
|
+
TRAJECTORY_FRAME = "trajectory_frame"
|
|
58
|
+
TRAJECTORY_EXPORT = "trajectory_export"
|
|
59
|
+
VIB_INTERACTIVE = "vib_interactive"
|
|
60
|
+
VIB_EXPORT = "vib_export"
|
|
61
|
+
ORBITAL_ISOSURFACE = "orbital_isosurface"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class VizPreference(StrEnum):
|
|
65
|
+
"""User-selectable backend preference. `AUTO` defers to the task's primary."""
|
|
66
|
+
|
|
67
|
+
AUTO = "auto"
|
|
68
|
+
PY3DMOL = "py3dmol"
|
|
69
|
+
PLOTLYMOL = "plotlymol"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class VizBackend(StrEnum):
|
|
73
|
+
"""Concrete backend identifier returned by `select_backend`."""
|
|
74
|
+
|
|
75
|
+
PY3DMOL = "py3dmol"
|
|
76
|
+
PLOTLYMOL = "plotlymol"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass(frozen=True)
|
|
80
|
+
class BackendAvailability:
|
|
81
|
+
"""Which backends are importable in the current Python environment."""
|
|
82
|
+
|
|
83
|
+
py3dmol: bool
|
|
84
|
+
plotlymol: bool
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def from_environment(cls) -> BackendAvailability:
|
|
88
|
+
"""Probe imports to detect installed backends. Call once at app startup."""
|
|
89
|
+
try:
|
|
90
|
+
import py3Dmol # noqa: F401
|
|
91
|
+
|
|
92
|
+
py3dmol_ok = True
|
|
93
|
+
except ImportError:
|
|
94
|
+
py3dmol_ok = False
|
|
95
|
+
try:
|
|
96
|
+
import plotlymol3d # noqa: F401
|
|
97
|
+
|
|
98
|
+
plotlymol_ok = True
|
|
99
|
+
except ImportError:
|
|
100
|
+
plotlymol_ok = False
|
|
101
|
+
return cls(py3dmol=py3dmol_ok, plotlymol=plotlymol_ok)
|
|
102
|
+
|
|
103
|
+
def supports(self, backend: VizBackend) -> bool:
|
|
104
|
+
if backend == VizBackend.PY3DMOL:
|
|
105
|
+
return self.py3dmol
|
|
106
|
+
if backend == VizBackend.PLOTLYMOL:
|
|
107
|
+
return self.plotlymol
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass(frozen=True)
|
|
112
|
+
class Decision:
|
|
113
|
+
"""Result of a routing decision.
|
|
114
|
+
|
|
115
|
+
`chosen` is None when no available backend can serve the requested task —
|
|
116
|
+
callers should render a graceful unavailable-state message in that case.
|
|
117
|
+
`fallback` reports the secondary option available for that task at the time
|
|
118
|
+
of the decision (informational; not a promise to retry automatically).
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
chosen: VizBackend | None
|
|
122
|
+
fallback: VizBackend | None
|
|
123
|
+
reason: str
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Capability routing policy.
|
|
127
|
+
#
|
|
128
|
+
# Maps task -> (primary backend, optional fallback backend).
|
|
129
|
+
# Tasks whose fallback is None are single-backend — user preference is ignored
|
|
130
|
+
# for these. Single-backend rationale by task:
|
|
131
|
+
# - TRAJECTORY_FRAME: py3Dmol-only. Plotlymol's RequireJS-driven re-render
|
|
132
|
+
# pattern causes flicker when frames swap rapidly; py3Dmol's WebGL path
|
|
133
|
+
# is the only viable real-time trajectory backend in this app.
|
|
134
|
+
# - TRAJECTORY_EXPORT / VIB_EXPORT: plotlymol produces self-contained HTML
|
|
135
|
+
# animations with embedded controls, which is the export contract.
|
|
136
|
+
# ORBITAL_ISOSURFACE is dual-backend: py3Dmol does native, full-resolution
|
|
137
|
+
# in-browser cube isosurfacing (primary); the Plotly cube-isosurface path is the
|
|
138
|
+
# fallback (downsampled). "plotlymol" here is the umbrella for that Plotly path,
|
|
139
|
+
# which only needs plotly itself — the dispatch site treats Plotly as the
|
|
140
|
+
# universal fallback when py3Dmol is not chosen.
|
|
141
|
+
_TASK_POLICY: dict[VizTask, tuple[VizBackend, VizBackend | None]] = {
|
|
142
|
+
VizTask.MOLECULE_PREVIEW: (VizBackend.PY3DMOL, VizBackend.PLOTLYMOL),
|
|
143
|
+
VizTask.STRUCTURE_VIEW_RESULTS: (VizBackend.PY3DMOL, VizBackend.PLOTLYMOL),
|
|
144
|
+
VizTask.ANALYSIS_STRUCTURE_VIEW: (VizBackend.PY3DMOL, VizBackend.PLOTLYMOL),
|
|
145
|
+
VizTask.HISTORY_STRUCTURE_REPLAY: (VizBackend.PY3DMOL, VizBackend.PLOTLYMOL),
|
|
146
|
+
VizTask.TRAJECTORY_FRAME: (VizBackend.PY3DMOL, None),
|
|
147
|
+
VizTask.TRAJECTORY_EXPORT: (VizBackend.PLOTLYMOL, None),
|
|
148
|
+
VizTask.VIB_INTERACTIVE: (VizBackend.PY3DMOL, VizBackend.PLOTLYMOL),
|
|
149
|
+
VizTask.VIB_EXPORT: (VizBackend.PLOTLYMOL, None),
|
|
150
|
+
VizTask.ORBITAL_ISOSURFACE: (VizBackend.PY3DMOL, VizBackend.PLOTLYMOL),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def select_backend(
|
|
155
|
+
task: VizTask,
|
|
156
|
+
preference: VizPreference,
|
|
157
|
+
availability: BackendAvailability,
|
|
158
|
+
) -> Decision:
|
|
159
|
+
"""Resolve the backend to use for a given render task.
|
|
160
|
+
|
|
161
|
+
Resolution order:
|
|
162
|
+
|
|
163
|
+
1. If the task is single-backend (export and isosurface paths), user
|
|
164
|
+
preference is ignored and the task's required backend is used if
|
|
165
|
+
available; otherwise `Decision.chosen` is None.
|
|
166
|
+
2. Otherwise, `preference` selects between py3Dmol and plotlymol3d.
|
|
167
|
+
`AUTO` resolves to the task's primary backend.
|
|
168
|
+
3. If the preferred backend is unavailable, fall back to the task's
|
|
169
|
+
fallback backend if it is available.
|
|
170
|
+
4. If neither is available, `Decision.chosen` is None.
|
|
171
|
+
"""
|
|
172
|
+
primary, fallback_policy = _TASK_POLICY[task]
|
|
173
|
+
|
|
174
|
+
# Single-backend tasks: preference is ignored. Used for export-quality
|
|
175
|
+
# renders (trajectory/vib export HTML) and the orbital isosurface path.
|
|
176
|
+
if fallback_policy is None:
|
|
177
|
+
if availability.supports(primary):
|
|
178
|
+
return Decision(
|
|
179
|
+
chosen=primary,
|
|
180
|
+
fallback=None,
|
|
181
|
+
reason=f"task '{task}' requires {primary}",
|
|
182
|
+
)
|
|
183
|
+
return Decision(
|
|
184
|
+
chosen=None,
|
|
185
|
+
fallback=None,
|
|
186
|
+
reason=f"task '{task}' requires {primary} but it is unavailable",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Multi-backend tasks: resolve preference -> preferred backend.
|
|
190
|
+
if preference == VizPreference.AUTO:
|
|
191
|
+
preferred = primary
|
|
192
|
+
reason_prefix = f"auto -> task primary ({primary})"
|
|
193
|
+
elif preference == VizPreference.PY3DMOL:
|
|
194
|
+
preferred = VizBackend.PY3DMOL
|
|
195
|
+
reason_prefix = "user preference (py3dmol)"
|
|
196
|
+
elif preference == VizPreference.PLOTLYMOL:
|
|
197
|
+
preferred = VizBackend.PLOTLYMOL
|
|
198
|
+
reason_prefix = "user preference (plotlymol)"
|
|
199
|
+
else: # defensive — should be unreachable given the StrEnum
|
|
200
|
+
preferred = primary
|
|
201
|
+
reason_prefix = f"unknown preference '{preference}' -> task primary ({primary})"
|
|
202
|
+
|
|
203
|
+
if availability.supports(preferred):
|
|
204
|
+
# Report the other backend (if available) as fallback for transparency.
|
|
205
|
+
other = fallback_policy if preferred == primary else primary
|
|
206
|
+
reported_fallback = other if availability.supports(other) else None
|
|
207
|
+
return Decision(
|
|
208
|
+
chosen=preferred,
|
|
209
|
+
fallback=reported_fallback,
|
|
210
|
+
reason=reason_prefix,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Preferred is unavailable — try the policy fallback if it differs.
|
|
214
|
+
if fallback_policy != preferred and availability.supports(fallback_policy):
|
|
215
|
+
return Decision(
|
|
216
|
+
chosen=fallback_policy,
|
|
217
|
+
fallback=None,
|
|
218
|
+
reason=(
|
|
219
|
+
f"preferred {preferred} unavailable -> "
|
|
220
|
+
f"fell back to {fallback_policy}"
|
|
221
|
+
),
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Some installs may have the policy primary but not the requested
|
|
225
|
+
# preference's fallback. Try the primary as a last resort if it differs.
|
|
226
|
+
if preferred != primary and availability.supports(primary):
|
|
227
|
+
return Decision(
|
|
228
|
+
chosen=primary,
|
|
229
|
+
fallback=None,
|
|
230
|
+
reason=(
|
|
231
|
+
f"preferred {preferred} unavailable -> "
|
|
232
|
+
f"fell back to task primary ({primary})"
|
|
233
|
+
),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
return Decision(
|
|
237
|
+
chosen=None,
|
|
238
|
+
fallback=None,
|
|
239
|
+
reason=(
|
|
240
|
+
f"no available backend for task '{task}' "
|
|
241
|
+
f"(preferred={preferred}, policy fallback={fallback_policy})"
|
|
242
|
+
),
|
|
243
|
+
)
|