chemparseplot 1.9.7__tar.gz → 1.9.9__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.
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/PKG-INFO +20 -1
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/_version.py +2 -2
- chemparseplot-1.9.9/chemparseplot/api.py +143 -0
- chemparseplot-1.9.9/chemparseplot/parse/__init__.py +20 -0
- chemparseplot-1.9.9/chemparseplot/parse/grammar/__init__.py +48 -0
- chemparseplot-1.9.9/chemparseplot/parse/grammar/_deps.py +68 -0
- chemparseplot-1.9.9/chemparseplot/parse/grammar/orca_text.py +235 -0
- chemparseplot-1.9.9/chemparseplot/parse/grammar/xyz.py +181 -0
- chemparseplot-1.9.9/chemparseplot/parse/orca/__init__.py +32 -0
- chemparseplot-1.9.9/chemparseplot/parse/orca/_opi.py +67 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/orca/neb/__init__.py +8 -4
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/orca/neb/opi_parser.py +79 -30
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/neb.py +118 -24
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/optimization.py +16 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/pyproject.toml +16 -1
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/readme.md +12 -0
- chemparseplot-1.9.9/tests/fixtures/h2mol.xyz +4 -0
- chemparseplot-1.9.9/tests/fixtures/orca_energy_coords_snippet.out +14 -0
- chemparseplot-1.9.9/tests/fixtures_geomscan_snippet.txt +9 -0
- {chemparseplot-1.9.7/chemparseplot/parse/orca → chemparseplot-1.9.9/tests/parse/grammar}/__init__.py +0 -4
- chemparseplot-1.9.9/tests/parse/grammar/test_grammar_deps.py +38 -0
- chemparseplot-1.9.9/tests/parse/grammar/test_orca_text_grammar.py +57 -0
- chemparseplot-1.9.9/tests/parse/grammar/test_xyz_grammar.py +61 -0
- chemparseplot-1.9.9/tests/parse/test_orca_public_api.py +168 -0
- chemparseplot-1.9.9/tests/plot/test_surface_auto_thin.py +114 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/test_coverage_batch.py +12 -0
- chemparseplot-1.9.9/tests/test_public_api.py +72 -0
- chemparseplot-1.9.7/chemparseplot/parse/__init__.py +0 -11
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/.gitignore +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/LICENSE +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/chemgp_jsonl.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/converter.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/_trajectory_common.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/con_io.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/gprd.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/min_trajectory.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/minimization.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/neb.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/saddle_search.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/eon/stitch.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/file_.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/neb_utils.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/orca/geomscan.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/orca/neb/interp.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/patterns.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/plumed.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/projection.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/sella/saddle_search.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/trajectory/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/trajectory/hdf5.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/trajectory/neb.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/parse/types.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/chemgp.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/geomscan.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/plumed.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/structs.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/plot/theme.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/units.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/chemparseplot/util.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/_optional_imports.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/conftest.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/_con_fixtures.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/orca/test_geomscan.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/orca/test_interp.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_chemgp_jsonl_types.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_con_io.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_converter.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_dimer_trajectory.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_eon_neb_types.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_eon_trajectory_common.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_min_trajectory.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_minimization_result.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_neb_stitch.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_neb_utils.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_parser_types.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_patterns.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_plumed.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_projection.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_readcon_boundary.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_saddle_results.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_sella_saddle.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_trajectory_hdf5.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/parse/test_trajectory_neb.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/__init__.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_chemgp_utils.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_neb_helpers.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_neb_renderers.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_optimization.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_orca_neb_payload.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_projection_refactor.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_strip_rendering.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/plot/test_structs_units.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/test_full_coverage.py +0 -0
- {chemparseplot-1.9.7 → chemparseplot-1.9.9}/tests/tutorials/test_chemparseplot.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chemparseplot
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.9
|
|
4
4
|
Summary: Parsers and plotting tools for computational chemistry
|
|
5
5
|
Project-URL: Documentation, https://chemparseplot.rgoswami.me
|
|
6
6
|
Project-URL: Issues, https://github.com/HaoZeke/chemparseplot/issues
|
|
@@ -25,6 +25,8 @@ Requires-Dist: ase>=3.22; extra == 'all'
|
|
|
25
25
|
Requires-Dist: cmcrameri>=1.7; extra == 'all'
|
|
26
26
|
Requires-Dist: h5py>=3.0; extra == 'all'
|
|
27
27
|
Requires-Dist: matplotlib>=3.8.2; extra == 'all'
|
|
28
|
+
Requires-Dist: orca-pi>=0.1.0; extra == 'all'
|
|
29
|
+
Requires-Dist: parsimonious>=0.10.0; extra == 'all'
|
|
28
30
|
Requires-Dist: polars>=0.20; extra == 'all'
|
|
29
31
|
Requires-Dist: readcon>=0.13.1; extra == 'all'
|
|
30
32
|
Requires-Dist: xyzrender>=0.1.2; extra == 'all'
|
|
@@ -39,6 +41,8 @@ Requires-Dist: sphinx-sitemap>=2.5.1; extra == 'doc'
|
|
|
39
41
|
Requires-Dist: sphinx-togglebutton>=0.3.2; extra == 'doc'
|
|
40
42
|
Requires-Dist: sphinx>=7.2.6; extra == 'doc'
|
|
41
43
|
Requires-Dist: sphinxcontrib-apidoc>=0.4; extra == 'doc'
|
|
44
|
+
Provides-Extra: grammar
|
|
45
|
+
Requires-Dist: parsimonious>=0.10.0; extra == 'grammar'
|
|
42
46
|
Provides-Extra: lint
|
|
43
47
|
Requires-Dist: ruff>=0.1.6; extra == 'lint'
|
|
44
48
|
Provides-Extra: neb
|
|
@@ -46,6 +50,8 @@ Requires-Dist: ase>=3.22; extra == 'neb'
|
|
|
46
50
|
Requires-Dist: h5py>=3.0; extra == 'neb'
|
|
47
51
|
Requires-Dist: polars>=0.20; extra == 'neb'
|
|
48
52
|
Requires-Dist: readcon>=0.13.1; extra == 'neb'
|
|
53
|
+
Provides-Extra: opi
|
|
54
|
+
Requires-Dist: orca-pi>=0.1.0; extra == 'opi'
|
|
49
55
|
Provides-Extra: plot
|
|
50
56
|
Requires-Dist: cmcrameri>=1.7; extra == 'plot'
|
|
51
57
|
Requires-Dist: matplotlib>=3.8.2; extra == 'plot'
|
|
@@ -58,6 +64,7 @@ Provides-Extra: test
|
|
|
58
64
|
Requires-Dist: ase>=3.22; extra == 'test'
|
|
59
65
|
Requires-Dist: h5py>=3.0; extra == 'test'
|
|
60
66
|
Requires-Dist: matplotlib>=3.8.2; extra == 'test'
|
|
67
|
+
Requires-Dist: parsimonious>=0.10.0; extra == 'test'
|
|
61
68
|
Requires-Dist: polars>=0.20; extra == 'test'
|
|
62
69
|
Requires-Dist: pytest-cov>=4.1.0; extra == 'test'
|
|
63
70
|
Requires-Dist: pytest>=7.4.3; extra == 'test'
|
|
@@ -115,6 +122,18 @@ publication-quality plots.
|
|
|
115
122
|
## Installation
|
|
116
123
|
|
|
117
124
|
pip install chemparseplot
|
|
125
|
+
|
|
126
|
+
Suite config (shared with rgpycrumbs — **no** `~/.config/chemparseplot`):
|
|
127
|
+
|
|
128
|
+
# ~/.config/rgpkgs/config.toml or ./rgpkgs.toml
|
|
129
|
+
# [pins] lock = "uv.lock"
|
|
130
|
+
# [pins.packages] …
|
|
131
|
+
|
|
132
|
+
Feature extras (`plot`, `neb`, …) are *transitional* install convenience. Target
|
|
133
|
+
is the same on-demand uv / `ensure_import` design as the rgpycrumbs hub; prefer
|
|
134
|
+
bare install + suite pins for new work.
|
|
135
|
+
|
|
136
|
+
# transitional:
|
|
118
137
|
# With plotting support
|
|
119
138
|
pip install "chemparseplot[plot]"
|
|
120
139
|
# Everything
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.9.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 9,
|
|
21
|
+
__version__ = version = '1.9.9'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 9, 9)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Stable public library entry points for chemparseplot.
|
|
5
|
+
|
|
6
|
+
Prefer this module for new consumers (cookbooks, wailord, notebooks).
|
|
7
|
+
|
|
8
|
+
**Representative in-process path (parse → typed result):**
|
|
9
|
+
|
|
10
|
+
.. code-block:: python
|
|
11
|
+
|
|
12
|
+
from chemparseplot.api import extract_orca_geomscan_energy
|
|
13
|
+
|
|
14
|
+
dist_bohr, energy_Eh = extract_orca_geomscan_energy(orca_out_text, "Actual Energy")
|
|
15
|
+
# dist_bohr, energy_Eh are pint Quantities (bohr, hartree)
|
|
16
|
+
|
|
17
|
+
Heavy plot stacks still live under ``chemparseplot.plot`` (optional deps /
|
|
18
|
+
transitional extras until suite uv design fully covers them).
|
|
19
|
+
|
|
20
|
+
Suite pins/config: use ``rgpycrumbs.api`` (shared rgpkgs TOML) — this package
|
|
21
|
+
does **not** invent ``~/.config/chemparseplot``.
|
|
22
|
+
|
|
23
|
+
.. versionadded:: 1.9.8
|
|
24
|
+
.. versionchanged:: 1.9.9
|
|
25
|
+
Expose ORCA geomscan energy parse as the stable library path.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import TYPE_CHECKING
|
|
31
|
+
|
|
32
|
+
from chemparseplot.parse.orca.geomscan import extract_energy_data
|
|
33
|
+
from chemparseplot.units import (
|
|
34
|
+
ENERGY_UNITS,
|
|
35
|
+
convert_energy_magnitude,
|
|
36
|
+
energy_quantity,
|
|
37
|
+
normalize_energy_unit,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from chemparseplot.units import Q_
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"ENERGY_UNITS",
|
|
45
|
+
"convert_energy_magnitude",
|
|
46
|
+
"energy_quantity",
|
|
47
|
+
"extract_orca_geomscan_energy",
|
|
48
|
+
"grammar_available",
|
|
49
|
+
"normalize_energy_unit",
|
|
50
|
+
"parse_orca_final_energy",
|
|
51
|
+
"parse_xyz",
|
|
52
|
+
"suite_pins",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def grammar_available() -> bool:
|
|
57
|
+
"""True if the optional parsimonious grammar track is importable."""
|
|
58
|
+
from chemparseplot.parse.grammar import grammar_available as _ga
|
|
59
|
+
|
|
60
|
+
return _ga()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _looks_like_path(s: str) -> bool:
|
|
64
|
+
"""True for short path-like strings (avoid Path on multi-line blobs)."""
|
|
65
|
+
if "\n" in s or len(s) > 4096:
|
|
66
|
+
return False
|
|
67
|
+
from pathlib import Path
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
return Path(s).is_file()
|
|
71
|
+
except OSError:
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_xyz(path_or_text: str):
|
|
76
|
+
"""Parse XYZ via grammar track (file path if exists, else text).
|
|
77
|
+
|
|
78
|
+
Requires ``chemparseplot[grammar]``.
|
|
79
|
+
"""
|
|
80
|
+
from chemparseplot.parse.grammar.xyz import parse_xyz_file, parse_xyz_text
|
|
81
|
+
|
|
82
|
+
if _looks_like_path(path_or_text):
|
|
83
|
+
return parse_xyz_file(path_or_text)
|
|
84
|
+
return parse_xyz_text(path_or_text)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_orca_final_energy(path_or_text: str):
|
|
88
|
+
"""Last ``FINAL SINGLE POINT ENERGY`` as a hartree Quantity.
|
|
89
|
+
|
|
90
|
+
Grammar track; requires ``chemparseplot[grammar]``.
|
|
91
|
+
"""
|
|
92
|
+
from chemparseplot.parse.grammar.orca_text import (
|
|
93
|
+
parse_orca_text_file,
|
|
94
|
+
parse_orca_text_summary,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if _looks_like_path(path_or_text):
|
|
98
|
+
summary = parse_orca_text_file(path_or_text)
|
|
99
|
+
else:
|
|
100
|
+
summary = parse_orca_text_summary(path_or_text)
|
|
101
|
+
return summary.final_energy
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def extract_orca_geomscan_energy(
|
|
106
|
+
data: str, energy_type: str = "Actual Energy"
|
|
107
|
+
) -> tuple[Q_, Q_]:
|
|
108
|
+
"""Parse ORCA geometry-scan surface energies from output text.
|
|
109
|
+
|
|
110
|
+
In-process library path: text → typed ``(distance, energy)`` pint Quantities
|
|
111
|
+
in Bohr and Hartree (ORCA defaults).
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
data:
|
|
116
|
+
Blob of ORCA output containing ``Calculated Surface`` blocks.
|
|
117
|
+
energy_type:
|
|
118
|
+
``\"Actual Energy\"`` or ``\"SCF energy\"`` (substring match in the block).
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
tuple[Q_, Q_]
|
|
123
|
+
Distances (bohr) and energies (hartree). Empty quantities if no match.
|
|
124
|
+
"""
|
|
125
|
+
return extract_energy_data(data, energy_type)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def suite_pins() -> dict[str, str]:
|
|
129
|
+
"""Return suite package pins from rgpkgs config + env (soft on old hub).
|
|
130
|
+
|
|
131
|
+
Empty dict if rgpycrumbs is missing or too old.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
from rgpycrumbs.api import load_config, pins_from_env
|
|
135
|
+
except ImportError:
|
|
136
|
+
return {}
|
|
137
|
+
pins = dict(pins_from_env())
|
|
138
|
+
try:
|
|
139
|
+
cfg = load_config()
|
|
140
|
+
pins.update(cfg.merged_package_pins_normalized())
|
|
141
|
+
except Exception: # noqa: BLE001 — soft fail for consumers
|
|
142
|
+
pass
|
|
143
|
+
return pins
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
from chemparseplot.parse import eon, orca, patterns
|
|
6
|
+
|
|
7
|
+
__all__ = ["eon", "grammar", "orca", "patterns"]
|
|
8
|
+
|
|
9
|
+
# Lazy imports for modules with optional heavy deps (h5py, pandas, parsimonious)
|
|
10
|
+
# Import directly: from chemparseplot.parse.chemgp_hdf5 import read_h5_table
|
|
11
|
+
# Or: from chemparseplot.parse import plumed, projection
|
|
12
|
+
# Grammar track: from chemparseplot.parse.grammar import parse_xyz_text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __getattr__(name: str):
|
|
16
|
+
if name == "grammar":
|
|
17
|
+
from chemparseplot.parse import grammar as _grammar
|
|
18
|
+
|
|
19
|
+
return _grammar
|
|
20
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Grammar / AST track for text-heavy formats (parsimonious).
|
|
5
|
+
|
|
6
|
+
Optional extra: ``pip install 'chemparseplot[grammar]'``.
|
|
7
|
+
|
|
8
|
+
Prefer structured APIs (OPI) for ORCA products that expose them; use this
|
|
9
|
+
package for XYZ and text-only ORCA sections (energy lines, Cartesian blocks).
|
|
10
|
+
|
|
11
|
+
```{versionadded} 1.9.9
|
|
12
|
+
```
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from chemparseplot.parse.grammar._deps import (
|
|
18
|
+
grammar_available,
|
|
19
|
+
reset_grammar_cache,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"grammar_available",
|
|
24
|
+
"parse_orca_text_file",
|
|
25
|
+
"parse_orca_text_summary",
|
|
26
|
+
"parse_xyz_file",
|
|
27
|
+
"parse_xyz_text",
|
|
28
|
+
"reset_grammar_cache",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def __getattr__(name: str):
|
|
33
|
+
if name in ("parse_xyz_text", "parse_xyz_file", "XyzFrame", "XyzAtom", "write_xyz_text"):
|
|
34
|
+
from chemparseplot.parse.grammar import xyz as _xyz
|
|
35
|
+
|
|
36
|
+
return getattr(_xyz, name)
|
|
37
|
+
if name in (
|
|
38
|
+
"parse_orca_text_summary",
|
|
39
|
+
"parse_orca_text_file",
|
|
40
|
+
"OrcaTextSummary",
|
|
41
|
+
"OrcaAtomCoord",
|
|
42
|
+
"extract_final_energies_hartree",
|
|
43
|
+
"extract_cartesian_angstrom_blocks",
|
|
44
|
+
):
|
|
45
|
+
from chemparseplot.parse.grammar import orca_text as _ot
|
|
46
|
+
|
|
47
|
+
return getattr(_ot, name)
|
|
48
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Lazy loading for optional ``parsimonious`` (grammar track).
|
|
5
|
+
|
|
6
|
+
```{versionadded} 1.9.9
|
|
7
|
+
```
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import importlib
|
|
13
|
+
import importlib.util
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
_grammar_mod: Any | None = None
|
|
17
|
+
_nodes_mod: Any | None = None
|
|
18
|
+
_probe: bool | None = None
|
|
19
|
+
|
|
20
|
+
_INSTALL_HINT = (
|
|
21
|
+
"parsimonious is not installed. "
|
|
22
|
+
"Install with: pip install 'chemparseplot[grammar]' "
|
|
23
|
+
"for grammar/AST text parsers (XYZ, text-heavy ORCA sections)."
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def grammar_available() -> bool:
|
|
28
|
+
"""Return True if ``parsimonious`` can be imported."""
|
|
29
|
+
global _probe
|
|
30
|
+
if _probe is not None:
|
|
31
|
+
return _probe
|
|
32
|
+
try:
|
|
33
|
+
_probe = importlib.util.find_spec("parsimonious") is not None
|
|
34
|
+
except (ImportError, ModuleNotFoundError, ValueError):
|
|
35
|
+
_probe = False
|
|
36
|
+
return _probe
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_grammar_class() -> Any:
|
|
40
|
+
"""Return ``parsimonious.grammar.Grammar``, importing lazily."""
|
|
41
|
+
global _grammar_mod
|
|
42
|
+
if _grammar_mod is not None:
|
|
43
|
+
return _grammar_mod.Grammar
|
|
44
|
+
try:
|
|
45
|
+
_grammar_mod = importlib.import_module("parsimonious.grammar")
|
|
46
|
+
except ImportError as exc:
|
|
47
|
+
raise ImportError(_INSTALL_HINT) from exc
|
|
48
|
+
return _grammar_mod.Grammar
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_node_visitor_class() -> Any:
|
|
52
|
+
"""Return ``parsimonious.nodes.NodeVisitor``, importing lazily."""
|
|
53
|
+
global _nodes_mod
|
|
54
|
+
if _nodes_mod is not None:
|
|
55
|
+
return _nodes_mod.NodeVisitor
|
|
56
|
+
try:
|
|
57
|
+
_nodes_mod = importlib.import_module("parsimonious.nodes")
|
|
58
|
+
except ImportError as exc:
|
|
59
|
+
raise ImportError(_INSTALL_HINT) from exc
|
|
60
|
+
return _nodes_mod.NodeVisitor
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def reset_grammar_cache() -> None:
|
|
64
|
+
"""Clear cached parsimonious import state (tests only)."""
|
|
65
|
+
global _grammar_mod, _nodes_mod, _probe
|
|
66
|
+
_grammar_mod = None
|
|
67
|
+
_nodes_mod = None
|
|
68
|
+
_probe = None
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Grammar-oriented extractors for text-heavy ORCA output sections.
|
|
5
|
+
|
|
6
|
+
Prefer structured backends (OPI) when available. This module covers sections
|
|
7
|
+
that remain text dumps: final single-point energies and Cartesian geometry
|
|
8
|
+
blocks. Foundation for VPT2/IR/populations ports from wailord.
|
|
9
|
+
|
|
10
|
+
```{versionadded} 1.9.9
|
|
11
|
+
```
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from chemparseplot.parse.grammar._deps import (
|
|
22
|
+
get_grammar_class,
|
|
23
|
+
get_node_visitor_class,
|
|
24
|
+
)
|
|
25
|
+
from chemparseplot.units import Q_
|
|
26
|
+
|
|
27
|
+
# Named rules are real expressions (not aliases) so visitors fire reliably.
|
|
28
|
+
_ENERGY_LINE_GRAMMAR_SRC = r"""
|
|
29
|
+
energy_line = label ws value ws?
|
|
30
|
+
label = "FINAL SINGLE POINT ENERGY"
|
|
31
|
+
value = ~r"[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?"
|
|
32
|
+
ws = ~r"[ \t]+"
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_CART_BLOCK_GRAMMAR_SRC = r"""
|
|
36
|
+
cart_block = header newline dashes newline (atom_line newline)+
|
|
37
|
+
header = "CARTESIAN COORDINATES (ANGSTROEM)"
|
|
38
|
+
dashes = ~r"-{3,}"
|
|
39
|
+
atom_line = ws? atype ws float_ ws float_ ws float_ ws?
|
|
40
|
+
atype = ~r"[A-Za-z][a-zA-Z0-9]{0,2}"
|
|
41
|
+
float_ = ~r"[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?"
|
|
42
|
+
newline = ~r"\n"
|
|
43
|
+
ws = ~r"[ \t]+"
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
_energy_grammar: Any | None = None
|
|
47
|
+
_cart_grammar: Any | None = None
|
|
48
|
+
|
|
49
|
+
_ENERGY_LINE_RE = re.compile(
|
|
50
|
+
r"^.*FINAL SINGLE POINT ENERGY[ \t]+"
|
|
51
|
+
r"([+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?).*$",
|
|
52
|
+
re.MULTILINE,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
_CART_BLOCK_RE = re.compile(
|
|
56
|
+
r"CARTESIAN COORDINATES \(ANGSTROEM\)\n"
|
|
57
|
+
r"-{3,}\n"
|
|
58
|
+
r"((?:[ \t]*[A-Za-z][a-zA-Z0-9]{0,2}[ \t]+"
|
|
59
|
+
r"[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?[ \t]+"
|
|
60
|
+
r"[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?[ \t]+"
|
|
61
|
+
r"[+-]?(?:\d+\.\d*|\.\d+|\d+)(?:[eE][+-]?\d+)?[ \t]*\n)+)",
|
|
62
|
+
re.MULTILINE,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
_N_ATOMS_RE = re.compile(
|
|
66
|
+
r"Number of atoms\s*(?:\.{3,}|\.\.\.)\s*(\d+)",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _energy_g() -> Any:
|
|
71
|
+
global _energy_grammar
|
|
72
|
+
if _energy_grammar is None:
|
|
73
|
+
Grammar = get_grammar_class()
|
|
74
|
+
_energy_grammar = Grammar(_ENERGY_LINE_GRAMMAR_SRC)
|
|
75
|
+
return _energy_grammar
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _cart_g() -> Any:
|
|
79
|
+
global _cart_grammar
|
|
80
|
+
if _cart_grammar is None:
|
|
81
|
+
Grammar = get_grammar_class()
|
|
82
|
+
_cart_grammar = Grammar(_CART_BLOCK_GRAMMAR_SRC)
|
|
83
|
+
return _cart_grammar
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _flatten(xs: Any) -> list[Any]:
|
|
87
|
+
out: list[Any] = []
|
|
88
|
+
if isinstance(xs, list):
|
|
89
|
+
for x in xs:
|
|
90
|
+
out.extend(_flatten(x))
|
|
91
|
+
elif xs is not None and xs != "":
|
|
92
|
+
out.append(xs)
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass(frozen=True, slots=True)
|
|
97
|
+
class OrcaAtomCoord:
|
|
98
|
+
"""One atom in an ORCA Cartesian (Å) block."""
|
|
99
|
+
|
|
100
|
+
symbol: str
|
|
101
|
+
x: float
|
|
102
|
+
y: float
|
|
103
|
+
z: float
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True, slots=True)
|
|
107
|
+
class OrcaTextSummary:
|
|
108
|
+
"""Structured slice of text ORCA output."""
|
|
109
|
+
|
|
110
|
+
energies_hartree: tuple[float, ...]
|
|
111
|
+
last_geometry: tuple[OrcaAtomCoord, ...]
|
|
112
|
+
n_atoms: int | None
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def final_energy_hartree(self) -> float | None:
|
|
116
|
+
if not self.energies_hartree:
|
|
117
|
+
return None
|
|
118
|
+
return self.energies_hartree[-1]
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def final_energy(self) -> Any:
|
|
122
|
+
"""Last final single-point energy as a pint Quantity (hartree)."""
|
|
123
|
+
e = self.final_energy_hartree
|
|
124
|
+
if e is None:
|
|
125
|
+
return Q_([], "hartree")
|
|
126
|
+
return Q_(e, "hartree")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def extract_final_energies_hartree(text: str) -> list[float]:
|
|
130
|
+
"""All ``FINAL SINGLE POINT ENERGY`` values in document order.
|
|
131
|
+
|
|
132
|
+
Candidate lines are selected with a line scan; each is validated and
|
|
133
|
+
extracted with a parsimonious line grammar.
|
|
134
|
+
"""
|
|
135
|
+
g = _energy_g()
|
|
136
|
+
NodeVisitor = get_node_visitor_class()
|
|
137
|
+
|
|
138
|
+
class EnergyVisitor(NodeVisitor):
|
|
139
|
+
def __init__(self) -> None:
|
|
140
|
+
self.value: float | None = None
|
|
141
|
+
|
|
142
|
+
def visit_value(self, node: Any, visited_children: Any) -> float:
|
|
143
|
+
self.value = float(node.text)
|
|
144
|
+
return self.value
|
|
145
|
+
|
|
146
|
+
def generic_visit(self, node: Any, visited_children: Any) -> Any:
|
|
147
|
+
return visited_children or node.text
|
|
148
|
+
|
|
149
|
+
out: list[float] = []
|
|
150
|
+
for m in _ENERGY_LINE_RE.finditer(text):
|
|
151
|
+
line = m.group(0).strip()
|
|
152
|
+
try:
|
|
153
|
+
tree = g.parse(line)
|
|
154
|
+
except Exception: # noqa: BLE001 — skip non-conforming noise
|
|
155
|
+
continue
|
|
156
|
+
v = EnergyVisitor()
|
|
157
|
+
v.visit(tree)
|
|
158
|
+
if v.value is not None:
|
|
159
|
+
out.append(v.value)
|
|
160
|
+
return out
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def extract_cartesian_angstrom_blocks(text: str) -> list[list[OrcaAtomCoord]]:
|
|
164
|
+
"""Parse every ``CARTESIAN COORDINATES (ANGSTROEM)`` block via grammar."""
|
|
165
|
+
g = _cart_g()
|
|
166
|
+
NodeVisitor = get_node_visitor_class()
|
|
167
|
+
|
|
168
|
+
class CartVisitor(NodeVisitor):
|
|
169
|
+
def __init__(self) -> None:
|
|
170
|
+
self.atoms: list[OrcaAtomCoord] = []
|
|
171
|
+
|
|
172
|
+
def visit_atom_line(self, node: Any, visited_children: Any) -> OrcaAtomCoord:
|
|
173
|
+
flat = _flatten(visited_children)
|
|
174
|
+
symbol = next(
|
|
175
|
+
x for x in flat if isinstance(x, str) and x.strip() and x[0].isalpha()
|
|
176
|
+
)
|
|
177
|
+
nums = [x for x in flat if isinstance(x, float)]
|
|
178
|
+
atom = OrcaAtomCoord(symbol=symbol, x=nums[0], y=nums[1], z=nums[2])
|
|
179
|
+
self.atoms.append(atom)
|
|
180
|
+
return atom
|
|
181
|
+
|
|
182
|
+
def visit_atype(self, node: Any, visited_children: Any) -> str:
|
|
183
|
+
return node.text
|
|
184
|
+
|
|
185
|
+
def visit_float_(self, node: Any, visited_children: Any) -> float:
|
|
186
|
+
return float(node.text)
|
|
187
|
+
|
|
188
|
+
def generic_visit(self, node: Any, visited_children: Any) -> Any:
|
|
189
|
+
return visited_children or node.text
|
|
190
|
+
|
|
191
|
+
blocks: list[list[OrcaAtomCoord]] = []
|
|
192
|
+
for m in _CART_BLOCK_RE.finditer(text):
|
|
193
|
+
chunk = (
|
|
194
|
+
"CARTESIAN COORDINATES (ANGSTROEM)\n"
|
|
195
|
+
"---------------------------------\n"
|
|
196
|
+
+ m.group(1)
|
|
197
|
+
)
|
|
198
|
+
if not chunk.endswith("\n"):
|
|
199
|
+
chunk += "\n"
|
|
200
|
+
try:
|
|
201
|
+
tree = g.parse(chunk)
|
|
202
|
+
except Exception: # noqa: BLE001
|
|
203
|
+
continue
|
|
204
|
+
v = CartVisitor()
|
|
205
|
+
v.visit(tree)
|
|
206
|
+
if v.atoms:
|
|
207
|
+
blocks.append(v.atoms)
|
|
208
|
+
return blocks
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def extract_n_atoms(text: str) -> int | None:
|
|
212
|
+
"""Best-effort ``Number of atoms`` from ORCA text (last occurrence)."""
|
|
213
|
+
matches = _N_ATOMS_RE.findall(text)
|
|
214
|
+
if not matches:
|
|
215
|
+
return None
|
|
216
|
+
return int(matches[-1])
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def parse_orca_text_summary(text: str) -> OrcaTextSummary:
|
|
220
|
+
"""Summarize final energies and last Cartesian geometry from ORCA text."""
|
|
221
|
+
energies = extract_final_energies_hartree(text)
|
|
222
|
+
blocks = extract_cartesian_angstrom_blocks(text)
|
|
223
|
+
last_geom: tuple[OrcaAtomCoord, ...] = ()
|
|
224
|
+
if blocks:
|
|
225
|
+
last_geom = tuple(blocks[-1])
|
|
226
|
+
return OrcaTextSummary(
|
|
227
|
+
energies_hartree=tuple(energies),
|
|
228
|
+
last_geometry=last_geom,
|
|
229
|
+
n_atoms=extract_n_atoms(text),
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def parse_orca_text_file(path: str | Path) -> OrcaTextSummary:
|
|
234
|
+
"""Read an ORCA ``.out`` and return :class:`OrcaTextSummary`."""
|
|
235
|
+
return parse_orca_text_summary(Path(path).read_text())
|