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,601 @@
|
|
|
1
|
+
"""
|
|
2
|
+
General-purpose plotting workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides flexible plotting utilities for arbitrary tabular data
|
|
5
|
+
(text, CSV, TSV, or whitespace-delimited files), without assuming any specific
|
|
6
|
+
ReaxFF file format or column headers.
|
|
7
|
+
|
|
8
|
+
It supports multiple plot types, including:
|
|
9
|
+
- single and multi-series line or scatter plots,
|
|
10
|
+
- directed (arrowed) line plots,
|
|
11
|
+
- dual y-axis plots,
|
|
12
|
+
- tornado plots for sensitivity-style visualization,
|
|
13
|
+
- 3D scatter plots with scalar coloring,
|
|
14
|
+
- 2D aggregated heatmaps projected from 3D data.
|
|
15
|
+
|
|
16
|
+
Columns are selected using simple 1-based column tokens (e.g. c1, c2, c3),
|
|
17
|
+
making the workflow suitable for rapid visualization of simulation outputs,
|
|
18
|
+
summaries, and post-processed analysis tables.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import argparse
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import List, Dict, Any, Sequence
|
|
26
|
+
|
|
27
|
+
import numpy as np
|
|
28
|
+
import pandas as pd
|
|
29
|
+
|
|
30
|
+
from reaxkit.utils.media.plotter import (
|
|
31
|
+
single_plot,
|
|
32
|
+
directed_plot,
|
|
33
|
+
dual_yaxis_plot,
|
|
34
|
+
tornado_plot,
|
|
35
|
+
scatter3d_points,
|
|
36
|
+
heatmap2d_from_3d,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------- Helpers ----------
|
|
41
|
+
|
|
42
|
+
def _load_table(path: str | Path) -> pd.DataFrame:
|
|
43
|
+
path = Path(path)
|
|
44
|
+
suf = path.suffix.lower()
|
|
45
|
+
|
|
46
|
+
# 1) CSV / TSV
|
|
47
|
+
if suf == ".csv":
|
|
48
|
+
return pd.read_csv(path, header=None, comment="#", engine="python")
|
|
49
|
+
if suf in {".tsv", ".tab"}:
|
|
50
|
+
return pd.read_csv(path, sep="\t", header=None, comment="#", engine="python")
|
|
51
|
+
|
|
52
|
+
# 2) Generic text: try whitespace first (summary.txt style)
|
|
53
|
+
try:
|
|
54
|
+
return pd.read_csv(
|
|
55
|
+
path,
|
|
56
|
+
sep=r"\s+",
|
|
57
|
+
engine="python",
|
|
58
|
+
header=None,
|
|
59
|
+
comment="#",
|
|
60
|
+
on_bad_lines="skip",
|
|
61
|
+
)
|
|
62
|
+
except Exception:
|
|
63
|
+
# 3) Last resort: let pandas sniff delimiter
|
|
64
|
+
return pd.read_csv(
|
|
65
|
+
path,
|
|
66
|
+
sep=None,
|
|
67
|
+
engine="python",
|
|
68
|
+
header=None,
|
|
69
|
+
comment="#",
|
|
70
|
+
on_bad_lines="skip",
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _parse_col_token(token: str) -> int:
|
|
76
|
+
"""
|
|
77
|
+
Parse a column selector like 'c1', 'c2', '3', etc. into a zero-based index.
|
|
78
|
+
|
|
79
|
+
- 'c1' -> 0
|
|
80
|
+
- 'c3' -> 2
|
|
81
|
+
- '1' -> 0
|
|
82
|
+
- '3' -> 2
|
|
83
|
+
"""
|
|
84
|
+
t = token.strip().lower()
|
|
85
|
+
if t.startswith("c"):
|
|
86
|
+
t = t[1:]
|
|
87
|
+
if not t:
|
|
88
|
+
raise ValueError(f"Invalid column token: '{token}'")
|
|
89
|
+
idx = int(t) - 1
|
|
90
|
+
if idx < 0:
|
|
91
|
+
raise ValueError(f"Column indices are 1-based: got '{token}'")
|
|
92
|
+
return idx
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_col_list(spec: str) -> List[int]:
|
|
96
|
+
"""
|
|
97
|
+
Parse a comma-separated list like 'c1,c3' -> [0, 2].
|
|
98
|
+
"""
|
|
99
|
+
tokens = [s for s in spec.split(",") if s.strip()]
|
|
100
|
+
if not tokens:
|
|
101
|
+
raise ValueError(f"Empty column specification: '{spec}'")
|
|
102
|
+
return [_parse_col_token(tok) for tok in tokens]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _extract_numeric_subframe(df_raw: pd.DataFrame, col_indices: Sequence[int]) -> pd.DataFrame:
|
|
106
|
+
"""
|
|
107
|
+
From a raw DataFrame, take the subset of columns with the given indices and
|
|
108
|
+
convert each to numeric, coercing invalid entries to NaN.
|
|
109
|
+
|
|
110
|
+
Returns a new DataFrame whose columns are named 'c1', 'c2', ... in the sense
|
|
111
|
+
of 1-based original indices, and which has rows where **any** of the selected
|
|
112
|
+
columns is NaN dropped (so all selected columns are aligned).
|
|
113
|
+
"""
|
|
114
|
+
cols_unique = sorted(set(col_indices))
|
|
115
|
+
data: Dict[str, Any] = {}
|
|
116
|
+
for idx in cols_unique:
|
|
117
|
+
if idx >= df_raw.shape[1]:
|
|
118
|
+
raise IndexError(
|
|
119
|
+
f"Requested column c{idx + 1}, but input file has only "
|
|
120
|
+
f"{df_raw.shape[1]} columns."
|
|
121
|
+
)
|
|
122
|
+
name = f"c{idx + 1}"
|
|
123
|
+
data[name] = pd.to_numeric(df_raw.iloc[:, idx], errors="coerce")
|
|
124
|
+
|
|
125
|
+
sub = pd.DataFrame(data)
|
|
126
|
+
# Drop rows where any selected column is NaN (header lines, malformed rows, etc.)
|
|
127
|
+
sub = sub.dropna(how="any").reset_index(drop=True)
|
|
128
|
+
if sub.empty:
|
|
129
|
+
raise ValueError("After cleaning, no valid numeric rows remain to plot.")
|
|
130
|
+
return sub
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# ---------- Tasks ----------
|
|
134
|
+
|
|
135
|
+
def _plotter_single_task(args: argparse.Namespace) -> int:
|
|
136
|
+
"""
|
|
137
|
+
reaxkit plotter single --file summary.txt --xaxis c1,c3 --yaxis c2,c4 [--save out.png] [--scatter]
|
|
138
|
+
|
|
139
|
+
- If len(xaxis) == len(yaxis): pair-wise (x_i, y_i) series.
|
|
140
|
+
- If len(xaxis) == 1 and len(yaxis) >= 1: use the same x for all y columns.
|
|
141
|
+
"""
|
|
142
|
+
df_raw = _load_table(args.file)
|
|
143
|
+
x_indices = _parse_col_list(args.xaxis)
|
|
144
|
+
y_indices = _parse_col_list(args.yaxis)
|
|
145
|
+
all_indices = x_indices + y_indices
|
|
146
|
+
sub = _extract_numeric_subframe(df_raw, all_indices)
|
|
147
|
+
|
|
148
|
+
# Build series list for single_plot
|
|
149
|
+
series: List[Dict[str, Any]] = []
|
|
150
|
+
|
|
151
|
+
def col_name(idx: int) -> str:
|
|
152
|
+
return f"c{idx + 1}"
|
|
153
|
+
|
|
154
|
+
x_names = [col_name(i) for i in x_indices]
|
|
155
|
+
y_names = [col_name(i) for i in y_indices]
|
|
156
|
+
|
|
157
|
+
# Series construction
|
|
158
|
+
if len(x_indices) == len(y_indices):
|
|
159
|
+
for xi, yi in zip(x_indices, y_indices):
|
|
160
|
+
xn = col_name(xi)
|
|
161
|
+
yn = col_name(yi)
|
|
162
|
+
series.append({
|
|
163
|
+
"x": sub[xn],
|
|
164
|
+
"y": sub[yn],
|
|
165
|
+
"label": f"{yn} vs {xn}",
|
|
166
|
+
})
|
|
167
|
+
elif len(x_indices) == 1 and len(y_indices) >= 1:
|
|
168
|
+
xi = x_indices[0]
|
|
169
|
+
xn = col_name(xi)
|
|
170
|
+
x_series = sub[xn]
|
|
171
|
+
for yi in y_indices:
|
|
172
|
+
yn = col_name(yi)
|
|
173
|
+
series.append({
|
|
174
|
+
"x": x_series,
|
|
175
|
+
"y": sub[yn],
|
|
176
|
+
"label": f"{yn} vs {xn}",
|
|
177
|
+
})
|
|
178
|
+
else:
|
|
179
|
+
raise ValueError(
|
|
180
|
+
"For 'single' plot: either provide the same number of x and y columns "
|
|
181
|
+
"OR one x column with multiple y columns."
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
plot_type = "scatter" if args.scatter else "line"
|
|
185
|
+
title = args.title or "single_plot"
|
|
186
|
+
|
|
187
|
+
if not args.plot and not args.save:
|
|
188
|
+
print("Nothing to do. Use --plot to display or --save filename to export.")
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
if args.plot:
|
|
192
|
+
single_plot(
|
|
193
|
+
series=series,
|
|
194
|
+
title=title,
|
|
195
|
+
xlabel=args.xlabel or "x",
|
|
196
|
+
ylabel=args.ylabel or "y",
|
|
197
|
+
legend=True,
|
|
198
|
+
plot_type=plot_type,
|
|
199
|
+
)
|
|
200
|
+
return 0
|
|
201
|
+
elif args.save:
|
|
202
|
+
single_plot(
|
|
203
|
+
series=series,
|
|
204
|
+
title=title,
|
|
205
|
+
xlabel=args.xlabel or "x",
|
|
206
|
+
ylabel=args.ylabel or "y",
|
|
207
|
+
legend=True,
|
|
208
|
+
save=args.save,
|
|
209
|
+
plot_type=plot_type,
|
|
210
|
+
)
|
|
211
|
+
return 0
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _plotter_directed_task(args: argparse.Namespace) -> int:
|
|
215
|
+
"""
|
|
216
|
+
reaxkit plotter directed --file summary.txt --xaxis c1 --yaxis c2 [--save dir.png]
|
|
217
|
+
"""
|
|
218
|
+
df_raw = _load_table(args.file)
|
|
219
|
+
x_idx = _parse_col_token(args.xaxis)
|
|
220
|
+
y_idx = _parse_col_token(args.yaxis)
|
|
221
|
+
sub = _extract_numeric_subframe(df_raw, [x_idx, y_idx])
|
|
222
|
+
|
|
223
|
+
xn = f"c{x_idx + 1}"
|
|
224
|
+
yn = f"c{y_idx + 1}"
|
|
225
|
+
|
|
226
|
+
if not args.plot and not args.save:
|
|
227
|
+
print("Nothing to do. Use --plot to display or --save filename to export.")
|
|
228
|
+
return 0
|
|
229
|
+
|
|
230
|
+
if args.plot:
|
|
231
|
+
directed_plot(
|
|
232
|
+
x=sub[xn].to_numpy(),
|
|
233
|
+
y=sub[yn].to_numpy(),
|
|
234
|
+
title=args.title or "directed_plot",
|
|
235
|
+
xlabel=args.xlabel or xn,
|
|
236
|
+
ylabel=args.ylabel or yn,
|
|
237
|
+
)
|
|
238
|
+
return 0
|
|
239
|
+
elif args.save:
|
|
240
|
+
directed_plot(
|
|
241
|
+
x=sub[xn].to_numpy(),
|
|
242
|
+
y=sub[yn].to_numpy(),
|
|
243
|
+
title=args.title or "directed_plot",
|
|
244
|
+
xlabel=args.xlabel or xn,
|
|
245
|
+
ylabel=args.ylabel or yn,
|
|
246
|
+
save=args.save,
|
|
247
|
+
)
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _plotter_dual_task(args: argparse.Namespace) -> int:
|
|
252
|
+
"""
|
|
253
|
+
reaxkit plotter dual --file summary.txt --xaxis c1 --y1 c2 --y2 c3 [--save dual.png]
|
|
254
|
+
"""
|
|
255
|
+
df_raw = _load_table(args.file)
|
|
256
|
+
x_idx = _parse_col_token(args.xaxis)
|
|
257
|
+
y1_idx = _parse_col_token(args.y1)
|
|
258
|
+
y2_idx = _parse_col_token(args.y2)
|
|
259
|
+
|
|
260
|
+
sub = _extract_numeric_subframe(df_raw, [x_idx, y1_idx, y2_idx])
|
|
261
|
+
|
|
262
|
+
xn = f"c{x_idx + 1}"
|
|
263
|
+
y1n = f"c{y1_idx + 1}"
|
|
264
|
+
y2n = f"c{y2_idx + 1}"
|
|
265
|
+
|
|
266
|
+
if not args.plot and not args.save:
|
|
267
|
+
print("Nothing to do. Use --plot to display or --save filename to export.")
|
|
268
|
+
return 0
|
|
269
|
+
|
|
270
|
+
if args.plot:
|
|
271
|
+
dual_yaxis_plot(
|
|
272
|
+
x=sub[xn].to_numpy(),
|
|
273
|
+
y1=sub[y1n].to_numpy(),
|
|
274
|
+
y2=sub[y2n].to_numpy(),
|
|
275
|
+
title=args.title or "dual_yaxis_plot",
|
|
276
|
+
xlabel=args.xlabel or xn,
|
|
277
|
+
ylabel1=args.ylabel1 or y1n,
|
|
278
|
+
ylabel2=args.ylabel2 or y2n,
|
|
279
|
+
)
|
|
280
|
+
return 0
|
|
281
|
+
elif args.save:
|
|
282
|
+
dual_yaxis_plot(
|
|
283
|
+
x=sub[xn].to_numpy(),
|
|
284
|
+
y1=sub[y1n].to_numpy(),
|
|
285
|
+
y2=sub[y2n].to_numpy(),
|
|
286
|
+
title=args.title or "dual_yaxis_plot",
|
|
287
|
+
xlabel=args.xlabel or xn,
|
|
288
|
+
ylabel1=args.ylabel1 or y1n,
|
|
289
|
+
ylabel2=args.ylabel2 or y2n,
|
|
290
|
+
save=args.save,
|
|
291
|
+
)
|
|
292
|
+
return 0
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _plotter_tornado_task(args: argparse.Namespace) -> int:
|
|
296
|
+
"""
|
|
297
|
+
reaxkit plotter tornado --file summary.txt --label c1 --min c2 --max c3 [--median c4]
|
|
298
|
+
[--top 10] [--vline 0.0] [--save out.png]
|
|
299
|
+
"""
|
|
300
|
+
df_raw = _load_table(args.file)
|
|
301
|
+
|
|
302
|
+
label_idx = _parse_col_token(args.label)
|
|
303
|
+
min_idx = _parse_col_token(args.min)
|
|
304
|
+
max_idx = _parse_col_token(args.max)
|
|
305
|
+
median_idx = _parse_col_token(args.median) if args.median else None
|
|
306
|
+
|
|
307
|
+
# Labels stay as strings
|
|
308
|
+
labels = df_raw.iloc[:, label_idx].astype(str)
|
|
309
|
+
|
|
310
|
+
# Numeric columns
|
|
311
|
+
min_series = pd.to_numeric(df_raw.iloc[:, min_idx], errors="coerce")
|
|
312
|
+
max_series = pd.to_numeric(df_raw.iloc[:, max_idx], errors="coerce")
|
|
313
|
+
median_series = (
|
|
314
|
+
pd.to_numeric(df_raw.iloc[:, median_idx], errors="coerce")
|
|
315
|
+
if median_idx is not None
|
|
316
|
+
else None
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
data = pd.DataFrame({
|
|
320
|
+
"label": labels,
|
|
321
|
+
"min": min_series,
|
|
322
|
+
"max": max_series,
|
|
323
|
+
})
|
|
324
|
+
if median_series is not None:
|
|
325
|
+
data["median"] = median_series
|
|
326
|
+
|
|
327
|
+
# Drop rows where min or max is NaN
|
|
328
|
+
data = data.dropna(subset=["min", "max"]).reset_index(drop=True)
|
|
329
|
+
if data.empty:
|
|
330
|
+
raise ValueError("No valid rows for tornado plot after cleaning.")
|
|
331
|
+
|
|
332
|
+
median_vals = data["median"] if "median" in data.columns else None
|
|
333
|
+
|
|
334
|
+
if not args.plot and not args.save:
|
|
335
|
+
print("Nothing to do. Use --plot to display or --save filename to export.")
|
|
336
|
+
return 0
|
|
337
|
+
|
|
338
|
+
if args.plot:
|
|
339
|
+
tornado_plot(
|
|
340
|
+
labels=data["label"].tolist(),
|
|
341
|
+
min_vals=data["min"].to_numpy(),
|
|
342
|
+
max_vals=data["max"].to_numpy(),
|
|
343
|
+
median_vals=median_vals.to_numpy() if median_vals is not None else None,
|
|
344
|
+
title=args.title or "Tornado Plot",
|
|
345
|
+
xlabel=args.xlabel or "Value",
|
|
346
|
+
ylabel=args.ylabel or "Parameter",
|
|
347
|
+
top=args.top or 0,
|
|
348
|
+
vline=args.vline,
|
|
349
|
+
)
|
|
350
|
+
return 0
|
|
351
|
+
elif args.save:
|
|
352
|
+
tornado_plot(
|
|
353
|
+
labels=data["label"].tolist(),
|
|
354
|
+
min_vals=data["min"].to_numpy(),
|
|
355
|
+
max_vals=data["max"].to_numpy(),
|
|
356
|
+
median_vals=median_vals.to_numpy() if median_vals is not None else None,
|
|
357
|
+
title=args.title or "Tornado Plot",
|
|
358
|
+
xlabel=args.xlabel or "Value",
|
|
359
|
+
ylabel=args.ylabel or "Parameter",
|
|
360
|
+
top=args.top or 0,
|
|
361
|
+
vline=args.vline,
|
|
362
|
+
save=args.save,
|
|
363
|
+
)
|
|
364
|
+
return 0
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def _plotter_scatter3d_task(args: argparse.Namespace) -> int:
|
|
368
|
+
"""
|
|
369
|
+
reaxkit plotter scatter3d --file summary.txt --x c1 --y c2 --z c3 --value c4 [--save out.png]
|
|
370
|
+
"""
|
|
371
|
+
df_raw = _load_table(args.file)
|
|
372
|
+
x_idx = _parse_col_token(args.x)
|
|
373
|
+
y_idx = _parse_col_token(args.y)
|
|
374
|
+
z_idx = _parse_col_token(args.z)
|
|
375
|
+
v_idx = _parse_col_token(args.value)
|
|
376
|
+
|
|
377
|
+
sub = _extract_numeric_subframe(df_raw, [x_idx, y_idx, z_idx, v_idx])
|
|
378
|
+
|
|
379
|
+
xn = f"c{x_idx + 1}"
|
|
380
|
+
yn = f"c{y_idx + 1}"
|
|
381
|
+
zn = f"c{z_idx + 1}"
|
|
382
|
+
vn = f"c{v_idx + 1}"
|
|
383
|
+
|
|
384
|
+
coords = np.column_stack(
|
|
385
|
+
[sub[xn].to_numpy(), sub[yn].to_numpy(), sub[zn].to_numpy()]
|
|
386
|
+
)
|
|
387
|
+
values = sub[vn].to_numpy()
|
|
388
|
+
|
|
389
|
+
if not args.plot and not args.save:
|
|
390
|
+
print("Nothing to do. Use --plot to display or --save filename to export.")
|
|
391
|
+
return 0
|
|
392
|
+
|
|
393
|
+
if args.plot:
|
|
394
|
+
scatter3d_points(
|
|
395
|
+
coords=coords,
|
|
396
|
+
values=values,
|
|
397
|
+
title=args.title or "scatter3d",
|
|
398
|
+
)
|
|
399
|
+
return 0
|
|
400
|
+
elif args.save:
|
|
401
|
+
scatter3d_points(
|
|
402
|
+
coords=coords,
|
|
403
|
+
values=values,
|
|
404
|
+
title=args.title or "scatter3d",
|
|
405
|
+
save=args.save,
|
|
406
|
+
)
|
|
407
|
+
return 0
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _plotter_heatmap2d_task(args: argparse.Namespace) -> int:
|
|
411
|
+
"""
|
|
412
|
+
reaxkit plotter heatmap2d --file summary.txt --x c1 --y c2 --z c3 --value c4
|
|
413
|
+
[--plane xy|xz|yz] [--bins 50] [--save out.png]
|
|
414
|
+
|
|
415
|
+
Uses heatmap2d_from_3d under the hood.
|
|
416
|
+
"""
|
|
417
|
+
df_raw = _load_table(args.file)
|
|
418
|
+
x_idx = _parse_col_token(args.x)
|
|
419
|
+
y_idx = _parse_col_token(args.y)
|
|
420
|
+
z_idx = _parse_col_token(args.z)
|
|
421
|
+
v_idx = _parse_col_token(args.value)
|
|
422
|
+
|
|
423
|
+
sub = _extract_numeric_subframe(df_raw, [x_idx, y_idx, z_idx, v_idx])
|
|
424
|
+
|
|
425
|
+
xn = f"c{x_idx + 1}"
|
|
426
|
+
yn = f"c{y_idx + 1}"
|
|
427
|
+
zn = f"c{z_idx + 1}"
|
|
428
|
+
vn = f"c{v_idx + 1}"
|
|
429
|
+
|
|
430
|
+
coords = np.column_stack(
|
|
431
|
+
[sub[xn].to_numpy(), sub[yn].to_numpy(), sub[zn].to_numpy()]
|
|
432
|
+
)
|
|
433
|
+
values = sub[vn].to_numpy()
|
|
434
|
+
|
|
435
|
+
bins = args.bins
|
|
436
|
+
if "," in str(bins):
|
|
437
|
+
# allow e.g. "--bins 50,100"
|
|
438
|
+
bx, by = (int(p.strip()) for p in str(bins).split(","))
|
|
439
|
+
bins_arg = (bx, by)
|
|
440
|
+
else:
|
|
441
|
+
bins_arg = int(bins)
|
|
442
|
+
|
|
443
|
+
if not args.plot and not args.save:
|
|
444
|
+
print("Nothing to do. Use --plot to display or --save filename to export.")
|
|
445
|
+
return 0
|
|
446
|
+
|
|
447
|
+
if args.plot:
|
|
448
|
+
heatmap2d_from_3d(
|
|
449
|
+
coords=coords,
|
|
450
|
+
values=values,
|
|
451
|
+
plane=args.plane,
|
|
452
|
+
bins=bins_arg,
|
|
453
|
+
title=args.title or "2D aggregated heatmap",
|
|
454
|
+
save=args.save,
|
|
455
|
+
)
|
|
456
|
+
return 0
|
|
457
|
+
elif args.save:
|
|
458
|
+
heatmap2d_from_3d(
|
|
459
|
+
coords=coords,
|
|
460
|
+
values=values,
|
|
461
|
+
plane=args.plane,
|
|
462
|
+
bins=bins_arg,
|
|
463
|
+
title=args.title or "2D aggregated heatmap",
|
|
464
|
+
save=args.save,
|
|
465
|
+
)
|
|
466
|
+
return 0
|
|
467
|
+
|
|
468
|
+
# ---------- Registration ----------
|
|
469
|
+
|
|
470
|
+
def _add_common_io_args(p: argparse.ArgumentParser) -> None:
|
|
471
|
+
p.add_argument(
|
|
472
|
+
"--file",
|
|
473
|
+
required=True,
|
|
474
|
+
help="Path to input txt/csv/tsv table.",
|
|
475
|
+
)
|
|
476
|
+
p.add_argument(
|
|
477
|
+
"--save",
|
|
478
|
+
default=None,
|
|
479
|
+
help="Path to save plot (file or directory). If omitted, show interactively.",
|
|
480
|
+
)
|
|
481
|
+
p.add_argument(
|
|
482
|
+
"--title",
|
|
483
|
+
default=None,
|
|
484
|
+
help="Optional custom plot title.",
|
|
485
|
+
)
|
|
486
|
+
p.add_argument(
|
|
487
|
+
"--plot",
|
|
488
|
+
action="store_true",
|
|
489
|
+
help="Generate and display/save the plot",
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
494
|
+
"""
|
|
495
|
+
Register coordination-related CLI subcommands.
|
|
496
|
+
|
|
497
|
+
This function defines the task-level interface for the
|
|
498
|
+
`reaxkit file` workflow and attaches its tasks (i.e., subcommands).
|
|
499
|
+
|
|
500
|
+
Each task may share common input arguments and defines task-specific options as needed.
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
# ---- single ----
|
|
504
|
+
p_single = subparsers.add_parser(
|
|
505
|
+
"single",
|
|
506
|
+
help="Plot one or multiple y columns vs x columns (line or scatter). || "
|
|
507
|
+
"reaxkit plotter single --file summary.txt --xaxis c1 --yaxis c2 --plot || "
|
|
508
|
+
"reaxkit plotter single --file summary.txt --xaxis c1 --yaxis c2 --scatter --plot || "
|
|
509
|
+
"reaxkit plotter single --file summary.txt --xaxis c1,c3 --yaxis c2,c4 --plot || "
|
|
510
|
+
"reaxkit plotter single --file summary.txt --xaxis c1 --yaxis c2,c3,c4 --scatter --plot",
|
|
511
|
+
)
|
|
512
|
+
_add_common_io_args(p_single)
|
|
513
|
+
p_single.add_argument("--xaxis", required=True,
|
|
514
|
+
help="Comma-separated list of x columns (e.g., 'c1' or 'c1,c3').",
|
|
515
|
+
)
|
|
516
|
+
p_single.add_argument("--yaxis", required=True,
|
|
517
|
+
help="Comma-separated list of y columns (e.g., 'c2' or 'c2,c4').",
|
|
518
|
+
)
|
|
519
|
+
p_single.add_argument("--xlabel", default=None, help="Optional x-axis label.")
|
|
520
|
+
p_single.add_argument("--ylabel", default=None, help="Optional y-axis label.")
|
|
521
|
+
p_single.add_argument("--scatter", action="store_true", help="Use scatter instead of line plot.")
|
|
522
|
+
p_single.set_defaults(_run=_plotter_single_task)
|
|
523
|
+
|
|
524
|
+
# ---- directed ----
|
|
525
|
+
p_directed = subparsers.add_parser(
|
|
526
|
+
"directed",
|
|
527
|
+
help="Line plot with arrows showing direction along the path. || "
|
|
528
|
+
"reaxkit plotter directed --file summary.txt --xaxis c1 --yaxis c2 --save directed.png",
|
|
529
|
+
)
|
|
530
|
+
_add_common_io_args(p_directed)
|
|
531
|
+
p_directed.add_argument("--xaxis", required=True, help="Single x column (e.g., 'c1').")
|
|
532
|
+
p_directed.add_argument("--yaxis", required=True, help="Single y column (e.g., 'c2').")
|
|
533
|
+
p_directed.add_argument("--xlabel", default=None, help="Optional x-axis label.")
|
|
534
|
+
p_directed.add_argument("--ylabel", default=None, help="Optional y-axis label.")
|
|
535
|
+
p_directed.set_defaults(_run=_plotter_directed_task)
|
|
536
|
+
|
|
537
|
+
# ---- dual ----
|
|
538
|
+
p_dual = subparsers.add_parser(
|
|
539
|
+
"dual",
|
|
540
|
+
help="Dual y-axis plot: one x column, two y columns. || "
|
|
541
|
+
"reaxkit plotter dual --file summary.txt --xaxis c1 --y1 c2 --y2 c3 --save dual_plot.png",
|
|
542
|
+
)
|
|
543
|
+
_add_common_io_args(p_dual)
|
|
544
|
+
p_dual.add_argument("--xaxis", required=True, help="Single x column (e.g., 'c1').")
|
|
545
|
+
p_dual.add_argument("--y1", required=True, help="Left y-axis column (e.g., 'c2').")
|
|
546
|
+
p_dual.add_argument("--y2", required=True, help="Right y-axis column (e.g., 'c3').")
|
|
547
|
+
p_dual.add_argument("--xlabel", default=None, help="Optional x-axis label.")
|
|
548
|
+
p_dual.add_argument("--ylabel1", default=None, help="Optional left y-axis label.")
|
|
549
|
+
p_dual.add_argument("--ylabel2", default=None, help="Optional right y-axis label.")
|
|
550
|
+
p_dual.set_defaults(_run=_plotter_dual_task)
|
|
551
|
+
|
|
552
|
+
# ---- tornado ----
|
|
553
|
+
p_tornado = subparsers.add_parser(
|
|
554
|
+
"tornado",
|
|
555
|
+
help="Tornado plot: label + min/max (and optional median). || "
|
|
556
|
+
"reaxkit plotter tornado --file summary.txt --label c1 --min c2 --max c3 --median c4 --top 10 --save tornado.png || "
|
|
557
|
+
"reaxkit plotter tornado --file summary.txt --label c1 --min c2 --max c3",
|
|
558
|
+
)
|
|
559
|
+
_add_common_io_args(p_tornado)
|
|
560
|
+
p_tornado.add_argument("--label", required=True, help="Column for labels (e.g., 'c1').")
|
|
561
|
+
p_tornado.add_argument("--min", required=True, help="Column for minimum values (e.g., 'c2').")
|
|
562
|
+
p_tornado.add_argument("--max", required=True, help="Column for maximum values (e.g., 'c3').")
|
|
563
|
+
p_tornado.add_argument("--median", default=None, help="Optional column for median values (e.g., 'c4').")
|
|
564
|
+
p_tornado.add_argument("--top", type=int, default=0, help="Show only top-N widest bars (0 = all).")
|
|
565
|
+
p_tornado.add_argument("--vline", type=float, default=None, help="Optional vertical reference line (e.g., 0.0).")
|
|
566
|
+
p_tornado.add_argument("--xlabel", default=None, help="Optional x-axis label.")
|
|
567
|
+
p_tornado.add_argument("--ylabel", default=None, help="Optional y-axis label.")
|
|
568
|
+
p_tornado.set_defaults(_run=_plotter_tornado_task)
|
|
569
|
+
|
|
570
|
+
# ---- scatter3d ----
|
|
571
|
+
p_scatter3d = subparsers.add_parser(
|
|
572
|
+
"scatter3d",
|
|
573
|
+
help="3D scatter of (x,y,z) points colored by a value. || "
|
|
574
|
+
"reaxkit plotter scatter3d --file summary.txt --x c1 --y c2 --z c3 --value c4 --save 3dscatter.png",
|
|
575
|
+
)
|
|
576
|
+
_add_common_io_args(p_scatter3d)
|
|
577
|
+
p_scatter3d.add_argument("--x", required=True, help="x coordinate column (e.g., 'c1').")
|
|
578
|
+
p_scatter3d.add_argument("--y", required=True, help="y coordinate column (e.g., 'c2').")
|
|
579
|
+
p_scatter3d.add_argument("--z", required=True, help="z coordinate column (e.g., 'c3').")
|
|
580
|
+
p_scatter3d.add_argument("--value", required=True, help="Value column for coloring (e.g., 'c4').")
|
|
581
|
+
p_scatter3d.set_defaults(_run=_plotter_scatter3d_task)
|
|
582
|
+
|
|
583
|
+
# ---- heatmap2d ----
|
|
584
|
+
p_heatmap2d = subparsers.add_parser(
|
|
585
|
+
"heatmap2d",
|
|
586
|
+
help="2D heatmap from 3D coords + values (projection plane selectable). || "
|
|
587
|
+
"reaxkit plotter heatmap2d --file summary.txt --x c1 --y c2 --z c3 --value c4 --plane xz "
|
|
588
|
+
"--bins 100,80 --save heat_xz.png",
|
|
589
|
+
)
|
|
590
|
+
_add_common_io_args(p_heatmap2d)
|
|
591
|
+
p_heatmap2d.add_argument("--x", required=True, help="x coordinate column (e.g., 'c1').")
|
|
592
|
+
p_heatmap2d.add_argument("--y", required=True, help="y coordinate column (e.g., 'c2').")
|
|
593
|
+
p_heatmap2d.add_argument("--z", required=True, help="z coordinate column (e.g., 'c3').")
|
|
594
|
+
p_heatmap2d.add_argument("--value", required=True, help="value column to aggregate (e.g., 'c4').")
|
|
595
|
+
p_heatmap2d.add_argument("--plane", choices=["xy", "xz", "yz"], default="xy",
|
|
596
|
+
help="Projection plane for heatmap (default: xy).",
|
|
597
|
+
)
|
|
598
|
+
p_heatmap2d.add_argument("--bins", default="50",
|
|
599
|
+
help="Grid resolution: int (e.g., 50) or 'nx,ny' (e.g., '50,100').",
|
|
600
|
+
)
|
|
601
|
+
p_heatmap2d.set_defaults(_run=_plotter_heatmap2d_task)
|
|
File without changes
|