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.
Files changed (130) hide show
  1. reaxkit/__init__.py +0 -0
  2. reaxkit/analysis/__init__.py +0 -0
  3. reaxkit/analysis/composed/RDF_analyzer.py +560 -0
  4. reaxkit/analysis/composed/__init__.py +0 -0
  5. reaxkit/analysis/composed/connectivity_analyzer.py +706 -0
  6. reaxkit/analysis/composed/coordination_analyzer.py +144 -0
  7. reaxkit/analysis/composed/electrostatics_analyzer.py +687 -0
  8. reaxkit/analysis/per_file/__init__.py +0 -0
  9. reaxkit/analysis/per_file/control_analyzer.py +165 -0
  10. reaxkit/analysis/per_file/eregime_analyzer.py +108 -0
  11. reaxkit/analysis/per_file/ffield_analyzer.py +305 -0
  12. reaxkit/analysis/per_file/fort13_analyzer.py +79 -0
  13. reaxkit/analysis/per_file/fort57_analyzer.py +106 -0
  14. reaxkit/analysis/per_file/fort73_analyzer.py +61 -0
  15. reaxkit/analysis/per_file/fort74_analyzer.py +65 -0
  16. reaxkit/analysis/per_file/fort76_analyzer.py +191 -0
  17. reaxkit/analysis/per_file/fort78_analyzer.py +154 -0
  18. reaxkit/analysis/per_file/fort79_analyzer.py +83 -0
  19. reaxkit/analysis/per_file/fort7_analyzer.py +393 -0
  20. reaxkit/analysis/per_file/fort99_analyzer.py +411 -0
  21. reaxkit/analysis/per_file/molfra_analyzer.py +359 -0
  22. reaxkit/analysis/per_file/params_analyzer.py +258 -0
  23. reaxkit/analysis/per_file/summary_analyzer.py +84 -0
  24. reaxkit/analysis/per_file/trainset_analyzer.py +84 -0
  25. reaxkit/analysis/per_file/vels_analyzer.py +95 -0
  26. reaxkit/analysis/per_file/xmolout_analyzer.py +528 -0
  27. reaxkit/cli.py +181 -0
  28. reaxkit/count_loc.py +276 -0
  29. reaxkit/data/alias.yaml +89 -0
  30. reaxkit/data/constants.yaml +27 -0
  31. reaxkit/data/reaxff_input_files_contents.yaml +186 -0
  32. reaxkit/data/reaxff_output_files_contents.yaml +301 -0
  33. reaxkit/data/units.yaml +38 -0
  34. reaxkit/help/__init__.py +0 -0
  35. reaxkit/help/help_index_loader.py +531 -0
  36. reaxkit/help/introspection_utils.py +131 -0
  37. reaxkit/io/__init__.py +0 -0
  38. reaxkit/io/base_handler.py +165 -0
  39. reaxkit/io/generators/__init__.py +0 -0
  40. reaxkit/io/generators/control_generator.py +123 -0
  41. reaxkit/io/generators/eregime_generator.py +341 -0
  42. reaxkit/io/generators/geo_generator.py +967 -0
  43. reaxkit/io/generators/trainset_generator.py +1758 -0
  44. reaxkit/io/generators/tregime_generator.py +113 -0
  45. reaxkit/io/generators/vregime_generator.py +164 -0
  46. reaxkit/io/generators/xmolout_generator.py +304 -0
  47. reaxkit/io/handlers/__init__.py +0 -0
  48. reaxkit/io/handlers/control_handler.py +209 -0
  49. reaxkit/io/handlers/eregime_handler.py +122 -0
  50. reaxkit/io/handlers/ffield_handler.py +812 -0
  51. reaxkit/io/handlers/fort13_handler.py +123 -0
  52. reaxkit/io/handlers/fort57_handler.py +143 -0
  53. reaxkit/io/handlers/fort73_handler.py +145 -0
  54. reaxkit/io/handlers/fort74_handler.py +155 -0
  55. reaxkit/io/handlers/fort76_handler.py +195 -0
  56. reaxkit/io/handlers/fort78_handler.py +142 -0
  57. reaxkit/io/handlers/fort79_handler.py +227 -0
  58. reaxkit/io/handlers/fort7_handler.py +264 -0
  59. reaxkit/io/handlers/fort99_handler.py +128 -0
  60. reaxkit/io/handlers/geo_handler.py +224 -0
  61. reaxkit/io/handlers/molfra_handler.py +184 -0
  62. reaxkit/io/handlers/params_handler.py +137 -0
  63. reaxkit/io/handlers/summary_handler.py +135 -0
  64. reaxkit/io/handlers/trainset_handler.py +658 -0
  65. reaxkit/io/handlers/vels_handler.py +293 -0
  66. reaxkit/io/handlers/xmolout_handler.py +174 -0
  67. reaxkit/utils/__init__.py +0 -0
  68. reaxkit/utils/alias.py +219 -0
  69. reaxkit/utils/cache.py +77 -0
  70. reaxkit/utils/constants.py +75 -0
  71. reaxkit/utils/equation_of_states.py +96 -0
  72. reaxkit/utils/exceptions.py +27 -0
  73. reaxkit/utils/frame_utils.py +175 -0
  74. reaxkit/utils/log.py +43 -0
  75. reaxkit/utils/media/__init__.py +0 -0
  76. reaxkit/utils/media/convert.py +90 -0
  77. reaxkit/utils/media/make_video.py +91 -0
  78. reaxkit/utils/media/plotter.py +812 -0
  79. reaxkit/utils/numerical/__init__.py +0 -0
  80. reaxkit/utils/numerical/extrema_finder.py +96 -0
  81. reaxkit/utils/numerical/moving_average.py +103 -0
  82. reaxkit/utils/numerical/numerical_calcs.py +75 -0
  83. reaxkit/utils/numerical/signal_ops.py +135 -0
  84. reaxkit/utils/path.py +55 -0
  85. reaxkit/utils/units.py +104 -0
  86. reaxkit/webui/__init__.py +0 -0
  87. reaxkit/webui/app.py +0 -0
  88. reaxkit/webui/components.py +0 -0
  89. reaxkit/webui/layouts.py +0 -0
  90. reaxkit/webui/utils.py +0 -0
  91. reaxkit/workflows/__init__.py +0 -0
  92. reaxkit/workflows/composed/__init__.py +0 -0
  93. reaxkit/workflows/composed/coordination_workflow.py +393 -0
  94. reaxkit/workflows/composed/electrostatics_workflow.py +587 -0
  95. reaxkit/workflows/composed/xmolout_fort7_workflow.py +343 -0
  96. reaxkit/workflows/meta/__init__.py +0 -0
  97. reaxkit/workflows/meta/help_workflow.py +136 -0
  98. reaxkit/workflows/meta/introspection_workflow.py +235 -0
  99. reaxkit/workflows/meta/make_video_workflow.py +61 -0
  100. reaxkit/workflows/meta/plotter_workflow.py +601 -0
  101. reaxkit/workflows/per_file/__init__.py +0 -0
  102. reaxkit/workflows/per_file/control_workflow.py +110 -0
  103. reaxkit/workflows/per_file/eregime_workflow.py +267 -0
  104. reaxkit/workflows/per_file/ffield_workflow.py +390 -0
  105. reaxkit/workflows/per_file/fort13_workflow.py +86 -0
  106. reaxkit/workflows/per_file/fort57_workflow.py +137 -0
  107. reaxkit/workflows/per_file/fort73_workflow.py +151 -0
  108. reaxkit/workflows/per_file/fort74_workflow.py +88 -0
  109. reaxkit/workflows/per_file/fort76_workflow.py +188 -0
  110. reaxkit/workflows/per_file/fort78_workflow.py +135 -0
  111. reaxkit/workflows/per_file/fort79_workflow.py +314 -0
  112. reaxkit/workflows/per_file/fort7_workflow.py +592 -0
  113. reaxkit/workflows/per_file/fort83_workflow.py +60 -0
  114. reaxkit/workflows/per_file/fort99_workflow.py +223 -0
  115. reaxkit/workflows/per_file/geo_workflow.py +554 -0
  116. reaxkit/workflows/per_file/molfra_workflow.py +577 -0
  117. reaxkit/workflows/per_file/params_workflow.py +135 -0
  118. reaxkit/workflows/per_file/summary_workflow.py +161 -0
  119. reaxkit/workflows/per_file/trainset_workflow.py +356 -0
  120. reaxkit/workflows/per_file/tregime_workflow.py +79 -0
  121. reaxkit/workflows/per_file/vels_workflow.py +309 -0
  122. reaxkit/workflows/per_file/vregime_workflow.py +75 -0
  123. reaxkit/workflows/per_file/xmolout_workflow.py +678 -0
  124. reaxkit-1.0.0.dist-info/METADATA +128 -0
  125. reaxkit-1.0.0.dist-info/RECORD +130 -0
  126. reaxkit-1.0.0.dist-info/WHEEL +5 -0
  127. reaxkit-1.0.0.dist-info/entry_points.txt +2 -0
  128. reaxkit-1.0.0.dist-info/licenses/AUTHORS.md +20 -0
  129. reaxkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  130. 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)