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
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
|