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
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
File without changes
reaxkit/webui/utils.py ADDED
File without changes
File without changes
File without changes