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,592 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fort.7 analysis workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides tools for inspecting and analyzing ReaxFF `fort.7` files,
|
|
5
|
+
which contain per-frame bond-order information and derived atomic connectivity.
|
|
6
|
+
|
|
7
|
+
It supports:
|
|
8
|
+
- Extracting atom-level or system-level features (e.g. charges, coordination,
|
|
9
|
+
bond-order–derived quantities) as functions of frame, iteration, or time.
|
|
10
|
+
- Building explicit connectivity representations (edge lists) from bond orders,
|
|
11
|
+
with configurable thresholds and directionality.
|
|
12
|
+
- Computing connection statistics over time, such as mean or maximum coordination.
|
|
13
|
+
- Generating bond-order time series for specific atom pairs.
|
|
14
|
+
- Detecting bond formation and breakage events using threshold and hysteresis
|
|
15
|
+
criteria, with optional diagnostic visualizations.
|
|
16
|
+
|
|
17
|
+
The workflow is designed to bridge raw ReaxFF bond-order output with higher-level
|
|
18
|
+
connectivity, dynamics, and event-based analyses in a reproducible, CLI-driven way.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
import argparse
|
|
24
|
+
import pandas as pd
|
|
25
|
+
from typing import Optional, Sequence, Union
|
|
26
|
+
|
|
27
|
+
# --- Direct imports (no _import_or_die, no fallbacks) ---
|
|
28
|
+
from reaxkit.utils.path import resolve_output_path
|
|
29
|
+
from reaxkit.io.handlers.fort7_handler import Fort7Handler
|
|
30
|
+
from reaxkit.analysis.per_file.fort7_analyzer import get_fort7_data_per_atom, get_fort7_data_summaries
|
|
31
|
+
from reaxkit.analysis.composed.connectivity_analyzer import (
|
|
32
|
+
connection_list,
|
|
33
|
+
connection_stats_over_frames,
|
|
34
|
+
bond_timeseries,
|
|
35
|
+
bond_events,
|
|
36
|
+
debug_bond_trace_overlay,
|
|
37
|
+
)
|
|
38
|
+
from reaxkit.utils.media.plotter import single_plot
|
|
39
|
+
from reaxkit.utils.frame_utils import parse_frames
|
|
40
|
+
from reaxkit.utils.alias import normalize_choice
|
|
41
|
+
from reaxkit.utils.media.convert import convert_xaxis
|
|
42
|
+
|
|
43
|
+
FramesT = Optional[Union[slice, Sequence[int]]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ==========================================================
|
|
47
|
+
# Task: GET (atomic or summary feature)
|
|
48
|
+
# ==========================================================
|
|
49
|
+
def _parse_atom_selection(atom_arg, max_atoms=None):
|
|
50
|
+
"""
|
|
51
|
+
Parse --atom argument.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
- None → no atom selection (summary mode)
|
|
55
|
+
- "all" → all atoms (if max_atoms unknown)
|
|
56
|
+
- [int, ...] → list of atom numbers
|
|
57
|
+
"""
|
|
58
|
+
if atom_arg is None:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
s = str(atom_arg).strip().lower()
|
|
62
|
+
if s == "all":
|
|
63
|
+
if max_atoms is None:
|
|
64
|
+
return "all"
|
|
65
|
+
return list(range(1, max_atoms + 1))
|
|
66
|
+
|
|
67
|
+
if "," in s:
|
|
68
|
+
return [int(tok) for tok in s.split(",") if tok.strip()]
|
|
69
|
+
|
|
70
|
+
return [int(s)]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _task_get(args: argparse.Namespace) -> int:
|
|
74
|
+
h = Fort7Handler(args.file)
|
|
75
|
+
|
|
76
|
+
frames_sel = parse_frames(args.frames)
|
|
77
|
+
feat = (args.yaxis or "").strip()
|
|
78
|
+
feat = normalize_choice(feat, domain="feature")
|
|
79
|
+
use_regex = bool(args.regex)
|
|
80
|
+
atom_sel = _parse_atom_selection(args.atom, max_atoms=getattr(h, "num_atoms", None))
|
|
81
|
+
is_atom_scope = atom_sel is not None
|
|
82
|
+
|
|
83
|
+
if is_atom_scope:
|
|
84
|
+
df = get_fort7_data_per_atom(h, feat, frames=frames_sel, regex=use_regex, add_index_cols=True)
|
|
85
|
+
if df.empty:
|
|
86
|
+
raise SystemExit("❌ No atom-level rows matched your request.")
|
|
87
|
+
|
|
88
|
+
# Ensure atom_num exists
|
|
89
|
+
if "atom_num" not in df.columns:
|
|
90
|
+
needed = {"frame_idx", "atom_idx"}
|
|
91
|
+
if not needed.issubset(df.columns):
|
|
92
|
+
raise SystemExit("❌ Internal: missing frame_idx/atom_idx to recover atom numbers.")
|
|
93
|
+
rows = []
|
|
94
|
+
for fi, g in df.groupby("frame_idx"):
|
|
95
|
+
fr = h._frames[int(fi)]
|
|
96
|
+
take = g.copy()
|
|
97
|
+
take["atom_num"] = fr.loc[take["atom_idx"].values, "atom_num"].to_numpy()
|
|
98
|
+
rows.append(take)
|
|
99
|
+
df = pd.concat(rows, ignore_index=True)
|
|
100
|
+
|
|
101
|
+
# Filter to requested atoms (unless "all")
|
|
102
|
+
if atom_sel != "all":
|
|
103
|
+
df = df[df["atom_num"].isin(atom_sel)].copy()
|
|
104
|
+
if df.empty:
|
|
105
|
+
raise SystemExit(f"❌ No atoms {atom_sel} found in selected frames.")
|
|
106
|
+
|
|
107
|
+
# --- pick feature column(s) and collapse to a single value per row ---
|
|
108
|
+
keep_meta = {"frame_idx", "iter", "atom_idx", "atom_num"}
|
|
109
|
+
ycols = [c for c in df.columns if c not in keep_meta]
|
|
110
|
+
if not ycols:
|
|
111
|
+
raise SystemExit("❌ No feature columns found after selection.")
|
|
112
|
+
|
|
113
|
+
if len(ycols) == 1:
|
|
114
|
+
df["__value__"] = df[ycols[0]]
|
|
115
|
+
base_name = (args.yaxis or ycols[0]).strip()
|
|
116
|
+
else:
|
|
117
|
+
df["__value__"] = df[ycols].mean(axis=1)
|
|
118
|
+
base_name = (feat or "value").strip()
|
|
119
|
+
|
|
120
|
+
# --- choose x-axis column ---
|
|
121
|
+
xchoice = normalize_choice(args.xaxis, domain="xaxis")
|
|
122
|
+
if xchoice == "frame":
|
|
123
|
+
x_col = "frame_idx"
|
|
124
|
+
xlabel = "frame"
|
|
125
|
+
elif xchoice == "iter":
|
|
126
|
+
x_col = "iter"
|
|
127
|
+
xlabel = "iter"
|
|
128
|
+
else: # time
|
|
129
|
+
x_vals, xlabel = convert_xaxis(df["iter"].to_numpy(), "time", control_file=args.control)
|
|
130
|
+
df["time"] = x_vals
|
|
131
|
+
x_col = "time"
|
|
132
|
+
|
|
133
|
+
# --- wide pivot: one column per atom ---
|
|
134
|
+
wide = (
|
|
135
|
+
df.groupby([x_col, "atom_num"], as_index=False)["__value__"]
|
|
136
|
+
.mean()
|
|
137
|
+
.pivot(index=x_col, columns="atom_num", values="__value__")
|
|
138
|
+
.reset_index()
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Rename columns: e.g. iter, charge_atom_1, charge_atom_2, ...
|
|
142
|
+
new_cols = [xlabel]
|
|
143
|
+
for c in wide.columns[1:]:
|
|
144
|
+
new_cols.append(f"{base_name}_atom_{c}")
|
|
145
|
+
wide.columns = new_cols
|
|
146
|
+
|
|
147
|
+
out_df = wide
|
|
148
|
+
ylab = base_name # for plot labels later
|
|
149
|
+
|
|
150
|
+
else:
|
|
151
|
+
df = get_fort7_data_summaries(h, feat, frames=frames_sel, regex=use_regex, add_index_cols=True)
|
|
152
|
+
if df.empty:
|
|
153
|
+
raise SystemExit("❌ No summary rows matched your request.")
|
|
154
|
+
keep_meta = {"frame_idx", "iter"}
|
|
155
|
+
ycols = [c for c in df.columns if c not in keep_meta]
|
|
156
|
+
if not ycols:
|
|
157
|
+
raise SystemExit("❌ No summary feature columns found.")
|
|
158
|
+
if len(ycols) == 1:
|
|
159
|
+
ylab = ycols[0]
|
|
160
|
+
y = df[ylab].to_numpy()
|
|
161
|
+
else:
|
|
162
|
+
ylab = f"mean({feat})"
|
|
163
|
+
y = df[ycols].mean(axis=1).to_numpy()
|
|
164
|
+
|
|
165
|
+
xchoice = normalize_choice(args.xaxis, domain="xaxis")
|
|
166
|
+
if xchoice == "frame":
|
|
167
|
+
x_raw = df["frame_idx"].to_numpy(); xlabel = "frame"
|
|
168
|
+
else:
|
|
169
|
+
x_raw = df["iter"].to_numpy(); xlabel = "iter"
|
|
170
|
+
if xchoice == "time":
|
|
171
|
+
x, xlabel = convert_xaxis(x_raw, "time", control_file=args.control)
|
|
172
|
+
else:
|
|
173
|
+
x = x_raw
|
|
174
|
+
|
|
175
|
+
out_df = pd.DataFrame({xlabel: x, ylab: y})
|
|
176
|
+
|
|
177
|
+
workflow_name = args.kind
|
|
178
|
+
if args.export:
|
|
179
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
180
|
+
out_df.to_csv(out, index=False)
|
|
181
|
+
print(f"[Done] Exported data to {out}")
|
|
182
|
+
|
|
183
|
+
if args.save or args.plot:
|
|
184
|
+
series = [{'x': out_df.iloc[:, 1].to_numpy() if out_df.columns[0] == "atom_num" else out_df.iloc[:, 0].to_numpy(),
|
|
185
|
+
'y': out_df.iloc[:, -1].to_numpy(),
|
|
186
|
+
'label': ylab}]
|
|
187
|
+
# fix X for atom_num case
|
|
188
|
+
xlabel = out_df.columns[1] if out_df.columns[0] == "atom_num" else out_df.columns[0]
|
|
189
|
+
if args.save:
|
|
190
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
191
|
+
single_plot(series=series, title=f"{ylab} vs {xlabel}", xlabel=xlabel, ylabel=ylab, save=out,
|
|
192
|
+
legend=True)
|
|
193
|
+
elif args.plot:
|
|
194
|
+
single_plot(series=series, title=f"{ylab} vs {xlabel}", xlabel=xlabel, ylabel=ylab, save=None,
|
|
195
|
+
legend=True)
|
|
196
|
+
|
|
197
|
+
if not (args.export or args.plot or args.save):
|
|
198
|
+
print("ℹ️ No action selected. Use one or more of --plot, --save, --export.")
|
|
199
|
+
return 0
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# ==========================================================
|
|
203
|
+
# Task: EDGES (connection_list → tidy edge table)
|
|
204
|
+
# ==========================================================
|
|
205
|
+
def _task_edges(args: argparse.Namespace) -> int:
|
|
206
|
+
h = Fort7Handler(args.file)
|
|
207
|
+
frames_sel = parse_frames(args.frames)
|
|
208
|
+
edges = connection_list(
|
|
209
|
+
h,
|
|
210
|
+
frames=frames_sel,
|
|
211
|
+
iterations=None,
|
|
212
|
+
min_bo=args.min_bo,
|
|
213
|
+
undirected=not args.directed,
|
|
214
|
+
aggregate=args.aggregate,
|
|
215
|
+
include_self=args.include_self,
|
|
216
|
+
)
|
|
217
|
+
if edges.empty:
|
|
218
|
+
print("ℹ️ No edges found for the given selection.")
|
|
219
|
+
|
|
220
|
+
workflow_name = args.kind
|
|
221
|
+
if args.export:
|
|
222
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
223
|
+
edges.to_csv(out, index=False)
|
|
224
|
+
print(f"[Done] Exported edges to {out}")
|
|
225
|
+
|
|
226
|
+
if args.save or args.plot:
|
|
227
|
+
if edges.empty:
|
|
228
|
+
print("ℹ️ Nothing to plot.")
|
|
229
|
+
else:
|
|
230
|
+
counts = edges.groupby("frame_idx", as_index=False).size().rename(columns={"size": "edges"})
|
|
231
|
+
xchoice = normalize_choice(args.xaxis, domain="xaxis")
|
|
232
|
+
if xchoice == "iter":
|
|
233
|
+
f2i = edges.drop_duplicates("frame_idx")[["frame_idx", "iter"]]
|
|
234
|
+
counts = counts.merge(f2i, on="frame_idx", how="left")
|
|
235
|
+
x_raw, xlabel = counts["iter"].to_numpy(), "iter"
|
|
236
|
+
elif xchoice == "time":
|
|
237
|
+
f2i = edges.drop_duplicates("frame_idx")[["frame_idx", "iter"]]
|
|
238
|
+
counts = counts.merge(f2i, on="frame_idx", how="left")
|
|
239
|
+
x_raw, xlabel = convert_xaxis(counts["iter"].to_numpy(), "time", control_file=args.control)
|
|
240
|
+
else:
|
|
241
|
+
x_raw, xlabel = counts["frame_idx"].to_numpy(), "frame"
|
|
242
|
+
y = counts["edges"].to_numpy()
|
|
243
|
+
series = [{'x': x_raw, 'y': y, 'label': '#edges'}]
|
|
244
|
+
if args.save:
|
|
245
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
246
|
+
single_plot(series=series, title=f"#edges vs {xlabel}", xlabel=xlabel, ylabel="#edges",
|
|
247
|
+
save=out, legend=False)
|
|
248
|
+
elif args.plot:
|
|
249
|
+
single_plot(series=series, title=f"#edges vs {xlabel}", xlabel=xlabel, ylabel="#edges",
|
|
250
|
+
save=None, legend=False)
|
|
251
|
+
|
|
252
|
+
if not (args.export or args.plot or args.save):
|
|
253
|
+
print("ℹ️ No action selected. Use --export to save CSV or --plot/--save to visualize edge counts.")
|
|
254
|
+
return 0
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ==========================================================
|
|
258
|
+
# Task: CONSTATs (connection_stats_over_frames)
|
|
259
|
+
# ==========================================================
|
|
260
|
+
def _task_constats(args: argparse.Namespace) -> int:
|
|
261
|
+
h = Fort7Handler(args.file)
|
|
262
|
+
frames_sel = parse_frames(args.frames)
|
|
263
|
+
stats = connection_stats_over_frames(
|
|
264
|
+
h,
|
|
265
|
+
frames=frames_sel,
|
|
266
|
+
iterations=None,
|
|
267
|
+
min_bo=args.min_bo,
|
|
268
|
+
undirected=not args.directed,
|
|
269
|
+
how=args.how,
|
|
270
|
+
)
|
|
271
|
+
if stats.empty:
|
|
272
|
+
print("ℹ️ No connection stats for the given selection.")
|
|
273
|
+
workflow_name = args.kind
|
|
274
|
+
if args.export:
|
|
275
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
276
|
+
stats.to_csv(out, index=False)
|
|
277
|
+
print(f"[Done] Exported stats to {out}")
|
|
278
|
+
else:
|
|
279
|
+
print("ℹ️ Use --export to save stats (src, dst, value).")
|
|
280
|
+
return 0
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ==========================================================
|
|
284
|
+
# Task: BOND-TS (bond_timeseries)
|
|
285
|
+
# ==========================================================
|
|
286
|
+
def _task_bond_ts(args: argparse.Namespace) -> int:
|
|
287
|
+
h = Fort7Handler(args.file)
|
|
288
|
+
frames_sel = parse_frames(args.frames)
|
|
289
|
+
ts = bond_timeseries(
|
|
290
|
+
h,
|
|
291
|
+
frames=frames_sel,
|
|
292
|
+
iterations=None,
|
|
293
|
+
undirected=not args.directed,
|
|
294
|
+
bo_threshold=args.bo_threshold,
|
|
295
|
+
as_wide=args.wide,
|
|
296
|
+
)
|
|
297
|
+
if ts is None or getattr(ts, "empty", False):
|
|
298
|
+
print("ℹ️ No bond time series produced.")
|
|
299
|
+
return 0
|
|
300
|
+
|
|
301
|
+
workflow_name = args.kind
|
|
302
|
+
if args.export:
|
|
303
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
304
|
+
ts.to_csv(out, index=True if args.wide else False)
|
|
305
|
+
print(f"[Done] Exported bond time series to {out}")
|
|
306
|
+
|
|
307
|
+
if (args.save or args.plot) and (args.src and args.dst) and not args.wide:
|
|
308
|
+
a, b = int(args.src), int(args.dst)
|
|
309
|
+
if not args.directed and a > b:
|
|
310
|
+
a, b = b, a
|
|
311
|
+
g = ts[(ts["src"] == a) & (ts["dst"] == b)].copy()
|
|
312
|
+
if g.empty:
|
|
313
|
+
print(f"ℹ️ Bond {a}-{b} not found in the selection.")
|
|
314
|
+
else:
|
|
315
|
+
xchoice = normalize_choice(args.xaxis, domain="xaxis")
|
|
316
|
+
if xchoice == "frame":
|
|
317
|
+
x_raw, xlabel = g["frame_idx"].to_numpy(), "frame"
|
|
318
|
+
elif xchoice == "time":
|
|
319
|
+
x_raw, xlabel = convert_xaxis(g["iter"].to_numpy(), "time", control_file=args.control)
|
|
320
|
+
else:
|
|
321
|
+
x_raw, xlabel = g["iter"].to_numpy(), "iter"
|
|
322
|
+
y = g["bo"].to_numpy()
|
|
323
|
+
if args.save:
|
|
324
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
325
|
+
single_plot(series=[{'x': x_raw, 'y': y, 'label': f'BO {a}-{b}'}],
|
|
326
|
+
title=f"BO({a}-{b}) vs {xlabel}", xlabel=xlabel, ylabel="BO", save=out, legend=False)
|
|
327
|
+
elif args.plot:
|
|
328
|
+
single_plot(series=[{'x': x_raw, 'y': y, 'label': f'BO {a}-{b}'}],
|
|
329
|
+
title=f"BO({a}-{b}) vs {xlabel}", xlabel=xlabel, ylabel="BO", save=None, legend=False)
|
|
330
|
+
elif (args.save or args.plot) and not (args.src and args.dst):
|
|
331
|
+
print("ℹ️ Provide --src and --dst to plot a specific bond trace.")
|
|
332
|
+
|
|
333
|
+
if not (args.export or args.plot or args.save):
|
|
334
|
+
print("ℹ️ No action selected. Use --export to save CSV, or --plot/--save with --src/--dst to visualize a bond.")
|
|
335
|
+
return 0
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# ==========================================================
|
|
339
|
+
# Task: BOND-EVENTS (bond_events + optional overlay)
|
|
340
|
+
# ==========================================================
|
|
341
|
+
def _task_bond_events(args: argparse.Namespace) -> int:
|
|
342
|
+
print('This is a time-consuming task; please be patient ...')
|
|
343
|
+
h = Fort7Handler(args.file)
|
|
344
|
+
frames_sel = parse_frames(args.frames)
|
|
345
|
+
events = bond_events(
|
|
346
|
+
h,
|
|
347
|
+
frames=frames_sel,
|
|
348
|
+
iterations=None,
|
|
349
|
+
src=args.src,
|
|
350
|
+
dst=args.dst,
|
|
351
|
+
threshold=args.threshold,
|
|
352
|
+
hysteresis=args.hysteresis,
|
|
353
|
+
smooth=args.smooth,
|
|
354
|
+
window=args.window,
|
|
355
|
+
ema_alpha=args.ema_alpha,
|
|
356
|
+
min_run=args.min_run,
|
|
357
|
+
xaxis=args.xaxis if args.xaxis in ("iter", "frame") else "iter",
|
|
358
|
+
undirected=not args.directed,
|
|
359
|
+
)
|
|
360
|
+
if events.empty:
|
|
361
|
+
print("ℹ️ No bond formation/breakage events detected with the given settings.")
|
|
362
|
+
|
|
363
|
+
workflow_name = args.kind
|
|
364
|
+
if args.export:
|
|
365
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
366
|
+
events.to_csv(out, index=False)
|
|
367
|
+
print(f"[Done] Exported events to {out}")
|
|
368
|
+
|
|
369
|
+
if args.save or args.plot and args.src and args.dst:
|
|
370
|
+
out = resolve_output_path(args.save, workflow_name)
|
|
371
|
+
if args.save:
|
|
372
|
+
debug_bond_trace_overlay(
|
|
373
|
+
h,
|
|
374
|
+
src=int(args.src),
|
|
375
|
+
dst=int(args.dst),
|
|
376
|
+
smooth=("ema" if args.smooth == "ema" else "ma"),
|
|
377
|
+
window=int(args.window),
|
|
378
|
+
hysteresis=float(args.hysteresis),
|
|
379
|
+
threshold=float(args.threshold),
|
|
380
|
+
min_run=int(args.min_run or 0),
|
|
381
|
+
xaxis=("iter" if args.xaxis == "iter" else "frame"),
|
|
382
|
+
save=out,
|
|
383
|
+
)
|
|
384
|
+
else:
|
|
385
|
+
debug_bond_trace_overlay(
|
|
386
|
+
h,
|
|
387
|
+
src=int(args.src),
|
|
388
|
+
dst=int(args.dst),
|
|
389
|
+
smooth=("ema" if args.smooth == "ema" else "ma"),
|
|
390
|
+
window=int(args.window),
|
|
391
|
+
hysteresis=float(args.hysteresis),
|
|
392
|
+
threshold=float(args.threshold),
|
|
393
|
+
min_run=int(args.min_run or 0),
|
|
394
|
+
xaxis=("iter" if args.xaxis == "iter" else "frame"),
|
|
395
|
+
save=None,
|
|
396
|
+
)
|
|
397
|
+
elif args.save:
|
|
398
|
+
print("ℹ️ --save requires both --src and --dst to be set.")
|
|
399
|
+
|
|
400
|
+
if not (args.export or args.save):
|
|
401
|
+
print("ℹ️ No action selected. Use --export to save CSV or --save to write a debug figure.")
|
|
402
|
+
return 0
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
# ==========================================================
|
|
406
|
+
# CLI wiring
|
|
407
|
+
# ==========================================================
|
|
408
|
+
import argparse
|
|
409
|
+
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
# Common argument helpers
|
|
412
|
+
# ---------------------------------------------------------------------------
|
|
413
|
+
|
|
414
|
+
def _add_common_fort7_file_arg(p: argparse.ArgumentParser) -> None:
|
|
415
|
+
p.add_argument("--file", default="fort.7", help="Path to fort.7 file.")
|
|
416
|
+
|
|
417
|
+
def _add_common_io_args(
|
|
418
|
+
p: argparse.ArgumentParser,
|
|
419
|
+
*,
|
|
420
|
+
include_plot: bool = False,
|
|
421
|
+
save_help: str = "Path to save plot image.",
|
|
422
|
+
export_help: str = "Path to export CSV data.",
|
|
423
|
+
) -> None:
|
|
424
|
+
if include_plot:
|
|
425
|
+
p.add_argument("--plot", action="store_true", help="Show plot interactively.")
|
|
426
|
+
p.add_argument("--save", default=None, help=save_help)
|
|
427
|
+
p.add_argument("--export", default=None, help=export_help)
|
|
428
|
+
|
|
429
|
+
# ---------------------------------------------------------------------------
|
|
430
|
+
# Wiring functions
|
|
431
|
+
# ---------------------------------------------------------------------------
|
|
432
|
+
|
|
433
|
+
def _wire_get(p: argparse.ArgumentParser) -> None:
|
|
434
|
+
_add_common_fort7_file_arg(p)
|
|
435
|
+
p.add_argument("--yaxis", required=True, help="Feature name or regex (with --regex).")
|
|
436
|
+
p.add_argument(
|
|
437
|
+
"--atom",
|
|
438
|
+
type=str,
|
|
439
|
+
default=None,
|
|
440
|
+
help="Atom selection: a number (e.g., 5), a list (e.g., 1,2,7), or 'all'."
|
|
441
|
+
)
|
|
442
|
+
p.add_argument("--frames", default=None, help="Frame selection: 'a:b[:c]' or 'i,j,k'.")
|
|
443
|
+
p.add_argument("--xaxis", default="iter", choices=["iter","frame","time"], help="X-axis mode.")
|
|
444
|
+
p.add_argument("--control", default="control", help="Control file (for --xaxis time).")
|
|
445
|
+
p.add_argument("--regex", action="store_true", help="Interpret --feature as regex, which calls all"
|
|
446
|
+
"y-columns with the given --yaxis")
|
|
447
|
+
_add_common_io_args(p, include_plot=True,
|
|
448
|
+
save_help="Save feature plot image.",
|
|
449
|
+
export_help="Export extracted feature(s) as CSV.")
|
|
450
|
+
p.set_defaults(_run=_task_get)
|
|
451
|
+
|
|
452
|
+
def _wire_edges(p: argparse.ArgumentParser) -> None:
|
|
453
|
+
_add_common_fort7_file_arg(p)
|
|
454
|
+
p.add_argument("--frames", default=None, help="Frame selection.")
|
|
455
|
+
p.add_argument("--min-bo", type=float, default=0.0, dest="min_bo", help="Minimum BO.")
|
|
456
|
+
p.add_argument("--directed", action="store_true", help="Treat edges as directed.")
|
|
457
|
+
p.add_argument("--aggregate", choices=["max","mean"], default="max",
|
|
458
|
+
help="Aggregation for undirected edges.")
|
|
459
|
+
p.add_argument("--include-self", action="store_true", dest="include_self", help="Keep self-edges.")
|
|
460
|
+
p.add_argument("--xaxis", default="frame", choices=["iter","frame","time"],
|
|
461
|
+
help="X-axis for quick plot.")
|
|
462
|
+
p.add_argument("--control", default="control", help="Control file for --xaxis time.")
|
|
463
|
+
_add_common_io_args(p, include_plot=True,
|
|
464
|
+
save_help="Save edge-count plot.",
|
|
465
|
+
export_help="Export edge list CSV.")
|
|
466
|
+
p.set_defaults(_run=_task_edges)
|
|
467
|
+
|
|
468
|
+
def _wire_constats(p: argparse.ArgumentParser) -> None:
|
|
469
|
+
_add_common_fort7_file_arg(p)
|
|
470
|
+
p.add_argument("--frames", default=None, help="Frame selection.")
|
|
471
|
+
p.add_argument("--min-bo", type=float, default=0.0, dest="min_bo", help="BO threshold before stats.")
|
|
472
|
+
p.add_argument("--directed", action="store_true", help="Do not merge A–B with B–A.")
|
|
473
|
+
p.add_argument("--how", choices=["mean","max","count"], default="mean", help="Statistic to compute.")
|
|
474
|
+
_add_common_io_args(p, include_plot=False,
|
|
475
|
+
save_help="(Unused) No plot.",
|
|
476
|
+
export_help="Export connection stats as CSV.")
|
|
477
|
+
p.set_defaults(_run=_task_constats)
|
|
478
|
+
|
|
479
|
+
def _wire_bond_ts(p: argparse.ArgumentParser) -> None:
|
|
480
|
+
_add_common_fort7_file_arg(p)
|
|
481
|
+
p.add_argument("--frames", default=None, help="Frame selection.")
|
|
482
|
+
p.add_argument("--directed", action="store_true", help="Do not merge A–B with B–A.")
|
|
483
|
+
p.add_argument("--bo-threshold", type=float, default=0.0, dest="bo_threshold",
|
|
484
|
+
help="Zero out BO below this.")
|
|
485
|
+
p.add_argument("--wide", action="store_true", help="Return wide matrix (frames × bonds).")
|
|
486
|
+
p.add_argument("--xaxis", default="iter", choices=["iter","frame","time"],
|
|
487
|
+
help="X-axis for quick plot.")
|
|
488
|
+
p.add_argument("--control", default="control", help="Control file for --xaxis time.")
|
|
489
|
+
p.add_argument("--src", type=int, help="Source atom for quick plot.")
|
|
490
|
+
p.add_argument("--dst", type=int, help="Destination atom for quick plot.")
|
|
491
|
+
_add_common_io_args(p, include_plot=True,
|
|
492
|
+
save_help="Save bond time-series plot.",
|
|
493
|
+
export_help="Export bond-order time series CSV.")
|
|
494
|
+
p.set_defaults(_run=_task_bond_ts)
|
|
495
|
+
|
|
496
|
+
def _wire_bond_events(p: argparse.ArgumentParser) -> None:
|
|
497
|
+
_add_common_fort7_file_arg(p)
|
|
498
|
+
p.add_argument("--frames", default=None, help="Frame selection.")
|
|
499
|
+
p.add_argument("--src", type=int, help="Source atom.")
|
|
500
|
+
p.add_argument("--dst", type=int, help="Destination atom.")
|
|
501
|
+
p.add_argument("--threshold", type=float, default=0.35, help="Schmitt trigger base threshold.")
|
|
502
|
+
p.add_argument("--hysteresis", type=float, default=0.05,
|
|
503
|
+
help="Hysteresis width around threshold.")
|
|
504
|
+
p.add_argument("--smooth", choices=["ma","ema","none"], default="ma", help="Smoothing method.")
|
|
505
|
+
p.add_argument("--window", type=int, default=7, help="Window size for MA/EMA.")
|
|
506
|
+
p.add_argument("--ema-alpha", type=float, default=None, dest="ema_alpha", help="Optional EMA alpha.")
|
|
507
|
+
p.add_argument("--min-run", type=int, default=3, dest="min_run", help="Minimum consecutive points.")
|
|
508
|
+
p.add_argument("--xaxis", default="iter", choices=["iter","frame"], help="Internal event x-axis.")
|
|
509
|
+
p.add_argument("--directed", action="store_true", help="Do not merge A–B/B–A.")
|
|
510
|
+
_add_common_io_args(p, include_plot=False,
|
|
511
|
+
save_help="Save debug overlay (requires --src --dst).",
|
|
512
|
+
export_help="Export detected events CSV.")
|
|
513
|
+
p.set_defaults(_run=_task_bond_events)
|
|
514
|
+
|
|
515
|
+
# ---------------------------------------------------------------------------
|
|
516
|
+
# Task registration
|
|
517
|
+
# ---------------------------------------------------------------------------
|
|
518
|
+
|
|
519
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
520
|
+
"""
|
|
521
|
+
Register subcommands under the 'fort7' namespace.
|
|
522
|
+
"""
|
|
523
|
+
|
|
524
|
+
# GET
|
|
525
|
+
p_get = subparsers.add_parser(
|
|
526
|
+
"get",
|
|
527
|
+
help="Extract a feature and optionally plot/save/export.",
|
|
528
|
+
description=(
|
|
529
|
+
"Examples:\n"
|
|
530
|
+
" reaxkit fort7 get --yaxis charge --atom 'all' --plot\n"
|
|
531
|
+
" reaxkit fort7 get --yaxis charge --atom 1 --plot\n"
|
|
532
|
+
" reaxkit fort7 get --yaxis q_.* --regex --export charges.csv to get all columns starting with 'q_' \n"
|
|
533
|
+
),
|
|
534
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
535
|
+
)
|
|
536
|
+
_wire_get(p_get)
|
|
537
|
+
|
|
538
|
+
# EDGES
|
|
539
|
+
p_edges = subparsers.add_parser(
|
|
540
|
+
"edges",
|
|
541
|
+
help="Build a tidy edge list from fort.7.",
|
|
542
|
+
description=(
|
|
543
|
+
"Examples:\n"
|
|
544
|
+
" reaxkit fort7 edges --frames 0:1000:10 --min-bo 0.4 --export edges.csv\n"
|
|
545
|
+
" reaxkit fort7 edges --plot --min-bo 0.3\n"
|
|
546
|
+
),
|
|
547
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
548
|
+
)
|
|
549
|
+
_wire_edges(p_edges)
|
|
550
|
+
|
|
551
|
+
# CONSTATs
|
|
552
|
+
p_constats = subparsers.add_parser(
|
|
553
|
+
"constats",
|
|
554
|
+
help="Aggregate connection statistics across frames.",
|
|
555
|
+
description=(
|
|
556
|
+
"Examples:\n"
|
|
557
|
+
" reaxkit fort7 constats --frames 0:1000 --how mean --export stats.csv\n"
|
|
558
|
+
" reaxkit fort7 constats --how count --min-bo 0.4 --export counts.csv\n"
|
|
559
|
+
),
|
|
560
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
561
|
+
)
|
|
562
|
+
_wire_constats(p_constats)
|
|
563
|
+
|
|
564
|
+
# BOND-TS
|
|
565
|
+
p_bts = subparsers.add_parser(
|
|
566
|
+
"bond-ts",
|
|
567
|
+
help="Bond-order time series; optional quick plot.",
|
|
568
|
+
description=(
|
|
569
|
+
"Examples:\n"
|
|
570
|
+
" reaxkit fort7 bond-ts --frames 0:500 --export bo.csv\n"
|
|
571
|
+
" reaxkit fort7 bond-ts --src 1 --dst 19 --plot\n"
|
|
572
|
+
" reaxkit fort7 bond-ts --wide --bo-threshold 0.1 --export wide.csv\n"
|
|
573
|
+
),
|
|
574
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
575
|
+
)
|
|
576
|
+
_wire_bond_ts(p_bts)
|
|
577
|
+
|
|
578
|
+
# BOND-EVENTS
|
|
579
|
+
p_bev = subparsers.add_parser(
|
|
580
|
+
"bond-events",
|
|
581
|
+
help="Detect bond formation/breakage events.",
|
|
582
|
+
description=(
|
|
583
|
+
"Examples:\n"
|
|
584
|
+
" reaxkit fort7 bond-events --export events.csv\n"
|
|
585
|
+
" reaxkit fort7 bond-events --src 1 --dst 19 --threshold 0.38 "
|
|
586
|
+
"--hysteresis 0.10 --smooth ema --window 7 --min-run 4 "
|
|
587
|
+
"--export events_1_19.csv --save overlay.png\n"
|
|
588
|
+
),
|
|
589
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
590
|
+
)
|
|
591
|
+
_wire_bond_events(p_bev)
|
|
592
|
+
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fort.83 post-processing workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides a lightweight utility for processing ReaxFF `fort.83`
|
|
5
|
+
files, which typically record force-field optimization progress and error
|
|
6
|
+
information during training or fitting runs.
|
|
7
|
+
|
|
8
|
+
It supports:
|
|
9
|
+
- Locating the final occurrence of the marker line `Error force field` in a
|
|
10
|
+
`fort.83` file.
|
|
11
|
+
- Extracting all subsequent lines, which usually correspond to the optimized
|
|
12
|
+
force-field parameter block.
|
|
13
|
+
- Writing the extracted content to a new output file for reuse, inspection,
|
|
14
|
+
or archiving.
|
|
15
|
+
|
|
16
|
+
The workflow is intended to streamline recovery of optimized force-field
|
|
17
|
+
parameters after ReaxFF training runs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _fort83_update_task(args: argparse.Namespace) -> int:
|
|
25
|
+
"""
|
|
26
|
+
Reads fort.83, finds last occurrence of 'Error force field',
|
|
27
|
+
and exports all lines after it to output file.
|
|
28
|
+
"""
|
|
29
|
+
with open(args.file, "r") as f:
|
|
30
|
+
lines = f.readlines()
|
|
31
|
+
|
|
32
|
+
start_index = None
|
|
33
|
+
for i, line in enumerate(lines):
|
|
34
|
+
if "Error force field" in line:
|
|
35
|
+
start_index = i
|
|
36
|
+
|
|
37
|
+
if start_index is None:
|
|
38
|
+
print("[Warning] 'Error force field' not found in file.")
|
|
39
|
+
return 1
|
|
40
|
+
|
|
41
|
+
extracted_lines = lines[start_index + 1:]
|
|
42
|
+
|
|
43
|
+
with open(args.export, "w") as out:
|
|
44
|
+
out.writelines(extracted_lines)
|
|
45
|
+
|
|
46
|
+
print(f"[Done] Extracted content written to {args.export}")
|
|
47
|
+
return 0
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Directly register 'get' under fort83 without extra nesting.
|
|
53
|
+
"""
|
|
54
|
+
p = subparsers.add_parser(
|
|
55
|
+
"update",
|
|
56
|
+
help="Extract all lines after last 'Error force field' in fort.83",
|
|
57
|
+
)
|
|
58
|
+
p.add_argument("--file", default="fort.83", help="Path to fort.83 file")
|
|
59
|
+
p.add_argument("--export", default="ffield_optimized", help="Output file name")
|
|
60
|
+
p.set_defaults(_run=_fort83_update_task)
|