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,587 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Electrostatics workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides tools for computing and analyzing electrostatic properties
|
|
5
|
+
from ReaxFF simulations, including dipole moments, polarizations, and
|
|
6
|
+
polarization–electric-field hysteresis behavior.
|
|
7
|
+
|
|
8
|
+
It supports:
|
|
9
|
+
- Dipole moment and polarization calculations for single frames, at both
|
|
10
|
+
total-system and local (core-atom cluster) levels.
|
|
11
|
+
- Polarization–field hysteresis analysis using time-dependent electric fields
|
|
12
|
+
(via fort.78 and control files), including extraction of coercive fields and
|
|
13
|
+
remnant polarizations.
|
|
14
|
+
- Visualization of local electrostatics through 3D scatter plots and 2D
|
|
15
|
+
projected heatmaps based on atomic coordinates.
|
|
16
|
+
|
|
17
|
+
The workflow integrates xmolout, fort.7, fort.78, and control files, and is
|
|
18
|
+
designed to bridge atomistic ReaxFF simulations with experiment-facing
|
|
19
|
+
electrostatic observables.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
import argparse
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import Optional, Sequence, Tuple, Union
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
import pandas as pd
|
|
30
|
+
|
|
31
|
+
from reaxkit.io.handlers.xmolout_handler import XmoloutHandler
|
|
32
|
+
from reaxkit.io.handlers.fort7_handler import Fort7Handler
|
|
33
|
+
from reaxkit.io.handlers.fort78_handler import Fort78Handler
|
|
34
|
+
from reaxkit.io.handlers.control_handler import ControlHandler
|
|
35
|
+
from reaxkit.analysis.per_file.xmolout_analyzer import get_atom_trajectories
|
|
36
|
+
from reaxkit.utils.media.plotter import scatter3d_points, heatmap2d_from_3d
|
|
37
|
+
from reaxkit.utils.frame_utils import parse_frames, resolve_indices
|
|
38
|
+
from reaxkit.utils.alias import resolve_alias_from_columns
|
|
39
|
+
from reaxkit.utils.path import resolve_output_path
|
|
40
|
+
from reaxkit.utils.media.plotter import single_plot
|
|
41
|
+
|
|
42
|
+
from reaxkit.utils.alias import (
|
|
43
|
+
normalize_choice,
|
|
44
|
+
_resolve_alias,
|
|
45
|
+
)
|
|
46
|
+
from reaxkit.analysis.composed.electrostatics_analyzer import (
|
|
47
|
+
dipoles_polarizations_over_multiple_frames,
|
|
48
|
+
polarization_field_analysis,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# -------------------------------------------------------------------------
|
|
53
|
+
# Tasks
|
|
54
|
+
# -------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _dipole_task(args: argparse.Namespace) -> int:
|
|
58
|
+
"""
|
|
59
|
+
reaxkit elect dipole
|
|
60
|
+
--xmolout xmolout --fort7 fort.7
|
|
61
|
+
--frame X --scope {total,local}
|
|
62
|
+
[--core Al,Mg] --export out.csv [--polarization]
|
|
63
|
+
|
|
64
|
+
total scope:
|
|
65
|
+
exports total dipole or polarization for a single frame.
|
|
66
|
+
|
|
67
|
+
local scope:
|
|
68
|
+
exports local dipole or polarization for each core atom cluster for a single frame.
|
|
69
|
+
NOTE: local polarization uses bbox volume by default (fast). Use hull only if you add an option.
|
|
70
|
+
"""
|
|
71
|
+
# Handlers
|
|
72
|
+
xh = XmoloutHandler(args.xmolout)
|
|
73
|
+
f7 = Fort7Handler(args.fort7)
|
|
74
|
+
|
|
75
|
+
# Mode: dipole vs polarization
|
|
76
|
+
mode = "polarization" if args.polarization else "dipole"
|
|
77
|
+
|
|
78
|
+
# Scope/core types
|
|
79
|
+
scope = args.scope
|
|
80
|
+
core_types: Optional[list[str]] = None
|
|
81
|
+
if scope == "local":
|
|
82
|
+
if not args.core:
|
|
83
|
+
raise ValueError("When --scope local is used, --core must be provided (e.g. --core Al,Mg).")
|
|
84
|
+
core_types = [c.strip() for c in args.core.split(",") if c.strip()]
|
|
85
|
+
|
|
86
|
+
# Volume method policy (per your new analyzer design)
|
|
87
|
+
# - total polarization: hull (reasonable)
|
|
88
|
+
# - local polarization: bbox (fast default)
|
|
89
|
+
volume_method = None
|
|
90
|
+
if mode == "polarization":
|
|
91
|
+
volume_method = "bbox" if scope == "local" else "hull"
|
|
92
|
+
|
|
93
|
+
# Compute for the requested single frame (by passing frames=[...])
|
|
94
|
+
df = dipoles_polarizations_over_multiple_frames(
|
|
95
|
+
xh,
|
|
96
|
+
f7,
|
|
97
|
+
frames=[args.frame],
|
|
98
|
+
scope=scope,
|
|
99
|
+
core_types=core_types,
|
|
100
|
+
mode=mode,
|
|
101
|
+
volume_method=volume_method,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
workflow_name = args.kind
|
|
105
|
+
|
|
106
|
+
# Export
|
|
107
|
+
out = resolve_output_path(args.export, workflow_name)
|
|
108
|
+
df.to_csv(out, index=False)
|
|
109
|
+
print(f"\n[Done] Exported data to {out}")
|
|
110
|
+
|
|
111
|
+
return 0
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _hyst_task(args: argparse.Namespace) -> int:
|
|
115
|
+
"""
|
|
116
|
+
reaxkit elect hyst
|
|
117
|
+
--xmolout xmolout --fort7 fort.7 --fort78 fort.78 --control control
|
|
118
|
+
[--plot] [--save hyst.png]
|
|
119
|
+
[--yaxis pol_z] [--xaxis time|field_z]
|
|
120
|
+
[--aggregate mean|max|min|last]
|
|
121
|
+
[--export hysteresis.csv]
|
|
122
|
+
[--summary hysteresis_summary.txt]
|
|
123
|
+
[--roots]
|
|
124
|
+
|
|
125
|
+
- Uses polarization_field_analysis to build polarization + field dataset.
|
|
126
|
+
- Aggregates according to --aggregate.
|
|
127
|
+
- Exports aggregated joint DataFrame (if --export is given).
|
|
128
|
+
- Writes coercive fields and remnant polarizations to a text file (summary).
|
|
129
|
+
- If --roots is present, also prints these values to the terminal.
|
|
130
|
+
- Makes a hysteresis / time-series plot according to --xaxis/--yaxis
|
|
131
|
+
and plot/save flags, using alias_utils to map names.
|
|
132
|
+
"""
|
|
133
|
+
# Handlers
|
|
134
|
+
xh = XmoloutHandler(args.xmolout)
|
|
135
|
+
f7 = Fort7Handler(args.fort7)
|
|
136
|
+
f78 = Fort78Handler(args.fort78)
|
|
137
|
+
ctrl = ControlHandler(args.control)
|
|
138
|
+
|
|
139
|
+
# Run basic polarization vs field analysis; keep x/y for roots as field_z vs P_z
|
|
140
|
+
full_df, agg_df, coercive_fields, remnant_pols = polarization_field_analysis(
|
|
141
|
+
xh,
|
|
142
|
+
f7,
|
|
143
|
+
f78,
|
|
144
|
+
ctrl,
|
|
145
|
+
field_var="field_z",
|
|
146
|
+
aggregate=args.aggregate,
|
|
147
|
+
x_variable="field_z",
|
|
148
|
+
y_variable="P_z (uC/cm^2)",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# ------------------------------------------------------------------
|
|
152
|
+
# Optionally add time information to full_df (for plotting vs time)
|
|
153
|
+
# ------------------------------------------------------------------
|
|
154
|
+
try:
|
|
155
|
+
sim_df = xh.dataframe()
|
|
156
|
+
iter_col = _resolve_alias(sim_df, "iter")
|
|
157
|
+
time_col_name = _resolve_alias(sim_df, "time")
|
|
158
|
+
sim_small = sim_df[[iter_col, time_col_name]].copy()
|
|
159
|
+
sim_small = sim_small.rename(columns={iter_col: "iter", time_col_name: "time"})
|
|
160
|
+
full_df = full_df.merge(sim_small, on="iter", how="left")
|
|
161
|
+
except Exception:
|
|
162
|
+
# If no time is resolvable, we simply skip it; plotting vs time may fail later.
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# ------------------------------------------------------------------
|
|
166
|
+
# Export aggregated joint DataFrame
|
|
167
|
+
# ------------------------------------------------------------------
|
|
168
|
+
workflow_name = args.kind
|
|
169
|
+
if args.export:
|
|
170
|
+
# Export aggregated data (agg_df)
|
|
171
|
+
out_csv = resolve_output_path(args.export, workflow_name)
|
|
172
|
+
agg_df.to_csv(out_csv, index=False)
|
|
173
|
+
print(f"\n[Done] Exported aggregated joint hysteresis data to {out_csv}")
|
|
174
|
+
|
|
175
|
+
# Export full dataframe in same directory
|
|
176
|
+
full_save_dir = out_csv.parent # Directory where --export was saved
|
|
177
|
+
full_save_dir.mkdir(parents=True, exist_ok=True) # Ensure directory exists
|
|
178
|
+
full_save_path = full_save_dir / "hysteresis_full_data.csv"
|
|
179
|
+
full_df.to_csv(full_save_path, index=False)
|
|
180
|
+
print(f"[Done] Exported full hysteresis dataset to {full_save_path}\n")
|
|
181
|
+
|
|
182
|
+
# ------------------------------------------------------------------
|
|
183
|
+
# Write coercive fields & remnant polarizations to a text file
|
|
184
|
+
# ------------------------------------------------------------------
|
|
185
|
+
summary_path = Path(args.summary or "hysteresis_summary.txt")
|
|
186
|
+
|
|
187
|
+
def _fmt_list(values) -> str:
|
|
188
|
+
if not values:
|
|
189
|
+
return "None found"
|
|
190
|
+
return ", ".join(f"{v:.6g}" for v in values)
|
|
191
|
+
|
|
192
|
+
with summary_path.open("w", encoding="utf-8") as f:
|
|
193
|
+
f.write("Hysteresis Analysis Summary\n")
|
|
194
|
+
f.write("===========================\n\n")
|
|
195
|
+
f.write("Coercive fields (where polarization crosses zero vs field_z)\n")
|
|
196
|
+
f.write("Units: MV/cm\n")
|
|
197
|
+
f.write(f"Values: {_fmt_list(coercive_fields)}\n\n")
|
|
198
|
+
f.write("Remnant polarizations (where field_z crosses zero vs P_z)\n")
|
|
199
|
+
f.write("Units: µC/cm^2\n")
|
|
200
|
+
f.write(f"Values: {_fmt_list(remnant_pols)}\n")
|
|
201
|
+
|
|
202
|
+
print(f"[Done] Wrote hysteresis summary to {summary_path}")
|
|
203
|
+
|
|
204
|
+
# Also print to terminal if --roots is requested
|
|
205
|
+
if args.roots:
|
|
206
|
+
print("\n[Hysteresis roots]")
|
|
207
|
+
print(" Coercive fields (MV/cm):", _fmt_list(coercive_fields))
|
|
208
|
+
print(" Remnant polarizations (µC/cm^2):", _fmt_list(remnant_pols))
|
|
209
|
+
|
|
210
|
+
# ------------------------------------------------------------------
|
|
211
|
+
# Plot: use alias_utils to map --xaxis / --yaxis
|
|
212
|
+
# - If xaxis == time → use per-frame data (full_df)
|
|
213
|
+
# - Else → use aggregated data (agg_df) for hysteresis
|
|
214
|
+
# ------------------------------------------------------------------
|
|
215
|
+
if args.plot or args.save:
|
|
216
|
+
# Canonical keys
|
|
217
|
+
canonical_x = normalize_choice(args.xaxis or "field_z", domain="xaxis")
|
|
218
|
+
canonical_y = normalize_choice(args.yaxis or "P_z (uC/cm^2)", domain="yaxis")
|
|
219
|
+
|
|
220
|
+
# Choose DataFrame for plotting
|
|
221
|
+
if canonical_x == "time":
|
|
222
|
+
df_plot = full_df
|
|
223
|
+
else:
|
|
224
|
+
df_plot = agg_df
|
|
225
|
+
|
|
226
|
+
# Resolve actual column names present in df_plot
|
|
227
|
+
x_col = _resolve_alias(df_plot, canonical_x)
|
|
228
|
+
y_col = _resolve_alias(df_plot, canonical_y)
|
|
229
|
+
|
|
230
|
+
x = df_plot[x_col]
|
|
231
|
+
y = df_plot[y_col]
|
|
232
|
+
|
|
233
|
+
title = f"{y_col} vs {x_col}"
|
|
234
|
+
xlabel = x_col
|
|
235
|
+
ylabel = y_col
|
|
236
|
+
|
|
237
|
+
single_plot(x, y, title=title, xlabel=xlabel, ylabel=ylabel, save=args.save, figsize=(6,4))
|
|
238
|
+
|
|
239
|
+
return 0
|
|
240
|
+
|
|
241
|
+
# ------------------------- internals -------------------------
|
|
242
|
+
|
|
243
|
+
def _indices_from_spec(xh: XmoloutHandler, frames_spec) -> list[int]:
|
|
244
|
+
"""Turn a frames spec (slice or list) into concrete frame indices for xh."""
|
|
245
|
+
return resolve_indices(xh, frames=frames_spec, iterations=None, step=None)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def _local_pol_with_coords(
|
|
249
|
+
xh: XmoloutHandler,
|
|
250
|
+
f7: Fort7Handler,
|
|
251
|
+
*,
|
|
252
|
+
core_types: Sequence[str],
|
|
253
|
+
frames_spec,
|
|
254
|
+
mode: str = "polarization", # or "dipole"
|
|
255
|
+
) -> pd.DataFrame:
|
|
256
|
+
"""
|
|
257
|
+
Build a DataFrame with local electrostatics + core-atom coordinates:
|
|
258
|
+
|
|
259
|
+
columns include:
|
|
260
|
+
frame_index, iter, core_atom_type, core_atom_id,
|
|
261
|
+
x, y, z,
|
|
262
|
+
mu_x (debye), mu_y (debye), mu_z (debye),
|
|
263
|
+
P_x (uC/cm^2), P_y (uC/cm^2), P_z (uC/cm^2), volume (angstrom^3)
|
|
264
|
+
"""
|
|
265
|
+
# 1) local dipole/polarization over all frames
|
|
266
|
+
df_local = dipoles_polarizations_over_multiple_frames(
|
|
267
|
+
xh,
|
|
268
|
+
f7,
|
|
269
|
+
scope="local",
|
|
270
|
+
core_types=core_types,
|
|
271
|
+
mode=mode,
|
|
272
|
+
)
|
|
273
|
+
if df_local.empty:
|
|
274
|
+
raise ValueError("No local polarization/dipole data found. Check core types and files.")
|
|
275
|
+
|
|
276
|
+
# Restrict to requested frames
|
|
277
|
+
idx_list = _indices_from_spec(xh, frames_spec)
|
|
278
|
+
df_local = df_local[df_local["frame_index"].isin(idx_list)].copy()
|
|
279
|
+
if df_local.empty:
|
|
280
|
+
raise ValueError("No local data in the requested frames.")
|
|
281
|
+
|
|
282
|
+
# 2) coordinates of atoms from xmolout (long format)
|
|
283
|
+
traj = get_atom_trajectories(
|
|
284
|
+
xh,
|
|
285
|
+
frames=idx_list,
|
|
286
|
+
every=1,
|
|
287
|
+
atoms=None,
|
|
288
|
+
atom_types=None,
|
|
289
|
+
dims=("x", "y", "z"),
|
|
290
|
+
format="long",
|
|
291
|
+
)
|
|
292
|
+
if traj.empty:
|
|
293
|
+
raise ValueError("No trajectory data returned for requested frames.")
|
|
294
|
+
|
|
295
|
+
traj = traj[["frame_index", "atom_id", "x", "y", "z"]].copy()
|
|
296
|
+
traj = traj.rename(columns={"atom_id": "core_atom_id"})
|
|
297
|
+
|
|
298
|
+
# 3) join local pol/dipole with core-atom coordinates
|
|
299
|
+
df = pd.merge(
|
|
300
|
+
df_local,
|
|
301
|
+
traj,
|
|
302
|
+
on=["frame_index", "core_atom_id"],
|
|
303
|
+
how="inner",
|
|
304
|
+
)
|
|
305
|
+
if df.empty:
|
|
306
|
+
raise ValueError("Could not match local polarization entries to core atom coordinates.")
|
|
307
|
+
|
|
308
|
+
return df
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# ========================= 3D SCATTER =========================
|
|
312
|
+
|
|
313
|
+
def _local_pol_plot3d_task(args: argparse.Namespace) -> int:
|
|
314
|
+
"""
|
|
315
|
+
3D scatter of local polarization/dipole (one point per core atom) across frames.
|
|
316
|
+
|
|
317
|
+
Example:
|
|
318
|
+
reaxkit elect plot3d --core Al,Mg --component pol_z --frames 0:200:20 --save figs/local_pol3d
|
|
319
|
+
"""
|
|
320
|
+
xh = XmoloutHandler(args.xmolout)
|
|
321
|
+
f7 = Fort7Handler(args.fort7)
|
|
322
|
+
|
|
323
|
+
frames_spec = parse_frames(args.frames)
|
|
324
|
+
core_types = [c.strip() for c in args.core.split(",") if c.strip()]
|
|
325
|
+
|
|
326
|
+
df = _local_pol_with_coords(
|
|
327
|
+
xh,
|
|
328
|
+
f7,
|
|
329
|
+
core_types=core_types,
|
|
330
|
+
frames_spec=frames_spec,
|
|
331
|
+
mode="polarization" if args.polarization else "dipole",
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Resolve which column to color by (mu_z, P_z, etc.) using alias map
|
|
335
|
+
col = resolve_alias_from_columns(df.columns, args.component)
|
|
336
|
+
if col is None:
|
|
337
|
+
raise ValueError(
|
|
338
|
+
f"Component '{args.component}' not found. "
|
|
339
|
+
f"Available columns include: {list(df.columns)[:12]} ..."
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Output dir
|
|
343
|
+
save_dir = Path(args.save) if args.save else None
|
|
344
|
+
if save_dir:
|
|
345
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
346
|
+
|
|
347
|
+
# Per-frame 3D plot
|
|
348
|
+
for fi in sorted(df["frame_index"].unique()):
|
|
349
|
+
sub = df[df["frame_index"] == fi]
|
|
350
|
+
coords = sub[["x", "y", "z"]].to_numpy(float)
|
|
351
|
+
vals = sub[col].to_numpy(float)
|
|
352
|
+
|
|
353
|
+
# skip frames with all-NaN or empty
|
|
354
|
+
if coords.size == 0 or not np.isfinite(vals).any():
|
|
355
|
+
continue
|
|
356
|
+
|
|
357
|
+
vmin = args.vmin if args.vmin is not None else float(np.nanmin(vals))
|
|
358
|
+
vmax = args.vmax if args.vmax is not None else float(np.nanmax(vals))
|
|
359
|
+
|
|
360
|
+
title = f"{col}_local_3D_frame_{fi}"
|
|
361
|
+
scatter3d_points(
|
|
362
|
+
coords,
|
|
363
|
+
vals,
|
|
364
|
+
title=title,
|
|
365
|
+
s=args.size,
|
|
366
|
+
alpha=args.alpha,
|
|
367
|
+
cmap=args.cmap,
|
|
368
|
+
vmin=vmin,
|
|
369
|
+
vmax=vmax,
|
|
370
|
+
elev=args.elev,
|
|
371
|
+
azim=args.azim,
|
|
372
|
+
save=(save_dir / f"{title}.png" if save_dir else None),
|
|
373
|
+
show_message=False,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
if args.save:
|
|
377
|
+
print(f"[Done] All 3D local polarization plots saved in {args.save}")
|
|
378
|
+
return 0
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# ========================= 2D HEATMAP =========================
|
|
382
|
+
|
|
383
|
+
def _parse_bins(bins: str) -> Union[int, Tuple[int, int]]:
|
|
384
|
+
if "," in bins:
|
|
385
|
+
nx, ny = [int(x) for x in bins.split(",")]
|
|
386
|
+
return (nx, ny)
|
|
387
|
+
return int(bins)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _local_pol_heatmap2d_task(args: argparse.Namespace) -> int:
|
|
391
|
+
"""
|
|
392
|
+
2D heatmap of local polarization/dipole on core-atom positions.
|
|
393
|
+
|
|
394
|
+
Example:
|
|
395
|
+
reaxkit elect heatmap2d --core Al,Mg --component pol_z --plane xz --bins 40 --frames 0:200:20 --save figs/local_pol_heat
|
|
396
|
+
"""
|
|
397
|
+
xh = XmoloutHandler(args.xmolout)
|
|
398
|
+
f7 = Fort7Handler(args.fort7)
|
|
399
|
+
|
|
400
|
+
frames_spec = parse_frames(args.frames)
|
|
401
|
+
core_types = [c.strip() for c in args.core.split(",") if c.strip()]
|
|
402
|
+
|
|
403
|
+
df = _local_pol_with_coords(
|
|
404
|
+
xh,
|
|
405
|
+
f7,
|
|
406
|
+
core_types=core_types,
|
|
407
|
+
frames_spec=frames_spec,
|
|
408
|
+
mode="polarization" if args.polarization else "dipole",
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
col = resolve_alias_from_columns(df.columns, args.component)
|
|
412
|
+
if col is None:
|
|
413
|
+
raise ValueError(
|
|
414
|
+
f"Component '{args.component}' not found. "
|
|
415
|
+
f"Available columns include: {list(df.columns)[:12]} ..."
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
bins = _parse_bins(args.bins)
|
|
419
|
+
|
|
420
|
+
save_dir = Path(args.save) if args.save else None
|
|
421
|
+
if save_dir:
|
|
422
|
+
save_dir.mkdir(parents=True, exist_ok=True)
|
|
423
|
+
|
|
424
|
+
for fi in sorted(df["frame_index"].unique()):
|
|
425
|
+
sub = df[df["frame_index"] == fi]
|
|
426
|
+
coords = sub[["x", "y", "z"]].to_numpy(float)
|
|
427
|
+
vals = sub[col].to_numpy(float)
|
|
428
|
+
|
|
429
|
+
if coords.size == 0 or not np.isfinite(vals).any():
|
|
430
|
+
continue
|
|
431
|
+
|
|
432
|
+
m = np.isfinite(vals)
|
|
433
|
+
coords = coords[m]
|
|
434
|
+
vals = vals[m]
|
|
435
|
+
|
|
436
|
+
vmin = args.vmin if args.vmin is not None else float(np.nanmin(vals))
|
|
437
|
+
vmax = args.vmax if args.vmax is not None else float(np.nanmax(vals))
|
|
438
|
+
|
|
439
|
+
title = f"{col}_local_{args.plane}_frame_{fi}"
|
|
440
|
+
out_path = (save_dir / f"{title}.png") if save_dir else None
|
|
441
|
+
|
|
442
|
+
heatmap2d_from_3d(
|
|
443
|
+
coords,
|
|
444
|
+
vals,
|
|
445
|
+
plane=args.plane,
|
|
446
|
+
bins=bins,
|
|
447
|
+
agg=args.agg,
|
|
448
|
+
vmin=vmin,
|
|
449
|
+
vmax=vmax,
|
|
450
|
+
cmap=args.cmap,
|
|
451
|
+
title=title,
|
|
452
|
+
save=out_path,
|
|
453
|
+
show_message=False,
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
if args.save:
|
|
457
|
+
print(f"[Done] All 2D local polarization heatmaps saved in {args.save}")
|
|
458
|
+
return 0
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# -------------------------------------------------------------------------
|
|
462
|
+
# Registration
|
|
463
|
+
# -------------------------------------------------------------------------
|
|
464
|
+
|
|
465
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
466
|
+
"""
|
|
467
|
+
Register coordination-related CLI subcommands.
|
|
468
|
+
|
|
469
|
+
This function defines the task-level interface for the
|
|
470
|
+
`reaxkit file` workflow and attaches its tasks (i.e., subcommands).
|
|
471
|
+
|
|
472
|
+
Each task may share common input arguments and defines task-specific options as needed.
|
|
473
|
+
"""
|
|
474
|
+
# ---------------------- dipole ----------------------
|
|
475
|
+
p_dip = subparsers.add_parser(
|
|
476
|
+
"dipole",
|
|
477
|
+
help="Compute dipole moment or polarization for a single frame\n",
|
|
478
|
+
description=(
|
|
479
|
+
"Examples:\n"
|
|
480
|
+
" reaxkit elect dipole --frame 10 --scope total --export dipole_pol_total_frame10.csv --polarization \n"
|
|
481
|
+
" reaxkit elect dipole --frame 10 --scope local --core Al --export dipole_pol_local_frame10.csv --polarization \n"
|
|
482
|
+
),
|
|
483
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
484
|
+
)
|
|
485
|
+
p_dip.add_argument("--xmolout", default="xmolout", help="Path to xmolout file")
|
|
486
|
+
p_dip.add_argument("--fort7", default="fort.7", help="Path to fort.7 file")
|
|
487
|
+
p_dip.add_argument("--frame", type=int, required=True, help="0-based frame index in xmolout")
|
|
488
|
+
p_dip.add_argument("--scope", choices=["total", "local"],
|
|
489
|
+
default="total", help="Electrostatics scope: total or local")
|
|
490
|
+
p_dip.add_argument("--core", default=None,
|
|
491
|
+
help="Comma-separated core atom types for local scope (e.g. Al,Mg)")
|
|
492
|
+
p_dip.add_argument("--export", required=True, help="CSV file to export the dipole/polarization data")
|
|
493
|
+
p_dip.add_argument("--polarization", action="store_true",
|
|
494
|
+
help="If present, compute polarization instead of dipole")
|
|
495
|
+
p_dip.set_defaults(_run=_dipole_task)
|
|
496
|
+
|
|
497
|
+
# ---------------------- hysteresis ----------------------
|
|
498
|
+
p_hyst = subparsers.add_parser(
|
|
499
|
+
"hyst",
|
|
500
|
+
help="Polarization-field hysteresis analysis\n",
|
|
501
|
+
description=(
|
|
502
|
+
"Examples:\n"
|
|
503
|
+
" reaxkit elect hyst --plot --yaxis pol_z --xaxis field_z --aggregate mean --roots"
|
|
504
|
+
),
|
|
505
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
506
|
+
)
|
|
507
|
+
p_hyst.add_argument("--xmolout", default="xmolout", help="Path to xmolout file")
|
|
508
|
+
p_hyst.add_argument("--fort7", default="fort.7", help="Path to fort.7 file")
|
|
509
|
+
p_hyst.add_argument("--fort78", default="fort.78", help="Path to fort.78 file")
|
|
510
|
+
p_hyst.add_argument("--control", default="control", help="Path to control file")
|
|
511
|
+
p_hyst.add_argument("--plot", action="store_true", help="Show the hysteresis or time-series plot")
|
|
512
|
+
p_hyst.add_argument("--save", default='hysteresis_aggregated.png',
|
|
513
|
+
help="If set, save plot to file (e.g. hyst.png)")
|
|
514
|
+
p_hyst.add_argument("--export", default='hysteresis_aggregated.csv',
|
|
515
|
+
help="CSV file to export aggregated hysteresis data")
|
|
516
|
+
p_hyst.add_argument("--summary", default="hysteresis_summary.txt",
|
|
517
|
+
help="Text file to write coercive fields and remnant polarizations")
|
|
518
|
+
p_hyst.add_argument("--yaxis", default="pol_z", help="Quantity for y-axis (e.g. pol_z, mu_z, time, P_z)")
|
|
519
|
+
p_hyst.add_argument("--xaxis", default="field_z", help="Quantity for x-axis (e.g. field_z, time, iter)")
|
|
520
|
+
p_hyst.add_argument("--aggregate", choices=["mean", "max", "min", "last"],
|
|
521
|
+
default="mean", help="Aggregation method")
|
|
522
|
+
|
|
523
|
+
p_hyst.add_argument("--roots", action="store_true", help="Also print coercive and remnant values to terminal")
|
|
524
|
+
p_hyst.set_defaults(_run=_hyst_task)
|
|
525
|
+
|
|
526
|
+
# ---------------------- 3D local polarization scatter ----------------------
|
|
527
|
+
p3d = subparsers.add_parser(
|
|
528
|
+
"plot3d",
|
|
529
|
+
help="3D scatter of local polarization/dipole on core-atom positions\n",
|
|
530
|
+
description=(
|
|
531
|
+
"Examples:\n"
|
|
532
|
+
" reaxkit elect plot3d --core Al --component mu_z --frames 0:3:1 "
|
|
533
|
+
"--save reaxkit_outputs/elect/local_mu_3d/"
|
|
534
|
+
),
|
|
535
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
536
|
+
)
|
|
537
|
+
p3d.add_argument("--xmolout", default="xmolout", help="Path to xmolout file")
|
|
538
|
+
p3d.add_argument("--fort7", default="fort.7", help="Path to fort.7 file")
|
|
539
|
+
p3d.add_argument("--core", required=True, help="Comma-separated core atom types (e.g. Al,Mg)")
|
|
540
|
+
p3d.add_argument("--component", default="pol_z",
|
|
541
|
+
help="Which component to color by (e.g. pol_z, P_z (uC/cm^2), mu_z)")
|
|
542
|
+
p3d.add_argument("--frames", default=None, help='Frames: "0,10,20" or "0:100:5"')
|
|
543
|
+
p3d.add_argument("--polarization", action="store_true",
|
|
544
|
+
help="Use polarization components (P_x/y/z) instead of dipole")
|
|
545
|
+
p3d.add_argument("--save", default=None, help="Directory to save PNGs (one per frame)")
|
|
546
|
+
p3d.add_argument("--vmin", type=float, default=None, help="Color scale min (auto if not set)")
|
|
547
|
+
p3d.add_argument("--vmax", type=float, default=None, help="Color scale max (auto if not set)")
|
|
548
|
+
p3d.add_argument("--size", type=float, default=20.0, help="Marker size")
|
|
549
|
+
p3d.add_argument("--alpha", type=float, default=0.9, help="Marker transparency")
|
|
550
|
+
p3d.add_argument("--cmap", default="coolwarm", help="Matplotlib colormap")
|
|
551
|
+
p3d.add_argument("--elev", type=float, default=22.0, help="3D view elevation")
|
|
552
|
+
p3d.add_argument("--azim", type=float, default=38.0, help="3D view azimuth")
|
|
553
|
+
p3d.set_defaults(_run=_local_pol_plot3d_task)
|
|
554
|
+
|
|
555
|
+
# ---------------------- 2D local polarization heatmap ----------------------
|
|
556
|
+
p2d = subparsers.add_parser(
|
|
557
|
+
"heatmap2d",
|
|
558
|
+
help="2D heatmap of local polarization/dipole on core-atom positions",
|
|
559
|
+
description=(
|
|
560
|
+
"Examples:\n"
|
|
561
|
+
" reaxkit elect heatmap2d --core Al --component mu_z --plane xz --bins 10 --frames 0:2:1 "
|
|
562
|
+
"--save reaxkit_outputs/elect/local_mu_2d"
|
|
563
|
+
),
|
|
564
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
565
|
+
)
|
|
566
|
+
p2d.add_argument("--xmolout", default="xmolout", help="Path to xmolout file")
|
|
567
|
+
p2d.add_argument("--fort7", default="fort.7", help="Path to fort.7 file")
|
|
568
|
+
p2d.add_argument("--core", required=True, help="Comma-separated core atom types (e.g. Al,Mg)")
|
|
569
|
+
p2d.add_argument("--component", default="pol_z",
|
|
570
|
+
help="Which component to aggregate (e.g. pol_z, P_z (uC/cm^2), mu_z)")
|
|
571
|
+
p2d.add_argument("--frames", default=None, help='Frames: "0,10,20" or "0:100:5"')
|
|
572
|
+
p2d.add_argument("--polarization", action="store_true",
|
|
573
|
+
help="Use polarization components (P_x/y/z) instead of dipole")
|
|
574
|
+
|
|
575
|
+
# Projection/grid
|
|
576
|
+
p2d.add_argument("--plane", default="xy", choices=["xy", "xz", "yz"], help="Projection plane")
|
|
577
|
+
p2d.add_argument("--bins", default="40", help='Grid bins: "N" or "Nx,Ny" (e.g., "10,25")')
|
|
578
|
+
p2d.add_argument("--agg", default="mean", help="Aggregation: mean|max|min|sum|count")
|
|
579
|
+
|
|
580
|
+
# Viz
|
|
581
|
+
p2d.add_argument("--vmin", type=float, default=None, help="Color scale min (auto if not set)")
|
|
582
|
+
p2d.add_argument("--vmax", type=float, default=None, help="Color scale max (auto if not set)")
|
|
583
|
+
p2d.add_argument("--cmap", default="viridis", help="Matplotlib colormap")
|
|
584
|
+
p2d.add_argument("--save", default=None, help="Directory to save PNGs (one per frame)")
|
|
585
|
+
p2d.set_defaults(_run=_local_pol_heatmap2d_task)
|
|
586
|
+
|
|
587
|
+
|