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
reaxkit/utils/cache.py ADDED
@@ -0,0 +1,77 @@
1
+ """
2
+ Lightweight caching utilities for ReaxKit objects.
3
+
4
+ This module provides minimal helpers for serializing and deserializing
5
+ Python objects to disk using pickle. It is intended for caching intermediate
6
+ results such as parsed handlers, analysis outputs, or precomputed tables
7
+ to avoid repeated expensive computations.
8
+
9
+ The API is intentionally small and explicit to keep caching behavior
10
+ predictable and easy to reason about.
11
+ """
12
+
13
+
14
+ from __future__ import annotations
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Any, Optional
18
+ import pickle
19
+
20
+ @dataclass
21
+ class CacheConfig:
22
+ """
23
+ Configuration options for cache serialization.
24
+
25
+ Parameters
26
+ ----------
27
+ protocol : int
28
+ Pickle protocol version to use when saving objects.
29
+ compress : bool
30
+ Placeholder flag for future compression support.
31
+ """
32
+ protocol: int = pickle.HIGHEST_PROTOCOL
33
+ compress: bool = False # placeholder if you later want compression
34
+
35
+ def save_cache_blob(path: Path, obj: Any, *, cfg: Optional[CacheConfig] = None) -> None:
36
+ """
37
+ Save a Python object to a cache file.
38
+
39
+ The object is serialized using pickle and written to the specified
40
+ path. Existing files will be overwritten.
41
+
42
+ Parameters
43
+ ----------
44
+ path : pathlib.Path
45
+ Destination path for the cache file.
46
+ obj : Any
47
+ Python object to serialize.
48
+ cfg : CacheConfig, optional
49
+ Cache configuration controlling serialization behavior.
50
+
51
+ Returns
52
+ -------
53
+ None
54
+ """
55
+ cfg = cfg or CacheConfig()
56
+ with open(path, "wb") as f:
57
+ pickle.dump(obj, f, protocol=cfg.protocol)
58
+
59
+ def load_cache_blob(path: Path, *, cfg: Optional[CacheConfig] = None) -> Any:
60
+ """
61
+ Load a Python object from a cache file.
62
+
63
+ Parameters
64
+ ----------
65
+ path : pathlib.Path
66
+ Path to the cache file.
67
+ cfg : CacheConfig, optional
68
+ Cache configuration (reserved for future extensions).
69
+
70
+ Returns
71
+ -------
72
+ Any
73
+ Deserialized Python object stored in the cache.
74
+ """
75
+ cfg = cfg or CacheConfig()
76
+ with open(path, "rb") as f:
77
+ return pickle.load(f)
@@ -0,0 +1,75 @@
1
+ """
2
+ Physical and numerical constants utilities for ReaxKit.
3
+
4
+ This module provides access to commonly used constants defined in a packaged
5
+ YAML file and exposes a small, cached API for retrieving them by name.
6
+
7
+ Constant values are stored in ``reaxkit/data/constants.yaml`` and loaded on
8
+ demand.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from functools import lru_cache
14
+ from typing import Dict, Optional
15
+
16
+ import yaml
17
+ import importlib.resources as ir
18
+
19
+
20
+ @lru_cache(maxsize=1)
21
+ def _load_constants() -> Dict[str, float]:
22
+ """
23
+ Load packaged constants into a dictionary.
24
+
25
+ Constants are read from ``constants.yaml`` and cached after the first call
26
+ to avoid repeated disk access.
27
+
28
+ Returns
29
+ -------
30
+ dict[str, float]
31
+ Mapping of constant names to numeric values.
32
+
33
+ Raises
34
+ ------
35
+ FileNotFoundError
36
+ If the packaged constants file cannot be located.
37
+ """
38
+ pkg = "reaxkit"
39
+ rel = "data/constants.yaml"
40
+
41
+ try:
42
+ with ir.files(pkg).joinpath(rel).open("r", encoding="utf-8") as f:
43
+ doc = yaml.safe_load(f) or {}
44
+ except FileNotFoundError as e:
45
+ raise FileNotFoundError(
46
+ f"Could not find packaged constants at '{pkg}/{rel}'. "
47
+ "Make sure constants.yaml is included as package data."
48
+ ) from e
49
+
50
+ raw = doc.get("constants") or {}
51
+ return {str(k): float(v) for k, v in raw.items()}
52
+
53
+
54
+ def const(name: str, default: Optional[float] = None) -> Optional[float]:
55
+ """
56
+ Retrieve a named constant.
57
+
58
+ Parameters
59
+ ----------
60
+ name : str
61
+ Name of the constant to retrieve.
62
+ default : float, optional
63
+ Value to return if the constant is not defined.
64
+
65
+ Returns
66
+ -------
67
+ float or None
68
+ Constant value if found; otherwise ``default``.
69
+
70
+ Examples
71
+ --------
72
+ >>> const("kB")
73
+ >>> const("e_charge", default=1.0)
74
+ """
75
+ return _load_constants().get(name, default)
@@ -0,0 +1,96 @@
1
+ """
2
+ Equation of state (EOS) utilities.
3
+
4
+ This module centralizes common equation-of-state formulas used across ReaxKit.
5
+ Currently includes Vinet EOS in two forms:
6
+
7
+ 1) Energy–volume form in eV (used for fitting E(V) from fort.99/fort.74).
8
+ 2) Legacy "trainset" form matching the translated Fortran elastic_energy_v2 generator.
9
+
10
+ Notes
11
+ -----
12
+ - The two Vinet implementations use different parameterizations/units, so they are
13
+ *conceptually related* but not drop-in replacements for each other.
14
+ - Explanation for the Rose–Vinet equation of state can be found here:
15
+ doi:10.1029/JB092iB09p09319
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ from typing import Union
21
+ import numpy as np
22
+
23
+
24
+ def vinet_energy_ev(V: np.ndarray, E0: float, K0_eV_A3: float, V0: float, C: float) -> np.ndarray:
25
+ """
26
+ Vinet EOS: energy–volume form (eV, eV/Å^3).
27
+
28
+ Parameters
29
+ ----------
30
+ V : numpy.ndarray
31
+ Volume(s) (Å^3).
32
+ E0 : float
33
+ Equilibrium energy (eV).
34
+ K0_eV_A3 : float
35
+ Bulk modulus at equilibrium (eV/Å^3).
36
+ V0 : float
37
+ Equilibrium volume (Å^3).
38
+ C : float
39
+ Vinet shape parameter (dimensionless).
40
+
41
+ Returns
42
+ -------
43
+ numpy.ndarray
44
+ Energy at each volume V (eV).
45
+ """
46
+ nu = V / V0
47
+ eta = nu ** (1.0 / 3.0)
48
+ term = 1.0 - (1.0 + C * (eta - 1.0)) * np.exp(C * (1.0 - eta))
49
+ return E0 + 9.0 * K0_eV_A3 * V0 / (C ** 2) * term
50
+
51
+
52
+ def vinet_energy_trainset(
53
+ *,
54
+ volume: float,
55
+ reference_volume: float,
56
+ bulk_modulus_gpa: float,
57
+ bulk_modulus_pressure_derivative: float,
58
+ reference_energy: float = 0.0,
59
+ energy_conversion_factor: float,
60
+ ) -> float:
61
+ """
62
+ Vinet EOS: legacy trainset-generator form.
63
+
64
+ This matches your translated Fortran elastic_energy_v2 bulk block logic.
65
+ It returns energies in the generator's legacy energy units.
66
+
67
+ Parameters
68
+ ----------
69
+ volume : float
70
+ Current volume V (Å^3).
71
+ reference_volume : float
72
+ Reference volume V0 (Å^3).
73
+ bulk_modulus_gpa : float
74
+ Bulk modulus B0 (GPa).
75
+ bulk_modulus_pressure_derivative : float
76
+ Pressure derivative B0' (dimensionless).
77
+ reference_energy : float, optional
78
+ Reference energy offset E0 (legacy units).
79
+ energy_conversion_factor : float
80
+ The generator's conversion factor (the one you currently call ENERGY_CONVERSION_FACTOR).
81
+
82
+ Returns
83
+ -------
84
+ float
85
+ EOS energy in the generator's legacy energy units.
86
+ """
87
+ converted_bulk_modulus = bulk_modulus_gpa / energy_conversion_factor
88
+ eos_prefactor = 9.0 * converted_bulk_modulus * reference_volume / 16.0
89
+
90
+ x = (reference_volume / volume) ** (2.0 / 3.0)
91
+ x_minus_one = x - 1.0
92
+
93
+ term_cubic = (x_minus_one ** 3) * bulk_modulus_pressure_derivative
94
+ term_quadratic = (x_minus_one ** 2) * (6.0 - 4.0 * x)
95
+
96
+ return reference_energy + eos_prefactor * (term_cubic + term_quadratic)
@@ -0,0 +1,27 @@
1
+ """
2
+ Custom exception types used throughout ReaxKit.
3
+
4
+ This module defines lightweight, domain-specific exceptions that allow
5
+ ReaxKit workflows to distinguish between parsing failures and analysis
6
+ failures, enabling clearer error reporting and recovery.
7
+ """
8
+
9
+ class ParseError(Exception):
10
+ """
11
+ Error raised when a ReaxFF file cannot be parsed correctly.
12
+
13
+ This exception is intended for failures occurring during file reading,
14
+ tokenization, or structural interpretation of input or output files.
15
+ """
16
+ pass
17
+
18
+
19
+ class AnalysisError(Exception):
20
+ """
21
+ Error raised when an analysis step fails.
22
+
23
+ This exception should be used for errors occurring after successful
24
+ parsing, such as invalid data assumptions, numerical failures, or
25
+ unsupported analysis configurations.
26
+ """
27
+ pass
@@ -0,0 +1,175 @@
1
+ """
2
+ Frame and atom selection utilities for ReaxKit analyses.
3
+
4
+ This module provides common helpers for parsing flexible user input
5
+ (e.g., CLI arguments or configuration strings) that specify frame and
6
+ atom selections, and for resolving those selections into concrete,
7
+ ordered indices usable by handlers and analyzers.
8
+
9
+ Typical use cases include:
10
+
11
+ - parsing frame ranges such as ``"0:100:5"`` or explicit lists like ``"10,20,30"``
12
+ - selecting subsets of rows from DataFrames by frame index
13
+ - resolving iteration numbers into frame indices via handler metadata
14
+ - parsing atom index lists for per-atom analyses
15
+ """
16
+
17
+
18
+ from __future__ import annotations
19
+ from typing import Optional, Sequence, Union, Iterable, List
20
+ import pandas as pd
21
+
22
+ FramesT = Optional[Union[slice, Sequence[int]]]
23
+
24
+ def parse_frames(arg: Optional[str]) -> FramesT:
25
+ """
26
+ Parse a frame-selection string into a slice or index list.
27
+
28
+ Supported formats are:
29
+ - ``"start:stop[:step]"`` → ``slice``
30
+ - ``"i,j,k"`` → list of integers
31
+ - ``None`` or empty string → ``None`` (select all frames)
32
+
33
+ Parameters
34
+ ----------
35
+ arg : str or None
36
+ Frame selection string.
37
+
38
+ Returns
39
+ -------
40
+ slice or list[int] or None
41
+ Parsed frame selection.
42
+ """
43
+ if arg is None or str(arg).strip() == "":
44
+ return None
45
+ s = str(arg).strip()
46
+ if ":" in s:
47
+ parts = [p.strip() for p in s.split(":")]
48
+ start = int(parts[0]) if parts[0] else None
49
+ stop = int(parts[1]) if len(parts) > 1 and parts[1] else None
50
+ step = int(parts[2]) if len(parts) > 2 and parts[2] else None
51
+ return slice(start, stop, step)
52
+ return [int(p.strip()) for p in s.split(",") if p.strip()]
53
+
54
+ # Back-compat aliases requested in project notes
55
+ _parse_frames = parse_frames
56
+
57
+ def select_frames(df: pd.DataFrame, frames: FramesT) -> pd.DataFrame:
58
+ """
59
+ Select rows from a DataFrame based on frame indices.
60
+
61
+ Selection is performed using row-position indexing.
62
+
63
+ Parameters
64
+ ----------
65
+ df : pandas.DataFrame
66
+ Input DataFrame containing per-frame data.
67
+ frames : slice or list[int] or None
68
+ Frame selection returned by ``parse_frames``.
69
+
70
+ Returns
71
+ -------
72
+ pandas.DataFrame
73
+ DataFrame restricted to the selected frames.
74
+ """
75
+ if frames is None:
76
+ return df
77
+ if isinstance(frames, slice):
78
+ return df.iloc[frames]
79
+ return df.iloc[list(frames)]
80
+
81
+ def _select_frames(xh, start: Optional[int], stop: Optional[int], every: int) -> range:
82
+ """
83
+ Construct a range of frame indices for a handler.
84
+
85
+ Notes
86
+ -----
87
+ This is an internal helper used to support legacy workflows and
88
+ handler-based frame iteration.
89
+ """
90
+ try:
91
+ n_frames = xh.n_frames()
92
+ except Exception:
93
+ # fallback from dataframe length
94
+ df = xh.dataframe()
95
+ n_frames = len(df)
96
+ s = 0 if start is None else max(0, int(start))
97
+ e = (n_frames - 1) if stop is None else min(n_frames - 1, int(stop))
98
+ ev = max(1, int(every))
99
+ if e < s:
100
+ return range(0, 0) # empty
101
+ return range(s, e + 1, ev)
102
+
103
+ def parse_atoms(arg: Optional[str]) -> Optional[List[int]]:
104
+ """
105
+ Parse an atom-index selection string.
106
+
107
+ Parameters
108
+ ----------
109
+ arg : str or None
110
+ Comma- or space-separated atom indices.
111
+
112
+ Returns
113
+ -------
114
+ list[int] or None
115
+ Parsed atom indices, or ``None`` if no selection is provided.
116
+ """
117
+ if arg is None or str(arg).strip() == "":
118
+ return None
119
+ parts = [p for chunk in str(arg).split(",") for p in chunk.split()]
120
+ out: List[int] = []
121
+ for p in parts:
122
+ try:
123
+ out.append(int(p))
124
+ except ValueError:
125
+ continue
126
+ return out or None
127
+
128
+ def resolve_indices(handler, frames: FramesT = None, iterations: Optional[Iterable[int]] = None, step: Optional[int] = None) -> list[int]:
129
+ """
130
+ Resolve user-specified frame or iteration selections into frame indices.
131
+
132
+ Frame selection is resolved in the following order:
133
+ 1. Explicit frame indices or slices (if provided)
134
+ 2. Iteration numbers mapped to frame indices via ``handler.dataframe()['iter']``
135
+
136
+ An optional stride may be applied to decimate the result.
137
+
138
+ Parameters
139
+ ----------
140
+ handler
141
+ Handler providing access to per-frame simulation data.
142
+ frames : slice or list[int], optional
143
+ Explicit frame selection.
144
+ iterations : iterable of int, optional
145
+ Iteration numbers to map to frame indices.
146
+ step : int, optional
147
+ Stride applied to the resolved frame indices.
148
+
149
+ Returns
150
+ -------
151
+ list[int]
152
+ Ordered list of resolved frame indices.
153
+ """
154
+ sim_df = handler.dataframe()
155
+ n = len(sim_df)
156
+ all_idx = list(range(n))
157
+
158
+ # Start from frames
159
+ if frames is None:
160
+ chosen = set(all_idx)
161
+ elif isinstance(frames, slice):
162
+ chosen = set(range(n)[frames])
163
+ else:
164
+ chosen = set(int(i) for i in frames)
165
+
166
+ # Filter by iteration numbers if provided
167
+ if iterations is not None:
168
+ iters = set(int(i) for i in (iterations if not isinstance(iterations, int) else [iterations]))
169
+ iter_to_idx = {int(sim_df.iloc[i]["iter"]): i for i in all_idx}
170
+ chosen &= {iter_to_idx[it] for it in iters if it in iter_to_idx}
171
+
172
+ idx = sorted(i for i in chosen if 0 <= i < n)
173
+ if step and int(step) > 1:
174
+ idx = idx[::int(step)]
175
+ return idx
reaxkit/utils/log.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ Logging utilities for ReaxKit.
3
+
4
+ This module provides a small helper for creating consistently formatted
5
+ loggers across ReaxKit modules, ensuring uniform log messages in both
6
+ CLI workflows and programmatic use.
7
+ """
8
+
9
+ import logging
10
+
11
+ def get_logger(name: str) -> logging.Logger:
12
+ """
13
+ Create or retrieve a consistently formatted logger.
14
+
15
+ The returned logger uses a standard ReaxKit format and avoids attaching
16
+ duplicate handlers when called multiple times with the same name.
17
+
18
+ Parameters
19
+ ----------
20
+ name : str
21
+ Name of the logger, typically ``__name__``.
22
+
23
+ Returns
24
+ -------
25
+ logging.Logger
26
+ Configured logger instance.
27
+
28
+ Examples
29
+ --------
30
+ >>> logger = get_logger(__name__)
31
+ >>> logger.info("Parsing started")
32
+ """
33
+ logger = logging.getLogger(name)
34
+ if not logger.handlers: # avoid duplicate handlers if called multiple times
35
+ handler = logging.StreamHandler()
36
+ formatter = logging.Formatter(
37
+ fmt="%(asctime)s | %(levelname)-7s | %(name)s | %(message)s",
38
+ datefmt="%H:%M:%S",
39
+ )
40
+ handler.setFormatter(formatter)
41
+ logger.addHandler(handler)
42
+ logger.setLevel(logging.INFO)
43
+ return logger
File without changes
@@ -0,0 +1,90 @@
1
+ """
2
+ X-axis conversion utilities for ReaxKit plots and analyses.
3
+
4
+ This module provides helpers for converting iteration indices to alternative
5
+ x-axis representations such as simulation frames or physical time, based on
6
+ information read from a ReaxFF control file.
7
+
8
+ Typical use cases include:
9
+
10
+ - plotting observables versus simulation time instead of iteration number
11
+ - switching between iteration, frame, and time axes in workflows
12
+ - automatically choosing appropriate time units (fs, ps, ns)
13
+ """
14
+
15
+ import numpy as np
16
+
17
+ from reaxkit.io.handlers.control_handler import ControlHandler
18
+
19
+
20
+ def convert_xaxis(iters, xaxis, control_file: str = "control"):
21
+ """
22
+ Convert iteration indices to a different x-axis representation.
23
+
24
+ Supported target axes include iteration number, frame index, and physical
25
+ simulation time. When converting to time, the function automatically
26
+ selects appropriate units (fs, ps, or ns) based on the total time span.
27
+
28
+ Parameters
29
+ ----------
30
+ iters : array-like
31
+ Iteration indices to convert.
32
+ xaxis : {'iter', 'frame', 'time'}
33
+ Target x-axis representation.
34
+ control_file : str, optional
35
+ Path to the ReaxFF control file used to determine the time step.
36
+
37
+ Returns
38
+ -------
39
+ tuple[numpy.ndarray, str]
40
+ Converted x-axis values and a human-readable axis label.
41
+
42
+ Raises
43
+ ------
44
+ ValueError
45
+ If the requested x-axis is unknown or the time step cannot be
46
+ determined from the control file.
47
+
48
+ Examples
49
+ --------
50
+ >>> x, label = convert_xaxis(iters, "time")
51
+ >>> x, label = convert_xaxis(iters, "frame")
52
+ """
53
+ if xaxis == "iter":
54
+ return iters, "iter"
55
+
56
+ elif xaxis == "frame":
57
+ return np.arange(len(iters)), "Frame"
58
+
59
+ elif xaxis == "time":
60
+ handler = ControlHandler(control_file)
61
+ tstep = (
62
+ handler.general_parameters.get("tstep")
63
+ or handler.md_parameters.get("tstep")
64
+ )
65
+
66
+ if tstep is None:
67
+ raise ValueError("❌ Could not find 'tstep' in control file.")
68
+
69
+ # Compute total time in femtoseconds
70
+ time_fs = np.asarray(iters) * tstep
71
+
72
+ # Automatically choose scale
73
+ max_time = np.max(time_fs)
74
+ if max_time >= 1e6:
75
+ # Convert fs → ns
76
+ time_scaled = time_fs / 1e6
77
+ label = "Time (ns)"
78
+ elif max_time >= 1e3:
79
+ # Convert fs → ps
80
+ time_scaled = time_fs / 1e3
81
+ label = "Time (ps)"
82
+ else:
83
+ # Keep in fs
84
+ time_scaled = time_fs
85
+ label = "Time (fs)"
86
+
87
+ return time_scaled, label
88
+
89
+ else:
90
+ raise ValueError(f"❌ Unknown xaxis: {xaxis}")
@@ -0,0 +1,91 @@
1
+ """
2
+ Image-to-video conversion utilities.
3
+
4
+ This module provides lightweight helpers for assembling a sequence of image
5
+ files into a video file, primarily for visualization of simulation snapshots,
6
+ plots, or frame-based outputs.
7
+
8
+ Typical use cases include:
9
+
10
+ - creating MP4 videos from sequentially saved plot images
11
+ - visualizing time evolution of simulation frames
12
+ - generating animations for presentations or reports
13
+ """
14
+
15
+
16
+ import os
17
+ import re
18
+ import imageio
19
+
20
+
21
+ def _extract_numeric_index(filename: str) -> int:
22
+ """
23
+ Extract the first numeric index from a filename for sorting purposes.
24
+ """
25
+ nums = re.findall(r"\d+", filename)
26
+ return int(nums[0]) if nums else -1
27
+
28
+
29
+ def images_to_video(
30
+ folder_path: str,
31
+ output_file: str = "output_video.mp4",
32
+ fps: int = 10,
33
+ ext: tuple[str, ...] = (".png", ".jpg", ".jpeg"),
34
+ ) -> str:
35
+ """
36
+ Create a video from an ordered sequence of images in a folder.
37
+
38
+ Images are collected from the specified directory, filtered by extension,
39
+ sorted numerically based on the first number appearing in each filename,
40
+ and encoded into a video file.
41
+
42
+ Parameters
43
+ ----------
44
+ folder_path : str
45
+ Path to the directory containing image files.
46
+ output_file : str, optional
47
+ Output video filename, including extension (e.g., ``.mp4`` or ``.avi``).
48
+ fps : int, optional
49
+ Frames per second for the generated video.
50
+ ext : tuple[str, ...], optional
51
+ Accepted image file extensions.
52
+
53
+ Returns
54
+ -------
55
+ str
56
+ Absolute path to the saved video file.
57
+
58
+ Raises
59
+ ------
60
+ FileNotFoundError
61
+ If the folder does not exist or contains no valid image files.
62
+
63
+ Examples
64
+ --------
65
+ >>> images_to_video("frames/", output_file="traj.mp4", fps=15)
66
+ """
67
+
68
+ if not os.path.isdir(folder_path):
69
+ raise FileNotFoundError(f"Folder not found: {folder_path}")
70
+
71
+ # Collect image files
72
+ files = [
73
+ f for f in os.listdir(folder_path)
74
+ if f.lower().endswith(ext)
75
+ ]
76
+ if not files:
77
+ raise FileNotFoundError(f"No image files with extensions {ext} in {folder_path}")
78
+
79
+ # Sort numerically
80
+ files.sort(key=_extract_numeric_index)
81
+
82
+ # Read images
83
+ images = [
84
+ imageio.v2.imread(os.path.join(folder_path, f))
85
+ for f in files
86
+ ]
87
+
88
+ # Save video
89
+ imageio.mimsave(output_file, images, fps=fps)
90
+
91
+ return os.path.abspath(output_file)