chemparseplot 1.9.6__tar.gz → 1.9.8__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.6 → chemparseplot-1.9.8}/PKG-INFO +13 -1
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/_version.py +2 -2
- chemparseplot-1.9.8/chemparseplot/api.py +91 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/optimization.py +113 -39
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/readme.md +12 -0
- chemparseplot-1.9.8/tests/fixtures_geomscan_snippet.txt +9 -0
- chemparseplot-1.9.8/tests/test_public_api.py +72 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/.gitignore +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/LICENSE +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/chemgp_jsonl.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/converter.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/_trajectory_common.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/con_io.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/gprd.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/min_trajectory.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/minimization.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/neb.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/saddle_search.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/eon/stitch.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/file_.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/neb_utils.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/orca/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/orca/geomscan.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/orca/neb/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/orca/neb/interp.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/patterns.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/plumed.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/projection.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/sella/saddle_search.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/trajectory/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/trajectory/hdf5.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/trajectory/neb.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/parse/types.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/chemgp.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/geomscan.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/neb.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/plumed.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/structs.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/plot/theme.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/units.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/chemparseplot/util.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/pyproject.toml +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/_optional_imports.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/conftest.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/_con_fixtures.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/orca/test_geomscan.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/orca/test_interp.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_chemgp_jsonl_types.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_con_io.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_converter.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_dimer_trajectory.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_eon_neb_types.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_eon_trajectory_common.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_min_trajectory.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_minimization_result.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_neb_stitch.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_neb_utils.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_parser_types.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_patterns.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_plumed.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_projection.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_readcon_boundary.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_saddle_results.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_sella_saddle.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_trajectory_hdf5.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/parse/test_trajectory_neb.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/__init__.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_chemgp_utils.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_neb_helpers.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_neb_renderers.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_optimization.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_orca_neb_payload.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_projection_refactor.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_strip_rendering.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/plot/test_structs_units.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/test_coverage_batch.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/tests/test_full_coverage.py +0 -0
- {chemparseplot-1.9.6 → chemparseplot-1.9.8}/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.8
|
|
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
|
|
@@ -115,6 +115,18 @@ publication-quality plots.
|
|
|
115
115
|
## Installation
|
|
116
116
|
|
|
117
117
|
pip install chemparseplot
|
|
118
|
+
|
|
119
|
+
Suite config (shared with rgpycrumbs — **no** `~/.config/chemparseplot`):
|
|
120
|
+
|
|
121
|
+
# ~/.config/rgpkgs/config.toml or ./rgpkgs.toml
|
|
122
|
+
# [pins] lock = "uv.lock"
|
|
123
|
+
# [pins.packages] …
|
|
124
|
+
|
|
125
|
+
Feature extras (`plot`, `neb`, …) are *transitional* install convenience. Target
|
|
126
|
+
is the same on-demand uv / `ensure_import` design as the rgpycrumbs hub; prefer
|
|
127
|
+
bare install + suite pins for new work.
|
|
128
|
+
|
|
129
|
+
# transitional:
|
|
118
130
|
# With plotting support
|
|
119
131
|
pip install "chemparseplot[plot]"
|
|
120
132
|
# 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.8'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 9, 8)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,91 @@
|
|
|
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
|
+
"normalize_energy_unit",
|
|
49
|
+
"suite_pins",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def extract_orca_geomscan_energy(
|
|
54
|
+
data: str, energy_type: str = "Actual Energy"
|
|
55
|
+
) -> tuple[Q_, Q_]:
|
|
56
|
+
"""Parse ORCA geometry-scan surface energies from output text.
|
|
57
|
+
|
|
58
|
+
In-process library path: text → typed ``(distance, energy)`` pint Quantities
|
|
59
|
+
in Bohr and Hartree (ORCA defaults).
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
data:
|
|
64
|
+
Blob of ORCA output containing ``Calculated Surface`` blocks.
|
|
65
|
+
energy_type:
|
|
66
|
+
``\"Actual Energy\"`` or ``\"SCF energy\"`` (substring match in the block).
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
tuple[Q_, Q_]
|
|
71
|
+
Distances (bohr) and energies (hartree). Empty quantities if no match.
|
|
72
|
+
"""
|
|
73
|
+
return extract_energy_data(data, energy_type)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def suite_pins() -> dict[str, str]:
|
|
77
|
+
"""Return suite package pins from rgpkgs config + env (soft on old hub).
|
|
78
|
+
|
|
79
|
+
Empty dict if rgpycrumbs is missing or too old.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
from rgpycrumbs.api import load_config, pins_from_env
|
|
83
|
+
except ImportError:
|
|
84
|
+
return {}
|
|
85
|
+
pins = dict(pins_from_env())
|
|
86
|
+
try:
|
|
87
|
+
cfg = load_config()
|
|
88
|
+
pins.update(cfg.merged_package_pins_normalized())
|
|
89
|
+
except Exception: # noqa: BLE001 — soft fail for consumers
|
|
90
|
+
pass
|
|
91
|
+
return pins
|
|
@@ -59,13 +59,15 @@ def create_landscape_axes(
|
|
|
59
59
|
dpi: int,
|
|
60
60
|
has_strip: bool,
|
|
61
61
|
theme,
|
|
62
|
-
base_size: float =
|
|
63
|
-
strip_height_ratio: float = 0.
|
|
64
|
-
strip_hspace: float = 0.
|
|
62
|
+
base_size: float = 6.0,
|
|
63
|
+
strip_height_ratio: float = 0.38,
|
|
64
|
+
strip_hspace: float = 0.22,
|
|
65
65
|
):
|
|
66
66
|
"""Create a landscape figure with an optional structure strip axis."""
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
# Extra bottom room so x-label / ticks are not crushed by the strip.
|
|
69
|
+
fig_h = base_size + (2.15 if has_strip else 0.35)
|
|
70
|
+
fig = plt.figure(figsize=(base_size + 1.1, fig_h), dpi=dpi)
|
|
69
71
|
if has_strip:
|
|
70
72
|
gs = GridSpec(
|
|
71
73
|
2,
|
|
@@ -73,6 +75,10 @@ def create_landscape_axes(
|
|
|
73
75
|
height_ratios=[1, strip_height_ratio],
|
|
74
76
|
hspace=strip_hspace,
|
|
75
77
|
figure=fig,
|
|
78
|
+
left=0.14,
|
|
79
|
+
right=0.86,
|
|
80
|
+
top=0.90,
|
|
81
|
+
bottom=0.08,
|
|
76
82
|
)
|
|
77
83
|
ax = fig.add_subplot(gs[0])
|
|
78
84
|
ax_strip = fig.add_subplot(gs[1])
|
|
@@ -102,22 +108,24 @@ def annotate_endpoint(ax, x: float, y: float, label: str, *, boxed: bool):
|
|
|
102
108
|
"""Annotate an optimization endpoint consistently."""
|
|
103
109
|
|
|
104
110
|
kwargs = {
|
|
105
|
-
"fontsize":
|
|
111
|
+
"fontsize": 12,
|
|
106
112
|
"fontweight": "bold",
|
|
113
|
+
"color": "black",
|
|
107
114
|
"ha": "center",
|
|
108
115
|
"va": "bottom",
|
|
109
|
-
"zorder":
|
|
116
|
+
"zorder": 120,
|
|
110
117
|
}
|
|
111
118
|
if boxed:
|
|
112
119
|
kwargs.update(
|
|
113
120
|
{
|
|
114
|
-
"xytext": (0,
|
|
121
|
+
"xytext": (0, 8),
|
|
115
122
|
"textcoords": "offset points",
|
|
116
123
|
"bbox": {
|
|
117
|
-
"boxstyle": "round,pad=0.
|
|
124
|
+
"boxstyle": "round,pad=0.25",
|
|
118
125
|
"facecolor": "white",
|
|
119
|
-
"edgecolor": "
|
|
120
|
-
"
|
|
126
|
+
"edgecolor": "black",
|
|
127
|
+
"linewidth": 0.8,
|
|
128
|
+
"alpha": 0.95,
|
|
121
129
|
},
|
|
122
130
|
}
|
|
123
131
|
)
|
|
@@ -160,7 +168,8 @@ def render_endpoint_strip(
|
|
|
160
168
|
show_dividers=strip_dividers,
|
|
161
169
|
perspective_tilt=perspective_tilt,
|
|
162
170
|
xyzrender_config=xyzrender_config,
|
|
163
|
-
width_fill_fraction=0.
|
|
171
|
+
width_fill_fraction=0.72,
|
|
172
|
+
prefer_single_row=True,
|
|
164
173
|
)
|
|
165
174
|
|
|
166
175
|
|
|
@@ -204,15 +213,21 @@ def save_landscape_figure(
|
|
|
204
213
|
ax=None,
|
|
205
214
|
ax_strip=None,
|
|
206
215
|
) -> None:
|
|
207
|
-
"""Save optimization landscapes
|
|
216
|
+
"""Save optimization landscapes; always crop with a pad so labels stay visible."""
|
|
208
217
|
|
|
209
218
|
if has_strip and ax is not None and ax_strip is not None:
|
|
210
|
-
enforce_strip_clearance(fig, ax, ax_strip)
|
|
211
|
-
|
|
219
|
+
enforce_strip_clearance(fig, ax, ax_strip, min_clearance_px=40.0)
|
|
220
|
+
elif not has_strip:
|
|
212
221
|
fig.tight_layout()
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
# Always tight-crop: without it strip layouts left large white margins and
|
|
223
|
+
# could hide axis labels depending on the viewer/frame.
|
|
224
|
+
fig.savefig(
|
|
225
|
+
str(output),
|
|
226
|
+
dpi=dpi,
|
|
227
|
+
bbox_inches="tight",
|
|
228
|
+
pad_inches=0.25,
|
|
229
|
+
facecolor=fig.get_facecolor(),
|
|
230
|
+
)
|
|
216
231
|
plt.close(fig)
|
|
217
232
|
|
|
218
233
|
|
|
@@ -314,6 +329,8 @@ def render_single_ended_landscape(
|
|
|
314
329
|
energy_unit: str = "eV",
|
|
315
330
|
energy_cap: float | None = None,
|
|
316
331
|
energy_cap_window: float | None = None,
|
|
332
|
+
relative_energy: bool = True,
|
|
333
|
+
title: str | None = None,
|
|
317
334
|
cmap: str = "viridis",
|
|
318
335
|
output: Path,
|
|
319
336
|
dpi: int = 150,
|
|
@@ -321,8 +338,8 @@ def render_single_ended_landscape(
|
|
|
321
338
|
plot_structures: str = "none",
|
|
322
339
|
strip_structs: Sequence[Any] | None = None,
|
|
323
340
|
strip_labels: Sequence[str] | None = None,
|
|
324
|
-
endpoint_start_label: str = "
|
|
325
|
-
endpoint_end_label: str = "
|
|
341
|
+
endpoint_start_label: str = "initial",
|
|
342
|
+
endpoint_end_label: str = "minimized",
|
|
326
343
|
endpoint_boxed: bool = True,
|
|
327
344
|
annotate_overlay_starts: bool = False,
|
|
328
345
|
overlay_start_label: str = "R",
|
|
@@ -345,6 +362,8 @@ def render_single_ended_landscape(
|
|
|
345
362
|
```{versionadded} 1.8.1
|
|
346
363
|
```
|
|
347
364
|
"""
|
|
365
|
+
from matplotlib import ticker
|
|
366
|
+
|
|
348
367
|
from chemparseplot.parse.neb_utils import (
|
|
349
368
|
calculate_landscape_coords,
|
|
350
369
|
compute_synthetic_gradients,
|
|
@@ -363,33 +382,46 @@ def render_single_ended_landscape(
|
|
|
363
382
|
energies = np.asarray(energies_eV, dtype=float)
|
|
364
383
|
n = min(len(rmsd_a), len(energies))
|
|
365
384
|
rmsd_a, rmsd_b, energies = rmsd_a[:n], rmsd_b[:n], energies[:n]
|
|
366
|
-
|
|
385
|
+
# Relative energy keeps colorbars readable (absolute eV often span <0.2 eV
|
|
386
|
+
# and all ticks collapse to the same printed value).
|
|
387
|
+
e_ref = float(np.min(energies)) if relative_energy else 0.0
|
|
388
|
+
energies_plot = energies - e_ref
|
|
389
|
+
display_e = convert_energy(energies_plot, energy_unit)
|
|
367
390
|
cap = energy_cap
|
|
368
391
|
if cap is None and energy_cap_window is not None:
|
|
369
392
|
cap = float(np.min(display_e)) + energy_cap_window
|
|
370
393
|
if cap is not None:
|
|
371
|
-
# Cap in display units, map back to eV for the surface routine which
|
|
372
|
-
# converts from eV once.
|
|
373
394
|
factor = float(convert_energy(np.array([1.0]), energy_unit)[0])
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
energies_plot = np.minimum(
|
|
396
|
+
energies_plot, cap / factor if factor else cap
|
|
397
|
+
)
|
|
398
|
+
display_e = convert_energy(energies_plot, energy_unit)
|
|
376
399
|
|
|
377
|
-
f_para = -np.gradient(
|
|
400
|
+
f_para = -np.gradient(energies_plot)
|
|
378
401
|
grad_a, grad_b = compute_synthetic_gradients(rmsd_a, rmsd_b, f_para)
|
|
379
402
|
|
|
380
|
-
|
|
403
|
+
z_label = (
|
|
404
|
+
f"Relative energy ({energy_unit})"
|
|
405
|
+
if relative_energy
|
|
406
|
+
else energy_axis_label(energy_unit)
|
|
407
|
+
)
|
|
408
|
+
cb = plot_optimization_landscape(
|
|
381
409
|
ax,
|
|
382
410
|
rmsd_a,
|
|
383
411
|
rmsd_b,
|
|
384
412
|
grad_a,
|
|
385
413
|
grad_b,
|
|
386
|
-
|
|
414
|
+
energies_plot,
|
|
387
415
|
project_path=project_path,
|
|
388
416
|
method=surface_type,
|
|
389
417
|
cmap=cmap,
|
|
390
418
|
label_mode="optimization",
|
|
391
419
|
energy_unit=energy_unit,
|
|
420
|
+
z_label=z_label,
|
|
392
421
|
)
|
|
422
|
+
if cb is not None:
|
|
423
|
+
cb.ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.3f"))
|
|
424
|
+
cb.set_label(z_label, rotation=270, labelpad=18)
|
|
393
425
|
|
|
394
426
|
basis = None
|
|
395
427
|
if project_path:
|
|
@@ -408,7 +440,9 @@ def render_single_ended_landscape(
|
|
|
408
440
|
)
|
|
409
441
|
m = min(len(ra), len(atoms_ov))
|
|
410
442
|
ra, rb = ra[:m], rb[:m]
|
|
411
|
-
px, py, _ = project_landscape_path(
|
|
443
|
+
px, py, _ = project_landscape_path(
|
|
444
|
+
ra, rb, project_path=project_path, basis=basis
|
|
445
|
+
)
|
|
412
446
|
color = OVERLAY_COLORS[idx % len(OVERLAY_COLORS)]
|
|
413
447
|
if len(overlays) > 1:
|
|
414
448
|
ax.plot(
|
|
@@ -423,37 +457,77 @@ def render_single_ended_landscape(
|
|
|
423
457
|
label=lbl,
|
|
424
458
|
)
|
|
425
459
|
if annotate_overlay_starts and len(px):
|
|
426
|
-
annotate_endpoint(
|
|
460
|
+
annotate_endpoint(
|
|
461
|
+
ax, float(px[0]), float(py[0]), overlay_start_label, boxed=False
|
|
462
|
+
)
|
|
427
463
|
|
|
428
464
|
plot_x, plot_y, _ = project_landscape_path(
|
|
429
465
|
rmsd_a, rmsd_b, project_path=project_path, basis=basis
|
|
430
466
|
)
|
|
431
467
|
annotate_endpoint(
|
|
432
|
-
ax,
|
|
468
|
+
ax,
|
|
469
|
+
float(plot_x[0]),
|
|
470
|
+
float(plot_y[0]),
|
|
471
|
+
endpoint_start_label,
|
|
472
|
+
boxed=endpoint_boxed,
|
|
433
473
|
)
|
|
434
474
|
annotate_endpoint(
|
|
435
|
-
ax,
|
|
475
|
+
ax,
|
|
476
|
+
float(plot_x[-1]),
|
|
477
|
+
float(plot_y[-1]),
|
|
478
|
+
endpoint_end_label,
|
|
479
|
+
boxed=endpoint_boxed,
|
|
436
480
|
)
|
|
437
481
|
|
|
482
|
+
# True 1:1 Å panel: Δd window matches Δs (same RMSD metric).
|
|
483
|
+
if project_path and len(plot_x) > 1:
|
|
484
|
+
x0, x1 = float(np.min(plot_x)), float(np.max(plot_x))
|
|
485
|
+
y0, y1 = float(np.min(plot_y)), float(np.max(plot_y))
|
|
486
|
+
s_pad = max((x1 - x0) * 0.06, 0.01)
|
|
487
|
+
x0, x1 = x0 - s_pad, x1 + s_pad
|
|
488
|
+
half = max(0.5 * (x1 - x0), abs(y0), abs(y1), 0.02)
|
|
489
|
+
x_mid = 0.5 * (x0 + x1)
|
|
490
|
+
ax.set_xlim(x_mid - half, x_mid + half)
|
|
491
|
+
ax.set_ylim(-half, half)
|
|
492
|
+
ax.set_aspect("equal", adjustable="box")
|
|
493
|
+
|
|
494
|
+
# Re-assert axis labels after surface/path (bold, large enough to read).
|
|
495
|
+
if project_path:
|
|
496
|
+
ax.set_xlabel(_LABELS["optimization"]["x"], fontweight="bold", fontsize=12)
|
|
497
|
+
ax.set_ylabel(_LABELS["optimization"]["y"], fontweight="bold", fontsize=12)
|
|
498
|
+
else:
|
|
499
|
+
ax.set_xlabel(r"RMSD from ref A ($\AA$)", fontweight="bold", fontsize=12)
|
|
500
|
+
ax.set_ylabel(r"RMSD from ref B ($\AA$)", fontweight="bold", fontsize=12)
|
|
501
|
+
ax.tick_params(axis="both", labelsize=10)
|
|
502
|
+
if title:
|
|
503
|
+
ax.set_title(title, fontweight="bold", fontsize=13, pad=10)
|
|
504
|
+
|
|
438
505
|
if overlays and len(overlays) > 1:
|
|
439
|
-
ax.legend(frameon=True, framealpha=0.
|
|
506
|
+
ax.legend(frameon=True, framealpha=0.95, loc="best")
|
|
440
507
|
|
|
441
508
|
if has_strip and ax_strip is not None and strip_structs is not None:
|
|
509
|
+
zoom = (
|
|
510
|
+
strip_zoom
|
|
511
|
+
if strip_zoom is not None
|
|
512
|
+
else max(0.45, default_strip_zoom(list(strip_structs)) * 1.6)
|
|
513
|
+
)
|
|
442
514
|
render_endpoint_strip(
|
|
443
515
|
ax_strip,
|
|
444
516
|
list(strip_structs),
|
|
445
517
|
list(strip_labels or []),
|
|
446
|
-
strip_zoom=
|
|
518
|
+
strip_zoom=zoom,
|
|
447
519
|
rotation=rotation,
|
|
448
520
|
theme=theme,
|
|
449
521
|
strip_renderer=strip_renderer,
|
|
450
|
-
strip_spacing=strip_spacing,
|
|
522
|
+
strip_spacing=max(strip_spacing, 2.2),
|
|
451
523
|
strip_dividers=strip_dividers,
|
|
452
524
|
perspective_tilt=perspective_tilt,
|
|
453
525
|
xyzrender_config=xyzrender_config,
|
|
454
526
|
)
|
|
455
527
|
|
|
456
|
-
save_landscape_figure(
|
|
528
|
+
save_landscape_figure(
|
|
529
|
+
fig, output, dpi=dpi, has_strip=has_strip, ax=ax, ax_strip=ax_strip
|
|
530
|
+
)
|
|
457
531
|
|
|
458
532
|
|
|
459
533
|
def plot_optimization_landscape(
|
|
@@ -533,11 +607,11 @@ def plot_optimization_landscape(
|
|
|
533
607
|
|
|
534
608
|
labels = _LABELS.get(label_mode, _LABELS["optimization"])
|
|
535
609
|
if project_path:
|
|
536
|
-
ax.set_xlabel(labels["x"])
|
|
537
|
-
ax.set_ylabel(labels["y"])
|
|
610
|
+
ax.set_xlabel(labels["x"], fontweight="bold", fontsize=12)
|
|
611
|
+
ax.set_ylabel(labels["y"], fontweight="bold", fontsize=12)
|
|
538
612
|
else:
|
|
539
|
-
ax.set_xlabel(r"RMSD from ref A ($\AA$)")
|
|
540
|
-
ax.set_ylabel(r"RMSD from ref B ($\AA$)")
|
|
613
|
+
ax.set_xlabel(r"RMSD from ref A ($\AA$)", fontweight="bold", fontsize=12)
|
|
614
|
+
ax.set_ylabel(r"RMSD from ref B ($\AA$)", fontweight="bold", fontsize=12)
|
|
541
615
|
|
|
542
616
|
return cb
|
|
543
617
|
|
|
@@ -45,6 +45,18 @@ publication-quality plots.
|
|
|
45
45
|
## Installation
|
|
46
46
|
|
|
47
47
|
pip install chemparseplot
|
|
48
|
+
|
|
49
|
+
Suite config (shared with rgpycrumbs — **no** `~/.config/chemparseplot`):
|
|
50
|
+
|
|
51
|
+
# ~/.config/rgpkgs/config.toml or ./rgpkgs.toml
|
|
52
|
+
# [pins] lock = "uv.lock"
|
|
53
|
+
# [pins.packages] …
|
|
54
|
+
|
|
55
|
+
Feature extras (`plot`, `neb`, …) are *transitional* install convenience. Target
|
|
56
|
+
is the same on-demand uv / `ensure_import` design as the rgpycrumbs hub; prefer
|
|
57
|
+
bare install + suite pins for new work.
|
|
58
|
+
|
|
59
|
+
# transitional:
|
|
48
60
|
# With plotting support
|
|
49
61
|
pip install "chemparseplot[plot]"
|
|
50
62
|
# Everything
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""Drive shipped chemparseplot.api entry points (parse → typed result)."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from chemparseplot.api import (
|
|
14
|
+
convert_energy_magnitude,
|
|
15
|
+
extract_orca_geomscan_energy,
|
|
16
|
+
normalize_energy_unit,
|
|
17
|
+
suite_pins,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Inline fixture (same content as tests/fixtures_geomscan_snippet.txt)
|
|
21
|
+
_GEOMSCAN_SNIPPET = """
|
|
22
|
+
The Calculated Surface using the 'Actual Energy'
|
|
23
|
+
7.55890395 -0.74239862
|
|
24
|
+
7.32930292 -0.74349939
|
|
25
|
+
7.09970189 -0.74467446
|
|
26
|
+
|
|
27
|
+
The Calculated Surface using the SCF energy
|
|
28
|
+
7.55890395 -0.74239862
|
|
29
|
+
7.32930292 -0.74349939
|
|
30
|
+
7.09970189 -0.74467446
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
FIXTURE_FILE = Path(__file__).parent / "fixtures_geomscan_snippet.txt"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_normalize_and_convert_energy_library_path():
|
|
37
|
+
assert normalize_energy_unit("ev") == "eV"
|
|
38
|
+
kcal = convert_energy_magnitude(1.0, "kcal/mol")
|
|
39
|
+
val = float(np.asarray(kcal).reshape(-1)[0])
|
|
40
|
+
assert 20.0 < val < 25.0
|
|
41
|
+
back = float(np.asarray(convert_energy_magnitude(val, "eV", source_unit="kcal/mol")))
|
|
42
|
+
assert abs(back - 1.0) < 1e-6
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_extract_orca_geomscan_energy_typed_nonempty():
|
|
46
|
+
"""Public parse path: ORCA geomscan text → pint Quantities with data."""
|
|
47
|
+
dist, energy = extract_orca_geomscan_energy(_GEOMSCAN_SNIPPET, "Actual Energy")
|
|
48
|
+
assert dist.size == 3
|
|
49
|
+
assert energy.size == 3
|
|
50
|
+
assert str(dist.units) == "bohr"
|
|
51
|
+
assert str(energy.units) == "hartree"
|
|
52
|
+
np.testing.assert_allclose(
|
|
53
|
+
dist.magnitude,
|
|
54
|
+
[7.55890395, 7.32930292, 7.09970189],
|
|
55
|
+
)
|
|
56
|
+
np.testing.assert_allclose(
|
|
57
|
+
energy.magnitude,
|
|
58
|
+
[-0.74239862, -0.74349939, -0.74467446],
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_extract_orca_geomscan_energy_from_fixture_file():
|
|
63
|
+
text = FIXTURE_FILE.read_text(encoding="utf-8")
|
|
64
|
+
dist, energy = extract_orca_geomscan_energy(text, "SCF energy")
|
|
65
|
+
assert dist.size >= 1
|
|
66
|
+
assert energy.size == dist.size
|
|
67
|
+
assert dist.size == 3
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_suite_pins_returns_dict():
|
|
71
|
+
pins = suite_pins()
|
|
72
|
+
assert isinstance(pins, dict)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|