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
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
1D extrema detection utilities.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions for identifying extrema in one-dimensional
|
|
5
|
+
data series commonly produced by ReaxFF simulations, such as energy profiles,
|
|
6
|
+
bond-order trajectories, dipole signals, polarization loops, or field-response
|
|
7
|
+
curves.
|
|
8
|
+
|
|
9
|
+
Typical use cases include:
|
|
10
|
+
|
|
11
|
+
- locating global or local energy minima and maxima
|
|
12
|
+
- identifying peak responses in field-driven simulations
|
|
13
|
+
- detecting switching or transition points in time-series data
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_extrema_points(y_series, x_series, mode='max', chunk_size=None):
|
|
22
|
+
"""
|
|
23
|
+
Identify extrema points in a 1D data series.
|
|
24
|
+
|
|
25
|
+
This function extracts global or local extrema by locating maximum and/or
|
|
26
|
+
minimum values of a y-series with respect to a corresponding x-series.
|
|
27
|
+
Optionally, the x-axis may be partitioned into windows to detect local
|
|
28
|
+
extrema within each segment.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
y_series : pandas.Series or array-like
|
|
33
|
+
Dependent variable values (e.g., energy, polarization, bond order).
|
|
34
|
+
x_series : pandas.Series or array-like
|
|
35
|
+
Independent variable values (e.g., iteration index or time).
|
|
36
|
+
mode : {'max', 'min', 'minmax'}, optional
|
|
37
|
+
Type of extrema to extract:
|
|
38
|
+
- ``'max'``: maxima only
|
|
39
|
+
- ``'min'``: minima only
|
|
40
|
+
- ``'minmax'``: both minima and maxima
|
|
41
|
+
chunk_size : float or int, optional
|
|
42
|
+
Size of the x-axis window used to find local extrema. If not provided,
|
|
43
|
+
only global extrema are returned.
|
|
44
|
+
|
|
45
|
+
Returns
|
|
46
|
+
-------
|
|
47
|
+
list of tuple[float, float]
|
|
48
|
+
List of ``(x, y)`` pairs corresponding to detected extrema points.
|
|
49
|
+
|
|
50
|
+
Examples
|
|
51
|
+
--------
|
|
52
|
+
>>> get_extrema_points(energy, iters, mode="min")
|
|
53
|
+
>>> get_extrema_points(signal, time, mode="minmax", chunk_size=50)
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
assert mode in ['max', 'min', 'minmax'], "Mode must be 'max', 'min', or 'minmax'"
|
|
57
|
+
assert len(y_series) == len(x_series), "Series must be of the same length"
|
|
58
|
+
|
|
59
|
+
x_series = pd.Series(x_series).reset_index(drop=True)
|
|
60
|
+
y_series = pd.Series(y_series).reset_index(drop=True)
|
|
61
|
+
|
|
62
|
+
def _extreme_idx(chunk, kind):
|
|
63
|
+
return chunk.idxmax() if kind == 'max' else chunk.idxmin()
|
|
64
|
+
|
|
65
|
+
results = []
|
|
66
|
+
|
|
67
|
+
if chunk_size:
|
|
68
|
+
min_x, max_x = x_series.min(), x_series.max()
|
|
69
|
+
bins = np.arange(min_x, max_x + chunk_size, chunk_size)
|
|
70
|
+
|
|
71
|
+
for i in range(len(bins) - 1):
|
|
72
|
+
x_start, x_end = bins[i], bins[i + 1]
|
|
73
|
+
mask = (x_series >= x_start) & (x_series < x_end)
|
|
74
|
+
if not mask.any():
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
y_chunk = y_series[mask]
|
|
78
|
+
|
|
79
|
+
if mode in ['max', 'minmax']:
|
|
80
|
+
idx = _extreme_idx(y_chunk, 'max')
|
|
81
|
+
results.append((x_series.loc[idx], y_series.loc[idx]))
|
|
82
|
+
|
|
83
|
+
if mode in ['min', 'minmax']:
|
|
84
|
+
idx = _extreme_idx(y_chunk, 'min')
|
|
85
|
+
results.append((x_series.loc[idx], y_series.loc[idx]))
|
|
86
|
+
|
|
87
|
+
else:
|
|
88
|
+
if mode in ['max', 'minmax']:
|
|
89
|
+
idx = _extreme_idx(y_series, 'max')
|
|
90
|
+
results.append((x_series.loc[idx], y_series.loc[idx]))
|
|
91
|
+
|
|
92
|
+
if mode in ['min', 'minmax']:
|
|
93
|
+
idx = _extreme_idx(y_series, 'min')
|
|
94
|
+
results.append((x_series.loc[idx], y_series.loc[idx]))
|
|
95
|
+
|
|
96
|
+
return results
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Time-series smoothing utilities.
|
|
3
|
+
|
|
4
|
+
This module provides simple and exponential moving-average functions for
|
|
5
|
+
smoothing one-dimensional data series commonly produced by ReaxFF simulations,
|
|
6
|
+
such as energies, bond orders, dipole moments, or polarization signals.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- reducing high-frequency noise in MD trajectories
|
|
11
|
+
- smoothing field-response or hysteresis curves
|
|
12
|
+
- preparing time-series data for extrema or trend analysis
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
from typing import Optional, Union
|
|
18
|
+
import numpy as np
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
ArrayLike = Union[np.ndarray, pd.Series, list, tuple]
|
|
22
|
+
|
|
23
|
+
def simple_moving_average(
|
|
24
|
+
y: ArrayLike,
|
|
25
|
+
window: int = 5,
|
|
26
|
+
*,
|
|
27
|
+
center: bool = True,
|
|
28
|
+
min_periods: Optional[int] = 1,
|
|
29
|
+
) -> pd.Series:
|
|
30
|
+
"""
|
|
31
|
+
Compute a simple moving average (SMA) of a 1D data series.
|
|
32
|
+
|
|
33
|
+
The moving average is computed over a fixed-size sliding window and
|
|
34
|
+
returned as a pandas Series. If the input is already a Series, its index
|
|
35
|
+
is preserved.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
y : array-like
|
|
40
|
+
Input data values to smooth.
|
|
41
|
+
window : int, optional
|
|
42
|
+
Size of the moving window.
|
|
43
|
+
center : bool, optional
|
|
44
|
+
Whether the window is centered on each data point.
|
|
45
|
+
min_periods : int, optional
|
|
46
|
+
Minimum number of observations required to compute a value.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
pandas.Series
|
|
51
|
+
Smoothed data series using a simple moving average.
|
|
52
|
+
|
|
53
|
+
Examples
|
|
54
|
+
--------
|
|
55
|
+
>>> simple_moving_average(energy, window=10)
|
|
56
|
+
"""
|
|
57
|
+
if window < 1:
|
|
58
|
+
raise ValueError("window must be >= 1")
|
|
59
|
+
s = y if isinstance(y, pd.Series) else pd.Series(np.asarray(y, dtype=float))
|
|
60
|
+
return s.rolling(window, center=center, min_periods=min_periods).mean()
|
|
61
|
+
|
|
62
|
+
def exponential_moving_average(
|
|
63
|
+
y: ArrayLike,
|
|
64
|
+
*,
|
|
65
|
+
window: Optional[int] = None,
|
|
66
|
+
alpha: Optional[float] = None,
|
|
67
|
+
adjust: bool = False,
|
|
68
|
+
) -> pd.Series:
|
|
69
|
+
"""
|
|
70
|
+
Compute an exponential moving average (EMA) of a 1D data series.
|
|
71
|
+
|
|
72
|
+
The exponential moving average applies exponentially decreasing weights
|
|
73
|
+
to past observations. The smoothing factor may be specified directly
|
|
74
|
+
via ``alpha`` or indirectly via a window size.
|
|
75
|
+
|
|
76
|
+
Parameters
|
|
77
|
+
----------
|
|
78
|
+
y : array-like
|
|
79
|
+
Input data values to smooth.
|
|
80
|
+
window : int, optional
|
|
81
|
+
Window size used to derive the smoothing factor
|
|
82
|
+
(``alpha = 2 / (window + 1)``).
|
|
83
|
+
alpha : float, optional
|
|
84
|
+
Smoothing factor in the interval ``(0, 1]``.
|
|
85
|
+
adjust : bool, optional
|
|
86
|
+
Whether to use bias-adjusted weights.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
pandas.Series
|
|
91
|
+
Smoothed data series using an exponential moving average.
|
|
92
|
+
|
|
93
|
+
Examples
|
|
94
|
+
--------
|
|
95
|
+
>>> exponential_moving_average(signal, window=8)
|
|
96
|
+
>>> exponential_moving_average(signal, alpha=0.2)
|
|
97
|
+
"""
|
|
98
|
+
if alpha is None:
|
|
99
|
+
if window is None or window < 1:
|
|
100
|
+
raise ValueError("Provide alpha in (0,1] or a window >= 1.")
|
|
101
|
+
alpha = 2.0 / (window + 1.0)
|
|
102
|
+
s = y if isinstance(y, pd.Series) else pd.Series(np.asarray(y, dtype=float))
|
|
103
|
+
return s.ewm(alpha=float(alpha), adjust=adjust).mean()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Numerical analysis helper utilities.
|
|
3
|
+
|
|
4
|
+
This module provides lightweight numerical tools for analyzing one-dimensional
|
|
5
|
+
data series, such as detecting zero crossings or sign changes in curves
|
|
6
|
+
produced by ReaxFF simulations.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- locating x-axis crossings in energy or force curves
|
|
11
|
+
- identifying switching points in polarization or field-response signals
|
|
12
|
+
- detecting sign changes in derived observables
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
from typing import Sequence, List
|
|
18
|
+
import numpy as np
|
|
19
|
+
from scipy.interpolate import interp1d
|
|
20
|
+
from scipy.optimize import brentq
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_zero_crossings(x: Sequence[float], y: Sequence[float]) -> List[float]:
|
|
24
|
+
"""
|
|
25
|
+
Find x-values where a 1D function crosses zero.
|
|
26
|
+
|
|
27
|
+
Zero crossings are identified by detecting sign changes between consecutive
|
|
28
|
+
data points and estimating the root location using interpolation and
|
|
29
|
+
numerical bracketing. Exact zeros at sampled points are also included.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
x : sequence of float
|
|
34
|
+
Monotonically ordered x-values (e.g., time, iteration index).
|
|
35
|
+
y : sequence of float
|
|
36
|
+
Function values corresponding to ``x``.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
list of float
|
|
41
|
+
Sorted x-values at which ``y(x) = 0``.
|
|
42
|
+
|
|
43
|
+
Examples
|
|
44
|
+
--------
|
|
45
|
+
>>> find_zero_crossings(time, polarization)
|
|
46
|
+
>>> find_zero_crossings(iters, energy - energy.mean())
|
|
47
|
+
"""
|
|
48
|
+
x_arr = np.asarray(x, dtype=float)
|
|
49
|
+
y_arr = np.asarray(y, dtype=float)
|
|
50
|
+
|
|
51
|
+
if x_arr.size != y_arr.size:
|
|
52
|
+
raise ValueError("x and y must have the same length")
|
|
53
|
+
|
|
54
|
+
# exact zeros at sample points
|
|
55
|
+
zeros = list(x_arr[y_arr == 0])
|
|
56
|
+
|
|
57
|
+
if x_arr.size < 2:
|
|
58
|
+
return sorted(set(zeros))
|
|
59
|
+
|
|
60
|
+
interp = interp1d(x_arr, y_arr, fill_value="extrapolate")
|
|
61
|
+
|
|
62
|
+
for i in range(len(y_arr) - 1):
|
|
63
|
+
if np.isnan(y_arr[i]) or np.isnan(y_arr[i+1]):
|
|
64
|
+
continue
|
|
65
|
+
if y_arr[i] * y_arr[i+1] < 0:
|
|
66
|
+
try:
|
|
67
|
+
root = brentq(interp, x_arr[i], x_arr[i+1])
|
|
68
|
+
zeros.append(root)
|
|
69
|
+
except ValueError:
|
|
70
|
+
# If brentq fails for some weird numerical reason, skip that interval
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
sorted(set(zeros))
|
|
74
|
+
zeros = [float(z) for z in zeros]
|
|
75
|
+
return zeros
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Signal-processing utilities for binary state detection.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions for applying hysteresis-based
|
|
5
|
+
state detection and post-processing of boolean time-series data,
|
|
6
|
+
commonly encountered in ReaxFF simulations and field-driven analyses.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- detecting ON/OFF states in polarization or dipole signals
|
|
11
|
+
- applying Schmitt-trigger hysteresis to noisy response curves
|
|
12
|
+
- removing spurious state flickering in thresholded time series
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
import numpy as np
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
def schmitt_hysteresis(y: np.ndarray, th: float, hys: float, init_on: Optional[bool] = None) -> np.ndarray:
|
|
20
|
+
"""
|
|
21
|
+
Apply Schmitt-trigger hysteresis to a 1D signal.
|
|
22
|
+
|
|
23
|
+
This function converts a continuous signal into a boolean state
|
|
24
|
+
using separate ON and OFF thresholds to suppress noise-induced
|
|
25
|
+
state switching.
|
|
26
|
+
|
|
27
|
+
The switching rules are:
|
|
28
|
+
- ON when ``y >= th + hys / 2``
|
|
29
|
+
- OFF when ``y <= th - hys / 2``
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
y : array-like
|
|
34
|
+
Input signal values (e.g., polarization, dipole moment).
|
|
35
|
+
th : float
|
|
36
|
+
Central threshold value.
|
|
37
|
+
hys : float
|
|
38
|
+
Total hysteresis width.
|
|
39
|
+
init_on : bool, optional
|
|
40
|
+
Initial state at the first sample. If not provided, the
|
|
41
|
+
initial state is inferred from the first data point.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
numpy.ndarray
|
|
46
|
+
Boolean array representing the ON/OFF state over the signal.
|
|
47
|
+
|
|
48
|
+
Examples
|
|
49
|
+
--------
|
|
50
|
+
>>> state = schmitt_hysteresis(signal, th=0.0, hys=0.1)
|
|
51
|
+
"""
|
|
52
|
+
y = np.asarray(y, dtype=float)
|
|
53
|
+
h = max(0.0, float(hys))
|
|
54
|
+
th_on = float(th) + h/2.0
|
|
55
|
+
th_off = float(th) - h/2.0
|
|
56
|
+
|
|
57
|
+
state = np.zeros_like(y, dtype=bool)
|
|
58
|
+
on = (bool(init_on) if init_on is not None else (y[0] >= th_on))
|
|
59
|
+
for i, v in enumerate(y):
|
|
60
|
+
if not on and v >= th_on:
|
|
61
|
+
on = True
|
|
62
|
+
elif on and v <= th_off:
|
|
63
|
+
on = False
|
|
64
|
+
state[i] = on
|
|
65
|
+
return state
|
|
66
|
+
|
|
67
|
+
def clean_flicker(state: np.ndarray, min_run: int) -> np.ndarray:
|
|
68
|
+
"""
|
|
69
|
+
Remove short-lived state transitions in a boolean sequence.
|
|
70
|
+
|
|
71
|
+
This function suppresses brief ON or OFF segments shorter than a
|
|
72
|
+
specified minimum run length, producing a cleaner and more
|
|
73
|
+
physically meaningful state trajectory.
|
|
74
|
+
|
|
75
|
+
Parameters
|
|
76
|
+
----------
|
|
77
|
+
state : array-like of bool
|
|
78
|
+
Boolean state sequence (e.g., output of ``schmitt_hysteresis``).
|
|
79
|
+
min_run : int
|
|
80
|
+
Minimum number of consecutive samples required to retain a
|
|
81
|
+
state segment.
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
numpy.ndarray
|
|
86
|
+
Cleaned boolean state array with flicker removed.
|
|
87
|
+
|
|
88
|
+
Examples
|
|
89
|
+
--------
|
|
90
|
+
>>> clean_state = clean_flicker(state, min_run=5)
|
|
91
|
+
"""
|
|
92
|
+
s = np.asarray(state, dtype=bool)
|
|
93
|
+
if min_run <= 1 or s.size == 0:
|
|
94
|
+
return s
|
|
95
|
+
|
|
96
|
+
ints = s.astype(int)
|
|
97
|
+
edges = np.diff(ints, prepend=ints[0])
|
|
98
|
+
idx = np.flatnonzero(edges != 0)
|
|
99
|
+
starts = np.r_[0, idx]
|
|
100
|
+
ends = np.r_[idx, len(s)]
|
|
101
|
+
vals = ints[starts]
|
|
102
|
+
|
|
103
|
+
keep = np.ones_like(vals, dtype=bool)
|
|
104
|
+
for i in range(len(vals)):
|
|
105
|
+
if (ends[i] - starts[i]) < min_run and len(vals) > 1:
|
|
106
|
+
keep[i] = False
|
|
107
|
+
|
|
108
|
+
out = np.empty_like(ints)
|
|
109
|
+
cursor = 0
|
|
110
|
+
last_val = None
|
|
111
|
+
i = 0
|
|
112
|
+
while i < len(vals):
|
|
113
|
+
if not keep[i]:
|
|
114
|
+
j = i + 1
|
|
115
|
+
while j < len(vals) and not keep[j]:
|
|
116
|
+
j += 1
|
|
117
|
+
val = vals[i-1] if i > 0 else (vals[j] if j < len(vals) else vals[i])
|
|
118
|
+
end = ends[j-1] if j > 0 else ends[i]
|
|
119
|
+
out[cursor:end] = val
|
|
120
|
+
cursor = end
|
|
121
|
+
i = j
|
|
122
|
+
continue
|
|
123
|
+
j = i + 1
|
|
124
|
+
end = ends[i]
|
|
125
|
+
while j < len(vals) and not keep[j]:
|
|
126
|
+
end = ends[j]
|
|
127
|
+
j += 1
|
|
128
|
+
out[cursor:end] = vals[i]
|
|
129
|
+
cursor = end
|
|
130
|
+
last_val = vals[i]
|
|
131
|
+
i = j
|
|
132
|
+
|
|
133
|
+
if cursor < len(out):
|
|
134
|
+
out[cursor:] = last_val if last_val is not None else ints[0]
|
|
135
|
+
return out.astype(bool)
|
reaxkit/utils/path.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output path resolution utilities for ReaxKit workflows.
|
|
3
|
+
|
|
4
|
+
This module centralizes the logic used to determine where analysis results,
|
|
5
|
+
exports, and generated files are written. By default, outputs are placed
|
|
6
|
+
under a standardized directory structure:
|
|
7
|
+
|
|
8
|
+
reaxkit_outputs/<workflow>/<filename>
|
|
9
|
+
|
|
10
|
+
If the user supplies an explicit path (absolute or containing directories),
|
|
11
|
+
that path is respected exactly.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
DEFAULT_OUTROOT = Path("reaxkit_outputs")
|
|
18
|
+
|
|
19
|
+
def resolve_output_path(user_value: str, workflow: str) -> Path:
|
|
20
|
+
"""
|
|
21
|
+
Resolve the output path for a workflow result.
|
|
22
|
+
|
|
23
|
+
If the user provides only a bare filename, the file is written under
|
|
24
|
+
``reaxkit_outputs/<workflow>/``. If the user provides an absolute path
|
|
25
|
+
or a path containing directories, that path is used directly.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
user_value : str
|
|
30
|
+
User-specified output path or filename.
|
|
31
|
+
workflow : str
|
|
32
|
+
Name of the workflow producing the output.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
pathlib.Path
|
|
37
|
+
Resolved output path with parent directories created if needed.
|
|
38
|
+
|
|
39
|
+
Examples
|
|
40
|
+
--------
|
|
41
|
+
>>> resolve_output_path("results.csv", workflow="xmolout")
|
|
42
|
+
>>> resolve_output_path("out/results.csv", workflow="fort7")
|
|
43
|
+
"""
|
|
44
|
+
p = Path(user_value)
|
|
45
|
+
|
|
46
|
+
# If user gave an absolute path or a relative path with dirs,
|
|
47
|
+
# respect it exactly.
|
|
48
|
+
if p.is_absolute() or p.parent != Path("."):
|
|
49
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
50
|
+
return p
|
|
51
|
+
|
|
52
|
+
# Otherwise, user gave just a bare filename -> use default tree
|
|
53
|
+
outdir = DEFAULT_OUTROOT / workflow
|
|
54
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
return outdir / p.name
|
reaxkit/utils/units.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit metadata utilities for ReaxKit quantities.
|
|
3
|
+
|
|
4
|
+
This module provides access to display units associated with canonical
|
|
5
|
+
quantity keys used across ReaxFF files and ReaxKit analyses.
|
|
6
|
+
|
|
7
|
+
Unit definitions are stored in the packaged file
|
|
8
|
+
``reaxkit/data/units.yaml`` and loaded on demand.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from functools import lru_cache
|
|
15
|
+
from typing import Dict, Optional
|
|
16
|
+
|
|
17
|
+
import yaml
|
|
18
|
+
import importlib.resources as ir
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@lru_cache(maxsize=1)
|
|
22
|
+
def _load_units_map() -> Dict[str, str]:
|
|
23
|
+
"""
|
|
24
|
+
Load the canonical key → unit mapping.
|
|
25
|
+
|
|
26
|
+
Units are read from the packaged ``units.yaml`` file and cached after
|
|
27
|
+
the first call to avoid repeated disk access.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
dict[str, str]
|
|
32
|
+
Mapping of canonical quantity keys to display units.
|
|
33
|
+
|
|
34
|
+
Raises
|
|
35
|
+
------
|
|
36
|
+
FileNotFoundError
|
|
37
|
+
If the packaged units file cannot be located.
|
|
38
|
+
"""
|
|
39
|
+
pkg = "reaxkit"
|
|
40
|
+
rel = "data/units.yaml"
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
with ir.files(pkg).joinpath(rel).open("r", encoding="utf-8") as f:
|
|
44
|
+
doc = yaml.safe_load(f) or {}
|
|
45
|
+
except FileNotFoundError as e:
|
|
46
|
+
raise FileNotFoundError(
|
|
47
|
+
f"Could not find packaged units map at '{pkg}/{rel}'. "
|
|
48
|
+
"Make sure units.yaml is included as package data."
|
|
49
|
+
) from e
|
|
50
|
+
|
|
51
|
+
raw = doc.get("units") or {}
|
|
52
|
+
return {str(k): str(v) for k, v in raw.items() if v is not None}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def unit_for(key: str, default: Optional[str] = None) -> Optional[str]:
|
|
56
|
+
"""
|
|
57
|
+
Retrieve the display unit for a canonical quantity key.
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
key : str
|
|
62
|
+
Canonical quantity key (e.g., ``"energy"``, ``"time"``).
|
|
63
|
+
default : str, optional
|
|
64
|
+
Unit string to return if the key is not defined.
|
|
65
|
+
|
|
66
|
+
Returns
|
|
67
|
+
-------
|
|
68
|
+
str or None
|
|
69
|
+
Unit string if found; otherwise ``default``.
|
|
70
|
+
|
|
71
|
+
Examples
|
|
72
|
+
--------
|
|
73
|
+
>>> unit_for("energy")
|
|
74
|
+
>>> unit_for("pressure", default="MPa")
|
|
75
|
+
"""
|
|
76
|
+
return _load_units_map().get(str(key), default)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _label_with_unit(label: str, key: str) -> str:
|
|
80
|
+
"""
|
|
81
|
+
Construct a label string with an associated unit.
|
|
82
|
+
|
|
83
|
+
If a unit exists for the given key, it is appended in parentheses;
|
|
84
|
+
otherwise, the label is returned unchanged.
|
|
85
|
+
|
|
86
|
+
Parameters
|
|
87
|
+
----------
|
|
88
|
+
label : str
|
|
89
|
+
Base label text (e.g., ``"Energy"``).
|
|
90
|
+
key : str
|
|
91
|
+
Canonical quantity key used to look up the unit.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
str
|
|
96
|
+
Label with unit appended if available.
|
|
97
|
+
|
|
98
|
+
Examples
|
|
99
|
+
--------
|
|
100
|
+
>>> _label_with_unit("Energy", "energy")
|
|
101
|
+
>>> _label_with_unit("Time", "time")
|
|
102
|
+
"""
|
|
103
|
+
u = unit_for(key)
|
|
104
|
+
return f"{label} ({u})" if u else label
|
|
File without changes
|
reaxkit/webui/app.py
ADDED
|
File without changes
|
|
File without changes
|
reaxkit/webui/layouts.py
ADDED
|
File without changes
|
reaxkit/webui/utils.py
ADDED
|
File without changes
|
|
File without changes
|
|
File without changes
|