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
quantui/c_stderr.py ADDED
@@ -0,0 +1,124 @@
1
+ """POSIX file-descriptor stderr capture.
2
+
3
+ PySCF and its C-extension dependencies (libcint, BLAS/LAPACK, dftd3) write
4
+ diagnostic messages directly to file-descriptor 2 (the OS-level stderr),
5
+ bypassing Python's ``sys.stderr`` and PySCF's own ``mol.stdout`` routing.
6
+ In a Voilà notebook those bytes surface as red error text above the cell
7
+ output even when the calculation succeeded — visually alarming, and
8
+ indistinguishable at a glance from a real failure.
9
+
10
+ This module provides ``capture_c_stderr(relay_stream=...)``, a context
11
+ manager that redirects fd 2 to a private temp file for the duration of
12
+ the block, then drains the captured bytes into the supplied relay stream
13
+ on exit. The end result: C-level diagnostics still reach the user (no
14
+ information loss), but through the normal live-log channel rather than
15
+ the red-text channel.
16
+
17
+ The implementation is POSIX-only (uses ``os.dup`` / ``os.dup2`` on fd 2).
18
+ On Windows the context is a no-op and yields immediately — safe to use
19
+ unconditionally since PySCF is Linux/macOS/WSL only and the rest of the
20
+ app's runtime gates on platform separately.
21
+
22
+ Thread-safety note: fd 2 is a process-global resource. QuantUI runs at
23
+ most one calculation at a time (the Run button is disabled during a run
24
+ and the work happens on a single background thread), so the standard
25
+ guidance is "use this only when no other code in the process is writing
26
+ to fd 2 concurrently". Nested contexts work correctly — each push/pop
27
+ saves and restores the previous fd 2 binding.
28
+ """
29
+
30
+ from __future__ import annotations
31
+
32
+ import contextlib
33
+ import os
34
+ import sys
35
+ import tempfile
36
+ from typing import IO, Optional
37
+
38
+
39
+ @contextlib.contextmanager
40
+ def capture_c_stderr(relay_stream: Optional[IO[str]] = None):
41
+ """Capture fd-level stderr to a temp file, relay on exit.
42
+
43
+ Parameters
44
+ ----------
45
+ relay_stream:
46
+ Optional writable text stream that receives the captured bytes
47
+ (decoded UTF-8, replace on bad bytes) when the context exits. When
48
+ ``None``, captured output is silently dropped — useful when the
49
+ caller only wants the noise gone, not surfaced anywhere.
50
+
51
+ Notes
52
+ -----
53
+ Output is buffered to a temp file during the block and flushed to
54
+ ``relay_stream`` exactly once at exit. For long-running calculations
55
+ that emit periodic warnings (e.g. an iterative SCF that prints one
56
+ warning per cycle), the user sees the warnings as a single batch at
57
+ the end rather than streamed in real time. This is a conscious trade-
58
+ off: real-time streaming would require a pipe + drainer thread, which
59
+ isn't worth the complexity for the typical "occasional libcint /
60
+ BLAS warning" use case.
61
+
62
+ The temp file is unlinked automatically by ``TemporaryFile``; no
63
+ cleanup is required from the caller.
64
+
65
+ On non-POSIX platforms the context manager yields immediately and
66
+ relay_stream is never written to.
67
+ """
68
+ if os.name != "posix":
69
+ yield
70
+ return
71
+
72
+ # Flush any Python-level stderr first so it doesn't get mixed in
73
+ # with what we're about to capture.
74
+ try:
75
+ sys.stderr.flush()
76
+ except Exception:
77
+ pass
78
+
79
+ # Binary temp file: C-level writes are bytes, not text.
80
+ tmp = tempfile.TemporaryFile(mode="w+b")
81
+ saved_fd: Optional[int] = None
82
+ try:
83
+ saved_fd = os.dup(2)
84
+ os.dup2(tmp.fileno(), 2)
85
+ try:
86
+ yield
87
+ finally:
88
+ # Flush stderr (Python-level) before we tear the fd back so
89
+ # any pending writes land in the temp file rather than getting
90
+ # routed to the restored fd.
91
+ try:
92
+ sys.stderr.flush()
93
+ except Exception:
94
+ pass
95
+ # Restore fd 2 before reading the temp file — otherwise any
96
+ # write to stderr during the read (e.g. by relay_stream itself)
97
+ # would loop back into the still-redirected fd.
98
+ os.dup2(saved_fd, 2)
99
+ finally:
100
+ if saved_fd is not None:
101
+ try:
102
+ os.close(saved_fd)
103
+ except OSError:
104
+ pass
105
+
106
+ # Drain captured bytes (best-effort) and relay.
107
+ captured = b""
108
+ try:
109
+ tmp.flush()
110
+ tmp.seek(0)
111
+ captured = tmp.read()
112
+ except Exception:
113
+ pass
114
+ finally:
115
+ try:
116
+ tmp.close()
117
+ except Exception:
118
+ pass
119
+
120
+ if captured and relay_stream is not None:
121
+ try:
122
+ relay_stream.write(captured.decode("utf-8", errors="replace"))
123
+ except Exception:
124
+ pass
quantui/cactus.py ADDED
@@ -0,0 +1,88 @@
1
+ """NCI CACTUS Chemical Identifier Resolver.
2
+
3
+ A chained fallback after PubChem. CACTUS resolves a wide range of identifiers
4
+ (common name, IUPAC name, CAS number, InChI, SMILES, formula) to a 3D SDF with
5
+ no API key. It often answers queries PubChem misses — CAS numbers in
6
+ particular.
7
+
8
+ Best-effort by design: every failure mode (miss, network error, malformed
9
+ response) raises one of the shared PubChem exception types so the provider
10
+ chain in :mod:`quantui.structure_providers` can treat all resolvers uniformly.
11
+ """
12
+
13
+ import logging
14
+ from typing import Any, Dict, Tuple
15
+ from urllib.parse import quote
16
+
17
+ import requests
18
+
19
+ from . import config
20
+ from .pubchem import MoleculeNotFoundError, PubChemAPIError, sdf_to_xyz
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # CACTUS resolver base. The ``/file?format=sdf&get3d=true`` form returns a 3D
25
+ # SDF; the bare ``/sdf`` form returns whatever (often 2D) CACTUS has.
26
+ CACTUS_BASE_URL = "https://cactus.nci.nih.gov/chemical/structure"
27
+
28
+
29
+ def _looks_like_sdf(text: str) -> bool:
30
+ """CACTUS returns an HTML error page (HTTP 200) for unknown identifiers.
31
+
32
+ A real SDF molfile always carries the ``V2000``/``V3000`` counts-line tag
33
+ and the ``M END`` terminator, so key off those rather than the status code.
34
+ """
35
+ return "M END" in text and ("V2000" in text or "V3000" in text)
36
+
37
+
38
+ def resolve_to_sdf(identifier: str, conformer_3d: bool = True) -> str:
39
+ """Resolve an identifier to SDF text via CACTUS.
40
+
41
+ Tries the 3D endpoint first, then falls back to the plain ``/sdf`` form
42
+ (whose coordinates RDKit will embed downstream if they are 2D).
43
+
44
+ Raises:
45
+ MoleculeNotFoundError: CACTUS has no structure for the identifier.
46
+ PubChemAPIError: network/transport failure reaching CACTUS.
47
+ """
48
+ enc = quote(identifier, safe="")
49
+ urls = []
50
+ if conformer_3d:
51
+ urls.append(f"{CACTUS_BASE_URL}/{enc}/file?format=sdf&get3d=true")
52
+ urls.append(f"{CACTUS_BASE_URL}/{enc}/sdf")
53
+
54
+ # (connect, read) timeouts: fail fast on an unreachable host, and cap the
55
+ # read so a slow CACTUS can't hang the search (it's only a fallback).
56
+ timeout = (config.CACTUS_CONNECT_TIMEOUT_S, config.CACTUS_TIMEOUT_S)
57
+ last_status = None
58
+ try:
59
+ for url in urls:
60
+ logger.debug(f"CACTUS resolving: {url}")
61
+ response = requests.get(url, timeout=timeout)
62
+ last_status = response.status_code
63
+ if response.status_code == 200 and _looks_like_sdf(response.text):
64
+ return str(response.text)
65
+ except requests.RequestException as e:
66
+ logger.error(f"CACTUS request failed: {e}")
67
+ raise PubChemAPIError(f"Failed to connect to CACTUS: {e}") from e
68
+
69
+ raise MoleculeNotFoundError(
70
+ f"CACTUS could not resolve '{identifier}' (last status: {last_status})"
71
+ )
72
+
73
+
74
+ def fetch_from_cactus(
75
+ identifier: str, conformer_3d: bool = True
76
+ ) -> Tuple[str, Dict[str, Any]]:
77
+ """Resolve an identifier to ``(xyz_string, metadata)`` via CACTUS.
78
+
79
+ Metadata carries ``source="cactus"`` and a ``conformer_origin`` describing
80
+ whether RDKit had to embed the coordinates.
81
+ """
82
+ sdf_content = resolve_to_sdf(identifier, conformer_3d=conformer_3d)
83
+ xyz, metadata = sdf_to_xyz(sdf_content)
84
+ metadata["source"] = "cactus"
85
+ metadata["conformer_origin"] = (
86
+ "rdkit-embedded" if metadata.get("coords_embedded") else "cactus"
87
+ )
88
+ return xyz, metadata