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,554 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geometry (GEO) manipulation workflow for ReaxKit.
|
|
3
|
+
|
|
4
|
+
This workflow provides a collection of utilities for creating, transforming,
|
|
5
|
+
and modifying atomic geometry files used in ReaxFF simulations, with a focus
|
|
6
|
+
on the GEO (XTLGRF) format and ASE-compatible structure files.
|
|
7
|
+
|
|
8
|
+
It supports:
|
|
9
|
+
- Converting XYZ structures to GEO format with explicit cell dimensions
|
|
10
|
+
and angles.
|
|
11
|
+
- Building surface slabs from bulk structures (CIF, POSCAR, etc.), including
|
|
12
|
+
Miller-index selection, supercell expansion, and vacuum padding.
|
|
13
|
+
- Sorting atoms in GEO files by index, coordinate, or atom type.
|
|
14
|
+
- Orthogonalizing hexagonal unit cells (90°, 90°, 120°) into orthorhombic
|
|
15
|
+
representations (90°, 90°, 90°).
|
|
16
|
+
- Randomly placing multiple copies of a molecule into a simulation box or
|
|
17
|
+
around an existing structure using a placement algorithm.
|
|
18
|
+
- Inserting sample or user-defined restraint blocks (bond, angle, torsion,
|
|
19
|
+
mass-center) into GEO files for constrained simulations.
|
|
20
|
+
|
|
21
|
+
The workflow is designed to streamline preparation of ReaxFF input geometries
|
|
22
|
+
and to support reproducible, scriptable structure generation from the command line.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import List
|
|
31
|
+
|
|
32
|
+
from reaxkit.io.handlers.geo_handler import GeoHandler
|
|
33
|
+
from reaxkit.io.generators.geo_generator import (
|
|
34
|
+
xtob,
|
|
35
|
+
read_structure,
|
|
36
|
+
build_surface,
|
|
37
|
+
make_supercell,
|
|
38
|
+
write_structure,
|
|
39
|
+
_format_crystx,
|
|
40
|
+
_format_hetatm_line,
|
|
41
|
+
orthogonalize_hexagonal_cell,
|
|
42
|
+
place2,
|
|
43
|
+
)
|
|
44
|
+
from reaxkit.io.generators.geo_generator import add_restraints_to_geo
|
|
45
|
+
|
|
46
|
+
# ----------------------------------------------------------------------
|
|
47
|
+
# Helpers
|
|
48
|
+
# ----------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def _parse_csv_floats(value: str, expected: int, name: str) -> List[float]:
|
|
51
|
+
"""
|
|
52
|
+
Parse a comma-separated string of floats and validate length.
|
|
53
|
+
|
|
54
|
+
Example: "1,2,3" -> [1.0, 2.0, 3.0]
|
|
55
|
+
"""
|
|
56
|
+
parts = [v.strip() for v in value.split(",") if v.strip()]
|
|
57
|
+
if len(parts) != expected:
|
|
58
|
+
raise argparse.ArgumentTypeError(
|
|
59
|
+
f"{name} must contain exactly {expected} comma-separated values, "
|
|
60
|
+
f"got {len(parts)} from {value!r}."
|
|
61
|
+
)
|
|
62
|
+
try:
|
|
63
|
+
return [float(v) for v in parts]
|
|
64
|
+
except ValueError as exc:
|
|
65
|
+
raise argparse.ArgumentTypeError(
|
|
66
|
+
f"{name} must be numeric, could not parse {value!r}."
|
|
67
|
+
) from exc
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _parse_csv_ints(value: str, expected: int, name: str) -> List[int]:
|
|
71
|
+
"""
|
|
72
|
+
Parse a comma-separated string of ints and validate length.
|
|
73
|
+
|
|
74
|
+
Example: "1,0,0" -> [1, 0, 0]
|
|
75
|
+
"""
|
|
76
|
+
parts = [v.strip() for v in value.split(",") if v.strip()]
|
|
77
|
+
if len(parts) != expected:
|
|
78
|
+
raise argparse.ArgumentTypeError(
|
|
79
|
+
f"{name} must contain exactly {expected} comma-separated values, "
|
|
80
|
+
f"got {len(parts)} from {value!r}."
|
|
81
|
+
)
|
|
82
|
+
try:
|
|
83
|
+
return [int(v) for v in parts]
|
|
84
|
+
except ValueError as exc:
|
|
85
|
+
raise argparse.ArgumentTypeError(
|
|
86
|
+
f"{name} must be integers, could not parse {value!r}."
|
|
87
|
+
) from exc
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ----------------------------------------------------------------------
|
|
91
|
+
# Task 1: xyz -> geo (xtob) format conversion
|
|
92
|
+
# ----------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
def _xtob_task(args: argparse.Namespace) -> int:
|
|
95
|
+
"""
|
|
96
|
+
Convert XYZ → GEO (XTLGRF) using reaxkit.io.geo_generator.xtob.
|
|
97
|
+
"""
|
|
98
|
+
xyz_path = Path(args.file)
|
|
99
|
+
|
|
100
|
+
if not xyz_path.is_file():
|
|
101
|
+
raise FileNotFoundError(f"Input XYZ file not found: {xyz_path}")
|
|
102
|
+
|
|
103
|
+
dims = _parse_csv_floats(args.dims, expected=3, name="--dims")
|
|
104
|
+
angles = _parse_csv_floats(args.angles, expected=3, name="--angles")
|
|
105
|
+
output = args.output or "geo"
|
|
106
|
+
|
|
107
|
+
xtob(
|
|
108
|
+
xyz_file=xyz_path,
|
|
109
|
+
geo_file=output,
|
|
110
|
+
box_lengths=dims,
|
|
111
|
+
box_angles=angles,
|
|
112
|
+
sort_by=args.sort,
|
|
113
|
+
ascending=not args.descending,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
print(f"[Done] Converted {xyz_path} → {output}")
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# ----------------------------------------------------------------------
|
|
121
|
+
# Task 2: build slab from CIF/POSCAR and write XYZ (make)
|
|
122
|
+
# ----------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
def _make_task(args: argparse.Namespace) -> int:
|
|
125
|
+
"""
|
|
126
|
+
Build a surface slab from a bulk structure and write it to XYZ (or any ASE format).
|
|
127
|
+
|
|
128
|
+
CLI:
|
|
129
|
+
reaxkit geo make --file X.cif --output Y.xyz \
|
|
130
|
+
--surface 1,0,0 --expand 4,4,6 --vacuum 15
|
|
131
|
+
|
|
132
|
+
Mapping:
|
|
133
|
+
--surface h,k,l -> miller = (h, k, l)
|
|
134
|
+
--expand nx,ny,l -> layers = l
|
|
135
|
+
repetition = (nx, ny, 1)
|
|
136
|
+
--vacuum v -> vacuum thickness in Å
|
|
137
|
+
"""
|
|
138
|
+
in_path = Path(args.file)
|
|
139
|
+
|
|
140
|
+
if not in_path.is_file():
|
|
141
|
+
raise FileNotFoundError(f"Input structure file not found: {in_path}")
|
|
142
|
+
|
|
143
|
+
# Parse Miller indices and expansion
|
|
144
|
+
miller = _parse_csv_ints(args.surface, expected=3, name="--surface")
|
|
145
|
+
expand = _parse_csv_ints(args.expand, expected=3, name="--expand")
|
|
146
|
+
nx, ny, layers = expand
|
|
147
|
+
|
|
148
|
+
vacuum = float(args.vacuum)
|
|
149
|
+
output = args.output or "slab.xyz"
|
|
150
|
+
|
|
151
|
+
# 1) Read bulk
|
|
152
|
+
bulk = read_structure(in_path)
|
|
153
|
+
|
|
154
|
+
# 2) Build surface slab
|
|
155
|
+
slab = build_surface(
|
|
156
|
+
bulk,
|
|
157
|
+
miller=tuple(miller),
|
|
158
|
+
layers=layers,
|
|
159
|
+
vacuum=vacuum,
|
|
160
|
+
center=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# 3) Expand in-plane only: (nx, ny, 1)
|
|
164
|
+
slab_expanded = make_supercell(slab, (nx, ny, 1))
|
|
165
|
+
|
|
166
|
+
# 4) Write output (ASE will infer format from extension, e.g. .xyz)
|
|
167
|
+
write_structure(slab_expanded, output)
|
|
168
|
+
|
|
169
|
+
print(
|
|
170
|
+
f"[Done] Built surface {tuple(miller)} with layers={layers}, "
|
|
171
|
+
f"expanded ({nx}, {ny}, 1) and wrote {output}"
|
|
172
|
+
)
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
# ----------------------------------------------------------------------
|
|
176
|
+
# Task 3: sort a geo file
|
|
177
|
+
# ----------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
def _sort_task(args: argparse.Namespace) -> int:
|
|
180
|
+
"""
|
|
181
|
+
Sort atoms in a GEO file and write a new GEO file.
|
|
182
|
+
|
|
183
|
+
CLI example:
|
|
184
|
+
reaxkit geo sort --file X.geo --output Y.geo --sort m --descending
|
|
185
|
+
|
|
186
|
+
where:
|
|
187
|
+
--sort m → sort by atom index (atom_id)
|
|
188
|
+
--sort x|y|z → sort by coordinate
|
|
189
|
+
--sort atom_type → sort by atom type
|
|
190
|
+
"""
|
|
191
|
+
in_path = Path(args.file)
|
|
192
|
+
if not in_path.is_file():
|
|
193
|
+
raise FileNotFoundError(f"Input GEO file not found: {in_path}")
|
|
194
|
+
|
|
195
|
+
handler = GeoHandler(in_path)
|
|
196
|
+
df = handler.dataframe().copy()
|
|
197
|
+
meta = handler.metadata()
|
|
198
|
+
|
|
199
|
+
sort_map = {"m": "atom_id", "x": "x", "y": "y", "z": "z", "atom_type": "atom_type"}
|
|
200
|
+
if args.sort not in sort_map:
|
|
201
|
+
raise ValueError(f"--sort must be one of {list(sort_map.keys())}, got {args.sort!r}")
|
|
202
|
+
|
|
203
|
+
sort_col = sort_map[args.sort]
|
|
204
|
+
ascending = not args.descending
|
|
205
|
+
|
|
206
|
+
df_sorted = df.sort_values(by=sort_col, ascending=ascending).reset_index(drop=True)
|
|
207
|
+
df_sorted["atom_id"] = df_sorted.index + 1 # renumber sequentially
|
|
208
|
+
|
|
209
|
+
descriptor = meta.get("descriptor") or ""
|
|
210
|
+
remark = meta.get("remark")
|
|
211
|
+
cell_lengths = meta.get("cell_lengths")
|
|
212
|
+
cell_angles = meta.get("cell_angles")
|
|
213
|
+
|
|
214
|
+
out_path = Path(args.output)
|
|
215
|
+
direction = "descending" if args.descending else "ascending"
|
|
216
|
+
sort_label_map = {"m": "atom index", "x": "x-coordinate", "y": "y-coordinate", "z": "z-coordinate", "atom_type": "atom type"}
|
|
217
|
+
sort_label = sort_label_map[args.sort]
|
|
218
|
+
|
|
219
|
+
with out_path.open("w") as fh:
|
|
220
|
+
fh.write("XTLGRF 200\n")
|
|
221
|
+
if descriptor:
|
|
222
|
+
fh.write(f"DESCRP {descriptor}\n")
|
|
223
|
+
if remark:
|
|
224
|
+
fh.write(f"REMARK {remark}\n")
|
|
225
|
+
fh.write(f"REMARK Structure sorted by {sort_label} ({direction})\n")
|
|
226
|
+
|
|
227
|
+
if cell_lengths and cell_angles:
|
|
228
|
+
try:
|
|
229
|
+
lengths = [cell_lengths.get(k) for k in ("a", "b", "c")]
|
|
230
|
+
angles = [cell_angles.get(k) for k in ("alpha", "beta", "gamma")]
|
|
231
|
+
if all(v is not None for v in lengths + angles):
|
|
232
|
+
fh.write(_format_crystx(lengths, angles) + "\n")
|
|
233
|
+
except Exception:
|
|
234
|
+
# If anything is weird, just skip CRYSTX instead of crashing
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
fh.write("FORMAT ATOM (a6,1x,i5,1x,a5,1x,a3,1x,a1,1x,a5,3f10.5,1x,a5,i3,i2,1x,f8.5)\n")
|
|
238
|
+
for row in df_sorted.itertuples(index=False):
|
|
239
|
+
line = _format_hetatm_line(row.atom_id, row.atom_type, row.x, row.y, row.z)
|
|
240
|
+
fh.write(line + "\n")
|
|
241
|
+
fh.write("END\n")
|
|
242
|
+
|
|
243
|
+
print(f"[Done] Sorted {in_path} by {sort_label} ({direction}) → {out_path}")
|
|
244
|
+
return 0
|
|
245
|
+
|
|
246
|
+
# --------------------------------------------------------------------------------------------------
|
|
247
|
+
# Task 4: convert a hexagonal cell (90°, 90°, 120°) into an orthorhombic (90°, 90°, 90°) cell
|
|
248
|
+
# --------------------------------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
def _ortho_task(args: argparse.Namespace) -> int:
|
|
251
|
+
"""
|
|
252
|
+
Orthogonalize a hexagonal (90,90,120) cell into an orthorhombic (90,90,90) cell.
|
|
253
|
+
|
|
254
|
+
CLI example:
|
|
255
|
+
reaxkit geo ortho --file AlN_hex.cif --output AlN_ortho.cif
|
|
256
|
+
"""
|
|
257
|
+
in_path = Path(args.file)
|
|
258
|
+
out_path = Path(args.output)
|
|
259
|
+
|
|
260
|
+
if not in_path.is_file():
|
|
261
|
+
raise FileNotFoundError(f"Input structure file not found: {in_path}")
|
|
262
|
+
|
|
263
|
+
# 1. Read structure using ASE
|
|
264
|
+
atoms = read_structure(in_path)
|
|
265
|
+
|
|
266
|
+
# 2. Apply orthogonalization transform
|
|
267
|
+
ortho_atoms = orthogonalize_hexagonal_cell(atoms)
|
|
268
|
+
|
|
269
|
+
# 3. Write output (ASE infers format from extension)
|
|
270
|
+
write_structure(ortho_atoms, out_path)
|
|
271
|
+
|
|
272
|
+
print(f"[Done] Converted hexagonal → orthorhombic: {in_path} → {out_path}")
|
|
273
|
+
return 0
|
|
274
|
+
|
|
275
|
+
# ------------------------------------------------------------------------------------
|
|
276
|
+
# task 5: place2 algorithm for placing n instances of a structure into a
|
|
277
|
+
# simulation box or within another structure
|
|
278
|
+
# ------------------------------------------------------------------------------------
|
|
279
|
+
def _place2_task(args: argparse.Namespace) -> int:
|
|
280
|
+
"""
|
|
281
|
+
Randomly place copies of an insert molecule into a simulation box,
|
|
282
|
+
optionally around/within a base structure.
|
|
283
|
+
|
|
284
|
+
CLI example:
|
|
285
|
+
reaxkit geo place2 \
|
|
286
|
+
--insert X.xyz \
|
|
287
|
+
--ncopy 40 \
|
|
288
|
+
--dims 1,2,3 \
|
|
289
|
+
--angles 90,90,90 \
|
|
290
|
+
--output Y.xyz \
|
|
291
|
+
[--base base.xyz] \
|
|
292
|
+
[--mindist 3.0] \
|
|
293
|
+
[--baseplace as-is|center|origin] \
|
|
294
|
+
[--maxattempt 50000] \
|
|
295
|
+
[--randomseed 1234]
|
|
296
|
+
|
|
297
|
+
If output is:
|
|
298
|
+
- *.xyz → write XYZ directly.
|
|
299
|
+
- geo or *.bgf or anything non-*.xyz →
|
|
300
|
+
1) write place2_output.xyz
|
|
301
|
+
2) run xtob on that to generate requested output.
|
|
302
|
+
"""
|
|
303
|
+
insert_path = Path(args.insert)
|
|
304
|
+
if not insert_path.is_file():
|
|
305
|
+
raise FileNotFoundError(f"Insert molecule not found: {insert_path}")
|
|
306
|
+
|
|
307
|
+
base_path = None
|
|
308
|
+
if args.base is not None:
|
|
309
|
+
base_path = Path(args.base)
|
|
310
|
+
if not base_path.is_file():
|
|
311
|
+
raise FileNotFoundError(f"Base structure not found: {base_path}")
|
|
312
|
+
|
|
313
|
+
# Parse box dimensions and angles
|
|
314
|
+
dims = _parse_csv_floats(args.dims, expected=3, name="--dims")
|
|
315
|
+
angles = _parse_csv_floats(args.angles, expected=3, name="--angles")
|
|
316
|
+
a, b, c = dims
|
|
317
|
+
alpha, beta, gamma = angles
|
|
318
|
+
|
|
319
|
+
# Optional parameters
|
|
320
|
+
min_dist = float(args.mindist)
|
|
321
|
+
baseplace = args.baseplace
|
|
322
|
+
max_attempt = int(args.maxattempt)
|
|
323
|
+
random_seed = None if args.randomseed is None else int(args.randomseed)
|
|
324
|
+
|
|
325
|
+
# Run the placement algorithm
|
|
326
|
+
atoms = place2(
|
|
327
|
+
insert_molecule=insert_path,
|
|
328
|
+
base_structure=base_path,
|
|
329
|
+
n_copies=int(args.ncopy),
|
|
330
|
+
box_length_x=a,
|
|
331
|
+
box_length_y=b,
|
|
332
|
+
box_length_z=c,
|
|
333
|
+
alpha=alpha,
|
|
334
|
+
beta=beta,
|
|
335
|
+
gamma=gamma,
|
|
336
|
+
min_interatomic_distance=min_dist,
|
|
337
|
+
base_structure_placement_mode=baseplace,
|
|
338
|
+
max_placement_attempts_per_copy=max_attempt,
|
|
339
|
+
random_seed=random_seed,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Handle output
|
|
343
|
+
out_path = Path(args.output)
|
|
344
|
+
ext = out_path.suffix.lower()
|
|
345
|
+
|
|
346
|
+
if ext == ".xyz":
|
|
347
|
+
# Direct XYZ write
|
|
348
|
+
write_structure(atoms, out_path)
|
|
349
|
+
print(f"[Done] Placed {args.ncopy} copies into box → {out_path}")
|
|
350
|
+
else:
|
|
351
|
+
# Intermediate XYZ then xtob → GEO/BGF/etc.
|
|
352
|
+
tmp_xyz = Path("place2_output.xyz")
|
|
353
|
+
write_structure(atoms, tmp_xyz)
|
|
354
|
+
xtob(
|
|
355
|
+
xyz_file=tmp_xyz,
|
|
356
|
+
geo_file=out_path,
|
|
357
|
+
box_lengths=dims,
|
|
358
|
+
box_angles=angles,
|
|
359
|
+
sort_by=None,
|
|
360
|
+
ascending=True,
|
|
361
|
+
)
|
|
362
|
+
print(
|
|
363
|
+
f"[Done] Placed {args.ncopy} copies into box → {tmp_xyz} "
|
|
364
|
+
f"→ converted to {out_path} via xtob"
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
return 0
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# ----------------------------------------------------------------------
|
|
371
|
+
# Task 6: add restraint block to GEO
|
|
372
|
+
# ----------------------------------------------------------------------
|
|
373
|
+
def _add_restraint_task(args: argparse.Namespace) -> int:
|
|
374
|
+
in_path = Path(args.file)
|
|
375
|
+
if not in_path.is_file():
|
|
376
|
+
raise FileNotFoundError(f"Input GEO file not found: {in_path}")
|
|
377
|
+
|
|
378
|
+
out_path = Path(args.output) if args.output else (in_path.parent / f"{in_path.name}_with_restraints")
|
|
379
|
+
|
|
380
|
+
# Build params dict: user can pass per-kind params or nothing -> defaults
|
|
381
|
+
params = {}
|
|
382
|
+
if args.bond is not None:
|
|
383
|
+
params["BOND"] = args.bond.strip()
|
|
384
|
+
if args.angle is not None:
|
|
385
|
+
params["ANGLE"] = args.angle.strip()
|
|
386
|
+
if args.torsion is not None:
|
|
387
|
+
params["TORSION"] = args.torsion.strip()
|
|
388
|
+
if args.mascen is not None:
|
|
389
|
+
params["MASCEN"] = args.mascen.strip()
|
|
390
|
+
|
|
391
|
+
# kinds are inferred from which flags user provided
|
|
392
|
+
kinds = []
|
|
393
|
+
if args.bond is not None:
|
|
394
|
+
kinds.append("BOND")
|
|
395
|
+
if args.angle is not None:
|
|
396
|
+
kinds.append("ANGLE")
|
|
397
|
+
if args.torsion is not None:
|
|
398
|
+
kinds.append("TORSION")
|
|
399
|
+
if args.mascen is not None:
|
|
400
|
+
kinds.append("MASCEN")
|
|
401
|
+
|
|
402
|
+
if not kinds:
|
|
403
|
+
raise ValueError(
|
|
404
|
+
"No restraints requested. Provide at least one of: "
|
|
405
|
+
"--bond, --angle, --torsion, --mascen"
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
out_written = add_restraints_to_geo(
|
|
409
|
+
in_path,
|
|
410
|
+
out_file=out_path,
|
|
411
|
+
kinds=kinds,
|
|
412
|
+
params=params,
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
print(f"[Done] Added restraints to {in_path} and the result is exported as {out_written}")
|
|
416
|
+
return 0
|
|
417
|
+
|
|
418
|
+
# ----------------------------------------------------------------------
|
|
419
|
+
# CLI registration
|
|
420
|
+
# ----------------------------------------------------------------------
|
|
421
|
+
|
|
422
|
+
def register_tasks(subparsers: argparse._SubParsersAction) -> None:
|
|
423
|
+
# ---- xtob ----
|
|
424
|
+
p_xtob = subparsers.add_parser(
|
|
425
|
+
"xtob",
|
|
426
|
+
help="Convert an XYZ file to GEO (XTLGRF) format \n",
|
|
427
|
+
description=(
|
|
428
|
+
"Examples:\n"
|
|
429
|
+
" reaxkit geo xtob --file slab.xyz --dims 11.0,12.0,100.0 --angles 90,90,90 --output slab_geo_from_xyz\n"
|
|
430
|
+
),
|
|
431
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
432
|
+
)
|
|
433
|
+
p_xtob.add_argument("--file", required=True, help="Input XYZ file (X.xyz)")
|
|
434
|
+
p_xtob.add_argument("--dims", required=True, help="Box dimensions a,b,c (e.g., 11.0,12.0,100.0)")
|
|
435
|
+
p_xtob.add_argument("--angles", required=True, help="Box angles alpha,beta,gamma (e.g., 90,90,90)")
|
|
436
|
+
p_xtob.add_argument("--output", default="geo", help="Output GEO file name (default: geo)")
|
|
437
|
+
p_xtob.add_argument("--sort", choices=["x", "y", "z", "atom_type"], help="Sort atoms before writing GEO")
|
|
438
|
+
p_xtob.add_argument("--descending", action="store_true", help="Sort in descending order")
|
|
439
|
+
p_xtob.set_defaults(_run=_xtob_task)
|
|
440
|
+
|
|
441
|
+
# ---- make ----
|
|
442
|
+
p_make = subparsers.add_parser(
|
|
443
|
+
"make",
|
|
444
|
+
help="Build a surface slab from bulk and write XYZ/CIF || ",
|
|
445
|
+
description=(
|
|
446
|
+
"Examples:\n"
|
|
447
|
+
" reaxkit geo make --file AlN.cif --output slab_from_AlN_cif.xyz --surface 1,0,0 --expand 4,4,6 --vacuum 15\n"
|
|
448
|
+
),
|
|
449
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
450
|
+
)
|
|
451
|
+
p_make.add_argument("--file", required=True, help="Input bulk file (CIF, POSCAR, etc.)")
|
|
452
|
+
p_make.add_argument("--output", required=True, help="Output file (XYZ, CIF, etc.)")
|
|
453
|
+
p_make.add_argument("--surface", required=True, help="Miller indices h,k,l (e.g., 1,0,0)")
|
|
454
|
+
p_make.add_argument("--expand", required=True, help="Supercell and layers nx,ny,layers (e.g., 4,4,6)")
|
|
455
|
+
p_make.add_argument("--vacuum", required=True, help="Vacuum thickness in Å (e.g., 15)")
|
|
456
|
+
p_make.set_defaults(_run=_make_task)
|
|
457
|
+
|
|
458
|
+
# ---- sort ----
|
|
459
|
+
p_sort = subparsers.add_parser(
|
|
460
|
+
"sort",
|
|
461
|
+
help="Sort atoms in a GEO file and write a new GEO \n",
|
|
462
|
+
description=(
|
|
463
|
+
"Examples:\n"
|
|
464
|
+
" reaxkit geo sort --file geo --output sorted_geo --sort x\n"
|
|
465
|
+
),
|
|
466
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
467
|
+
)
|
|
468
|
+
p_sort.add_argument("--file", required=True, help="Input GEO file (X.geo)")
|
|
469
|
+
p_sort.add_argument("--output", required=True, help="Output GEO file (Y.geo)")
|
|
470
|
+
p_sort.add_argument("--sort", required=True, choices=["m", "x", "y", "z", "atom_type"],
|
|
471
|
+
help="Sort key: m=atom index, x/y/z=coordinates, atom_type=element")
|
|
472
|
+
p_sort.add_argument("--descending", action="store_true", help="Sort in descending order")
|
|
473
|
+
p_sort.set_defaults(_run=_sort_task)
|
|
474
|
+
|
|
475
|
+
# ---- ortho (orthogonalize) ----
|
|
476
|
+
p_ortho = subparsers.add_parser(
|
|
477
|
+
"ortho",
|
|
478
|
+
help="Convert hexagonal (90,90,120) cell to orthorhombic (90,90,90) \n",
|
|
479
|
+
description=(
|
|
480
|
+
"Examples:\n"
|
|
481
|
+
" reaxkit geo ortho --file AlN.cif --output AlN_ortho_from_hex.cif\n"
|
|
482
|
+
),
|
|
483
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
484
|
+
)
|
|
485
|
+
p_ortho.add_argument("--file", required=True, help="Input CIF/POSCAR/GEO file to orthogonalize")
|
|
486
|
+
p_ortho.add_argument("--output", required=True, help="Output file (e.g., AlN_ortho.cif)")
|
|
487
|
+
p_ortho.set_defaults(_run=_ortho_task)
|
|
488
|
+
|
|
489
|
+
# ---- place2 ----
|
|
490
|
+
p_place2 = subparsers.add_parser(
|
|
491
|
+
"place2",
|
|
492
|
+
help="Randomly place copies of a molecule into a box and optionally around a base structure || ",
|
|
493
|
+
description=(
|
|
494
|
+
"Examples:\n"
|
|
495
|
+
" reaxkit geo place2 --insert template.xyz --ncopy 40 --dims 28.8,33.27,60 "
|
|
496
|
+
"--angles 90,90,90 --output place2_on_template_xyz_with_no_base.xyz\n"
|
|
497
|
+
" reaxkit geo place2 --insert template.xyz --ncopy 40 --dims 28.8,33.27,60 "
|
|
498
|
+
"--angles 90,90,90 --output place2__on_template_xyz_with_base.xyz --base base.xyz\n"
|
|
499
|
+
" reaxkit geo place2 --insert template.xyz --ncopy 40 --dims 28.8,33.27,60 "
|
|
500
|
+
"--angles 90,90,90 --output place2_geo_from_template_xyz --base base.xyz"
|
|
501
|
+
),
|
|
502
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
503
|
+
)
|
|
504
|
+
p_place2.add_argument("--insert", required=True,
|
|
505
|
+
help="Insert molecule (XYZ or any ASE-readable format, e.g., X.xyz)",
|
|
506
|
+
)
|
|
507
|
+
p_place2.add_argument("--ncopy", required=True, help="Number of copies of the insert molecule to place")
|
|
508
|
+
p_place2.add_argument("--dims", required=True, help="Box dimensions a,b,c (e.g., 30,30,60)")
|
|
509
|
+
p_place2.add_argument("--angles", required=True, help="Box angles alpha,beta,gamma (e.g., 90,90,90)")
|
|
510
|
+
p_place2.add_argument("--output", required=True, help="Output file: Y.xyz, Y.bgf, or 'geo'")
|
|
511
|
+
p_place2.add_argument("--base",help="Optional base structure (e.g., slab.xyz) to place molecules around")
|
|
512
|
+
p_place2.add_argument("--mindist", default=2.0,
|
|
513
|
+
help="Minimum interatomic distance between insert copies and base/system (Å), default=2.0",
|
|
514
|
+
)
|
|
515
|
+
p_place2.add_argument("--baseplace", default="as-is", choices=["as-is", "center", "origin"],
|
|
516
|
+
help="How to place the base structure: as-is, center, or origin (default: as-is)"
|
|
517
|
+
)
|
|
518
|
+
p_place2.add_argument("--maxattempt", default=50000, help="Maximum placement attempts per copy (default: 50000)")
|
|
519
|
+
p_place2.add_argument("--randomseed", default=None, help="Random seed for reproducible placement (optional)")
|
|
520
|
+
p_place2.set_defaults(_run=_place2_task)
|
|
521
|
+
|
|
522
|
+
# ---- add-restraint ----
|
|
523
|
+
p_rest = subparsers.add_parser(
|
|
524
|
+
"add-restraint",
|
|
525
|
+
help="Insert a sample restraint block (BOND/ANGLE/TORSION/MASCEN) into a GEO file",
|
|
526
|
+
description=(
|
|
527
|
+
"Examples:\n"
|
|
528
|
+
" reaxkit geo add-restraint --bond \n"
|
|
529
|
+
" reaxkit geo add-restraint --file geo --output geo_r --angle '1 2 3 109.5000 600.00 0.25000 0.0000000'\n"
|
|
530
|
+
),
|
|
531
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
p_rest.add_argument("--file", default="geo", help="Input GEO file (e.g., geo)")
|
|
535
|
+
p_rest.add_argument("--output", default="reaxkit_generated_inputs/geo_with_restraints",
|
|
536
|
+
help="Output GEO file (default: <input>_with_restraints)")
|
|
537
|
+
|
|
538
|
+
# Each flag can be:
|
|
539
|
+
# - omitted (not requested)
|
|
540
|
+
# - provided with "" (empty) to request a default sample
|
|
541
|
+
# - provided with a params string to use exactly that
|
|
542
|
+
p_rest.add_argument("--bond", nargs="?", const="", default=None,
|
|
543
|
+
help="Add ONE BOND restraint (optional params string; empty => default sample).")
|
|
544
|
+
p_rest.add_argument("--angle", nargs="?", const="", default=None,
|
|
545
|
+
help="Add ONE ANGLE restraint (optional params string; empty => default sample).")
|
|
546
|
+
p_rest.add_argument("--torsion", nargs="?", const="", default=None,
|
|
547
|
+
help="Add ONE TORSION restraint (optional params string; empty => default sample).")
|
|
548
|
+
p_rest.add_argument("--mascen", nargs="?", const="", default=None,
|
|
549
|
+
help="Add ONE MASCEN restraint (optional params string; empty => default sample).")
|
|
550
|
+
|
|
551
|
+
p_rest.set_defaults(_run=_add_restraint_task)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
|