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.
Files changed (130) hide show
  1. reaxkit/__init__.py +0 -0
  2. reaxkit/analysis/__init__.py +0 -0
  3. reaxkit/analysis/composed/RDF_analyzer.py +560 -0
  4. reaxkit/analysis/composed/__init__.py +0 -0
  5. reaxkit/analysis/composed/connectivity_analyzer.py +706 -0
  6. reaxkit/analysis/composed/coordination_analyzer.py +144 -0
  7. reaxkit/analysis/composed/electrostatics_analyzer.py +687 -0
  8. reaxkit/analysis/per_file/__init__.py +0 -0
  9. reaxkit/analysis/per_file/control_analyzer.py +165 -0
  10. reaxkit/analysis/per_file/eregime_analyzer.py +108 -0
  11. reaxkit/analysis/per_file/ffield_analyzer.py +305 -0
  12. reaxkit/analysis/per_file/fort13_analyzer.py +79 -0
  13. reaxkit/analysis/per_file/fort57_analyzer.py +106 -0
  14. reaxkit/analysis/per_file/fort73_analyzer.py +61 -0
  15. reaxkit/analysis/per_file/fort74_analyzer.py +65 -0
  16. reaxkit/analysis/per_file/fort76_analyzer.py +191 -0
  17. reaxkit/analysis/per_file/fort78_analyzer.py +154 -0
  18. reaxkit/analysis/per_file/fort79_analyzer.py +83 -0
  19. reaxkit/analysis/per_file/fort7_analyzer.py +393 -0
  20. reaxkit/analysis/per_file/fort99_analyzer.py +411 -0
  21. reaxkit/analysis/per_file/molfra_analyzer.py +359 -0
  22. reaxkit/analysis/per_file/params_analyzer.py +258 -0
  23. reaxkit/analysis/per_file/summary_analyzer.py +84 -0
  24. reaxkit/analysis/per_file/trainset_analyzer.py +84 -0
  25. reaxkit/analysis/per_file/vels_analyzer.py +95 -0
  26. reaxkit/analysis/per_file/xmolout_analyzer.py +528 -0
  27. reaxkit/cli.py +181 -0
  28. reaxkit/count_loc.py +276 -0
  29. reaxkit/data/alias.yaml +89 -0
  30. reaxkit/data/constants.yaml +27 -0
  31. reaxkit/data/reaxff_input_files_contents.yaml +186 -0
  32. reaxkit/data/reaxff_output_files_contents.yaml +301 -0
  33. reaxkit/data/units.yaml +38 -0
  34. reaxkit/help/__init__.py +0 -0
  35. reaxkit/help/help_index_loader.py +531 -0
  36. reaxkit/help/introspection_utils.py +131 -0
  37. reaxkit/io/__init__.py +0 -0
  38. reaxkit/io/base_handler.py +165 -0
  39. reaxkit/io/generators/__init__.py +0 -0
  40. reaxkit/io/generators/control_generator.py +123 -0
  41. reaxkit/io/generators/eregime_generator.py +341 -0
  42. reaxkit/io/generators/geo_generator.py +967 -0
  43. reaxkit/io/generators/trainset_generator.py +1758 -0
  44. reaxkit/io/generators/tregime_generator.py +113 -0
  45. reaxkit/io/generators/vregime_generator.py +164 -0
  46. reaxkit/io/generators/xmolout_generator.py +304 -0
  47. reaxkit/io/handlers/__init__.py +0 -0
  48. reaxkit/io/handlers/control_handler.py +209 -0
  49. reaxkit/io/handlers/eregime_handler.py +122 -0
  50. reaxkit/io/handlers/ffield_handler.py +812 -0
  51. reaxkit/io/handlers/fort13_handler.py +123 -0
  52. reaxkit/io/handlers/fort57_handler.py +143 -0
  53. reaxkit/io/handlers/fort73_handler.py +145 -0
  54. reaxkit/io/handlers/fort74_handler.py +155 -0
  55. reaxkit/io/handlers/fort76_handler.py +195 -0
  56. reaxkit/io/handlers/fort78_handler.py +142 -0
  57. reaxkit/io/handlers/fort79_handler.py +227 -0
  58. reaxkit/io/handlers/fort7_handler.py +264 -0
  59. reaxkit/io/handlers/fort99_handler.py +128 -0
  60. reaxkit/io/handlers/geo_handler.py +224 -0
  61. reaxkit/io/handlers/molfra_handler.py +184 -0
  62. reaxkit/io/handlers/params_handler.py +137 -0
  63. reaxkit/io/handlers/summary_handler.py +135 -0
  64. reaxkit/io/handlers/trainset_handler.py +658 -0
  65. reaxkit/io/handlers/vels_handler.py +293 -0
  66. reaxkit/io/handlers/xmolout_handler.py +174 -0
  67. reaxkit/utils/__init__.py +0 -0
  68. reaxkit/utils/alias.py +219 -0
  69. reaxkit/utils/cache.py +77 -0
  70. reaxkit/utils/constants.py +75 -0
  71. reaxkit/utils/equation_of_states.py +96 -0
  72. reaxkit/utils/exceptions.py +27 -0
  73. reaxkit/utils/frame_utils.py +175 -0
  74. reaxkit/utils/log.py +43 -0
  75. reaxkit/utils/media/__init__.py +0 -0
  76. reaxkit/utils/media/convert.py +90 -0
  77. reaxkit/utils/media/make_video.py +91 -0
  78. reaxkit/utils/media/plotter.py +812 -0
  79. reaxkit/utils/numerical/__init__.py +0 -0
  80. reaxkit/utils/numerical/extrema_finder.py +96 -0
  81. reaxkit/utils/numerical/moving_average.py +103 -0
  82. reaxkit/utils/numerical/numerical_calcs.py +75 -0
  83. reaxkit/utils/numerical/signal_ops.py +135 -0
  84. reaxkit/utils/path.py +55 -0
  85. reaxkit/utils/units.py +104 -0
  86. reaxkit/webui/__init__.py +0 -0
  87. reaxkit/webui/app.py +0 -0
  88. reaxkit/webui/components.py +0 -0
  89. reaxkit/webui/layouts.py +0 -0
  90. reaxkit/webui/utils.py +0 -0
  91. reaxkit/workflows/__init__.py +0 -0
  92. reaxkit/workflows/composed/__init__.py +0 -0
  93. reaxkit/workflows/composed/coordination_workflow.py +393 -0
  94. reaxkit/workflows/composed/electrostatics_workflow.py +587 -0
  95. reaxkit/workflows/composed/xmolout_fort7_workflow.py +343 -0
  96. reaxkit/workflows/meta/__init__.py +0 -0
  97. reaxkit/workflows/meta/help_workflow.py +136 -0
  98. reaxkit/workflows/meta/introspection_workflow.py +235 -0
  99. reaxkit/workflows/meta/make_video_workflow.py +61 -0
  100. reaxkit/workflows/meta/plotter_workflow.py +601 -0
  101. reaxkit/workflows/per_file/__init__.py +0 -0
  102. reaxkit/workflows/per_file/control_workflow.py +110 -0
  103. reaxkit/workflows/per_file/eregime_workflow.py +267 -0
  104. reaxkit/workflows/per_file/ffield_workflow.py +390 -0
  105. reaxkit/workflows/per_file/fort13_workflow.py +86 -0
  106. reaxkit/workflows/per_file/fort57_workflow.py +137 -0
  107. reaxkit/workflows/per_file/fort73_workflow.py +151 -0
  108. reaxkit/workflows/per_file/fort74_workflow.py +88 -0
  109. reaxkit/workflows/per_file/fort76_workflow.py +188 -0
  110. reaxkit/workflows/per_file/fort78_workflow.py +135 -0
  111. reaxkit/workflows/per_file/fort79_workflow.py +314 -0
  112. reaxkit/workflows/per_file/fort7_workflow.py +592 -0
  113. reaxkit/workflows/per_file/fort83_workflow.py +60 -0
  114. reaxkit/workflows/per_file/fort99_workflow.py +223 -0
  115. reaxkit/workflows/per_file/geo_workflow.py +554 -0
  116. reaxkit/workflows/per_file/molfra_workflow.py +577 -0
  117. reaxkit/workflows/per_file/params_workflow.py +135 -0
  118. reaxkit/workflows/per_file/summary_workflow.py +161 -0
  119. reaxkit/workflows/per_file/trainset_workflow.py +356 -0
  120. reaxkit/workflows/per_file/tregime_workflow.py +79 -0
  121. reaxkit/workflows/per_file/vels_workflow.py +309 -0
  122. reaxkit/workflows/per_file/vregime_workflow.py +75 -0
  123. reaxkit/workflows/per_file/xmolout_workflow.py +678 -0
  124. reaxkit-1.0.0.dist-info/METADATA +128 -0
  125. reaxkit-1.0.0.dist-info/RECORD +130 -0
  126. reaxkit-1.0.0.dist-info/WHEEL +5 -0
  127. reaxkit-1.0.0.dist-info/entry_points.txt +2 -0
  128. reaxkit-1.0.0.dist-info/licenses/AUTHORS.md +20 -0
  129. reaxkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  130. 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