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,393 @@
1
+ """
2
+ Coordination workflow for ReaxKit.
3
+
4
+ This workflow analyzes atomic coordination using ReaxFF bond-order data
5
+ from `fort.7` and structural information from `xmolout`.
6
+
7
+ It provides two main capabilities:
8
+
9
+ - Analyze coordination status per atom per frame (under-, correctly-, or over-coordinated)
10
+ based on atom valence and total bond order.
11
+ - Relabel atom types according to coordination status and write a new `xmolout` file
12
+ for visualization or downstream analysis.
13
+
14
+ The workflow supports flexible frame selection, user-defined or ffield-derived valences,
15
+ and multiple labeling modes.
16
+ """
17
+
18
+
19
+ from __future__ import annotations
20
+ import argparse
21
+ from typing import Dict, Any, Optional, Literal, List
22
+ import pandas as pd
23
+
24
+ from reaxkit.io.handlers.xmolout_handler import XmoloutHandler
25
+ from reaxkit.io.generators.xmolout_generator import write_xmolout_from_frames
26
+ from reaxkit.analysis.per_file.fort7_analyzer import per_atom_coordination_status_over_frames
27
+ from reaxkit.utils.path import resolve_output_path
28
+ from reaxkit.io.handlers.ffield_handler import FFieldHandler
29
+
30
+ # -----------------------------
31
+ # Helpers
32
+ # -----------------------------
33
+ def _normalize_frames(xh: XmoloutHandler, frames: Optional[str]) -> list[int]:
34
+ """
35
+ Normalize a frame-selection string into a sorted list of frame indices.
36
+
37
+ Supported formats:
38
+ - 'start:end:step' (any of the 3 can be omitted, e.g. '::5', '10:', ':100')
39
+ - 'i,j,k' or 'i j k' (explicit indices)
40
+ If frames is None, all frames [0, n_frames) are used.
41
+ """
42
+ n = xh.n_frames()
43
+
44
+ if not frames:
45
+ return list(range(n))
46
+
47
+ s = frames.strip()
48
+
49
+ # Slice-like 'start:end:step'
50
+ if ":" in s:
51
+ parts = s.split(":")
52
+ if len(parts) > 3:
53
+ raise ValueError(f"Invalid --frames slice specification: {frames!r}")
54
+ start_s = parts[0].strip() if len(parts) >= 1 else ""
55
+ end_s = parts[1].strip() if len(parts) >= 2 else ""
56
+ step_s = parts[2].strip() if len(parts) == 3 else ""
57
+
58
+ start = int(start_s) if start_s else 0
59
+ end = int(end_s) if end_s else n
60
+ step = int(step_s) if step_s else 1
61
+ if step == 0:
62
+ raise ValueError("Step in --frames slice cannot be 0.")
63
+
64
+ start = max(0, start)
65
+ end = min(n, end)
66
+ return list(range(start, end, step))
67
+
68
+ # Comma/space-separated explicit indices
69
+ idx: List[int] = []
70
+ for tok in s.replace(",", " ").split():
71
+ i = int(tok)
72
+ if 0 <= i < n:
73
+ idx.append(i)
74
+ return sorted(set(idx))
75
+
76
+
77
+ def _valences_from_ffield(ffield_path: str) -> Dict[str, float]:
78
+ """
79
+ Read atom valencies from ffield atom section.
80
+
81
+ Returns a dict that includes BOTH:
82
+ - symbol -> valency (e.g., "O": 2)
83
+ - atom_index -> valency as string key (e.g., "2": 2)
84
+ This makes it robust whether xmolout uses symbols or numeric atom types.
85
+ """
86
+ fh = FFieldHandler(ffield_path)
87
+ atom_df = fh.section_df(FFieldHandler.SECTION_ATOM)
88
+
89
+ if "valency" not in atom_df.columns:
90
+ raise SystemExit("❌ ffield atom section missing 'valency' column.")
91
+
92
+ out: Dict[str, float] = {}
93
+ used_pairs = [] # for printing
94
+
95
+ for atom_idx, row in atom_df.iterrows():
96
+ # atom_idx is the ffield atom index (usually 1-based)
97
+ sym = row.get("symbol")
98
+ val = row.get("valency")
99
+
100
+ if val is None or (isinstance(val, float) and pd.isna(val)):
101
+ continue
102
+
103
+ try:
104
+ v = float(val)
105
+ except Exception:
106
+ continue
107
+
108
+ # key by atom index (string) too, for numeric xmolout types
109
+ out[str(int(atom_idx))] = v
110
+
111
+ # key by symbol if present
112
+ if sym is not None and not (isinstance(sym, float) and pd.isna(sym)):
113
+ s = str(sym).strip()
114
+ if s:
115
+ out[s] = v
116
+ used_pairs.append(f"{s}={v:g}")
117
+ else:
118
+ used_pairs.append(f"{int(atom_idx)}={v:g}")
119
+ else:
120
+ used_pairs.append(f"{int(atom_idx)}={v:g}")
121
+
122
+ if not out:
123
+ raise SystemExit("❌ No usable valencies found in ffield atom section.")
124
+
125
+ # Print one clean line showing what you used (symbol-first when available)
126
+ # (You can keep it in this helper so both tasks benefit.)
127
+ print("[Valences] " + ", ".join(used_pairs))
128
+ return out
129
+
130
+
131
+ def _parse_kv_map(s: Optional[str], value_cast=float) -> Dict[str, float]:
132
+ if not s:
133
+ return {}
134
+ out: Dict[str, float] = {}
135
+ for item in s.split(","):
136
+ if not item.strip():
137
+ continue
138
+ if "=" not in item:
139
+ raise ValueError(f"Invalid mapping entry {item!r}; use key=value,comma-separated.")
140
+ k, v = item.split("=", 1)
141
+ out[k.strip()] = value_cast(v.strip())
142
+ return out
143
+
144
+
145
+ def _parse_status_labels(s: Optional[str]) -> Dict[int, str]:
146
+ if not s:
147
+ return {-1: "U", 0: "C", 1: "O"}
148
+ out: Dict[int, str] = {}
149
+ for item in s.split(","):
150
+ if not item.strip():
151
+ continue
152
+ if "=" not in item:
153
+ raise ValueError(f"Invalid label entry {item!r}; use -1=U,0=C,1=O")
154
+ k, v = item.split("=", 1)
155
+ ki = int(k.strip())
156
+ if ki not in (-1, 0, 1):
157
+ raise ValueError("Status keys must be -1, 0, or 1")
158
+ out[ki] = v.strip()
159
+ for k in (-1, 0, 1):
160
+ out.setdefault(k, {-1: "U", 0: "C", 1: "O"}[k])
161
+ return out
162
+
163
+
164
+ def _frame_record_from_handler(xh: XmoloutHandler, i: int) -> Dict[str, Any]:
165
+ fr = xh.frame(i)
166
+ df = xh.dataframe()
167
+ row = df.iloc[i] if i < len(df) else pd.Series()
168
+ return {
169
+ "iter": int(fr.get("iter", int(row["iter"]) if "iter" in row else i)),
170
+ "coords": fr["coords"],
171
+ "atom_types": fr["atom_types"],
172
+ "energy": float(row["energy"]) if "energy" in row else 0.0,
173
+ "a": float(row["a"]) if "a" in row else 1.0,
174
+ "b": float(row["b"]) if "b" in row else 1.0,
175
+ "c": float(row["c"]) if "c" in row else 1.0,
176
+ "alpha": float(row["alpha"]) if "alpha" in row else 90.0,
177
+ "beta": float(row["beta"]) if "beta" in row else 90.0,
178
+ "gamma": float(row["gamma"]) if "gamma" in row else 90.0,
179
+ }
180
+
181
+
182
+ # -----------------------------
183
+ # Action implementations
184
+ # -----------------------------
185
+ def _task_analyze(args: argparse.Namespace) -> int:
186
+ xh = XmoloutHandler(args.xmolout)
187
+ from reaxkit.io.handlers.fort7_handler import Fort7Handler
188
+ f7 = Fort7Handler(args.fort7)
189
+
190
+ valences = _parse_kv_map(args.valences, float)
191
+ if valences:
192
+ # User explicitly provided valences; print what will be used
193
+ print("[Valences] " + ", ".join(f"{k}={v:g}" for k, v in valences.items()))
194
+ else:
195
+ valences = _valences_from_ffield(args.ffield)
196
+ if not valences:
197
+ raise SystemExit("❌ Provide --valences like 'Mg=2,O=2'.")
198
+
199
+ frames = _normalize_frames(xh, args.frames)
200
+
201
+ df = per_atom_coordination_status_over_frames(
202
+ f7, xh,
203
+ valences=valences,
204
+ threshold=args.threshold,
205
+ frames=frames,
206
+ iterations=None,
207
+ require_all_valences=not args.allow_missing_valences,
208
+ )
209
+
210
+ workflow_name = args.kind
211
+ if args.export:
212
+ export_path = resolve_output_path(args.export, workflow_name)
213
+ export_path.parent.mkdir(parents=True, exist_ok=True)
214
+ df.to_csv(export_path, index=False)
215
+ print(f"[Done] Exported coordination table to {export_path}")
216
+ else:
217
+ summary = df.groupby(["frame_index", "status"], dropna=False) \
218
+ .size().unstack(fill_value=0)
219
+ print(summary)
220
+
221
+ return 0
222
+
223
+
224
+ def _task_relabel(args: argparse.Namespace) -> int:
225
+ xh = XmoloutHandler(args.xmolout)
226
+ from reaxkit.io.handlers.fort7_handler import Fort7Handler
227
+ f7 = Fort7Handler(args.fort7)
228
+
229
+ valences = _parse_kv_map(args.valences, float)
230
+ if valences:
231
+ # User explicitly provided valences; print what will be used
232
+ print("[Valences] " + ", ".join(f"{k}={v:g}" for k, v in valences.items()))
233
+ else:
234
+ valences = _valences_from_ffield(args.ffield)
235
+ if not valences:
236
+ raise SystemExit("❌ Provide --valences like 'Mg=2,O=2'.")
237
+
238
+ labels = _parse_status_labels(args.labels)
239
+ mode: Literal["global", "by_type"] = args.mode
240
+ frames = _normalize_frames(xh, args.frames)
241
+
242
+ status_df = per_atom_coordination_status_over_frames(
243
+ f7, xh,
244
+ valences=valences,
245
+ threshold=args.threshold,
246
+ frames=frames,
247
+ iterations=None,
248
+ require_all_valences=not args.allow_missing_valences,
249
+ )
250
+
251
+ if status_df.empty:
252
+ blocks = (_frame_record_from_handler(xh, i) for i in frames)
253
+ write_xmolout_from_frames(
254
+ blocks, args.output,
255
+ simulation_name=args.simulation or getattr(xh, "simulation_name", None) or "MD",
256
+ precision=args.precision,
257
+ )
258
+ print(f"⚠️ No frames selected or empty status; wrote pass-through xmolout to {args.output}")
259
+ return 0
260
+
261
+ sim_df = xh.dataframe()
262
+ frame_blocks: List[Dict[str, Any]] = []
263
+ for fi, g in status_df.groupby("frame_index", sort=True):
264
+ fi = int(fi)
265
+ fr = xh.frame(fi)
266
+ coords = fr["coords"]
267
+ orig_types = fr["atom_types"]
268
+ n = len(orig_types)
269
+
270
+ g_sorted = g.sort_values("atom_id") # 1-based ids
271
+ if len(g_sorted) != n:
272
+ raise SystemExit(f"❌ Frame {fi}: mismatch atoms between xmolout ({n}) and status rows ({len(g_sorted)}).")
273
+
274
+ new_types: List[str] = []
275
+ for k in range(n):
276
+ t0 = str(orig_types[k])
277
+ st_val = g_sorted.iloc[k]["status"]
278
+ if pd.isna(st_val):
279
+ new_types.append(t0)
280
+ continue
281
+ st = int(st_val)
282
+ tag = labels.get(st, {-1: "U", 0: "C", 1: "O"}[st])
283
+ if mode == "global":
284
+ new_types.append(str(tag))
285
+ else:
286
+ if st == 0 and args.keep_coord_original:
287
+ new_types.append(t0)
288
+ else:
289
+ new_types.append(f"{t0}{tag}")
290
+
291
+ row = sim_df.iloc[fi] if fi < len(sim_df) else pd.Series()
292
+ frame_blocks.append({
293
+ "iter": int(g_sorted.iloc[0]["iter"]) if "iter" in g_sorted.columns else int(fr["iter"]),
294
+ "coords": coords,
295
+ "atom_types": new_types,
296
+ "E_pot": float(row["E_pot"]) if "E_pot" in row else 0.0,
297
+ "a": float(row["a"]) if "a" in row else 1.0,
298
+ "b": float(row["b"]) if "b" in row else 1.0,
299
+ "c": float(row["c"]) if "c" in row else 1.0,
300
+ "alpha": float(row["alpha"]) if "alpha" in row else 90.0,
301
+ "beta": float(row["beta"]) if "beta" in row else 90.0,
302
+ "gamma": float(row["gamma"]) if "gamma" in row else 90.0,
303
+ })
304
+
305
+ workflow_name = args.kind
306
+ output_path = resolve_output_path(args.output, workflow_name)
307
+ output_path.parent.mkdir(parents=True, exist_ok=True)
308
+
309
+ write_xmolout_from_frames(
310
+ frame_blocks,
311
+ output_path,
312
+ simulation_name=args.simulation or getattr(xh, "simulation_name", None) or "MD",
313
+ precision=args.precision,
314
+ )
315
+
316
+ print(f"[Done] Wrote relabeled xmolout to {output_path}")
317
+ return 0
318
+
319
+
320
+ # -----------------------------
321
+ # Template-style registration
322
+ # -----------------------------
323
+ def _add_common_coord_args(p: argparse.ArgumentParser) -> None:
324
+ """Common args shared by coord analyze / relabel."""
325
+ p.add_argument("--xmolout", default="xmolout", help="Path to xmolout.")
326
+ p.add_argument("--fort7", default="fort.7", help="Path to fort.7 file.")
327
+ p.add_argument("--valences", default=None,
328
+ help="Optional: override type valences, e.g. 'Mg=2,O=2,H=1'. "
329
+ "If omitted, reads from ffield atom section ('valency').")
330
+ p.add_argument("--ffield", default="ffield", help="Path to ffield (used if --valences not given).")
331
+ p.add_argument("--threshold", type=float, default=0.3, help="Tolerance around valence.")
332
+ p.add_argument("--frames", default=None,
333
+ help="Frame selection, e.g. '0:100:5' or '0,5,10'.")
334
+ p.add_argument("--allow-missing-valences", action="store_true",
335
+ help="Keep atoms with unknown valence (status=NaN).")
336
+
337
+
338
+ def register_tasks(subparsers: argparse._SubParsersAction) -> None:
339
+ """
340
+ Register coordination-related CLI subcommands.
341
+
342
+ This function defines the task-level interface for the
343
+ `reaxkit file` workflow and attaches its tasks (i.e., subcommands).
344
+
345
+ Each task may share common input arguments and defines task-specific options as needed.
346
+ """
347
+
348
+ # -------------------- analyze --------------------
349
+ p1 = subparsers.add_parser(
350
+ "analyze",
351
+ help="Analyze coordination per atom per frame.",
352
+ description=(
353
+ "Analyzes coordination per atom per frame.\n"
354
+ "It determines if an atom is under-coordinated, over-coordinated, or coordinates, based on its valence and "
355
+ "total bond order.\n"
356
+ "Examples:\n"
357
+ " reaxkit coord analyze --export coord_analysis.csv\n"
358
+ " reaxkit coord analyze --valences 'Mg=2,O=2' --frames 0:200:2 --export coord_0_200_2.csv\n"
359
+ ),
360
+ formatter_class=argparse.RawTextHelpFormatter,
361
+ )
362
+
363
+ _add_common_coord_args(p1)
364
+ p1.add_argument("--export", default=None, help="Path to export coordination CSV.")
365
+ p1.set_defaults(_run=_task_analyze)
366
+
367
+
368
+ # -------------------- relabel --------------------
369
+ p2 = subparsers.add_parser(
370
+ "relabel",
371
+ help="Relabel atom types by coordination and write a new xmolout.",
372
+ description=(
373
+ "Relabel atom types by coordination and write a new xmolout.\n"
374
+ "Examples:\n"
375
+ " reaxkit coord relabel --output xmolout_relabeled --mode global --labels=-1=U,0=C,1=O\n"
376
+ " reaxkit coord relabel --output xmolout_type --mode by_type --keep-coord-original\n"
377
+ " reaxkit coord relabel --valences 'Mg=2,O=2,Zn=2' --frames 0:400:5 "
378
+ "--output xmolout_relabeled --mode global\n"
379
+ ),
380
+ formatter_class=argparse.RawTextHelpFormatter,
381
+ )
382
+
383
+ _add_common_coord_args(p2)
384
+ p2.add_argument("--output", required=True, help="Output xmolout path.")
385
+ p2.add_argument("--mode", choices=["global", "by_type"], default="global",
386
+ help="Relabeling mode.")
387
+ p2.add_argument("--labels", default=None,
388
+ help="Status→tag map, e.g. '-1=U,0=C,1=O'.")
389
+ p2.add_argument("--keep-coord-original", action="store_true",
390
+ help="In by_type mode, keep original label when status==0.")
391
+ p2.add_argument("--simulation", default=None, help="Override header simulation name.")
392
+ p2.add_argument("--precision", type=int, default=6, help="Float precision.")
393
+ p2.set_defaults(_run=_task_relabel)