reaxkit 1.0.0__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.
- reaxkit/__init__.py +0 -0
- reaxkit/analysis/__init__.py +0 -0
- reaxkit/analysis/composed/RDF_analyzer.py +560 -0
- reaxkit/analysis/composed/__init__.py +0 -0
- reaxkit/analysis/composed/connectivity_analyzer.py +706 -0
- reaxkit/analysis/composed/coordination_analyzer.py +144 -0
- reaxkit/analysis/composed/electrostatics_analyzer.py +687 -0
- reaxkit/analysis/per_file/__init__.py +0 -0
- reaxkit/analysis/per_file/control_analyzer.py +165 -0
- reaxkit/analysis/per_file/eregime_analyzer.py +108 -0
- reaxkit/analysis/per_file/ffield_analyzer.py +305 -0
- reaxkit/analysis/per_file/fort13_analyzer.py +79 -0
- reaxkit/analysis/per_file/fort57_analyzer.py +106 -0
- reaxkit/analysis/per_file/fort73_analyzer.py +61 -0
- reaxkit/analysis/per_file/fort74_analyzer.py +65 -0
- reaxkit/analysis/per_file/fort76_analyzer.py +191 -0
- reaxkit/analysis/per_file/fort78_analyzer.py +154 -0
- reaxkit/analysis/per_file/fort79_analyzer.py +83 -0
- reaxkit/analysis/per_file/fort7_analyzer.py +393 -0
- reaxkit/analysis/per_file/fort99_analyzer.py +411 -0
- reaxkit/analysis/per_file/molfra_analyzer.py +359 -0
- reaxkit/analysis/per_file/params_analyzer.py +258 -0
- reaxkit/analysis/per_file/summary_analyzer.py +84 -0
- reaxkit/analysis/per_file/trainset_analyzer.py +84 -0
- reaxkit/analysis/per_file/vels_analyzer.py +95 -0
- reaxkit/analysis/per_file/xmolout_analyzer.py +528 -0
- reaxkit/cli.py +181 -0
- reaxkit/count_loc.py +276 -0
- reaxkit/data/alias.yaml +89 -0
- reaxkit/data/constants.yaml +27 -0
- reaxkit/data/reaxff_input_files_contents.yaml +186 -0
- reaxkit/data/reaxff_output_files_contents.yaml +301 -0
- reaxkit/data/units.yaml +38 -0
- reaxkit/help/__init__.py +0 -0
- reaxkit/help/help_index_loader.py +531 -0
- reaxkit/help/introspection_utils.py +131 -0
- reaxkit/io/__init__.py +0 -0
- reaxkit/io/base_handler.py +165 -0
- reaxkit/io/generators/__init__.py +0 -0
- reaxkit/io/generators/control_generator.py +123 -0
- reaxkit/io/generators/eregime_generator.py +341 -0
- reaxkit/io/generators/geo_generator.py +967 -0
- reaxkit/io/generators/trainset_generator.py +1758 -0
- reaxkit/io/generators/tregime_generator.py +113 -0
- reaxkit/io/generators/vregime_generator.py +164 -0
- reaxkit/io/generators/xmolout_generator.py +304 -0
- reaxkit/io/handlers/__init__.py +0 -0
- reaxkit/io/handlers/control_handler.py +209 -0
- reaxkit/io/handlers/eregime_handler.py +122 -0
- reaxkit/io/handlers/ffield_handler.py +812 -0
- reaxkit/io/handlers/fort13_handler.py +123 -0
- reaxkit/io/handlers/fort57_handler.py +143 -0
- reaxkit/io/handlers/fort73_handler.py +145 -0
- reaxkit/io/handlers/fort74_handler.py +155 -0
- reaxkit/io/handlers/fort76_handler.py +195 -0
- reaxkit/io/handlers/fort78_handler.py +142 -0
- reaxkit/io/handlers/fort79_handler.py +227 -0
- reaxkit/io/handlers/fort7_handler.py +264 -0
- reaxkit/io/handlers/fort99_handler.py +128 -0
- reaxkit/io/handlers/geo_handler.py +224 -0
- reaxkit/io/handlers/molfra_handler.py +184 -0
- reaxkit/io/handlers/params_handler.py +137 -0
- reaxkit/io/handlers/summary_handler.py +135 -0
- reaxkit/io/handlers/trainset_handler.py +658 -0
- reaxkit/io/handlers/vels_handler.py +293 -0
- reaxkit/io/handlers/xmolout_handler.py +174 -0
- reaxkit/utils/__init__.py +0 -0
- reaxkit/utils/alias.py +219 -0
- reaxkit/utils/cache.py +77 -0
- reaxkit/utils/constants.py +75 -0
- reaxkit/utils/equation_of_states.py +96 -0
- reaxkit/utils/exceptions.py +27 -0
- reaxkit/utils/frame_utils.py +175 -0
- reaxkit/utils/log.py +43 -0
- reaxkit/utils/media/__init__.py +0 -0
- reaxkit/utils/media/convert.py +90 -0
- reaxkit/utils/media/make_video.py +91 -0
- reaxkit/utils/media/plotter.py +812 -0
- reaxkit/utils/numerical/__init__.py +0 -0
- reaxkit/utils/numerical/extrema_finder.py +96 -0
- reaxkit/utils/numerical/moving_average.py +103 -0
- reaxkit/utils/numerical/numerical_calcs.py +75 -0
- reaxkit/utils/numerical/signal_ops.py +135 -0
- reaxkit/utils/path.py +55 -0
- reaxkit/utils/units.py +104 -0
- reaxkit/webui/__init__.py +0 -0
- reaxkit/webui/app.py +0 -0
- reaxkit/webui/components.py +0 -0
- reaxkit/webui/layouts.py +0 -0
- reaxkit/webui/utils.py +0 -0
- reaxkit/workflows/__init__.py +0 -0
- reaxkit/workflows/composed/__init__.py +0 -0
- reaxkit/workflows/composed/coordination_workflow.py +393 -0
- reaxkit/workflows/composed/electrostatics_workflow.py +587 -0
- reaxkit/workflows/composed/xmolout_fort7_workflow.py +343 -0
- reaxkit/workflows/meta/__init__.py +0 -0
- reaxkit/workflows/meta/help_workflow.py +136 -0
- reaxkit/workflows/meta/introspection_workflow.py +235 -0
- reaxkit/workflows/meta/make_video_workflow.py +61 -0
- reaxkit/workflows/meta/plotter_workflow.py +601 -0
- reaxkit/workflows/per_file/__init__.py +0 -0
- reaxkit/workflows/per_file/control_workflow.py +110 -0
- reaxkit/workflows/per_file/eregime_workflow.py +267 -0
- reaxkit/workflows/per_file/ffield_workflow.py +390 -0
- reaxkit/workflows/per_file/fort13_workflow.py +86 -0
- reaxkit/workflows/per_file/fort57_workflow.py +137 -0
- reaxkit/workflows/per_file/fort73_workflow.py +151 -0
- reaxkit/workflows/per_file/fort74_workflow.py +88 -0
- reaxkit/workflows/per_file/fort76_workflow.py +188 -0
- reaxkit/workflows/per_file/fort78_workflow.py +135 -0
- reaxkit/workflows/per_file/fort79_workflow.py +314 -0
- reaxkit/workflows/per_file/fort7_workflow.py +592 -0
- reaxkit/workflows/per_file/fort83_workflow.py +60 -0
- reaxkit/workflows/per_file/fort99_workflow.py +223 -0
- reaxkit/workflows/per_file/geo_workflow.py +554 -0
- reaxkit/workflows/per_file/molfra_workflow.py +577 -0
- reaxkit/workflows/per_file/params_workflow.py +135 -0
- reaxkit/workflows/per_file/summary_workflow.py +161 -0
- reaxkit/workflows/per_file/trainset_workflow.py +356 -0
- reaxkit/workflows/per_file/tregime_workflow.py +79 -0
- reaxkit/workflows/per_file/vels_workflow.py +309 -0
- reaxkit/workflows/per_file/vregime_workflow.py +75 -0
- reaxkit/workflows/per_file/xmolout_workflow.py +678 -0
- reaxkit-1.0.0.dist-info/METADATA +128 -0
- reaxkit-1.0.0.dist-info/RECORD +130 -0
- reaxkit-1.0.0.dist-info/WHEEL +5 -0
- reaxkit-1.0.0.dist-info/entry_points.txt +2 -0
- reaxkit-1.0.0.dist-info/licenses/AUTHORS.md +20 -0
- reaxkit-1.0.0.dist-info/licenses/LICENSE +21 -0
- reaxkit-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xmolout trajectory-analysis workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides a comprehensive set of tools for extracting, analyzing,
|
|
5
|
+
and visualizing atomic trajectory data from ReaxFF `xmolout` files, which store
|
|
6
|
+
time-resolved atomic coordinates and simulation cell information.
|
|
7
|
+
|
|
8
|
+
It supports:
|
|
9
|
+
- Extracting atom trajectories for selected atoms or atom types in long or wide
|
|
10
|
+
tabular formats, with flexible frame selection and axis conversion.
|
|
11
|
+
- Computing mean squared displacement (MSD) for one or multiple atoms, with
|
|
12
|
+
support for combined plots or per-atom subplots.
|
|
13
|
+
- Computing radial distribution functions (RDFs) using FREUD or OVITO backends,
|
|
14
|
+
either as averaged curves or as RDF-derived properties over frames.
|
|
15
|
+
- Extracting simulation box (cell) dimensions as functions of frame, iteration,
|
|
16
|
+
or time, with optional plotting.
|
|
17
|
+
- Writing trimmed `xmolout` files that retain only atom types and coordinates
|
|
18
|
+
for lightweight storage or downstream processing.
|
|
19
|
+
|
|
20
|
+
The workflow is designed to bridge raw ReaxFF trajectory output with common
|
|
21
|
+
structural and dynamical analyses in a reproducible, CLI-driven manner.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
from typing import Optional, Iterable
|
|
27
|
+
import numpy as np
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
from reaxkit.io.handlers.xmolout_handler import XmoloutHandler
|
|
31
|
+
from reaxkit.utils.path import resolve_output_path
|
|
32
|
+
from reaxkit.io.generators.xmolout_generator import write_xmolout_from_handler
|
|
33
|
+
from reaxkit.utils.media.plotter import single_plot, multi_subplots
|
|
34
|
+
from reaxkit.utils.frame_utils import (
|
|
35
|
+
_select_frames,
|
|
36
|
+
parse_frames as _parse_frames,
|
|
37
|
+
parse_atoms,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
from reaxkit.analysis.per_file.xmolout_analyzer import (
|
|
41
|
+
get_mean_squared_displacement,
|
|
42
|
+
get_unit_cell_dimensions_across_frames,
|
|
43
|
+
get_atom_trajectories,
|
|
44
|
+
)
|
|
45
|
+
from reaxkit.analysis.composed.RDF_analyzer import (
|
|
46
|
+
rdf_using_freud,
|
|
47
|
+
rdf_using_ovito,
|
|
48
|
+
rdf_property_over_frames,
|
|
49
|
+
)
|
|
50
|
+
from reaxkit.utils.media.convert import convert_xaxis
|
|
51
|
+
from reaxkit.utils.units import unit_for
|
|
52
|
+
|
|
53
|
+
def _parse_types(s: Optional[str]):
|
|
54
|
+
if s is None or str(s).strip() == "":
|
|
55
|
+
return None
|
|
56
|
+
parts = [p for chunk in str(s).split(",") for p in chunk.split()]
|
|
57
|
+
return set(parts)
|
|
58
|
+
|
|
59
|
+
def _frames_from_args(xh: XmoloutHandler, args: argparse.Namespace) -> Iterable[int]:
|
|
60
|
+
if getattr(args, "frames", None):
|
|
61
|
+
sel = _parse_frames(args.frames)
|
|
62
|
+
if sel is None:
|
|
63
|
+
try:
|
|
64
|
+
return range(xh.n_frames())
|
|
65
|
+
except Exception:
|
|
66
|
+
return range(len(xh.dataframe()))
|
|
67
|
+
if isinstance(sel, slice):
|
|
68
|
+
try:
|
|
69
|
+
n = xh.n_frames()
|
|
70
|
+
except Exception:
|
|
71
|
+
n = len(xh.dataframe())
|
|
72
|
+
s, e, st = sel.indices(n)
|
|
73
|
+
return range(s, e, st if st != 0 else 1)
|
|
74
|
+
return [int(i) for i in sel]
|
|
75
|
+
return _select_frames(xh, getattr(args, "start", None), getattr(args, "stop", None), getattr(args, "every", 1))
|
|
76
|
+
|
|
77
|
+
# --------------------------
|
|
78
|
+
# TASK IMPLEMENTATIONS
|
|
79
|
+
# --------------------------
|
|
80
|
+
def _trajget_task(args: argparse.Namespace) -> int:
|
|
81
|
+
xh = XmoloutHandler(args.file)
|
|
82
|
+
frames = _frames_from_args(xh, args)
|
|
83
|
+
|
|
84
|
+
# atoms: optional, 1-based → 0-based
|
|
85
|
+
atoms = parse_atoms(args.atoms)
|
|
86
|
+
if atoms is not None:
|
|
87
|
+
atoms = [a - 1 for a in atoms]
|
|
88
|
+
|
|
89
|
+
# atom types
|
|
90
|
+
types = _parse_types(args.atom_types)
|
|
91
|
+
|
|
92
|
+
# dims
|
|
93
|
+
dims = args.dims or ["x", "y", "z"]
|
|
94
|
+
dims = [d for d in dims if d in ("x", "y", "z")]
|
|
95
|
+
if not dims:
|
|
96
|
+
raise ValueError("At least one of --dims x y z must be provided.")
|
|
97
|
+
|
|
98
|
+
# get trajectories
|
|
99
|
+
df = get_atom_trajectories(
|
|
100
|
+
xh,
|
|
101
|
+
frames=frames,
|
|
102
|
+
atoms=atoms,
|
|
103
|
+
atom_types=sorted(types) if types else None,
|
|
104
|
+
dims=dims,
|
|
105
|
+
format=args.format,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if df.empty:
|
|
109
|
+
print("No trajectory rows selected (check --atoms / --atom-types / frame range).")
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
# --- X-axis: iter/frame/time from frame_index ---
|
|
113
|
+
if "frame_index" not in df.columns:
|
|
114
|
+
raise KeyError("Expected 'frame_index' column in trajectory data.")
|
|
115
|
+
xvals, xlabel = convert_xaxis(df["frame_index"].to_numpy(), args.xaxis)
|
|
116
|
+
|
|
117
|
+
df = df.copy()
|
|
118
|
+
df.insert(0, "xaxis", xvals)
|
|
119
|
+
|
|
120
|
+
workflow_name = args.kind
|
|
121
|
+
# --- Export CSV ---
|
|
122
|
+
if args.export:
|
|
123
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
124
|
+
df.to_csv(out, index=False)
|
|
125
|
+
print(f"[Done] Exported trajectories to {out}")
|
|
126
|
+
|
|
127
|
+
# --- Plot / Save (only long-format & single dim) ---
|
|
128
|
+
if args.save or args.plot:
|
|
129
|
+
if args.format == "long" and len(dims) == 1:
|
|
130
|
+
dim = dims[0]
|
|
131
|
+
dff = df
|
|
132
|
+
|
|
133
|
+
# if multiple atoms, use the first one (as before)
|
|
134
|
+
if "atom_id" in df.columns:
|
|
135
|
+
atom_ids = df["atom_id"].unique().tolist()
|
|
136
|
+
if len(atom_ids) > 1:
|
|
137
|
+
print(f"ℹ️ Multiple atoms detected: {atom_ids}. Plotting only atom_id={atom_ids[0]}.")
|
|
138
|
+
aid = atom_ids[0]
|
|
139
|
+
dff = df[df["atom_id"] == aid]
|
|
140
|
+
|
|
141
|
+
x = dff["xaxis"].values
|
|
142
|
+
y = dff[dim].values
|
|
143
|
+
title = f"{dim} vs {xlabel}"
|
|
144
|
+
if "aid" in locals():
|
|
145
|
+
title += f" (atom={aid})"
|
|
146
|
+
|
|
147
|
+
u = unit_for(args.yaxis) or unit_for(dim)
|
|
148
|
+
if args.save:
|
|
149
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
150
|
+
single_plot(
|
|
151
|
+
x, y,
|
|
152
|
+
title=title,
|
|
153
|
+
xlabel=xlabel,
|
|
154
|
+
ylabel=f"{dim} ({u})" if u else dim,
|
|
155
|
+
save=out,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if args.plot:
|
|
159
|
+
single_plot(
|
|
160
|
+
x, y,
|
|
161
|
+
title=title,
|
|
162
|
+
xlabel=xlabel,
|
|
163
|
+
ylabel=f"{dim} ({u})" if u else dim,
|
|
164
|
+
save=None,
|
|
165
|
+
)
|
|
166
|
+
else:
|
|
167
|
+
print("⚠️ Plotting is only supported for long-format with a single dim (x|y|z).")
|
|
168
|
+
|
|
169
|
+
# --- No action fallback ---
|
|
170
|
+
if not args.export and not args.save and not args.plot:
|
|
171
|
+
print(df.head(10).to_string(index=False))
|
|
172
|
+
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
def _msd_task(args: argparse.Namespace) -> int:
|
|
176
|
+
xh = XmoloutHandler(args.file)
|
|
177
|
+
|
|
178
|
+
if not args.atoms:
|
|
179
|
+
print("❌ --atoms is required (comma/space-separated 1-based indices, e.g. '1,5,12').")
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
# Parse 1-based atom indices from string (do NOT use parse_atoms here;
|
|
183
|
+
# mean_squared_displacement expects 1-based atoms and handles conversion itself)
|
|
184
|
+
atoms: list[int] = []
|
|
185
|
+
for chunk in str(args.atoms).split(","):
|
|
186
|
+
for tok in chunk.split():
|
|
187
|
+
if tok:
|
|
188
|
+
atoms.append(int(tok))
|
|
189
|
+
|
|
190
|
+
if not atoms:
|
|
191
|
+
print("❌ Could not parse any atoms from --atoms.")
|
|
192
|
+
return 1
|
|
193
|
+
|
|
194
|
+
# Long-format MSD: frame_index, iter, atom_id, msd (per-atom, no averaging)
|
|
195
|
+
df_long = get_mean_squared_displacement(xh, atoms=atoms)
|
|
196
|
+
if df_long.empty:
|
|
197
|
+
print("No MSD data found for the selected atoms.")
|
|
198
|
+
return 1
|
|
199
|
+
|
|
200
|
+
workflow_name = args.kind
|
|
201
|
+
|
|
202
|
+
# ------------------------------------------------------------------
|
|
203
|
+
# X-axis: iter / frame / time (based on frame_index)
|
|
204
|
+
# ------------------------------------------------------------------
|
|
205
|
+
frames_unique = np.sort(df_long["frame_index"].unique())
|
|
206
|
+
xvals, xlabel = convert_xaxis(frames_unique, args.xaxis)
|
|
207
|
+
frame_to_x = dict(zip(frames_unique, xvals))
|
|
208
|
+
|
|
209
|
+
# ------------------------------------------------------------------
|
|
210
|
+
# Export CSV in WIDE format: [xlabel, frame_index, iter, msd[atom]...]
|
|
211
|
+
# ------------------------------------------------------------------
|
|
212
|
+
if args.export:
|
|
213
|
+
wide = (
|
|
214
|
+
df_long
|
|
215
|
+
.pivot(index=["frame_index", "iter"], columns="atom_id", values="msd")
|
|
216
|
+
.sort_index()
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Rename atom columns: atom_id → msd[atom_id]
|
|
220
|
+
wide.columns = [f"msd[{aid}]" for aid in wide.columns.to_list()]
|
|
221
|
+
wide = wide.reset_index()
|
|
222
|
+
|
|
223
|
+
# X column based on chosen xaxis
|
|
224
|
+
frames_wide = wide["frame_index"].to_numpy()
|
|
225
|
+
xvals_wide, xlabel_wide = convert_xaxis(frames_wide, args.xaxis)
|
|
226
|
+
wide.insert(0, xlabel_wide, xvals_wide)
|
|
227
|
+
|
|
228
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
229
|
+
wide.to_csv(out, index=False)
|
|
230
|
+
print(f"[Done] MSD wide table saved to {out}")
|
|
231
|
+
|
|
232
|
+
# ------------------------------------------------------------------
|
|
233
|
+
# Build plotting data (one series per atom)
|
|
234
|
+
# ------------------------------------------------------------------
|
|
235
|
+
series_combined = [] # for single_plot (all atoms in one axis)
|
|
236
|
+
subplots_data = [] # for multi_subplots (one subplot per atom)
|
|
237
|
+
|
|
238
|
+
atom_ids = sorted(df_long["atom_id"].unique())
|
|
239
|
+
for aid in atom_ids:
|
|
240
|
+
dfi = df_long[df_long["atom_id"] == aid].sort_values("frame_index")
|
|
241
|
+
xs = [frame_to_x[i] for i in dfi["frame_index"].to_numpy()]
|
|
242
|
+
ys = dfi["msd"].to_numpy()
|
|
243
|
+
s = {"x": xs, "y": ys, "label": f"atom {aid}"}
|
|
244
|
+
series_combined.append(s)
|
|
245
|
+
subplots_data.append([s]) # each subplot has one series: this atom
|
|
246
|
+
|
|
247
|
+
title = f"MSD of atoms: {', '.join(map(str, atom_ids))}"
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
# Plot / Save: either single plot or subplots
|
|
251
|
+
# ------------------------------------------------------------------
|
|
252
|
+
if args.save or args.plot:
|
|
253
|
+
if args.subplot:
|
|
254
|
+
# one subplot per atom
|
|
255
|
+
if args.save:
|
|
256
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
257
|
+
multi_subplots(
|
|
258
|
+
subplots=subplots_data,
|
|
259
|
+
title=title,
|
|
260
|
+
xlabel=xlabel,
|
|
261
|
+
ylabel="Ų",
|
|
262
|
+
legend=True,
|
|
263
|
+
save=out,
|
|
264
|
+
)
|
|
265
|
+
if args.plot:
|
|
266
|
+
multi_subplots(
|
|
267
|
+
subplots=subplots_data,
|
|
268
|
+
title=title,
|
|
269
|
+
xlabel=xlabel,
|
|
270
|
+
ylabel="Ų",
|
|
271
|
+
legend=True,
|
|
272
|
+
save=None,
|
|
273
|
+
)
|
|
274
|
+
else:
|
|
275
|
+
# all atoms in a single plot
|
|
276
|
+
if args.save:
|
|
277
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
278
|
+
single_plot(
|
|
279
|
+
series=series_combined,
|
|
280
|
+
title=title,
|
|
281
|
+
xlabel=xlabel,
|
|
282
|
+
ylabel="Ų",
|
|
283
|
+
legend=True,
|
|
284
|
+
save=out,
|
|
285
|
+
)
|
|
286
|
+
if args.plot:
|
|
287
|
+
single_plot(
|
|
288
|
+
series=series_combined,
|
|
289
|
+
title=title,
|
|
290
|
+
xlabel=xlabel,
|
|
291
|
+
ylabel="Ų",
|
|
292
|
+
legend=True,
|
|
293
|
+
save=None,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# ------------------------------------------------------------------
|
|
297
|
+
# No action fallback
|
|
298
|
+
# ------------------------------------------------------------------
|
|
299
|
+
if not args.export and not args.save and not args.plot:
|
|
300
|
+
print("ℹ️ No action selected. Use --plot, --save, or --export.")
|
|
301
|
+
print(df_long.head(10).to_string(index=False))
|
|
302
|
+
|
|
303
|
+
return 0
|
|
304
|
+
|
|
305
|
+
def _rdf_task(args: argparse.Namespace) -> int:
|
|
306
|
+
xh = XmoloutHandler(args.file)
|
|
307
|
+
types_a = _parse_types(args.types_a)
|
|
308
|
+
types_b = _parse_types(args.types_b)
|
|
309
|
+
frames = _frames_from_args(xh, args)
|
|
310
|
+
backend = args.backend.lower()
|
|
311
|
+
workflow_name = args.kind
|
|
312
|
+
|
|
313
|
+
# Property mode
|
|
314
|
+
if args.prop is not None:
|
|
315
|
+
df = rdf_property_over_frames(
|
|
316
|
+
xh,
|
|
317
|
+
frames=frames,
|
|
318
|
+
backend=backend,
|
|
319
|
+
property=args.prop, # one of: first_peak, dominant_peak, area, excess_area
|
|
320
|
+
r_max=args.r_max,
|
|
321
|
+
bins=args.bins,
|
|
322
|
+
types_a=sorted(types_a) if types_a else None,
|
|
323
|
+
types_b=sorted(types_b) if types_b else None,
|
|
324
|
+
)
|
|
325
|
+
if args.export:
|
|
326
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
327
|
+
df.to_csv(out, index=False)
|
|
328
|
+
print(f"[Done] Exported RDF property table to {out}")
|
|
329
|
+
|
|
330
|
+
if args.save or args.plot:
|
|
331
|
+
x = df["frame_index"].to_numpy()
|
|
332
|
+
# pick the right y-column
|
|
333
|
+
if args.prop == "area":
|
|
334
|
+
y = df["area"].to_numpy()
|
|
335
|
+
ylabel = "area"
|
|
336
|
+
elif args.prop == "excess_area":
|
|
337
|
+
y = df["excess_area"].to_numpy()
|
|
338
|
+
ylabel = "excess_area"
|
|
339
|
+
elif args.prop == "first_peak":
|
|
340
|
+
y = df["r_first_peak"].to_numpy()
|
|
341
|
+
ylabel = "r_first_peak (Å)"
|
|
342
|
+
else: # dominant_peak
|
|
343
|
+
y = df["r_peak"].to_numpy()
|
|
344
|
+
ylabel = "r_peak (Å)"
|
|
345
|
+
|
|
346
|
+
title = f"{args.prop} ({backend}) vs frame"
|
|
347
|
+
|
|
348
|
+
if args.save:
|
|
349
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
350
|
+
single_plot(
|
|
351
|
+
x,
|
|
352
|
+
y,
|
|
353
|
+
title=title,
|
|
354
|
+
xlabel="frame",
|
|
355
|
+
ylabel=ylabel,
|
|
356
|
+
save=out,
|
|
357
|
+
)
|
|
358
|
+
if args.plot:
|
|
359
|
+
single_plot(
|
|
360
|
+
x,
|
|
361
|
+
y,
|
|
362
|
+
title=title,
|
|
363
|
+
xlabel="frame",
|
|
364
|
+
ylabel=ylabel,
|
|
365
|
+
save=None,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
if not args.export and not args.save and not args.plot:
|
|
369
|
+
print(df.head(10).to_string(index=False))
|
|
370
|
+
return 0
|
|
371
|
+
|
|
372
|
+
# Curve mode (averaged g(r) vs r)
|
|
373
|
+
if backend == "freud":
|
|
374
|
+
r, g = rdf_using_freud(
|
|
375
|
+
xh,
|
|
376
|
+
frames=frames,
|
|
377
|
+
types_a=types_a,
|
|
378
|
+
types_b=types_b,
|
|
379
|
+
r_max=args.r_max if args.r_max is not None else None,
|
|
380
|
+
bins=args.bins,
|
|
381
|
+
average=True,
|
|
382
|
+
return_stack=False,
|
|
383
|
+
)
|
|
384
|
+
elif backend == "ovito":
|
|
385
|
+
r, g = rdf_using_ovito(
|
|
386
|
+
xh,
|
|
387
|
+
frames=frames,
|
|
388
|
+
r_max=(args.r_max if args.r_max is not None else 4.0),
|
|
389
|
+
bins=args.bins,
|
|
390
|
+
types_a=types_a,
|
|
391
|
+
types_b=types_b,
|
|
392
|
+
average=True,
|
|
393
|
+
return_stack=False,
|
|
394
|
+
)
|
|
395
|
+
else:
|
|
396
|
+
raise ValueError(f"Unknown backend: {backend}")
|
|
397
|
+
|
|
398
|
+
sel_txt = ""
|
|
399
|
+
if types_a or types_b:
|
|
400
|
+
sa = sorted(types_a) if types_a else ["*"]
|
|
401
|
+
sb = sorted(types_b) if types_b else ["*"]
|
|
402
|
+
sel_txt = f" A={sa}; B={sb}"
|
|
403
|
+
title = f"RDF [{backend}]{sel_txt}"
|
|
404
|
+
|
|
405
|
+
# Only plot if requested
|
|
406
|
+
if args.save or args.plot:
|
|
407
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
408
|
+
if args.save:
|
|
409
|
+
single_plot(
|
|
410
|
+
r,
|
|
411
|
+
g,
|
|
412
|
+
plot_type="line",
|
|
413
|
+
title=title,
|
|
414
|
+
xlabel="r (Å)",
|
|
415
|
+
ylabel="g(r)",
|
|
416
|
+
save=out,
|
|
417
|
+
)
|
|
418
|
+
if args.plot:
|
|
419
|
+
single_plot(
|
|
420
|
+
r,
|
|
421
|
+
g,
|
|
422
|
+
plot_type="line",
|
|
423
|
+
title=title,
|
|
424
|
+
xlabel="r (Å)",
|
|
425
|
+
ylabel="g(r)",
|
|
426
|
+
save=None,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
if args.export:
|
|
430
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
431
|
+
df = pd.DataFrame({"r": r, "g": g})
|
|
432
|
+
df.to_csv(out, index=False)
|
|
433
|
+
print(f"[Done] Exported RDF to {out}")
|
|
434
|
+
|
|
435
|
+
if not args.export and not args.save and not args.plot:
|
|
436
|
+
# simple preview if user didn't ask for plot or export
|
|
437
|
+
out = pd.DataFrame({"r": r, "g": g})
|
|
438
|
+
print(out.head(10).to_string(index=False))
|
|
439
|
+
|
|
440
|
+
return 0
|
|
441
|
+
|
|
442
|
+
def _boxdims_task(args: argparse.Namespace) -> int:
|
|
443
|
+
xh = XmoloutHandler(args.file)
|
|
444
|
+
|
|
445
|
+
# frames: use all if not provided
|
|
446
|
+
frames = _parse_frames(args.frames) if getattr(args, "frames", None) else None
|
|
447
|
+
|
|
448
|
+
df = get_unit_cell_dimensions_across_frames(xh, frames=frames)
|
|
449
|
+
if df.empty:
|
|
450
|
+
print("No box-dimension data found for the requested frames.")
|
|
451
|
+
return 1
|
|
452
|
+
|
|
453
|
+
# --- X axis: iter / frame / time ---
|
|
454
|
+
xvals, xlabel = convert_xaxis(df["frame_index"].to_numpy(), args.xaxis)
|
|
455
|
+
df = df.copy()
|
|
456
|
+
df.insert(0, "x", xvals)
|
|
457
|
+
|
|
458
|
+
workflow_name = args.kind
|
|
459
|
+
|
|
460
|
+
# --- Export CSV ---
|
|
461
|
+
if args.export:
|
|
462
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
463
|
+
df.to_csv(out, index=False)
|
|
464
|
+
print(f"[Done] Exported box-dimension table to {out}")
|
|
465
|
+
|
|
466
|
+
# --- Plot / Save using multi_subplots ---
|
|
467
|
+
if args.plot or args.save:
|
|
468
|
+
# plot a, b, c if present
|
|
469
|
+
ycols = [c for c in ("a", "b", "c") if c in df.columns]
|
|
470
|
+
if not ycols:
|
|
471
|
+
print("No a/b/c columns found in box-dimension table; nothing to plot.")
|
|
472
|
+
else:
|
|
473
|
+
subplots_data = []
|
|
474
|
+
for col in ycols:
|
|
475
|
+
subplots_data.append([
|
|
476
|
+
{
|
|
477
|
+
"x": df["x"].to_numpy(),
|
|
478
|
+
"y": df[col].to_numpy(),
|
|
479
|
+
"label": col,
|
|
480
|
+
}
|
|
481
|
+
])
|
|
482
|
+
|
|
483
|
+
save_target = resolve_output_path(args.save, workflow_name) if args.save else None
|
|
484
|
+
|
|
485
|
+
multi_subplots(
|
|
486
|
+
subplots=subplots_data,
|
|
487
|
+
title=f"Box dimensions vs {xlabel}",
|
|
488
|
+
xlabel=xlabel,
|
|
489
|
+
ylabel="Length (Å)",
|
|
490
|
+
legend=True,
|
|
491
|
+
save=save_target if (args.save or args.plot) else None,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# --- No action fallback ---
|
|
495
|
+
if not args.export and not args.save and not args.plot:
|
|
496
|
+
print(df.head(10).to_string(index=False))
|
|
497
|
+
|
|
498
|
+
return 0
|
|
499
|
+
|
|
500
|
+
def _trim_task(args: argparse.Namespace) -> int:
|
|
501
|
+
"""
|
|
502
|
+
Write a trimmed copy of an xmolout file that keeps only atom_type and x,y,z
|
|
503
|
+
columns for each atom line (drops any extra per-atom columns).
|
|
504
|
+
"""
|
|
505
|
+
xh = XmoloutHandler(args.file)
|
|
506
|
+
out_path = args.output or "xmolout_trimmed"
|
|
507
|
+
# include_extras=False ⇒ only atom_type + x y z
|
|
508
|
+
write_xmolout_from_handler(xh, out_path, include_extras=False)
|
|
509
|
+
print(f"[Done] Wrote trimmed xmolout (type + x,y,z only) to {out_path}")
|
|
510
|
+
return 0
|
|
511
|
+
|
|
512
|
+
# --------------------------
|
|
513
|
+
# CLI REGISTRATION
|
|
514
|
+
# --------------------------
|
|
515
|
+
def _add_common_xmolout_io_args(
|
|
516
|
+
p: argparse.ArgumentParser,
|
|
517
|
+
*,
|
|
518
|
+
include_plot: bool = False,
|
|
519
|
+
) -> None:
|
|
520
|
+
"""Common I/O flags for xmolout-based tasks."""
|
|
521
|
+
p.add_argument("--file", default="xmolout", help="Path to xmolout file.")
|
|
522
|
+
if include_plot:
|
|
523
|
+
p.add_argument("--plot", action="store_true", help="Show plot interactively.")
|
|
524
|
+
p.add_argument("--save", default=None, help="Path to save plot image.")
|
|
525
|
+
p.add_argument("--export", default=None, help="Path to export CSV data.")
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
529
|
+
# ------------------------------------------------------------------
|
|
530
|
+
# trajget (single or multi-atom trajectories)
|
|
531
|
+
# ------------------------------------------------------------------
|
|
532
|
+
pt = subparsers.add_parser(
|
|
533
|
+
"trajget",
|
|
534
|
+
help="Get atom trajectories per frame (single or multiple atoms).",
|
|
535
|
+
description=(
|
|
536
|
+
"Get atom trajectories from xmolout.\n\n"
|
|
537
|
+
"Examples:\n"
|
|
538
|
+
" reaxkit xmolout trajget --atoms 1 --dims z --xaxis time "
|
|
539
|
+
"--save atom1_z.png --export atom1_z.csv\n"
|
|
540
|
+
" reaxkit xmolout trajget --atom-types Al --dims x y z --format wide "
|
|
541
|
+
"--export Al_all_dims_traj.csv\n"
|
|
542
|
+
" reaxkit xmolout trajget --atom-types Al --frames 10:200:10 --dims z "
|
|
543
|
+
"--export Al_z_dim_traj.csv\n"
|
|
544
|
+
),
|
|
545
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
546
|
+
)
|
|
547
|
+
_add_common_xmolout_io_args(pt, include_plot=True)
|
|
548
|
+
pt.add_argument("--atoms", default=None,
|
|
549
|
+
help="Comma/space separated 1-based atom indices, e.g. '1,5,12'.")
|
|
550
|
+
pt.add_argument("--dims", nargs="+", default=None, choices=["x", "y", "z"],
|
|
551
|
+
help="Coordinate dimensions to include (default: x y z).")
|
|
552
|
+
pt.add_argument("--xaxis", default="frame", choices=["iter", "frame", "time"],
|
|
553
|
+
help="Quantity on x-axis (default: frame).")
|
|
554
|
+
pt.add_argument("--frames", default=None,
|
|
555
|
+
help="Frame selector: 'start:stop[:step]' or 'i,j,k' (default: all).")
|
|
556
|
+
pt.add_argument("--atom-types", "--types", dest="atom_types", default=None,
|
|
557
|
+
help="Comma/space separated atom types, e.g. 'Al,N'.")
|
|
558
|
+
pt.add_argument("--format", choices=["long", "wide"], default="long",
|
|
559
|
+
help="Output table layout: long or wide (default: long).")
|
|
560
|
+
pt.set_defaults(_run=_trajget_task)
|
|
561
|
+
|
|
562
|
+
# ------------------------------------------------------------------
|
|
563
|
+
# MSD
|
|
564
|
+
# ------------------------------------------------------------------
|
|
565
|
+
p2 = subparsers.add_parser(
|
|
566
|
+
"msd",
|
|
567
|
+
help="Compute mean squared displacement (MSD) for one or more atoms.",
|
|
568
|
+
description=(
|
|
569
|
+
"Compute MSD from xmolout.\n\n"
|
|
570
|
+
"Examples:\n"
|
|
571
|
+
" reaxkit xmolout msd --atoms 1 --xaxis frame "
|
|
572
|
+
"--save atom1_msd.png --export atom1_msd.csv\n"
|
|
573
|
+
" reaxkit xmolout msd --atoms 1,2,3 --xaxis time "
|
|
574
|
+
"--save msd.png --export msd.csv\n"
|
|
575
|
+
" reaxkit xmolout msd --atoms 1,2,3 --subplot "
|
|
576
|
+
"--save msd_subplot.png --export msd_subplot.csv\n"
|
|
577
|
+
),
|
|
578
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
579
|
+
)
|
|
580
|
+
_add_common_xmolout_io_args(p2, include_plot=True)
|
|
581
|
+
p2.add_argument("--atoms", required=True,
|
|
582
|
+
help="Comma/space separated 1-based atom indices, e.g. '1,5,12'.")
|
|
583
|
+
p2.add_argument("--xaxis", default="frame", choices=["iter", "frame", "time"],
|
|
584
|
+
help="Quantity on x-axis (default: frame).")
|
|
585
|
+
p2.add_argument("--subplot", action="store_true",
|
|
586
|
+
help="Plot each atom in its own subplot instead of a single combined plot.")
|
|
587
|
+
p2.set_defaults(_run=_msd_task)
|
|
588
|
+
|
|
589
|
+
# ------------------------------------------------------------------
|
|
590
|
+
# RDF
|
|
591
|
+
# ------------------------------------------------------------------
|
|
592
|
+
p3 = subparsers.add_parser(
|
|
593
|
+
"rdf",
|
|
594
|
+
help="Compute RDF curve or per-frame RDF-derived property.",
|
|
595
|
+
description=(
|
|
596
|
+
"Compute RDF using FREUD or OVITO backends.\n\n"
|
|
597
|
+
"Curve example:\n"
|
|
598
|
+
" reaxkit xmolout rdf --save rdf.png --export rdf.csv "
|
|
599
|
+
"--frames 0 --bins 200 --r-max 5\n\n"
|
|
600
|
+
"Property example:\n"
|
|
601
|
+
" reaxkit xmolout rdf --prop area --bins 200 --r-max 5 "
|
|
602
|
+
"--frames 0:10:1 --save rdf_area.png\n"
|
|
603
|
+
),
|
|
604
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
605
|
+
)
|
|
606
|
+
_add_common_xmolout_io_args(p3, include_plot=True)
|
|
607
|
+
p3.add_argument("--backend", choices=["freud", "ovito"], default="ovito",
|
|
608
|
+
help="RDF backend: freud or ovito (default: ovito).")
|
|
609
|
+
p3.add_argument(
|
|
610
|
+
"--prop",
|
|
611
|
+
choices=["first_peak", "dominant_peak", "area", "excess_area"],
|
|
612
|
+
default=None,
|
|
613
|
+
help="Compute this RDF-derived property per frame instead of a curve.",
|
|
614
|
+
)
|
|
615
|
+
p3.add_argument("--types-a", "--types_a", dest="types_a", default=None,
|
|
616
|
+
help="Comma/space separated atom types for set A, e.g. 'Al,N'.")
|
|
617
|
+
p3.add_argument("--types-b", "--types_b", dest="types_b", default=None,
|
|
618
|
+
help="Comma/space separated atom types for set B, e.g. 'N'.")
|
|
619
|
+
p3.add_argument("--bins", type=int, default=200, help="Number of RDF bins.")
|
|
620
|
+
p3.add_argument("--r-max", type=float, default=None,
|
|
621
|
+
help="Max radius in Å; default depends on backend.")
|
|
622
|
+
p3.add_argument("--frames", default=None,
|
|
623
|
+
help="Frame selector: 'start:stop[:step]' or 'i,j,k'.")
|
|
624
|
+
p3.add_argument("--every", type=int, default=1,
|
|
625
|
+
help="Use every Nth frame (default: 1).")
|
|
626
|
+
p3.add_argument("--start", type=int, default=None,
|
|
627
|
+
help="First frame index (0-based).")
|
|
628
|
+
p3.add_argument("--stop", type=int, default=None,
|
|
629
|
+
help="Last frame index (0-based).")
|
|
630
|
+
p3.add_argument("--norm", choices=["extent", "cell"], default="extent",
|
|
631
|
+
help="FREUD normalization: extent or cell (default: extent).")
|
|
632
|
+
p3.add_argument("--c-eff", type=float, default=None,
|
|
633
|
+
help="FREUD only: effective c-length for --norm cell.")
|
|
634
|
+
p3.set_defaults(_run=_rdf_task)
|
|
635
|
+
|
|
636
|
+
# ------------------------------------------------------------------
|
|
637
|
+
# BOX DIMS
|
|
638
|
+
# ------------------------------------------------------------------
|
|
639
|
+
pb = subparsers.add_parser(
|
|
640
|
+
"boxdims",
|
|
641
|
+
help="Get box/cell dimensions per frame and optionally plot them.",
|
|
642
|
+
description=(
|
|
643
|
+
"Get box/cell dimensions from xmolout.\n\n"
|
|
644
|
+
"Examples:\n"
|
|
645
|
+
" reaxkit xmolout boxdims --frames 0:500:5 --xaxis time "
|
|
646
|
+
"--export box_dims.csv\n"
|
|
647
|
+
" reaxkit xmolout boxdims --frames 0:100:10 --xaxis iter "
|
|
648
|
+
"--save box_dim_plots.png\n"
|
|
649
|
+
),
|
|
650
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
651
|
+
)
|
|
652
|
+
_add_common_xmolout_io_args(pb, include_plot=True)
|
|
653
|
+
pb.add_argument("--frames", default=None,
|
|
654
|
+
help="Frame selector: 'start:stop[:step]' or 'i,j,k'.")
|
|
655
|
+
pb.add_argument("--xaxis", choices=["frame", "iter", "time"], default="frame",
|
|
656
|
+
help="Quantity on x-axis (default: frame).")
|
|
657
|
+
pb.set_defaults(_run=_boxdims_task)
|
|
658
|
+
|
|
659
|
+
# ------------------------------------------------------------------
|
|
660
|
+
# TRIM
|
|
661
|
+
# ------------------------------------------------------------------
|
|
662
|
+
ptr = subparsers.add_parser(
|
|
663
|
+
"trim",
|
|
664
|
+
help="Write a trimmed copy of xmolout with only atom_type and x,y,z.",
|
|
665
|
+
description=(
|
|
666
|
+
"Trim xmolout to a lighter file with atom type and coordinates only.\n\n"
|
|
667
|
+
"Example:\n"
|
|
668
|
+
" reaxkit xmolout trim --file xmolout --output xmolout_trimmed\n"
|
|
669
|
+
),
|
|
670
|
+
)
|
|
671
|
+
ptr.add_argument("--file", default="xmolout",
|
|
672
|
+
help="Input xmolout file.")
|
|
673
|
+
ptr.add_argument("--output", default="xmolout_trimmed",
|
|
674
|
+
help="Output trimmed xmolout file.")
|
|
675
|
+
ptr.set_defaults(_run=_trim_task)
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
|