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
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)
|