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,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)
|