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,577 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Molecular-fragment (molfra) analysis workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides tools for analyzing ReaxFF `molfra.out` and
|
|
5
|
+
`molfra_ig.out` files, which describe molecular fragments, species
|
|
6
|
+
identities, and their evolution during a simulation.
|
|
7
|
+
|
|
8
|
+
It supports:
|
|
9
|
+
- Tracking occurrences of selected molecular species across frames,
|
|
10
|
+
with optional automatic selection based on occurrence thresholds.
|
|
11
|
+
- Computing and visualizing global totals, including total number of
|
|
12
|
+
molecules, total atoms, and total molecular mass versus iteration,
|
|
13
|
+
frame, or physical time.
|
|
14
|
+
- Identifying and analyzing the largest molecule in the system, either
|
|
15
|
+
by individual molecular mass or by per-element atom composition.
|
|
16
|
+
- Plotting results, saving figures, and exporting processed data to CSV
|
|
17
|
+
using standardized ReaxKit output paths.
|
|
18
|
+
|
|
19
|
+
The workflow is designed to enable systematic analysis of chemical
|
|
20
|
+
speciation, fragmentation, and growth processes in ReaxFF simulations.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
from typing import Optional, Sequence, Union, List
|
|
28
|
+
|
|
29
|
+
from reaxkit.io.handlers.molfra_handler import MolFraHandler
|
|
30
|
+
from reaxkit.analysis.per_file.molfra_analyzer import (
|
|
31
|
+
get_molfra_data_wide_format,
|
|
32
|
+
_qualifying_types,
|
|
33
|
+
get_molfra_totals_vs_axis,
|
|
34
|
+
largest_molecule_by_individual_mass,
|
|
35
|
+
atoms_in_the_largest_molecule_wide_format,
|
|
36
|
+
)
|
|
37
|
+
from reaxkit.utils.frame_utils import parse_frames, select_frames
|
|
38
|
+
from reaxkit.utils.media.convert import convert_xaxis
|
|
39
|
+
from reaxkit.utils.media.plotter import single_plot, multi_subplots
|
|
40
|
+
from reaxkit.utils.alias import normalize_choice
|
|
41
|
+
from reaxkit.utils.path import resolve_output_path
|
|
42
|
+
|
|
43
|
+
FramesT = Optional[Union[slice, Sequence[int]]]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ============================================================
|
|
47
|
+
# Occurrences task
|
|
48
|
+
# ============================================================
|
|
49
|
+
def _molfra_occur_task(args: argparse.Namespace) -> int:
|
|
50
|
+
# Normalize x-axis alias
|
|
51
|
+
args.xaxis = normalize_choice(args.xaxis, domain="xaxis") or "iter"
|
|
52
|
+
|
|
53
|
+
handler = MolFraHandler(args.file)
|
|
54
|
+
handler._parse()
|
|
55
|
+
|
|
56
|
+
# Plot behavior: export-only suppresses interactive plotting
|
|
57
|
+
do_plot = bool(args.plot) and not bool(args.export)
|
|
58
|
+
|
|
59
|
+
frames_sel = parse_frames(args.frames)
|
|
60
|
+
|
|
61
|
+
# -------------------------
|
|
62
|
+
# Determine which molecules
|
|
63
|
+
# -------------------------
|
|
64
|
+
molecules: Optional[List[str]] = args.molecules
|
|
65
|
+
if not molecules:
|
|
66
|
+
if args.threshold is None:
|
|
67
|
+
print("ℹ️ No molecules or threshold given. Use --molecules ... or --threshold N.")
|
|
68
|
+
return 0
|
|
69
|
+
excl = set(args.exclude or [])
|
|
70
|
+
molecules = _qualifying_types(
|
|
71
|
+
handler,
|
|
72
|
+
threshold=args.threshold,
|
|
73
|
+
exclude_types=(excl if excl else None),
|
|
74
|
+
)
|
|
75
|
+
if not molecules:
|
|
76
|
+
print(f"ℹ️ No species reached max occurrence ≥ {args.threshold}.")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
wide = get_molfra_data_wide_format(
|
|
80
|
+
handler,
|
|
81
|
+
molecules=molecules,
|
|
82
|
+
iters=None,
|
|
83
|
+
by_index=False,
|
|
84
|
+
fill_value=0,
|
|
85
|
+
)
|
|
86
|
+
if wide.empty:
|
|
87
|
+
print("ℹ️ No data available after selection.")
|
|
88
|
+
return 0
|
|
89
|
+
|
|
90
|
+
wide = select_frames(wide, frames_sel)
|
|
91
|
+
|
|
92
|
+
if wide.empty:
|
|
93
|
+
print("ℹ️ No data left after frame selection.")
|
|
94
|
+
return 0
|
|
95
|
+
|
|
96
|
+
# -------------------------
|
|
97
|
+
# Build x-axis
|
|
98
|
+
# -------------------------
|
|
99
|
+
iters = wide["iter"].to_numpy()
|
|
100
|
+
if args.xaxis == "iter":
|
|
101
|
+
x_vals = list(iters)
|
|
102
|
+
xlabel = "iter"
|
|
103
|
+
else:
|
|
104
|
+
x_vals, xlabel = convert_xaxis(iters, args.xaxis, control_file=args.control)
|
|
105
|
+
x_vals = list(x_vals)
|
|
106
|
+
|
|
107
|
+
# Series per molecule
|
|
108
|
+
series = {m: wide[m].tolist() for m in molecules if m in wide.columns}
|
|
109
|
+
|
|
110
|
+
# -------------------------
|
|
111
|
+
# Export
|
|
112
|
+
# -------------------------
|
|
113
|
+
if args.export:
|
|
114
|
+
workflow_name = getattr(args, "kind", "molfra")
|
|
115
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
116
|
+
|
|
117
|
+
xcol = (
|
|
118
|
+
xlabel.strip()
|
|
119
|
+
.lower()
|
|
120
|
+
.replace(" ", "_")
|
|
121
|
+
.replace("(", "")
|
|
122
|
+
.replace(")", "")
|
|
123
|
+
)
|
|
124
|
+
out_df = wide.copy()
|
|
125
|
+
if args.xaxis != "iter":
|
|
126
|
+
out_df.insert(0, xcol, x_vals)
|
|
127
|
+
out_df.to_csv(out, index=False)
|
|
128
|
+
print(f"[Done] Exported data to {out}")
|
|
129
|
+
|
|
130
|
+
# -------------------------
|
|
131
|
+
# Plot / save
|
|
132
|
+
# -------------------------
|
|
133
|
+
if args.save:
|
|
134
|
+
workflow_name = getattr(args, "kind", "molfra")
|
|
135
|
+
save_path = resolve_output_path(args.save, workflow_name)
|
|
136
|
+
else:
|
|
137
|
+
save_path = None
|
|
138
|
+
|
|
139
|
+
if not args.title:
|
|
140
|
+
if args.threshold is not None and not args.molecules:
|
|
141
|
+
title = f"Species with max occurrence ≥ {args.threshold}"
|
|
142
|
+
elif len(molecules) == 1:
|
|
143
|
+
title = f"{molecules[0]} occurrence"
|
|
144
|
+
else:
|
|
145
|
+
title = "Molecule occurrence"
|
|
146
|
+
else:
|
|
147
|
+
title = args.title
|
|
148
|
+
|
|
149
|
+
if (do_plot or save_path) and series:
|
|
150
|
+
series_for_plot = [
|
|
151
|
+
{"x": x_vals, "y": yvals, "label": name}
|
|
152
|
+
for name, yvals in series.items()
|
|
153
|
+
]
|
|
154
|
+
single_plot(
|
|
155
|
+
series=series_for_plot,
|
|
156
|
+
title=title,
|
|
157
|
+
xlabel=xlabel,
|
|
158
|
+
ylabel="occurrence",
|
|
159
|
+
save=str(save_path) if save_path else None,
|
|
160
|
+
legend=True,
|
|
161
|
+
)
|
|
162
|
+
elif not args.save and not args.export and not do_plot:
|
|
163
|
+
print("ℹ️ No action selected. Use one or more of --plot, --save, --export.")
|
|
164
|
+
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ============================================================
|
|
169
|
+
# Totals task
|
|
170
|
+
# ============================================================
|
|
171
|
+
def _molfra_total_task(args: argparse.Namespace) -> int:
|
|
172
|
+
"""
|
|
173
|
+
Handle `reaxkit molfra total`:
|
|
174
|
+
|
|
175
|
+
- If --plot and/or --save: create a multi-subplot figure of *all* totals
|
|
176
|
+
(total_molecules, total_atoms, total_molecular_mass) vs chosen x-axis.
|
|
177
|
+
- If --export: export all totals as CSV using get_molfra_totals_vs_axis().
|
|
178
|
+
"""
|
|
179
|
+
# Normalize x-axis alias
|
|
180
|
+
args.xaxis = normalize_choice(args.xaxis, domain="xaxis") or "iter"
|
|
181
|
+
|
|
182
|
+
handler = MolFraHandler(args.file)
|
|
183
|
+
handler._parse()
|
|
184
|
+
|
|
185
|
+
# Ensure totals table exists so we can inspect which columns are present
|
|
186
|
+
if not hasattr(handler, "_df_totals"):
|
|
187
|
+
print("⚠️ Totals dataframe not found in MolFraHandler. Parse handler with an updated version first.")
|
|
188
|
+
return 0
|
|
189
|
+
|
|
190
|
+
df_raw = handler._df_totals
|
|
191
|
+
if df_raw.empty:
|
|
192
|
+
print("⚠️ Totals dataframe is empty.")
|
|
193
|
+
return 0
|
|
194
|
+
|
|
195
|
+
# Determine which totals columns exist
|
|
196
|
+
totals_all = ("total_molecules", "total_atoms", "total_molecular_mass")
|
|
197
|
+
quantities = [c for c in totals_all if c in df_raw.columns]
|
|
198
|
+
if not quantities:
|
|
199
|
+
print("⚠️ No totals columns (total_molecules / total_atoms / total_molecular_mass) found.")
|
|
200
|
+
return 0
|
|
201
|
+
|
|
202
|
+
# Get totals vs requested x-axis using analyzer helper
|
|
203
|
+
df_tot = get_molfra_totals_vs_axis(
|
|
204
|
+
handler,
|
|
205
|
+
xaxis=args.xaxis,
|
|
206
|
+
control_file=args.control,
|
|
207
|
+
quantities=quantities,
|
|
208
|
+
)
|
|
209
|
+
if df_tot.empty:
|
|
210
|
+
print("⚠️ Totals table is empty after axis conversion.")
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
# Apply frame selection (position-based after filtering)
|
|
214
|
+
frames_sel = parse_frames(args.frames)
|
|
215
|
+
df_tot = select_frames(df_tot, frames_sel)
|
|
216
|
+
if df_tot.empty:
|
|
217
|
+
print("⚠️ Totals table empty after frame selection.")
|
|
218
|
+
return 0
|
|
219
|
+
|
|
220
|
+
# Identify x-axis column (whatever is not a totals column)
|
|
221
|
+
non_q = [c for c in df_tot.columns if c not in quantities]
|
|
222
|
+
if not non_q:
|
|
223
|
+
print("⚠️ Could not determine x-axis column.")
|
|
224
|
+
return 0
|
|
225
|
+
if "iter" in non_q and len(non_q) > 1:
|
|
226
|
+
# Prefer non-iter if both iter & time-like column exist
|
|
227
|
+
xcol = [c for c in non_q if c != "iter"][0]
|
|
228
|
+
else:
|
|
229
|
+
xcol = non_q[0]
|
|
230
|
+
|
|
231
|
+
x_vals = df_tot[xcol].tolist()
|
|
232
|
+
xlabel = xcol.replace("_", " ")
|
|
233
|
+
|
|
234
|
+
# Flags: plotting vs exporting
|
|
235
|
+
do_plot_or_save = bool(args.plot or args.save)
|
|
236
|
+
workflow_name = getattr(args, "kind", "molfra")
|
|
237
|
+
|
|
238
|
+
# ---------- Export all totals ----------
|
|
239
|
+
if args.export:
|
|
240
|
+
out_csv = resolve_output_path(args.export, workflow_name)
|
|
241
|
+
df_tot.to_csv(out_csv, index=False)
|
|
242
|
+
print(f"[Done] Exported totals to {out_csv}")
|
|
243
|
+
|
|
244
|
+
# ---------- Multi-subplots for totals ----------
|
|
245
|
+
if do_plot_or_save:
|
|
246
|
+
# Build subplot series: one subplot per quantity
|
|
247
|
+
subplots = []
|
|
248
|
+
for q in quantities:
|
|
249
|
+
subplots.append(
|
|
250
|
+
[
|
|
251
|
+
{
|
|
252
|
+
"x": x_vals,
|
|
253
|
+
"y": df_tot[q].tolist(),
|
|
254
|
+
"label": q,
|
|
255
|
+
}
|
|
256
|
+
]
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
# Resolve save path (None → interactive only)
|
|
260
|
+
save_path = resolve_output_path(args.save, workflow_name) if args.save else None
|
|
261
|
+
|
|
262
|
+
title = args.title or f"Totals vs {xlabel}"
|
|
263
|
+
multi_subplots(
|
|
264
|
+
subplots=subplots,
|
|
265
|
+
title=title,
|
|
266
|
+
xlabel=xlabel,
|
|
267
|
+
ylabel=["count", "count", "mass (a.u.)"],
|
|
268
|
+
sharex=True,
|
|
269
|
+
sharey=False,
|
|
270
|
+
legend=True,
|
|
271
|
+
figsize=(8.0, 2.5 * len(quantities)),
|
|
272
|
+
save=save_path,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if not (args.plot or args.save or args.export):
|
|
276
|
+
print("ℹ️ No action selected. Use one or more of --plot, --save, --export.")
|
|
277
|
+
|
|
278
|
+
return 0
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# ============================================================
|
|
282
|
+
# Largest-molecule task
|
|
283
|
+
# ============================================================
|
|
284
|
+
def _molfra_largest_task(args: argparse.Namespace) -> int:
|
|
285
|
+
# Normalize x-axis alias
|
|
286
|
+
args.xaxis = normalize_choice(args.xaxis, domain="xaxis") or "iter"
|
|
287
|
+
|
|
288
|
+
handler = MolFraHandler(args.file)
|
|
289
|
+
handler._parse()
|
|
290
|
+
|
|
291
|
+
do_plot = bool(args.plot) and not bool(args.export)
|
|
292
|
+
frames_sel = parse_frames(args.frames)
|
|
293
|
+
|
|
294
|
+
# Decide mode: mass vs atoms
|
|
295
|
+
mode_atoms = bool(args.atoms)
|
|
296
|
+
mode_mass = bool(args.mass)
|
|
297
|
+
|
|
298
|
+
if not mode_atoms and not mode_mass:
|
|
299
|
+
# default to atoms if nothing specified
|
|
300
|
+
mode_atoms = True
|
|
301
|
+
|
|
302
|
+
workflow_name = getattr(args, "kind", "molfra")
|
|
303
|
+
|
|
304
|
+
if mode_mass:
|
|
305
|
+
# ===============================
|
|
306
|
+
# Largest molecule mass vs x-axis
|
|
307
|
+
# ===============================
|
|
308
|
+
df = largest_molecule_by_individual_mass(handler)
|
|
309
|
+
if df.empty:
|
|
310
|
+
print("⚠️ No data for largest molecule by mass.")
|
|
311
|
+
return 0
|
|
312
|
+
|
|
313
|
+
df = select_frames(df, frames_sel)
|
|
314
|
+
if df.empty:
|
|
315
|
+
print("⚠️ No data for largest molecule after frame selection.")
|
|
316
|
+
return 0
|
|
317
|
+
|
|
318
|
+
iters = df["iter"].to_numpy()
|
|
319
|
+
if args.xaxis == "iter":
|
|
320
|
+
x_vals = list(iters)
|
|
321
|
+
xlabel = "iter"
|
|
322
|
+
else:
|
|
323
|
+
x_vals, xlabel = convert_xaxis(iters, args.xaxis, control_file=args.control)
|
|
324
|
+
x_vals = list(x_vals)
|
|
325
|
+
|
|
326
|
+
y = df["molecular_mass"].to_list()
|
|
327
|
+
|
|
328
|
+
# Export
|
|
329
|
+
if args.export:
|
|
330
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
331
|
+
xcol = (
|
|
332
|
+
xlabel.strip()
|
|
333
|
+
.lower()
|
|
334
|
+
.replace(" ", "_")
|
|
335
|
+
.replace("(", "")
|
|
336
|
+
.replace(")", "")
|
|
337
|
+
)
|
|
338
|
+
out_df = df.copy()
|
|
339
|
+
if args.xaxis != "iter":
|
|
340
|
+
out_df.insert(0, xcol, x_vals)
|
|
341
|
+
out_df.to_csv(out, index=False)
|
|
342
|
+
print(f"[Done] Exported data to {out}")
|
|
343
|
+
|
|
344
|
+
# Save path
|
|
345
|
+
if args.save:
|
|
346
|
+
save_path = resolve_output_path(args.save, workflow_name)
|
|
347
|
+
else:
|
|
348
|
+
save_path = None
|
|
349
|
+
|
|
350
|
+
if not args.title:
|
|
351
|
+
title = "Largest molecule mass vs x-axis"
|
|
352
|
+
else:
|
|
353
|
+
title = args.title
|
|
354
|
+
|
|
355
|
+
if do_plot or save_path:
|
|
356
|
+
series_for_plot = [
|
|
357
|
+
{
|
|
358
|
+
"x": x_vals,
|
|
359
|
+
"y": y,
|
|
360
|
+
"label": "largest_molecule_mass",
|
|
361
|
+
}
|
|
362
|
+
]
|
|
363
|
+
single_plot(
|
|
364
|
+
series=series_for_plot,
|
|
365
|
+
title=title,
|
|
366
|
+
xlabel=xlabel,
|
|
367
|
+
ylabel="Molecular mass",
|
|
368
|
+
save=str(save_path) if save_path else None,
|
|
369
|
+
legend=True,
|
|
370
|
+
)
|
|
371
|
+
elif not args.save and not args.export and not do_plot:
|
|
372
|
+
print("ℹ️ No action selected. Use one or more of --plot, --save, --export.")
|
|
373
|
+
|
|
374
|
+
return 0
|
|
375
|
+
|
|
376
|
+
# ===============================
|
|
377
|
+
# Largest-molecule atoms (wide)
|
|
378
|
+
# ===============================
|
|
379
|
+
df_wide = atoms_in_the_largest_molecule_wide_format(handler)
|
|
380
|
+
if df_wide.empty:
|
|
381
|
+
print("⚠️ No atom data available for largest molecule.")
|
|
382
|
+
return 0
|
|
383
|
+
|
|
384
|
+
df_wide = select_frames(df_wide, frames_sel)
|
|
385
|
+
if df_wide.empty:
|
|
386
|
+
print("⚠️ No atom data after frame selection.")
|
|
387
|
+
return 0
|
|
388
|
+
|
|
389
|
+
iters = df_wide["iter"].to_numpy()
|
|
390
|
+
if args.xaxis == "iter":
|
|
391
|
+
x_vals = list(iters)
|
|
392
|
+
xlabel = "iter"
|
|
393
|
+
else:
|
|
394
|
+
x_vals, xlabel = convert_xaxis(iters, args.xaxis, control_file=args.control)
|
|
395
|
+
x_vals = list(x_vals)
|
|
396
|
+
|
|
397
|
+
element_cols = [c for c in df_wide.columns if c != "iter"]
|
|
398
|
+
if not element_cols:
|
|
399
|
+
print("⚠️ No element columns found to plot.")
|
|
400
|
+
return 0
|
|
401
|
+
|
|
402
|
+
series = {elem: df_wide[elem].tolist() for elem in element_cols}
|
|
403
|
+
|
|
404
|
+
# Export
|
|
405
|
+
if args.export:
|
|
406
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
407
|
+
xcol = (
|
|
408
|
+
xlabel.strip()
|
|
409
|
+
.lower()
|
|
410
|
+
.replace(" ", "_")
|
|
411
|
+
.replace("(", "")
|
|
412
|
+
.replace(")", "")
|
|
413
|
+
)
|
|
414
|
+
out_df = df_wide.copy()
|
|
415
|
+
if args.xaxis != "iter":
|
|
416
|
+
out_df.insert(0, xcol, x_vals)
|
|
417
|
+
out_df.to_csv(out, index=False)
|
|
418
|
+
print(f"[Done] Exported data to {out}")
|
|
419
|
+
|
|
420
|
+
# Save path
|
|
421
|
+
if args.save:
|
|
422
|
+
save_path = resolve_output_path(args.save, workflow_name)
|
|
423
|
+
else:
|
|
424
|
+
save_path = None
|
|
425
|
+
|
|
426
|
+
if not args.title:
|
|
427
|
+
title = "Atom counts in largest molecule"
|
|
428
|
+
else:
|
|
429
|
+
title = args.title
|
|
430
|
+
|
|
431
|
+
if (do_plot or save_path) and series:
|
|
432
|
+
series_for_plot = [
|
|
433
|
+
{"x": x_vals, "y": df_wide[elem].tolist(), "label": elem}
|
|
434
|
+
for elem in element_cols
|
|
435
|
+
]
|
|
436
|
+
single_plot(
|
|
437
|
+
series=series_for_plot,
|
|
438
|
+
title=title,
|
|
439
|
+
xlabel=xlabel,
|
|
440
|
+
ylabel="Atom count",
|
|
441
|
+
save=str(save_path) if save_path else None,
|
|
442
|
+
legend=True,
|
|
443
|
+
)
|
|
444
|
+
elif not args.save and not args.export and not do_plot:
|
|
445
|
+
print("ℹ️ No action selected. Use one or more of --plot, --save, --export.")
|
|
446
|
+
|
|
447
|
+
return 0
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ============================================================
|
|
451
|
+
# Registry
|
|
452
|
+
# ============================================================
|
|
453
|
+
def _add_common_molfra_axes_args(p: argparse.ArgumentParser) -> None:
|
|
454
|
+
"""Common axis and frame-selection flags for molfra-based tasks."""
|
|
455
|
+
p.add_argument(
|
|
456
|
+
"--xaxis",
|
|
457
|
+
default="iter",
|
|
458
|
+
help="X-axis mode. Canonical: 'iter', 'time', 'frame'.",
|
|
459
|
+
)
|
|
460
|
+
p.add_argument(
|
|
461
|
+
"--control",
|
|
462
|
+
default="control",
|
|
463
|
+
help="Path to control file for time conversion (used when --xaxis time).",
|
|
464
|
+
)
|
|
465
|
+
p.add_argument(
|
|
466
|
+
"--frames",
|
|
467
|
+
default=None,
|
|
468
|
+
help="Frame selection (position-based after filtering): 'start:stop[:step]' or 'i,j,k'.",
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _add_common_molfra_output_args(
|
|
473
|
+
p: argparse.ArgumentParser,
|
|
474
|
+
*,
|
|
475
|
+
plot_help: str = "Show the plot interactively.",
|
|
476
|
+
save_help: str = "Save the plot (path or directory, resolved via resolve_output_path).",
|
|
477
|
+
export_help: str = "Export the data table to CSV (path or directory, resolved via resolve_output_path).",
|
|
478
|
+
) -> None:
|
|
479
|
+
"""Common output / I/O flags for molfra-based tasks."""
|
|
480
|
+
p.add_argument("--title", default=None, help="Custom plot title.")
|
|
481
|
+
p.add_argument("--plot", action="store_true", help=plot_help)
|
|
482
|
+
p.add_argument("--save", default=None, help=save_help)
|
|
483
|
+
p.add_argument("--export", default=None, help=export_help)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
487
|
+
# -----------------------
|
|
488
|
+
# Occurrences subcommand
|
|
489
|
+
# -----------------------
|
|
490
|
+
p_occur = subparsers.add_parser(
|
|
491
|
+
"occur",
|
|
492
|
+
help="Get molecule occurrences across frames and optionally plot, save, or export.",
|
|
493
|
+
description=(
|
|
494
|
+
"Examples:\n"
|
|
495
|
+
" reaxkit molfra occur --molecules H2O N128Al128 --save water_and_slab_occurrence.png\n"
|
|
496
|
+
" reaxkit molfra occur --threshold 3 --exclude Pt --export species_with_max_occur.csv\n"
|
|
497
|
+
),
|
|
498
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
499
|
+
)
|
|
500
|
+
p_occur.add_argument("--file", default="molfra.out", help="Path to molfra.out")
|
|
501
|
+
|
|
502
|
+
group = p_occur.add_mutually_exclusive_group(required=False)
|
|
503
|
+
group.add_argument("--molecules", nargs="+", default=None,
|
|
504
|
+
help="One or more molecule types to include (e.g., H2O OH N128Al128).",
|
|
505
|
+
)
|
|
506
|
+
group.add_argument("--threshold", type=int, default=None,
|
|
507
|
+
help="Auto-include all species whose max occurrence ≥ threshold.",
|
|
508
|
+
)
|
|
509
|
+
p_occur.add_argument("--exclude", nargs="*", default=None,
|
|
510
|
+
help="Species to exclude when using --threshold (e.g., Pt).",
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
_add_common_molfra_axes_args(p_occur)
|
|
514
|
+
_add_common_molfra_output_args(p_occur,
|
|
515
|
+
plot_help="Show the plot interactively.",
|
|
516
|
+
save_help="Save the plot (path or directory, resolved via resolve_output_path).",
|
|
517
|
+
export_help="Export the data table to CSV (path or directory, resolved via resolve_output_path).",
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
p_occur.set_defaults(_run=_molfra_occur_task, kind="molfra")
|
|
521
|
+
|
|
522
|
+
# -----------------------
|
|
523
|
+
# Totals subcommand
|
|
524
|
+
# -----------------------
|
|
525
|
+
p_total = subparsers.add_parser(
|
|
526
|
+
"total",
|
|
527
|
+
help="Plot/export totals (molecules, atoms, mass) vs x-axis.",
|
|
528
|
+
description=(
|
|
529
|
+
"Examples:\n"
|
|
530
|
+
" reaxkit molfra total --file molfra_ig.out "
|
|
531
|
+
"--export totals_data.csv --save totals_data.png\n"
|
|
532
|
+
),
|
|
533
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
534
|
+
)
|
|
535
|
+
p_total.add_argument("--file", default="molfra.out", help="Path to molfra.out")
|
|
536
|
+
|
|
537
|
+
_add_common_molfra_axes_args(p_total)
|
|
538
|
+
_add_common_molfra_output_args(p_total,
|
|
539
|
+
plot_help="Show the multi-subplot figure interactively.",
|
|
540
|
+
save_help="Save the multi-subplot figure (path or directory, resolved via resolve_output_path).",
|
|
541
|
+
export_help="Export all totals to CSV (path or directory, resolved via resolve_output_path).",
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
p_total.set_defaults(_run=_molfra_total_task, kind="molfra")
|
|
545
|
+
|
|
546
|
+
# -----------------------
|
|
547
|
+
# Largest subcommand
|
|
548
|
+
# -----------------------
|
|
549
|
+
p_largest = subparsers.add_parser(
|
|
550
|
+
"largest",
|
|
551
|
+
help="Analyze the largest molecule (by individual mass or atom composition).",
|
|
552
|
+
description=(
|
|
553
|
+
"Examples:\n"
|
|
554
|
+
" reaxkit molfra largest --atoms --frames '0:30:2' "
|
|
555
|
+
"--xaxis time --save largest.png --export largest.csv\n"
|
|
556
|
+
" reaxkit molfra largest --mass --xaxis time --export largest_mass.csv\n"
|
|
557
|
+
),
|
|
558
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
559
|
+
)
|
|
560
|
+
p_largest.add_argument("--file", default="molfra.out", help="Path to molfra.out")
|
|
561
|
+
|
|
562
|
+
mode_group = p_largest.add_mutually_exclusive_group(required=False)
|
|
563
|
+
mode_group.add_argument("--atoms", dest="atoms", action="store_true",
|
|
564
|
+
help="Use per-element atom counts for the largest molecule per iter (default).",
|
|
565
|
+
)
|
|
566
|
+
mode_group.add_argument("--mass", action="store_true",
|
|
567
|
+
help="Use largest molecule individual mass vs x-axis.",
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
_add_common_molfra_axes_args(p_largest)
|
|
571
|
+
_add_common_molfra_output_args(p_largest,
|
|
572
|
+
plot_help="Show the plot interactively.",
|
|
573
|
+
save_help="Save the plot (path or directory, resolved via resolve_output_path).",
|
|
574
|
+
export_help="Export the data table to CSV (path or directory, resolved via resolve_output_path).",
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
p_largest.set_defaults(_run=_molfra_largest_task, kind="molfra")
|