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.
Files changed (62) hide show
  1. quantui/__init__.py +311 -0
  2. quantui/analytics.py +609 -0
  3. quantui/app.py +5650 -0
  4. quantui/app_analysis.py +662 -0
  5. quantui/app_builders.py +2465 -0
  6. quantui/app_exports.py +194 -0
  7. quantui/app_formatters.py +493 -0
  8. quantui/app_history.py +624 -0
  9. quantui/app_runflow.py +1544 -0
  10. quantui/app_visualization.py +2620 -0
  11. quantui/ase_bridge.py +236 -0
  12. quantui/benchmarks.py +1543 -0
  13. quantui/c_stderr.py +124 -0
  14. quantui/cactus.py +88 -0
  15. quantui/calc_log.py +1116 -0
  16. quantui/calculator.py +204 -0
  17. quantui/cancellation.py +88 -0
  18. quantui/cli.py +288 -0
  19. quantui/comparison.py +306 -0
  20. quantui/config.py +725 -0
  21. quantui/data/js/3Dmol-min.js +2 -0
  22. quantui/data/js/3Dmol-min.js.LICENSE.txt +5 -0
  23. quantui/data/library/library.sqlite +0 -0
  24. quantui/data/manifests/bulk_qm9.json +1 -0
  25. quantui/data/manifests/curated.json +15482 -0
  26. quantui/data/manifests/presets.json +816 -0
  27. quantui/descriptor_cards.py +186 -0
  28. quantui/freq_calc.py +712 -0
  29. quantui/freq_ir_workers.py +229 -0
  30. quantui/gpu_offload.py +278 -0
  31. quantui/help_content.py +474 -0
  32. quantui/ir_plot.py +130 -0
  33. quantui/issue_tracker.py +170 -0
  34. quantui/live_log.py +387 -0
  35. quantui/log_utils.py +492 -0
  36. quantui/molecule.py +577 -0
  37. quantui/molecule_library.py +433 -0
  38. quantui/nmr_calc.py +437 -0
  39. quantui/optimizer.py +670 -0
  40. quantui/orbital_visualization.py +1102 -0
  41. quantui/pes_scan.py +420 -0
  42. quantui/preopt.py +355 -0
  43. quantui/progress.py +111 -0
  44. quantui/pubchem.py +1157 -0
  45. quantui/reorganization_energy.py +435 -0
  46. quantui/results_storage.py +902 -0
  47. quantui/security.py +14 -0
  48. quantui/session_calc.py +622 -0
  49. quantui/structure_providers.py +277 -0
  50. quantui/tddft_calc.py +307 -0
  51. quantui/user_settings.py +238 -0
  52. quantui/utils.py +287 -0
  53. quantui/vib_cache.py +247 -0
  54. quantui/visualization_py3dmol.py +593 -0
  55. quantui/viz_assets.py +101 -0
  56. quantui/viz_backend_router.py +243 -0
  57. quantui-0.5.1.dist-info/METADATA +533 -0
  58. quantui-0.5.1.dist-info/RECORD +62 -0
  59. quantui-0.5.1.dist-info/WHEEL +5 -0
  60. quantui-0.5.1.dist-info/entry_points.txt +2 -0
  61. quantui-0.5.1.dist-info/licenses/LICENSE +21 -0
  62. 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
+ )