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,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Temperature-regime (tregime.in) file generators.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for generating ReaxFF ``tregime.in`` files,
|
|
5
|
+
which define temperature control zones and thermostat parameters used
|
|
6
|
+
during molecular dynamics simulations.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- creating a valid sample ``tregime.in`` file for new simulations
|
|
11
|
+
- inspecting column layout and formatting rules
|
|
12
|
+
- using a template as a starting point for custom temperature schedules
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any, Dict, Sequence
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def write_sample_tregime(
|
|
21
|
+
out_path: str | Path = "tregime.in",
|
|
22
|
+
*,
|
|
23
|
+
n_rows: int = 3,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Write a sample tregime.in file with fixed-width, left-aligned columns.
|
|
27
|
+
|
|
28
|
+
Works on
|
|
29
|
+
--------
|
|
30
|
+
ReaxFF temperature-regime input — ``tregime.in``
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
out_path : str or pathlib.Path, optional
|
|
35
|
+
Output file path for the generated tregime file.
|
|
36
|
+
n_rows : int, optional
|
|
37
|
+
Number of example data rows to write.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
None
|
|
42
|
+
Writes a formatted ``tregime.in`` file to disk.
|
|
43
|
+
|
|
44
|
+
Examples
|
|
45
|
+
--------
|
|
46
|
+
>>> from reaxkit.io.generators.tregime_generator import write_sample_tregime
|
|
47
|
+
>>> write_sample_tregime("tregime.in", n_rows=2)
|
|
48
|
+
"""
|
|
49
|
+
out_path = Path(out_path)
|
|
50
|
+
|
|
51
|
+
# (name, width, kind)
|
|
52
|
+
COLS = [
|
|
53
|
+
("#Start", 10, "int"),
|
|
54
|
+
("#Zones", 10, "int"),
|
|
55
|
+
("At1", 8, "int"),
|
|
56
|
+
("At2", 8, "int"),
|
|
57
|
+
("Tset1", 10, "float1"),
|
|
58
|
+
("Tdamp1", 10, "float1"),
|
|
59
|
+
("dT1/dt", 10, "float3"),
|
|
60
|
+
("At3", 8, "int"),
|
|
61
|
+
("At4", 8, "int"),
|
|
62
|
+
("Tset2", 10, "float1"),
|
|
63
|
+
("Tdamp2", 10, "float1"),
|
|
64
|
+
("dT2/dt", 10, "float3"),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
SEP = " " # two spaces between columns (keeps it readable)
|
|
68
|
+
|
|
69
|
+
def _fmt_value(v: Any, kind: str) -> str:
|
|
70
|
+
if kind == "int":
|
|
71
|
+
return str(int(v))
|
|
72
|
+
if kind == "float1":
|
|
73
|
+
return f"{float(v):.1f}"
|
|
74
|
+
if kind == "float3":
|
|
75
|
+
return f"{float(v):.3f}"
|
|
76
|
+
return str(v)
|
|
77
|
+
|
|
78
|
+
def _pad_left(s: str, width: int) -> str:
|
|
79
|
+
# left-align content within fixed width
|
|
80
|
+
if len(s) > width:
|
|
81
|
+
return s[:width]
|
|
82
|
+
return s.ljust(width)
|
|
83
|
+
|
|
84
|
+
def format_header() -> str:
|
|
85
|
+
return SEP.join(_pad_left(name, width) for name, width, _ in COLS)
|
|
86
|
+
|
|
87
|
+
def format_row(values: Dict[str, Any]) -> str:
|
|
88
|
+
parts = []
|
|
89
|
+
for name, width, kind in COLS:
|
|
90
|
+
raw = _fmt_value(values.get(name, 0), kind)
|
|
91
|
+
parts.append(_pad_left(raw, width))
|
|
92
|
+
return SEP.join(parts)
|
|
93
|
+
|
|
94
|
+
# Example rows
|
|
95
|
+
rows: Sequence[Dict[str, Any]] = [
|
|
96
|
+
{
|
|
97
|
+
"#Start": 0, "#Zones": 2,
|
|
98
|
+
"At1": 1, "At2": 50, "Tset1": 300.0, "Tdamp1": 100.0, "dT1/dt": 0.050,
|
|
99
|
+
"At3": 51, "At4": 100, "Tset2": 600.0, "Tdamp2": 100.0, "dT2/dt": 0.100,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"#Start": 5000, "#Zones": 1,
|
|
103
|
+
"At1": 1, "At2": 100, "Tset1": 900.0, "Tdamp1": 200.0, "dT1/dt": 0.020,
|
|
104
|
+
"At3": 0, "At4": 0, "Tset2": 0.0, "Tdamp2": 0.0, "dT2/dt": 0.000,
|
|
105
|
+
},
|
|
106
|
+
][:n_rows]
|
|
107
|
+
|
|
108
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
with out_path.open("w", newline="\n") as fh:
|
|
110
|
+
fh.write(format_header() + "\n")
|
|
111
|
+
for r in rows:
|
|
112
|
+
fh.write(format_row(r) + "\n")
|
|
113
|
+
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Volume-regime (vregime.in) file generators.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for generating ReaxFF ``vregime.in`` files,
|
|
5
|
+
which define how simulation cell dimensions and angles are modified over
|
|
6
|
+
time during molecular dynamics simulations.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- creating a valid sample ``vregime.in`` file for new simulations
|
|
11
|
+
- inspecting column layout and formatting rules for volume schedules
|
|
12
|
+
- using a template as a starting point for custom volume/strain protocols
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Dict, List, Sequence
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def write_sample_vregime(
|
|
22
|
+
out_path: str | Path = "vregime.in",
|
|
23
|
+
*,
|
|
24
|
+
n_rows: int = 5,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Write a sample vregime.in file with fixed-width, left-aligned columns.
|
|
28
|
+
|
|
29
|
+
Works on
|
|
30
|
+
--------
|
|
31
|
+
ReaxFF volume-regime input — ``vregime.in``
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
out_path : str or pathlib.Path, optional
|
|
36
|
+
Output file path for the generated vregime file.
|
|
37
|
+
n_rows : int, optional
|
|
38
|
+
Number of example data rows to write.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
None
|
|
43
|
+
Writes a formatted ``vregime.in`` file to disk.
|
|
44
|
+
|
|
45
|
+
Examples
|
|
46
|
+
--------
|
|
47
|
+
>>> from reaxkit.io.generators.vregime_generator import write_sample_vregime
|
|
48
|
+
>>> write_sample_vregime("vregime.in", n_rows=3)
|
|
49
|
+
"""
|
|
50
|
+
out_path = Path(out_path)
|
|
51
|
+
|
|
52
|
+
# Fixed widths (LEFT aligned)
|
|
53
|
+
W_START = 6 # gives room for 4-digit start + spaces
|
|
54
|
+
W_V = 4 # '#V' column
|
|
55
|
+
W_TYPE = 6 # 'alfa', 'beta', 'a', 'b', etc.
|
|
56
|
+
W_CHANGE = 12 # change/it numeric field
|
|
57
|
+
W_RESCALE = 8 # 'y' / 'n'
|
|
58
|
+
|
|
59
|
+
SEP = " " # single-space like your working example
|
|
60
|
+
|
|
61
|
+
def _pad(s: str, w: int) -> str:
|
|
62
|
+
s = "" if s is None else str(s)
|
|
63
|
+
return s[:w].ljust(w)
|
|
64
|
+
|
|
65
|
+
def _fmt_start(v: Any) -> str:
|
|
66
|
+
# match the example: 0000, 0100, 0200, ...
|
|
67
|
+
return f"{int(v):04d}"
|
|
68
|
+
|
|
69
|
+
def _fmt_change(v: Any, *, decimals: int = 6) -> str:
|
|
70
|
+
# match the example's precision style (0.050000, -0.010000, etc.)
|
|
71
|
+
return f"{float(v):.{decimals}f}"
|
|
72
|
+
|
|
73
|
+
# Header
|
|
74
|
+
header1 = "#Volume regimes"
|
|
75
|
+
header2 = (
|
|
76
|
+
_pad("#start", W_START)
|
|
77
|
+
+ SEP
|
|
78
|
+
+ _pad("#V", W_V)
|
|
79
|
+
+ SEP
|
|
80
|
+
+ _pad("type1", W_TYPE)
|
|
81
|
+
+ SEP
|
|
82
|
+
+ _pad("change/it", W_CHANGE)
|
|
83
|
+
+ SEP
|
|
84
|
+
+ _pad("rescale", W_RESCALE)
|
|
85
|
+
+ SEP
|
|
86
|
+
+ _pad("type 2", W_TYPE)
|
|
87
|
+
+ SEP
|
|
88
|
+
+ _pad("change/it", W_CHANGE)
|
|
89
|
+
+ SEP
|
|
90
|
+
+ _pad("rescale", W_RESCALE)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Sample rows (mirrors your screenshot)
|
|
94
|
+
# Each row: {"start": int, "terms": [{"type": str, "change": float, "rescale": "y|n"}, ...]}
|
|
95
|
+
rows: List[Dict[str, Any]] = [
|
|
96
|
+
{
|
|
97
|
+
"start": 0,
|
|
98
|
+
"terms": [
|
|
99
|
+
{"type": "alfa", "change": 0.050000, "rescale": "y"},
|
|
100
|
+
{"type": "beta", "change": -0.050000, "rescale": "y"},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"start": 100,
|
|
105
|
+
"terms": [
|
|
106
|
+
{"type": "beta", "change": 0.050000, "rescale": "y"},
|
|
107
|
+
{"type": "alfa", "change": -0.050000, "rescale": "y"},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"start": 200,
|
|
112
|
+
"terms": [
|
|
113
|
+
{"type": "a", "change": 0.010000, "rescale": "y"},
|
|
114
|
+
{"type": "b", "change": -0.010000, "rescale": "y"},
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"start": 300,
|
|
119
|
+
"terms": [
|
|
120
|
+
{"type": "a", "change": -0.010000, "rescale": "y"},
|
|
121
|
+
{"type": "b", "change": 0.010000, "rescale": "y"},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"start": 400,
|
|
126
|
+
"terms": [
|
|
127
|
+
{"type": "a", "change": -0.010000, "rescale": "y"},
|
|
128
|
+
{"type": "alfa", "change": 0.050000, "rescale": "y"},
|
|
129
|
+
{"type": "b", "change": 0.010000, "rescale": "y"},
|
|
130
|
+
{"type": "beta", "change": 0.050000, "rescale": "y"},
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
][:n_rows]
|
|
134
|
+
|
|
135
|
+
def format_row(r: Dict[str, Any]) -> str:
|
|
136
|
+
terms = list(r.get("terms", []))
|
|
137
|
+
vcount = len(terms)
|
|
138
|
+
|
|
139
|
+
line = (
|
|
140
|
+
_pad(_fmt_start(r.get("start", 0)), W_START)
|
|
141
|
+
+ SEP
|
|
142
|
+
+ _pad(str(vcount), W_V)
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# Append each (type, change, rescale) group
|
|
146
|
+
for t in terms:
|
|
147
|
+
line += (
|
|
148
|
+
SEP
|
|
149
|
+
+ _pad(str(t.get("type", "")), W_TYPE)
|
|
150
|
+
+ SEP
|
|
151
|
+
+ _pad(_fmt_change(t.get("change", 0.0)), W_CHANGE)
|
|
152
|
+
+ SEP
|
|
153
|
+
+ _pad(str(t.get("rescale", "y")), W_RESCALE)
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return line.rstrip()
|
|
157
|
+
|
|
158
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
159
|
+
with out_path.open("w", newline="\n") as fh:
|
|
160
|
+
fh.write(header1 + "\n")
|
|
161
|
+
fh.write(header2.rstrip() + "\n")
|
|
162
|
+
for r in rows:
|
|
163
|
+
fh.write(format_row(r) + "\n")
|
|
164
|
+
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
XMOL trajectory file generators.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for generating new ReaxFF ``xmolout`` files
|
|
5
|
+
from in-memory trajectory data or from an existing ``XmoloutHandler``.
|
|
6
|
+
Generated files are fully compatible with downstream ReaxKit analyses
|
|
7
|
+
(e.g., coordination, connectivity, and visualization workflows).
|
|
8
|
+
|
|
9
|
+
Typical use cases include:
|
|
10
|
+
|
|
11
|
+
- writing a filtered or reduced ``xmolout`` from an existing trajectory
|
|
12
|
+
- exporting selected frames or atoms for focused analysis
|
|
13
|
+
- generating synthetic or post-processed trajectories with extra per-atom fields
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Iterable, Optional, Sequence, Union, Dict, Any, List
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
from reaxkit.io.handlers.xmolout_handler import XmoloutHandler
|
|
23
|
+
|
|
24
|
+
FrameSel = Optional[Union[Sequence[int], range, slice]]
|
|
25
|
+
AtomSel = Optional[Union[Sequence[int], slice]]
|
|
26
|
+
|
|
27
|
+
def _normalize_frames_for_write(xh: XmoloutHandler, frames: FrameSel) -> list[int]:
|
|
28
|
+
n = xh.n_frames()
|
|
29
|
+
if frames is None:
|
|
30
|
+
return list(range(n))
|
|
31
|
+
if isinstance(frames, slice):
|
|
32
|
+
return list(range(*frames.indices(n)))
|
|
33
|
+
return [int(i) for i in frames if 0 <= int(i) < n]
|
|
34
|
+
|
|
35
|
+
def _normalize_atoms_for_write(frame_dict: Dict[str, Any],
|
|
36
|
+
atoms: AtomSel,
|
|
37
|
+
atom_types: Optional[Sequence[str]]) -> list[int]:
|
|
38
|
+
n_atoms = frame_dict["coords"].shape[0]
|
|
39
|
+
if atoms is not None:
|
|
40
|
+
if isinstance(atoms, slice):
|
|
41
|
+
return list(range(*atoms.indices(n_atoms)))
|
|
42
|
+
return [int(a) for a in atoms if 0 <= int(a) < n_atoms]
|
|
43
|
+
if atom_types:
|
|
44
|
+
tset = {str(t) for t in atom_types}
|
|
45
|
+
return [j for j, t in enumerate(frame_dict["atom_types"]) if str(t) in tset]
|
|
46
|
+
return list(range(n_atoms))
|
|
47
|
+
|
|
48
|
+
def _format_header_line(sim_name: str,
|
|
49
|
+
iter: int,
|
|
50
|
+
E_pot: float,
|
|
51
|
+
a: float, b: float, c: float,
|
|
52
|
+
alpha: float, beta: float, gamma: float,
|
|
53
|
+
prec: int) -> str:
|
|
54
|
+
f = f"{{:.{prec}f}}"
|
|
55
|
+
return (
|
|
56
|
+
f"{sim_name} {iter} "
|
|
57
|
+
f"{f.format(E_pot)} {f.format(a)} {f.format(b)} {f.format(c)} "
|
|
58
|
+
f"{f.format(alpha)} {f.format(beta)} {f.format(gamma)}\n"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _safe_get(row: pd.Series, key: str, default: float = 0.0) -> float:
|
|
62
|
+
return float(row[key]) if (isinstance(row, pd.Series) and key in row and pd.notna(row[key])) else float(default)
|
|
63
|
+
|
|
64
|
+
def _get_frame_table(xh: XmoloutHandler, i: int) -> pd.DataFrame:
|
|
65
|
+
"""
|
|
66
|
+
Access the per-frame atom table (including any extra columns).
|
|
67
|
+
Falls back to a minimal table if not available.
|
|
68
|
+
"""
|
|
69
|
+
if hasattr(xh, "_frames") and i < len(xh._frames):
|
|
70
|
+
return xh._frames[i]
|
|
71
|
+
# minimal fallback (no extras)
|
|
72
|
+
fr = xh.frame(i)
|
|
73
|
+
df = pd.DataFrame({
|
|
74
|
+
"atom_type": fr["atom_types"],
|
|
75
|
+
"x": fr["coords"][:, 0], "y": fr["coords"][:, 1], "z": fr["coords"][:, 2],
|
|
76
|
+
})
|
|
77
|
+
return df
|
|
78
|
+
|
|
79
|
+
def _format_atom_line_extended(row: pd.Series, prec: int, extra_order: list[str] | None) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Format one atom line: type, x, y, z, then (optional) extras in the given order.
|
|
82
|
+
"""
|
|
83
|
+
f = f"{{:.{prec}f}}"
|
|
84
|
+
t = str(row["atom_type"])
|
|
85
|
+
x, y, z = float(row["x"]), float(row["y"]), float(row["z"])
|
|
86
|
+
parts = [f"{t:<3}", f.format(x), f.format(y), f.format(z)]
|
|
87
|
+
if extra_order:
|
|
88
|
+
for col in extra_order:
|
|
89
|
+
val = row.get(col, np.nan)
|
|
90
|
+
# Allow strings for label-like extras, but prefer floats when possible
|
|
91
|
+
if isinstance(val, (int, float, np.floating, np.integer)) or pd.isna(val):
|
|
92
|
+
parts.append(f.format(float(val)) if pd.notna(val) else "nan")
|
|
93
|
+
else:
|
|
94
|
+
parts.append(str(val))
|
|
95
|
+
return " ".join(parts) + "\n"
|
|
96
|
+
|
|
97
|
+
def write_xmolout_from_handler(
|
|
98
|
+
xh: XmoloutHandler,
|
|
99
|
+
out_path: str | Path,
|
|
100
|
+
*,
|
|
101
|
+
frames: FrameSel = None,
|
|
102
|
+
atoms: AtomSel = None,
|
|
103
|
+
atom_types: Optional[Sequence[str]] = None,
|
|
104
|
+
simulation_name: Optional[str] = None,
|
|
105
|
+
precision: int = 6,
|
|
106
|
+
include_extras: Union[bool, Sequence[str], str] = False,
|
|
107
|
+
) -> Path:
|
|
108
|
+
"""
|
|
109
|
+
Write a filtered xmolout file from an existing XmoloutHandler.
|
|
110
|
+
|
|
111
|
+
Works on
|
|
112
|
+
--------
|
|
113
|
+
XmoloutHandler — ``xmolout``
|
|
114
|
+
|
|
115
|
+
Parameters
|
|
116
|
+
----------
|
|
117
|
+
xh : XmoloutHandler
|
|
118
|
+
Parsed trajectory handler providing frame and metadata access.
|
|
119
|
+
out_path : str or pathlib.Path
|
|
120
|
+
Output path for the generated xmolout file.
|
|
121
|
+
frames : sequence of int, range, slice, or None, optional
|
|
122
|
+
Frame indices to include. If None, all frames are written.
|
|
123
|
+
atoms : sequence of int, slice, or None, optional
|
|
124
|
+
Atom indices to include per frame.
|
|
125
|
+
atom_types : sequence of str or None, optional
|
|
126
|
+
Atom types to include per frame (alternative to ``atoms``).
|
|
127
|
+
simulation_name : str or None, optional
|
|
128
|
+
Simulation name written to the xmolout header line.
|
|
129
|
+
precision : int, optional
|
|
130
|
+
Decimal precision for floating-point values.
|
|
131
|
+
include_extras : bool, str, or sequence of str, optional
|
|
132
|
+
Control writing of extra per-atom columns:
|
|
133
|
+
- False: write only atom type and coordinates
|
|
134
|
+
- True or "all": write all extra columns
|
|
135
|
+
- sequence: write only the specified columns
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
pathlib.Path
|
|
140
|
+
Path to the written xmolout file.
|
|
141
|
+
|
|
142
|
+
Examples
|
|
143
|
+
--------
|
|
144
|
+
>>> xh = XmoloutHandler("xmolout")
|
|
145
|
+
>>> write_xmolout_from_handler(
|
|
146
|
+
... xh,
|
|
147
|
+
... "xmolout_filtered",
|
|
148
|
+
... frames=range(0, 100),
|
|
149
|
+
... atom_types=["Al", "N"],
|
|
150
|
+
... )
|
|
151
|
+
"""
|
|
152
|
+
out_path = Path(out_path)
|
|
153
|
+
df = xh.dataframe()
|
|
154
|
+
sim_name = simulation_name or getattr(xh, "simulation_name", None) or "MD"
|
|
155
|
+
fidx = _normalize_frames_for_write(xh, frames)
|
|
156
|
+
|
|
157
|
+
with out_path.open("w", encoding="utf-8") as fh:
|
|
158
|
+
for i in fidx:
|
|
159
|
+
fr = xh.frame(i) # {"iter", "coords"(n,3), "atom_types"[n]}
|
|
160
|
+
sel = _normalize_atoms_for_write(fr, atoms, atom_types)
|
|
161
|
+
|
|
162
|
+
# Resolve header values
|
|
163
|
+
row = df.iloc[i] if (i < len(df)) else pd.Series()
|
|
164
|
+
it = int(fr.get("iter", int(row["iter"]) if "iter" in row else i))
|
|
165
|
+
E = _safe_get(row, "E_pot", 0.0)
|
|
166
|
+
a = _safe_get(row, "a", 1.0)
|
|
167
|
+
b = _safe_get(row, "b", 1.0)
|
|
168
|
+
c = _safe_get(row, "c", 1.0)
|
|
169
|
+
alpha = _safe_get(row, "alpha", 90.0)
|
|
170
|
+
beta = _safe_get(row, "beta", 90.0)
|
|
171
|
+
gamma = _safe_get(row, "gamma", 90.0)
|
|
172
|
+
|
|
173
|
+
# 1) number-of-atoms line
|
|
174
|
+
fh.write(f"{len(sel)}\n")
|
|
175
|
+
# 2) header line
|
|
176
|
+
fh.write(_format_header_line(sim_name, it, E, a, b, c, alpha, beta, gamma, precision))
|
|
177
|
+
|
|
178
|
+
# 3) atom lines (with optional extras)
|
|
179
|
+
frame_tbl = _get_frame_table(xh, i).reset_index(drop=True)
|
|
180
|
+
|
|
181
|
+
base_cols = ["atom_type", "x", "y", "z"]
|
|
182
|
+
present_cols = frame_tbl.columns.tolist()
|
|
183
|
+
extra_cols_default = [c for c in present_cols if c not in base_cols]
|
|
184
|
+
|
|
185
|
+
if include_extras is True or (isinstance(include_extras, str) and include_extras.lower() == "all"):
|
|
186
|
+
extra_order = extra_cols_default
|
|
187
|
+
elif isinstance(include_extras, (list, tuple)):
|
|
188
|
+
extra_order = [c for c in include_extras if c in present_cols and c not in base_cols]
|
|
189
|
+
else:
|
|
190
|
+
extra_order = []
|
|
191
|
+
|
|
192
|
+
# select atoms and write
|
|
193
|
+
# Ensure required base columns exist
|
|
194
|
+
need = base_cols + ([c for c in extra_order if c not in base_cols])
|
|
195
|
+
sub = frame_tbl.loc[sel, [c for c in need if c in frame_tbl.columns]].copy()
|
|
196
|
+
|
|
197
|
+
# If any requested extra is missing in a particular frame, add as NaN
|
|
198
|
+
for c in need:
|
|
199
|
+
if c not in sub.columns:
|
|
200
|
+
sub[c] = np.nan
|
|
201
|
+
|
|
202
|
+
for _, r in sub[base_cols + extra_order].iterrows():
|
|
203
|
+
fh.write(_format_atom_line_extended(r, precision, extra_order))
|
|
204
|
+
|
|
205
|
+
return out_path
|
|
206
|
+
|
|
207
|
+
def write_xmolout_from_frames(
|
|
208
|
+
frames: Iterable[Dict[str, Any]],
|
|
209
|
+
out_path: str | Path,
|
|
210
|
+
*,
|
|
211
|
+
simulation_name: str = "MD",
|
|
212
|
+
precision: int = 6,
|
|
213
|
+
defaults: Dict[str, float] | None = None,
|
|
214
|
+
) -> Path:
|
|
215
|
+
"""
|
|
216
|
+
Write an xmolout file from explicit per-frame dictionaries.
|
|
217
|
+
|
|
218
|
+
Works on
|
|
219
|
+
--------
|
|
220
|
+
In-memory frame dictionaries → ``xmolout``
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
frames : iterable of dict
|
|
225
|
+
Frame dictionaries with required keys:
|
|
226
|
+
``iter``, ``coords``, ``atom_types``.
|
|
227
|
+
Optional keys include:
|
|
228
|
+
``E_pot``, ``a``, ``b``, ``c``, ``alpha``, ``beta``, ``gamma``,
|
|
229
|
+
and ``extras`` for per-atom additional columns.
|
|
230
|
+
out_path : str or pathlib.Path
|
|
231
|
+
Output path for the generated xmolout file.
|
|
232
|
+
simulation_name : str, optional
|
|
233
|
+
Simulation name written to the xmolout header line.
|
|
234
|
+
precision : int, optional
|
|
235
|
+
Decimal precision for floating-point values.
|
|
236
|
+
defaults : dict or None, optional
|
|
237
|
+
Default header values used when missing from a frame.
|
|
238
|
+
|
|
239
|
+
Returns
|
|
240
|
+
-------
|
|
241
|
+
pathlib.Path
|
|
242
|
+
Path to the written xmolout file.
|
|
243
|
+
|
|
244
|
+
Examples
|
|
245
|
+
--------
|
|
246
|
+
>>> frames = [{
|
|
247
|
+
... "iter": 0,
|
|
248
|
+
... "coords": [[0,0,0], [1,0,0]],
|
|
249
|
+
... "atom_types": ["H", "H"],
|
|
250
|
+
... }]
|
|
251
|
+
>>> write_xmolout_from_frames(frames, "xmolout_test")
|
|
252
|
+
"""
|
|
253
|
+
out_path = Path(out_path)
|
|
254
|
+
defaults = defaults or {
|
|
255
|
+
"E_pot": 0.0,
|
|
256
|
+
"a": 1.0, "b": 1.0, "c": 1.0,
|
|
257
|
+
"alpha": 90.0, "beta": 90.0, "gamma": 90.0,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
with out_path.open("w", encoding="utf-8") as fh:
|
|
261
|
+
for fr in frames:
|
|
262
|
+
coords = np.asarray(fr["coords"], dtype=float)
|
|
263
|
+
if coords.ndim != 2 or coords.shape[1] != 3:
|
|
264
|
+
raise ValueError("Each frame['coords'] must be an (n,3) array-like.")
|
|
265
|
+
types = [str(t) for t in fr["atom_types"]]
|
|
266
|
+
if len(types) != coords.shape[0]:
|
|
267
|
+
raise ValueError("Length of frame['atom_types'] must match number of coordinate rows.")
|
|
268
|
+
|
|
269
|
+
it = int(fr["iter"])
|
|
270
|
+
E = float(fr.get("E_pot", defaults["E_pot"]))
|
|
271
|
+
a = float(fr.get("a", defaults["a"]))
|
|
272
|
+
b = float(fr.get("b", defaults["b"]))
|
|
273
|
+
c = float(fr.get("c", defaults["c"]))
|
|
274
|
+
alpha = float(fr.get("alpha", defaults["alpha"]))
|
|
275
|
+
beta = float(fr.get("beta", defaults["beta"]))
|
|
276
|
+
gamma = float(fr.get("gamma", defaults["gamma"]))
|
|
277
|
+
|
|
278
|
+
# Prepare extras (if any)
|
|
279
|
+
extras_dict: Dict[str, Any] = dict(fr.get("extras", {}))
|
|
280
|
+
extra_keys: List[str] = list(extras_dict.keys())
|
|
281
|
+
extras_cols: List[np.ndarray] = []
|
|
282
|
+
for k in extra_keys:
|
|
283
|
+
arr = np.asarray(extras_dict[k])
|
|
284
|
+
if arr.shape[0] != coords.shape[0]:
|
|
285
|
+
raise ValueError(f"extras['{k}'] length must match number of atoms.")
|
|
286
|
+
extras_cols.append(arr)
|
|
287
|
+
|
|
288
|
+
# 1) number-of-atoms line
|
|
289
|
+
fh.write(f"{coords.shape[0]}\n")
|
|
290
|
+
# 2) header line
|
|
291
|
+
fh.write(_format_header_line(simulation_name, it, E, a, b, c, alpha, beta, gamma, precision))
|
|
292
|
+
# 3) atom lines
|
|
293
|
+
f = f"{{:.{precision}f}}"
|
|
294
|
+
for idx, (t, (x, y, z)) in enumerate(zip(types, coords)):
|
|
295
|
+
parts = [f"{t:<3}", f.format(x), f.format(y), f.format(z)]
|
|
296
|
+
for col in extras_cols:
|
|
297
|
+
v = col[idx]
|
|
298
|
+
if isinstance(v, (int, float, np.floating, np.integer)) or (isinstance(v, str) and v.replace('.','',1).isdigit()):
|
|
299
|
+
parts.append(f.format(float(v)))
|
|
300
|
+
else:
|
|
301
|
+
parts.append(str(v))
|
|
302
|
+
fh.write(" ".join(parts) + "\n")
|
|
303
|
+
|
|
304
|
+
return out_path
|
|
File without changes
|