chemparseplot 1.9.12__tar.gz → 1.9.14__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.12 → chemparseplot-1.9.14}/PKG-INFO +2 -2
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/_version.py +2 -2
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/__init__.py +2 -2
- chemparseplot-1.9.14/chemparseplot/parse/eon/frame_series.py +191 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/neb.py +2 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/pyproject.toml +1 -1
- chemparseplot-1.9.14/tests/parse/test_frame_series.py +121 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_surface_auto_thin.py +9 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/.gitignore +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/LICENSE +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/api.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/chemgp_jsonl.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/converter.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/_trajectory_common.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/con_io.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/dimer_trajectory.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/gprd.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/min_trajectory.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/minimization.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/neb.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/saddle_search.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/eon/stitch.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/file_.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/grammar/_deps.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/grammar/orca_text.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/grammar/xyz.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/neb_utils.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/orca/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/orca/_opi.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/orca/geomscan.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/orca/neb/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/orca/neb/interp.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/orca/neb/opi_parser.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/patterns.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/plumed.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/projection.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/sella/saddle_search.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/trajectory/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/trajectory/hdf5.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/trajectory/neb.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/parse/types.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/chemgp.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/geomscan.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/optimization.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/plumed.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/structs.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/plot/theme.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/units.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/chemparseplot/util.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/readme.md +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/_optional_imports.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/conftest.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/fixtures/h2mol.xyz +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/fixtures/orca_energy_coords_snippet.out +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/fixtures_geomscan_snippet.txt +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/_con_fixtures.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/grammar/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/grammar/test_grammar_deps.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/grammar/test_orca_text_grammar.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/grammar/test_xyz_grammar.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/orca/test_geomscan.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/orca/test_interp.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_chemgp_hdf5.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_chemgp_jsonl_types.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_con_io.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_converter.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_dimer_trajectory.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_eon_neb_types.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_eon_trajectory_common.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_min_trajectory.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_minimization_result.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_neb_stitch.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_neb_utils.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_orca_public_api.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_parser_types.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_patterns.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_plumed.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_projection.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_readcon_boundary.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_saddle_results.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_sella_saddle.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_trajectory_hdf5.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/parse/test_trajectory_neb.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/__init__.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_chemgp_utils.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_neb_helpers.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_neb_renderers.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_optimization.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_orca_neb_payload.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_projection_refactor.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_strip_rendering.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/plot/test_structs_units.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/test_coverage_batch.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/test_full_coverage.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/tests/test_public_api.py +0 -0
- {chemparseplot-1.9.12 → chemparseplot-1.9.14}/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.14
|
|
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
|
|
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
20
|
-
Requires-Python: >=3.
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
21
|
Requires-Dist: numpy>=1.26.2
|
|
22
22
|
Requires-Dist: pint>=0.22
|
|
23
23
|
Provides-Extra: all
|
|
@@ -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.14'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 9, 14)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""ConFrame sequence → series / trajectory DTOs for eOn plots.
|
|
5
|
+
|
|
6
|
+
The plot packet is an ordered sequence of ``readcon.ConFrame`` (or duck-typed
|
|
7
|
+
frames with ``energy``, ``metadata``, ``to_ase`` / geometry). ASE and Matter
|
|
8
|
+
are adapters into this wire format; this module does not invent a second schema.
|
|
9
|
+
|
|
10
|
+
```{versionadded} 1.9.14
|
|
11
|
+
```
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Sequence
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
import polars as pl
|
|
21
|
+
from ase import Atoms
|
|
22
|
+
|
|
23
|
+
from chemparseplot.parse.eon._trajectory_common import (
|
|
24
|
+
frame_rows_to_table,
|
|
25
|
+
metadata_value,
|
|
26
|
+
)
|
|
27
|
+
from chemparseplot.parse.eon.dimer_trajectory import (
|
|
28
|
+
DIMER_METADATA_COLUMNS,
|
|
29
|
+
DimerTrajectoryData,
|
|
30
|
+
table_from_dimer_metadata,
|
|
31
|
+
)
|
|
32
|
+
from chemparseplot.parse.eon.min_trajectory import (
|
|
33
|
+
MIN_METADATA_COLUMNS,
|
|
34
|
+
MinTrajectoryData,
|
|
35
|
+
table_from_min_metadata,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"atoms_list_from_frames",
|
|
40
|
+
"dimer_trajectory_from_frames",
|
|
41
|
+
"energies_from_frames",
|
|
42
|
+
"min_trajectory_from_frames",
|
|
43
|
+
"neb_path_arrays",
|
|
44
|
+
"series_from_frames",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def energies_from_frames(frames: Sequence[Any]) -> np.ndarray:
|
|
49
|
+
"""Total energy (eV) per frame; raises if any frame lacks energy."""
|
|
50
|
+
if not frames:
|
|
51
|
+
msg = "energies_from_frames: empty frame sequence"
|
|
52
|
+
raise ValueError(msg)
|
|
53
|
+
out = np.empty(len(frames), dtype=float)
|
|
54
|
+
for i, frame in enumerate(frames):
|
|
55
|
+
e = metadata_value(frame, "energy")
|
|
56
|
+
if e is None:
|
|
57
|
+
msg = f"frame {i} has no energy stamp"
|
|
58
|
+
raise ValueError(msg)
|
|
59
|
+
out[i] = float(e)
|
|
60
|
+
return out
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def atoms_list_from_frames(frames: Sequence[Any]) -> list[Atoms]:
|
|
64
|
+
"""Convert frames to ASE Atoms with energy stored in ``atoms.info``."""
|
|
65
|
+
atoms_list: list[Atoms] = []
|
|
66
|
+
for i, frame in enumerate(frames):
|
|
67
|
+
to_ase = getattr(frame, "to_ase", None)
|
|
68
|
+
if not callable(to_ase):
|
|
69
|
+
msg = f"frame {i} has no to_ase(); need readcon.ConFrame or equivalent"
|
|
70
|
+
raise TypeError(msg)
|
|
71
|
+
atoms = to_ase()
|
|
72
|
+
e = metadata_value(frame, "energy")
|
|
73
|
+
if e is not None:
|
|
74
|
+
atoms.info["energy"] = float(e)
|
|
75
|
+
atoms_list.append(atoms)
|
|
76
|
+
return atoms_list
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def series_from_frames(
|
|
80
|
+
frames: Sequence[Any],
|
|
81
|
+
columns: Sequence[str],
|
|
82
|
+
*,
|
|
83
|
+
allow_leading_incomplete: bool = False,
|
|
84
|
+
) -> pl.DataFrame:
|
|
85
|
+
"""Build a polars table of metadata columns from frames (empty if incomplete)."""
|
|
86
|
+
return frame_rows_to_table(
|
|
87
|
+
frames,
|
|
88
|
+
columns,
|
|
89
|
+
allow_leading_incomplete=allow_leading_incomplete,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def neb_path_arrays(frames: Sequence[Any]) -> dict[str, Any]:
|
|
94
|
+
"""Extract NEB path series from stamped frames (writePathCon / path_frames).
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
dict
|
|
99
|
+
``atoms_list``, ``energies``, optional ``reaction_coordinate``,
|
|
100
|
+
``relative_energy``, ``parallel_force``, ``neb_bead``, ``frame_index``.
|
|
101
|
+
"""
|
|
102
|
+
if not frames:
|
|
103
|
+
msg = "neb_path_arrays: empty frame sequence"
|
|
104
|
+
raise ValueError(msg)
|
|
105
|
+
atoms_list = atoms_list_from_frames(frames)
|
|
106
|
+
energies = energies_from_frames(frames)
|
|
107
|
+
result: dict[str, Any] = {
|
|
108
|
+
"atoms_list": atoms_list,
|
|
109
|
+
"energies": energies,
|
|
110
|
+
"n_frames": len(frames),
|
|
111
|
+
}
|
|
112
|
+
for key in (
|
|
113
|
+
"reaction_coordinate",
|
|
114
|
+
"relative_energy",
|
|
115
|
+
"parallel_force",
|
|
116
|
+
"neb_bead",
|
|
117
|
+
"frame_index",
|
|
118
|
+
"lowest_eigenvalue",
|
|
119
|
+
):
|
|
120
|
+
vals = []
|
|
121
|
+
ok = True
|
|
122
|
+
for frame in frames:
|
|
123
|
+
v = metadata_value(frame, key)
|
|
124
|
+
if v is None and key in ("reaction_coordinate", "relative_energy", "parallel_force"):
|
|
125
|
+
# optional NEB stamps
|
|
126
|
+
ok = False
|
|
127
|
+
break
|
|
128
|
+
vals.append(v)
|
|
129
|
+
if ok and any(v is not None for v in vals):
|
|
130
|
+
result[key] = np.array(
|
|
131
|
+
[np.nan if v is None else float(v) for v in vals],
|
|
132
|
+
dtype=float,
|
|
133
|
+
)
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def min_trajectory_from_frames(frames: Sequence[Any]) -> MinTrajectoryData:
|
|
138
|
+
"""Build :class:`MinTrajectoryData` from stamped minimization ConFrames."""
|
|
139
|
+
if not frames:
|
|
140
|
+
msg = "min_trajectory_from_frames: empty frame sequence"
|
|
141
|
+
raise ValueError(msg)
|
|
142
|
+
atoms_list = atoms_list_from_frames(frames)
|
|
143
|
+
dat_df = table_from_min_metadata(list(frames))
|
|
144
|
+
if dat_df.is_empty():
|
|
145
|
+
# Minimal table from energy + sequential iteration
|
|
146
|
+
energies = energies_from_frames(frames)
|
|
147
|
+
dat_df = pl.DataFrame(
|
|
148
|
+
{
|
|
149
|
+
"iteration": list(range(len(frames))),
|
|
150
|
+
"step_size": [0.0] * len(frames),
|
|
151
|
+
"convergence": [0.0] * len(frames),
|
|
152
|
+
"energy": energies.tolist(),
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
return MinTrajectoryData(
|
|
156
|
+
atoms_list=atoms_list,
|
|
157
|
+
dat_df=dat_df,
|
|
158
|
+
initial_atoms=atoms_list[0],
|
|
159
|
+
final_atoms=atoms_list[-1],
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def dimer_trajectory_from_frames(frames: Sequence[Any]) -> DimerTrajectoryData:
|
|
164
|
+
"""Build :class:`DimerTrajectoryData` from stamped climb ConFrames."""
|
|
165
|
+
if not frames:
|
|
166
|
+
msg = "dimer_trajectory_from_frames: empty frame sequence"
|
|
167
|
+
raise ValueError(msg)
|
|
168
|
+
atoms_list = atoms_list_from_frames(frames)
|
|
169
|
+
dat_df = table_from_dimer_metadata(list(frames))
|
|
170
|
+
if dat_df.is_empty():
|
|
171
|
+
energies = energies_from_frames(frames)
|
|
172
|
+
e0 = float(energies[0])
|
|
173
|
+
dat_df = pl.DataFrame(
|
|
174
|
+
{
|
|
175
|
+
"iteration": list(range(len(frames))),
|
|
176
|
+
"step_size": [0.0] * len(frames),
|
|
177
|
+
"delta_e": (energies - e0).tolist(),
|
|
178
|
+
"convergence": [0.0] * len(frames),
|
|
179
|
+
"eigenvalue": [0.0] * len(frames),
|
|
180
|
+
"torque": [0.0] * len(frames),
|
|
181
|
+
"angle": [0.0] * len(frames),
|
|
182
|
+
"rotations": [0] * len(frames),
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
return DimerTrajectoryData(
|
|
186
|
+
atoms_list=atoms_list,
|
|
187
|
+
dat_df=dat_df,
|
|
188
|
+
initial_atoms=atoms_list[0],
|
|
189
|
+
saddle_atoms=atoms_list[-1],
|
|
190
|
+
mode_vector=None,
|
|
191
|
+
)
|
|
@@ -16,7 +16,7 @@ readme = "readme.md"
|
|
|
16
16
|
keywords = ["compchem", "parser", "plot"]
|
|
17
17
|
license = { text = "MIT" }
|
|
18
18
|
authors = [{ name = "Rohit Goswami", email = "rog32@hi.is" }]
|
|
19
|
-
requires-python = ">=3.
|
|
19
|
+
requires-python = ">=3.11"
|
|
20
20
|
classifiers = [
|
|
21
21
|
"Development Status :: 4 - Beta",
|
|
22
22
|
"Programming Language :: Python",
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: 2023-present Rohit Goswami <rog32@hi.is>
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
"""ConFrame sequence → series used by eOn plot paths."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
pytest.importorskip("readcon")
|
|
12
|
+
pytest.importorskip("ase")
|
|
13
|
+
pytest.importorskip("polars")
|
|
14
|
+
|
|
15
|
+
from ase import Atoms
|
|
16
|
+
|
|
17
|
+
from chemparseplot.parse.eon.frame_series import (
|
|
18
|
+
atoms_list_from_frames,
|
|
19
|
+
dimer_trajectory_from_frames,
|
|
20
|
+
energies_from_frames,
|
|
21
|
+
min_trajectory_from_frames,
|
|
22
|
+
neb_path_arrays,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _h_frame(energy: float, z: float, *, frame_index: int, **scalars):
|
|
27
|
+
import readcon
|
|
28
|
+
|
|
29
|
+
atoms = Atoms("H", positions=[[0.0, 0.0, z]], cell=[10, 10, 10], pbc=False)
|
|
30
|
+
frame = readcon.ConFrame.from_ase(atoms)
|
|
31
|
+
frame.set_energy(float(energy))
|
|
32
|
+
frame.set_frame_index(int(frame_index))
|
|
33
|
+
for key, value in scalars.items():
|
|
34
|
+
frame.set_scalar_metadata(key, float(value) if isinstance(value, float) else value)
|
|
35
|
+
return frame
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_energies_from_frames_match_stamps():
|
|
39
|
+
frames = [_h_frame(1.0 + 0.1 * i, float(i), frame_index=i) for i in range(4)]
|
|
40
|
+
e = energies_from_frames(frames)
|
|
41
|
+
assert e.shape == (4,)
|
|
42
|
+
assert np.allclose(e, [1.0, 1.1, 1.2, 1.3])
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_energies_from_frames_missing_raises():
|
|
46
|
+
import readcon
|
|
47
|
+
from ase import Atoms
|
|
48
|
+
|
|
49
|
+
bare = readcon.ConFrame.from_ase(Atoms("H", positions=[[0, 0, 0]]))
|
|
50
|
+
with pytest.raises(ValueError, match="no energy"):
|
|
51
|
+
energies_from_frames([bare])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_neb_path_arrays_length_and_stamps():
|
|
55
|
+
frames = []
|
|
56
|
+
for i in range(5):
|
|
57
|
+
fr = _h_frame(
|
|
58
|
+
-10.0 + i,
|
|
59
|
+
0.1 * i,
|
|
60
|
+
frame_index=i,
|
|
61
|
+
reaction_coordinate=0.2 * i,
|
|
62
|
+
relative_energy=float(i),
|
|
63
|
+
parallel_force=-0.01 * i,
|
|
64
|
+
)
|
|
65
|
+
fr.set_neb_bead(i)
|
|
66
|
+
frames.append(fr)
|
|
67
|
+
path = neb_path_arrays(frames)
|
|
68
|
+
assert path["n_frames"] == 5
|
|
69
|
+
assert len(path["atoms_list"]) == 5
|
|
70
|
+
assert np.allclose(path["energies"], [-10, -9, -8, -7, -6])
|
|
71
|
+
assert np.allclose(path["reaction_coordinate"], [0.0, 0.2, 0.4, 0.6, 0.8])
|
|
72
|
+
assert np.allclose(path["relative_energy"], [0, 1, 2, 3, 4])
|
|
73
|
+
assert path["atoms_list"][0].info["energy"] == pytest.approx(-10.0)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_min_trajectory_from_frames_metrics():
|
|
77
|
+
frames = [
|
|
78
|
+
_h_frame(
|
|
79
|
+
-5.0 + 0.5 * i,
|
|
80
|
+
0.0,
|
|
81
|
+
frame_index=i,
|
|
82
|
+
step_size=0.1 * (i + 1),
|
|
83
|
+
convergence=1.0 / (i + 1),
|
|
84
|
+
)
|
|
85
|
+
for i in range(3)
|
|
86
|
+
]
|
|
87
|
+
traj = min_trajectory_from_frames(frames)
|
|
88
|
+
assert len(traj.atoms_list) == 3
|
|
89
|
+
assert traj.dat_df.height == 3
|
|
90
|
+
assert "energy" in traj.dat_df.columns
|
|
91
|
+
assert traj.dat_df["energy"].to_list() == pytest.approx([-5.0, -4.5, -4.0])
|
|
92
|
+
assert traj.final_atoms is traj.atoms_list[-1]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_dimer_trajectory_from_frames_metrics():
|
|
96
|
+
frames = [
|
|
97
|
+
_h_frame(
|
|
98
|
+
0.0 + i,
|
|
99
|
+
0.0,
|
|
100
|
+
frame_index=i,
|
|
101
|
+
step_size=0.05,
|
|
102
|
+
delta_e=float(i),
|
|
103
|
+
convergence=0.1,
|
|
104
|
+
eigenvalue=-1.0,
|
|
105
|
+
torque=0.0,
|
|
106
|
+
angle=0.0,
|
|
107
|
+
rotations=0,
|
|
108
|
+
)
|
|
109
|
+
for i in range(3)
|
|
110
|
+
]
|
|
111
|
+
# rotations may need int via set_scalar_metadata
|
|
112
|
+
traj = dimer_trajectory_from_frames(frames)
|
|
113
|
+
assert len(traj.atoms_list) == 3
|
|
114
|
+
assert traj.dat_df.height == 3
|
|
115
|
+
assert "delta_e" in traj.dat_df.columns or "energy" in traj.dat_df.columns
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_atoms_list_from_frames_preserves_energy_info():
|
|
119
|
+
frames = [_h_frame(2.5, 0.0, frame_index=0)]
|
|
120
|
+
atoms = atoms_list_from_frames(frames)
|
|
121
|
+
assert atoms[0].info["energy"] == pytest.approx(2.5)
|
|
@@ -185,3 +185,12 @@ class TestSurfaceFitConfig:
|
|
|
185
185
|
)
|
|
186
186
|
assert seen["n"] <= 20
|
|
187
187
|
plt.close(fig)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def test_surface_fit_config_importable():
|
|
191
|
+
"""Regression: class body return annotations need future annotations (Py3.11+)."""
|
|
192
|
+
from chemparseplot.plot.neb import SurfaceFitConfig
|
|
193
|
+
|
|
194
|
+
cfg = SurfaceFitConfig.from_mapping({"auto_thin": True, "max_surface_points": 32})
|
|
195
|
+
assert cfg.auto_thin is True
|
|
196
|
+
assert cfg.max_surface_points == 32
|
|
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
|
|
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
|