reaxkit 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. reaxkit/__init__.py +0 -0
  2. reaxkit/analysis/__init__.py +0 -0
  3. reaxkit/analysis/composed/RDF_analyzer.py +560 -0
  4. reaxkit/analysis/composed/__init__.py +0 -0
  5. reaxkit/analysis/composed/connectivity_analyzer.py +706 -0
  6. reaxkit/analysis/composed/coordination_analyzer.py +144 -0
  7. reaxkit/analysis/composed/electrostatics_analyzer.py +687 -0
  8. reaxkit/analysis/per_file/__init__.py +0 -0
  9. reaxkit/analysis/per_file/control_analyzer.py +165 -0
  10. reaxkit/analysis/per_file/eregime_analyzer.py +108 -0
  11. reaxkit/analysis/per_file/ffield_analyzer.py +305 -0
  12. reaxkit/analysis/per_file/fort13_analyzer.py +79 -0
  13. reaxkit/analysis/per_file/fort57_analyzer.py +106 -0
  14. reaxkit/analysis/per_file/fort73_analyzer.py +61 -0
  15. reaxkit/analysis/per_file/fort74_analyzer.py +65 -0
  16. reaxkit/analysis/per_file/fort76_analyzer.py +191 -0
  17. reaxkit/analysis/per_file/fort78_analyzer.py +154 -0
  18. reaxkit/analysis/per_file/fort79_analyzer.py +83 -0
  19. reaxkit/analysis/per_file/fort7_analyzer.py +393 -0
  20. reaxkit/analysis/per_file/fort99_analyzer.py +411 -0
  21. reaxkit/analysis/per_file/molfra_analyzer.py +359 -0
  22. reaxkit/analysis/per_file/params_analyzer.py +258 -0
  23. reaxkit/analysis/per_file/summary_analyzer.py +84 -0
  24. reaxkit/analysis/per_file/trainset_analyzer.py +84 -0
  25. reaxkit/analysis/per_file/vels_analyzer.py +95 -0
  26. reaxkit/analysis/per_file/xmolout_analyzer.py +528 -0
  27. reaxkit/cli.py +181 -0
  28. reaxkit/count_loc.py +276 -0
  29. reaxkit/data/alias.yaml +89 -0
  30. reaxkit/data/constants.yaml +27 -0
  31. reaxkit/data/reaxff_input_files_contents.yaml +186 -0
  32. reaxkit/data/reaxff_output_files_contents.yaml +301 -0
  33. reaxkit/data/units.yaml +38 -0
  34. reaxkit/help/__init__.py +0 -0
  35. reaxkit/help/help_index_loader.py +531 -0
  36. reaxkit/help/introspection_utils.py +131 -0
  37. reaxkit/io/__init__.py +0 -0
  38. reaxkit/io/base_handler.py +165 -0
  39. reaxkit/io/generators/__init__.py +0 -0
  40. reaxkit/io/generators/control_generator.py +123 -0
  41. reaxkit/io/generators/eregime_generator.py +341 -0
  42. reaxkit/io/generators/geo_generator.py +967 -0
  43. reaxkit/io/generators/trainset_generator.py +1758 -0
  44. reaxkit/io/generators/tregime_generator.py +113 -0
  45. reaxkit/io/generators/vregime_generator.py +164 -0
  46. reaxkit/io/generators/xmolout_generator.py +304 -0
  47. reaxkit/io/handlers/__init__.py +0 -0
  48. reaxkit/io/handlers/control_handler.py +209 -0
  49. reaxkit/io/handlers/eregime_handler.py +122 -0
  50. reaxkit/io/handlers/ffield_handler.py +812 -0
  51. reaxkit/io/handlers/fort13_handler.py +123 -0
  52. reaxkit/io/handlers/fort57_handler.py +143 -0
  53. reaxkit/io/handlers/fort73_handler.py +145 -0
  54. reaxkit/io/handlers/fort74_handler.py +155 -0
  55. reaxkit/io/handlers/fort76_handler.py +195 -0
  56. reaxkit/io/handlers/fort78_handler.py +142 -0
  57. reaxkit/io/handlers/fort79_handler.py +227 -0
  58. reaxkit/io/handlers/fort7_handler.py +264 -0
  59. reaxkit/io/handlers/fort99_handler.py +128 -0
  60. reaxkit/io/handlers/geo_handler.py +224 -0
  61. reaxkit/io/handlers/molfra_handler.py +184 -0
  62. reaxkit/io/handlers/params_handler.py +137 -0
  63. reaxkit/io/handlers/summary_handler.py +135 -0
  64. reaxkit/io/handlers/trainset_handler.py +658 -0
  65. reaxkit/io/handlers/vels_handler.py +293 -0
  66. reaxkit/io/handlers/xmolout_handler.py +174 -0
  67. reaxkit/utils/__init__.py +0 -0
  68. reaxkit/utils/alias.py +219 -0
  69. reaxkit/utils/cache.py +77 -0
  70. reaxkit/utils/constants.py +75 -0
  71. reaxkit/utils/equation_of_states.py +96 -0
  72. reaxkit/utils/exceptions.py +27 -0
  73. reaxkit/utils/frame_utils.py +175 -0
  74. reaxkit/utils/log.py +43 -0
  75. reaxkit/utils/media/__init__.py +0 -0
  76. reaxkit/utils/media/convert.py +90 -0
  77. reaxkit/utils/media/make_video.py +91 -0
  78. reaxkit/utils/media/plotter.py +812 -0
  79. reaxkit/utils/numerical/__init__.py +0 -0
  80. reaxkit/utils/numerical/extrema_finder.py +96 -0
  81. reaxkit/utils/numerical/moving_average.py +103 -0
  82. reaxkit/utils/numerical/numerical_calcs.py +75 -0
  83. reaxkit/utils/numerical/signal_ops.py +135 -0
  84. reaxkit/utils/path.py +55 -0
  85. reaxkit/utils/units.py +104 -0
  86. reaxkit/webui/__init__.py +0 -0
  87. reaxkit/webui/app.py +0 -0
  88. reaxkit/webui/components.py +0 -0
  89. reaxkit/webui/layouts.py +0 -0
  90. reaxkit/webui/utils.py +0 -0
  91. reaxkit/workflows/__init__.py +0 -0
  92. reaxkit/workflows/composed/__init__.py +0 -0
  93. reaxkit/workflows/composed/coordination_workflow.py +393 -0
  94. reaxkit/workflows/composed/electrostatics_workflow.py +587 -0
  95. reaxkit/workflows/composed/xmolout_fort7_workflow.py +343 -0
  96. reaxkit/workflows/meta/__init__.py +0 -0
  97. reaxkit/workflows/meta/help_workflow.py +136 -0
  98. reaxkit/workflows/meta/introspection_workflow.py +235 -0
  99. reaxkit/workflows/meta/make_video_workflow.py +61 -0
  100. reaxkit/workflows/meta/plotter_workflow.py +601 -0
  101. reaxkit/workflows/per_file/__init__.py +0 -0
  102. reaxkit/workflows/per_file/control_workflow.py +110 -0
  103. reaxkit/workflows/per_file/eregime_workflow.py +267 -0
  104. reaxkit/workflows/per_file/ffield_workflow.py +390 -0
  105. reaxkit/workflows/per_file/fort13_workflow.py +86 -0
  106. reaxkit/workflows/per_file/fort57_workflow.py +137 -0
  107. reaxkit/workflows/per_file/fort73_workflow.py +151 -0
  108. reaxkit/workflows/per_file/fort74_workflow.py +88 -0
  109. reaxkit/workflows/per_file/fort76_workflow.py +188 -0
  110. reaxkit/workflows/per_file/fort78_workflow.py +135 -0
  111. reaxkit/workflows/per_file/fort79_workflow.py +314 -0
  112. reaxkit/workflows/per_file/fort7_workflow.py +592 -0
  113. reaxkit/workflows/per_file/fort83_workflow.py +60 -0
  114. reaxkit/workflows/per_file/fort99_workflow.py +223 -0
  115. reaxkit/workflows/per_file/geo_workflow.py +554 -0
  116. reaxkit/workflows/per_file/molfra_workflow.py +577 -0
  117. reaxkit/workflows/per_file/params_workflow.py +135 -0
  118. reaxkit/workflows/per_file/summary_workflow.py +161 -0
  119. reaxkit/workflows/per_file/trainset_workflow.py +356 -0
  120. reaxkit/workflows/per_file/tregime_workflow.py +79 -0
  121. reaxkit/workflows/per_file/vels_workflow.py +309 -0
  122. reaxkit/workflows/per_file/vregime_workflow.py +75 -0
  123. reaxkit/workflows/per_file/xmolout_workflow.py +678 -0
  124. reaxkit-1.0.0.dist-info/METADATA +128 -0
  125. reaxkit-1.0.0.dist-info/RECORD +130 -0
  126. reaxkit-1.0.0.dist-info/WHEEL +5 -0
  127. reaxkit-1.0.0.dist-info/entry_points.txt +2 -0
  128. reaxkit-1.0.0.dist-info/licenses/AUTHORS.md +20 -0
  129. reaxkit-1.0.0.dist-info/licenses/LICENSE +21 -0
  130. reaxkit-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,165 @@
1
+ """
2
+ Control file analysis utilities.
3
+
4
+ This module provides helper functions for querying and retrieving
5
+ parameters from a parsed ReaxFF control file via `ControlHandler`.
6
+
7
+ Typical use cases include:
8
+
9
+ - discovering available parameter sections
10
+ - listing available control keys
11
+ - retrieving parameter values with safe defaults
12
+ """
13
+
14
+
15
+ from __future__ import annotations
16
+ from typing import Optional
17
+
18
+ from reaxkit.io.handlers.control_handler import ControlHandler
19
+
20
+
21
+ def _get_available_sections(handler: ControlHandler) -> list[str]:
22
+ """List the available parameter sections in a ReaxFF control file.
23
+
24
+ Works on
25
+ --------
26
+ ControlHandler (control)
27
+
28
+ Parameters
29
+ ----------
30
+ handler : ControlHandler
31
+ Parsed control file handler.
32
+
33
+ Returns
34
+ -------
35
+ list[str]
36
+ Section names present in the file (e.g., "general", "md", "mm", "ff", "outdated").
37
+
38
+ Examples
39
+ --------
40
+ >>> from reaxkit.io.handlers.control_handler import ControlHandler
41
+ >>> from reaxkit.analysis.per_file.control_analyzer import _get_available_sections
42
+ >>> h = ControlHandler("control")
43
+ >>> sections = _get_available_sections(h)
44
+ """
45
+ sections = []
46
+ if handler.general_parameters:
47
+ sections.append("general")
48
+ if handler.md_parameters:
49
+ sections.append("md")
50
+ if handler.mm_parameters:
51
+ sections.append("mm")
52
+ if handler.ff_parameters:
53
+ sections.append("ff")
54
+ if handler.outdated_parameters:
55
+ sections.append("outdated")
56
+ return sections
57
+
58
+
59
+ def _get_available_keys(handler: ControlHandler, section: Optional[str] = None) -> list[str]:
60
+ """List available control-file parameter keys, optionally within a specific section.
61
+
62
+ Works on
63
+ --------
64
+ ControlHandler (control)
65
+
66
+ Parameters
67
+ ----------
68
+ handler : ControlHandler
69
+ Parsed control file handler.
70
+ section : str, optional
71
+ Section to query (one of: "general", "md", "mm", "ff", "outdated").
72
+ If None, keys from all sections are combined.
73
+
74
+ Returns
75
+ -------
76
+ list[str]
77
+ Sorted list of parameter keys.
78
+
79
+ Examples
80
+ --------
81
+ >>> from reaxkit.io.handlers.control_handler import ControlHandler
82
+ >>> from reaxkit.analysis.per_file.control_analyzer import _get_available_keys
83
+ >>> h = ControlHandler("control")
84
+ >>> keys = _get_available_keys(h, section="md")
85
+ """
86
+ section_map = {
87
+ "general": handler.general_parameters,
88
+ "md": handler.md_parameters,
89
+ "mm": handler.mm_parameters,
90
+ "ff": handler.ff_parameters,
91
+ "outdated": handler.outdated_parameters,
92
+ }
93
+
94
+ if section:
95
+ section = section.lower()
96
+ if section not in section_map:
97
+ raise ValueError(f"❌ Unknown section: {section}")
98
+ return sorted(section_map[section].keys())
99
+
100
+ # all keys from all sections
101
+ all_keys = set()
102
+ for d in section_map.values():
103
+ all_keys.update(d.keys())
104
+ return sorted(all_keys)
105
+
106
+
107
+ def get_control_data(
108
+ handler: ControlHandler,
109
+ key: str,
110
+ section: Optional[str] = None,
111
+ default=None,
112
+ ):
113
+ """
114
+ Retrieve a control-file parameter value by key, optionally restricted to a section.
115
+
116
+ Works on
117
+ --------
118
+ ControlHandler (control)
119
+
120
+ Parameters
121
+ ----------
122
+ handler : ControlHandler
123
+ Parsed control file handler.
124
+ key : str
125
+ Parameter name (case-insensitive).
126
+ section : str, optional
127
+ Section to search (one of: "general", "md", "mm", "ff", "outdated").
128
+ If None, all sections are searched in order.
129
+ default : any, optional
130
+ Value to return if the key is not found.
131
+
132
+ Returns
133
+ -------
134
+ any
135
+ The parameter value if found; otherwise `default`.
136
+
137
+ Examples
138
+ --------
139
+ >>> from reaxkit.io.handlers.control_handler import ControlHandler
140
+ >>> from reaxkit.analysis.per_file.control_analyzer import get_control_data
141
+ >>> h = ControlHandler("control")
142
+ >>> tstep = get_control_data(h, "tstep", section="md", default=None)
143
+ """
144
+ key = key.lower()
145
+ section_map = {
146
+ "general": handler.general_parameters,
147
+ "md": handler.md_parameters,
148
+ "mm": handler.mm_parameters,
149
+ "ff": handler.ff_parameters,
150
+ "outdated": handler.outdated_parameters,
151
+ }
152
+
153
+ if section:
154
+ section = section.lower()
155
+ if section not in section_map:
156
+ raise ValueError(f"❌ Unknown section: {section}")
157
+ try:
158
+ return section_map[section][key]
159
+ except:
160
+ return default
161
+
162
+ for d in section_map.values():
163
+ if key in d:
164
+ return d[key]
165
+ return default
@@ -0,0 +1,108 @@
1
+ """Eregime (eregime.in) analysis utilities.
2
+
3
+ This module provides helpers for extracting and plotting electric-field regime
4
+ data from a parsed ``eregime.in`` file via ``EregimeHandler``.
5
+
6
+ Typical use cases include:
7
+
8
+ - selecting a column (with alias support) such as field magnitude or direction
9
+ - converting the x-axis from iteration to frame index or physical time
10
+ - exporting a clean two-column table for plotting
11
+ """
12
+
13
+
14
+ from __future__ import annotations
15
+ import pandas as pd
16
+
17
+ from reaxkit.io.handlers.eregime_handler import EregimeHandler
18
+ from reaxkit.utils.alias import resolve_alias_from_columns, normalize_choice # uses shared alias map
19
+ from reaxkit.utils.media.convert import convert_xaxis # converts iter → time or frame
20
+
21
+
22
+ def _resolve_with_fallback(df: pd.DataFrame, name: str) -> str:
23
+ """
24
+ Resolve a requested column name using the shared alias map.
25
+ Falls back to enumerated columns (field1/field_dir1, etc.) if generic keys are absent.
26
+ """
27
+ if not name:
28
+ raise ValueError("Column name is empty.")
29
+
30
+ # Normalize common variants (case-insensitive) to a canonical key
31
+ canonical = normalize_choice(name)
32
+
33
+ # Try direct/aliased match
34
+ hit = resolve_alias_from_columns(df.columns, canonical)
35
+ if hit:
36
+ return hit
37
+
38
+ # Fallbacks for generic single-zone keys in multi-zone files
39
+ if canonical in ("field", "field_dir"):
40
+ # Prefer the first enumerated pair if present
41
+ for i in range(1, 16): # generous upper bound
42
+ cand = f"{canonical}{i}" if canonical != "field_dir" else f"field_dir{i}"
43
+ hit = resolve_alias_from_columns(df.columns, cand)
44
+ if hit:
45
+ return hit
46
+
47
+ raise ValueError(
48
+ f"Column '{name}' not found (after alias resolution). "
49
+ f"Available columns: {list(df.columns)}"
50
+ )
51
+
52
+
53
+ def get_eregime_data(
54
+ handler: EregimeHandler,
55
+ y: str,
56
+ xaxis: str = "iter",
57
+ control_file: str = "control",
58
+ ) -> pd.DataFrame:
59
+ """Extract a two-column table of a selected ``eregime.in`` quantity versus an x-axis.
60
+
61
+ Works on
62
+ --------
63
+ EregimeHandler — ``eregime.in``
64
+
65
+ Parameters
66
+ ----------
67
+ handler : EregimeHandler
68
+ Parsed ``eregime.in`` handler.
69
+ y : str
70
+ Name of the y-column to extract. Aliases are supported (e.g., ``E``, ``Ef``,
71
+ ``Magnitude(V/A)`` → field; ``direction``/``dir`` → field direction).
72
+ For multi-zone files, you may request enumerated keys like ``field2`` or
73
+ ``field_dir3``.
74
+ xaxis : {"iter", "frame", "time"}, default="iter"
75
+ X-axis to use:
76
+ - ``iter``: raw iteration index from the file
77
+ - ``frame``: 0..N-1
78
+ - ``time``: converts iteration → time using ``control_file`` and auto-scales fs/ps/ns
79
+ control_file : str, default="control"
80
+ Path to the ReaxFF control file used for ``xaxis="time"`` conversion.
81
+
82
+ Returns
83
+ -------
84
+ pandas.DataFrame
85
+ Two-column table with columns: ``[x_label, y]`` where ``x_label`` is one of
86
+ ``iter``, ``Frame``, or ``Time (fs/ps/ns)``.
87
+
88
+ Examples
89
+ --------
90
+ >>> from reaxkit.io.handlers.eregime_handler import EregimeHandler
91
+ >>> from reaxkit.analysis.per_file.eregime_analyzer import get_eregime_data
92
+ >>> h = EregimeHandler("eregime.in")
93
+ >>> df = get_eregime_data(h, y="E", xaxis="time", control_file="control")
94
+ """
95
+ df = handler.dataframe()
96
+
97
+ # Resolve iteration column (needed for x conversion)
98
+ iter_col = _resolve_with_fallback(df, "iter")
99
+
100
+ # Resolve Y column with aliases + fallbacks
101
+ y_col = _resolve_with_fallback(df, y)
102
+
103
+ # Convert x-axis
104
+ x_vals, x_label = convert_xaxis(df[iter_col].to_numpy(), xaxis=xaxis, control_file=control_file)
105
+
106
+ out = pd.DataFrame({x_label: x_vals, y: df[y_col].to_numpy()})
107
+ out = out.sort_values(x_label).reset_index(drop=True)
108
+ return out
@@ -0,0 +1,305 @@
1
+ """
2
+ ReaxFF force-field (ffield) analysis utilities.
3
+
4
+ This module provides helper functions for extracting and interpreting
5
+ sections of a ReaxFF ``ffield`` file via ``FFieldHandler``.
6
+
7
+ Typical use cases include:
8
+
9
+ - retrieving raw parameter tables for specific ffield sections
10
+ - interpreting numeric atom indices (e.g. 1–1) into chemical symbols (e.g. C–C)
11
+ - generating human-readable interaction labels for bonds, angles, torsions, and H-bonds
12
+ """
13
+
14
+
15
+ from __future__ import annotations
16
+ from typing import Dict, Iterable, Optional, Sequence
17
+
18
+ import pandas as pd
19
+
20
+ from reaxkit.io.base_handler import BaseHandler
21
+ from reaxkit.io.handlers.ffield_handler import FFieldHandler
22
+
23
+
24
+ # Map user-friendly names to canonical section keys
25
+ _SECTION_ALIASES: Dict[str, str] = {
26
+ "general": FFieldHandler.SECTION_GENERAL,
27
+
28
+ "atom": FFieldHandler.SECTION_ATOM,
29
+ "atoms": FFieldHandler.SECTION_ATOM,
30
+
31
+ "bond": FFieldHandler.SECTION_BOND,
32
+ "bonds": FFieldHandler.SECTION_BOND,
33
+
34
+ "off_diagonal": FFieldHandler.SECTION_OFF_DIAGONAL,
35
+ "off-diagonal": FFieldHandler.SECTION_OFF_DIAGONAL,
36
+ "offdiag": FFieldHandler.SECTION_OFF_DIAGONAL,
37
+
38
+ "angle": FFieldHandler.SECTION_ANGLE,
39
+ "angles": FFieldHandler.SECTION_ANGLE,
40
+
41
+ "torsion": FFieldHandler.SECTION_TORSION,
42
+ "torsions": FFieldHandler.SECTION_TORSION,
43
+
44
+ "hbond": FFieldHandler.SECTION_HBOND,
45
+ "hbonds": FFieldHandler.SECTION_HBOND,
46
+ "hydrogen_bond": FFieldHandler.SECTION_HBOND,
47
+ "hydrogen_bonds": FFieldHandler.SECTION_HBOND,
48
+ }
49
+
50
+
51
+ def _normalize_section_name(section: str) -> str:
52
+ """Normalize user input like 'Off-Diagonal', ' off diag ' -> 'off_diagonal'."""
53
+ return section.strip().lower().replace("-", "_").replace(" ", "_")
54
+
55
+
56
+ def get_ffield_data(handler: BaseHandler, *, section: str) -> pd.DataFrame:
57
+ """
58
+ Retrieve a specific section of the ReaxFF force-field file as a DataFrame.
59
+
60
+ Works on
61
+ --------
62
+ FFieldHandler — ``ffield``
63
+
64
+ Parameters
65
+ ----------
66
+ handler : FFieldHandler
67
+ Parsed force-field handler.
68
+ section : str
69
+ Section name to retrieve (e.g. ``atom``, ``bond``, ``off_diagonal``,
70
+ ``angle``, ``torsion``, ``hbond``). Aliases are supported.
71
+
72
+ Returns
73
+ -------
74
+ pandas.DataFrame
75
+ Table of parameters for the requested section.
76
+
77
+ Examples
78
+ --------
79
+ >>> from reaxkit.io.handlers.ffield_handler import FFieldHandler
80
+ >>> from reaxkit.analysis.per_file.ffield_analyzer import get_ffield_data
81
+ >>> h = FFieldHandler("ffield")
82
+ >>> df = get_ffield_data(h, section="bond")
83
+ """
84
+
85
+ # Type check
86
+ if not isinstance(handler, FFieldHandler):
87
+ raise TypeError(
88
+ f"get_sections_data requires an FFieldHandler, got {type(handler)!r}"
89
+ )
90
+
91
+ # Normalize the name
92
+ norm = _normalize_section_name(section)
93
+
94
+ if norm not in _SECTION_ALIASES:
95
+ raise KeyError(
96
+ f"Unknown section {section!r}. Valid options: {sorted(_SECTION_ALIASES.keys())}"
97
+ )
98
+
99
+ # Resolve canonical key
100
+ canonical = _SECTION_ALIASES[norm]
101
+
102
+ # Use your kept section_df method
103
+ try:
104
+ df = handler.section_df(canonical)
105
+ except KeyError:
106
+ raise KeyError(
107
+ f"Section {section!r} (canonical key {canonical!r}) not found in this ffield."
108
+ )
109
+
110
+ # Always return a copy
111
+ return df.copy()
112
+
113
+ ###############################################################################
114
+ # A “interpret indices → atom symbols” utility.
115
+ # It uses the atom section to build an index -> symbol map for bond,
116
+ # off-diagonal, angle, torsion, hbond.
117
+ # This is used to understand what 1 1 mean in bond data, which shows C-C
118
+ # bond if C is the atom number 1 in the ffield.
119
+ ###############################################################################
120
+
121
+ def _atom_index_to_symbol_map(handler: FFieldHandler) -> Dict[int, str]:
122
+ """
123
+ Build {atom_index: symbol} from the atom section.
124
+
125
+ Notes:
126
+ - atom_df index is 'atom_index' (1-based in your parser).
127
+ - symbol column is expected to be like 'C', 'H', 'O', ...
128
+ """
129
+ atom_df = handler.section_df(FFieldHandler.SECTION_ATOM)
130
+
131
+ if "symbol" not in atom_df.columns:
132
+ raise KeyError(
133
+ "Atom section does not contain a 'symbol' column. "
134
+ "Check FFieldHandler._parse_atom_section()."
135
+ )
136
+
137
+ out: Dict[int, str] = {}
138
+ for idx, row in atom_df.iterrows():
139
+ try:
140
+ i = int(idx)
141
+ except Exception:
142
+ continue
143
+ sym = row.get("symbol")
144
+ if sym is None or (isinstance(sym, float) and pd.isna(sym)) or str(sym).strip() == "":
145
+ sym = f"atom{i}"
146
+ out[i] = str(sym).strip()
147
+ return out
148
+
149
+
150
+ def _add_symbols_for_columns(
151
+ df: pd.DataFrame,
152
+ idx_to_sym: Dict[int, str],
153
+ cols: Sequence[str],
154
+ *,
155
+ term_col: str = "term",
156
+ sep: str = "-",
157
+ ) -> pd.DataFrame:
158
+ """
159
+ For each integer index column in `cols` (e.g. ['i','j','k']),
160
+ add '<col>_symbol' columns and a combined `term` column.
161
+ """
162
+ out = df.copy()
163
+
164
+ sym_cols: list[str] = []
165
+ for c in cols:
166
+ sc = f"{c}_symbol"
167
+ sym_cols.append(sc)
168
+
169
+ def _map_one(v):
170
+ if v is None or (isinstance(v, float) and pd.isna(v)):
171
+ return None
172
+ try:
173
+ iv = int(v)
174
+ except Exception:
175
+ return None
176
+ return idx_to_sym.get(iv, f"atom{iv}")
177
+
178
+ out[sc] = out[c].map(_map_one)
179
+
180
+ # Build readable label: e.g. C-H, C-C-C, O-H-O
181
+ out[term_col] = out[sym_cols].apply(
182
+ lambda r: sep.join([x for x in r.tolist() if x is not None]),
183
+ axis=1,
184
+ )
185
+ return out
186
+
187
+
188
+ def interpret_ffield_terms(
189
+ handler: FFieldHandler,
190
+ *,
191
+ sections: Optional[Iterable[str]] = None,
192
+ sep: str = "-",
193
+ ) -> Dict[str, pd.DataFrame]:
194
+ """
195
+ Interpret numeric atom indices in ffield sections into chemical symbols.
196
+
197
+ Works on
198
+ --------
199
+ FFieldHandler — ``ffield``
200
+
201
+ Parameters
202
+ ----------
203
+ handler : FFieldHandler
204
+ Parsed force-field handler.
205
+ sections : iterable of str, optional
206
+ Sections to interpret. Default interprets all relevant sections:
207
+ ``bond``, ``off_diagonal``, ``angle``, ``torsion``, ``hbond``.
208
+ sep : str, default="-"
209
+ Separator used when building human-readable interaction labels.
210
+
211
+ Returns
212
+ -------
213
+ dict[str, pandas.DataFrame]
214
+ Mapping from section name to interpreted DataFrame. Each DataFrame
215
+ includes:
216
+ - ``*_symbol`` columns (e.g. ``i_symbol``, ``j_symbol``)
217
+ - ``term`` column with readable labels (e.g. ``C-H``, ``C-C-C``)
218
+
219
+ Examples
220
+ --------
221
+ >>> from reaxkit.io.handlers.ffield_handler import FFieldHandler
222
+ >>> from reaxkit.analysis.per_file.ffield_analyzer import interpret_ffield_terms
223
+ >>> h = FFieldHandler("ffield")
224
+ >>> data = interpret_ffield_terms(h, sections=["bond", "angle"])
225
+ >>> bond_df = data["bond"]
226
+ """
227
+ if not isinstance(handler, FFieldHandler):
228
+ raise TypeError(f"interpret_ffield_terms requires FFieldHandler, got {type(handler)!r}")
229
+
230
+ idx_to_sym = _atom_index_to_symbol_map(handler)
231
+
232
+ targets = [
233
+ FFieldHandler.SECTION_BOND,
234
+ FFieldHandler.SECTION_OFF_DIAGONAL,
235
+ FFieldHandler.SECTION_ANGLE,
236
+ FFieldHandler.SECTION_TORSION,
237
+ FFieldHandler.SECTION_HBOND,
238
+ ]
239
+ if sections is not None:
240
+ wanted = {s.strip().lower() for s in sections}
241
+ targets = [t for t in targets if t in wanted]
242
+
243
+ out: Dict[str, pd.DataFrame] = {}
244
+
245
+ for sec in targets:
246
+ df = handler.section_df(sec).copy()
247
+
248
+ if sec in (FFieldHandler.SECTION_BOND, FFieldHandler.SECTION_OFF_DIAGONAL):
249
+ # columns: i, j
250
+ out[sec] = _add_symbols_for_columns(df, idx_to_sym, ["i", "j"], sep=sep)
251
+ elif sec == FFieldHandler.SECTION_ANGLE:
252
+ # columns: i, j, k
253
+ out[sec] = _add_symbols_for_columns(df, idx_to_sym, ["i", "j", "k"], sep=sep)
254
+ elif sec == FFieldHandler.SECTION_TORSION:
255
+ # columns: i, j, k, l
256
+ out[sec] = _add_symbols_for_columns(df, idx_to_sym, ["i", "j", "k", "l"], sep=sep)
257
+ elif sec == FFieldHandler.SECTION_HBOND:
258
+ # columns: i, j, k (commonly X-H-Y order in your example)
259
+ out[sec] = _add_symbols_for_columns(df, idx_to_sym, ["i", "j", "k"], sep=sep)
260
+ else:
261
+ out[sec] = df
262
+
263
+ return out
264
+
265
+
266
+ def interpret_one_section(
267
+ handler: FFieldHandler,
268
+ *,
269
+ section: str,
270
+ sep: str = "-",
271
+ ) -> pd.DataFrame:
272
+ """
273
+ Interpret a single ffield section into symbol-based interaction labels.
274
+
275
+ Works on
276
+ --------
277
+ FFieldHandler — ``ffield``
278
+
279
+ Parameters
280
+ ----------
281
+ handler : FFieldHandler
282
+ Parsed force-field handler.
283
+ section : str
284
+ Section to interpret (e.g. ``bond``, ``angle``, ``torsion``, ``hbond``).
285
+ sep : str, default="-"
286
+ Separator used when building the ``term`` label.
287
+
288
+ Returns
289
+ -------
290
+ pandas.DataFrame
291
+ Interpreted section DataFrame with ``*_symbol`` columns and a
292
+ human-readable ``term`` column.
293
+
294
+ Examples
295
+ --------
296
+ >>> from reaxkit.io.handlers.ffield_handler import FFieldHandler
297
+ >>> from reaxkit.analysis.per_file.ffield_analyzer import interpret_one_section
298
+ >>> h = FFieldHandler("ffield")
299
+ >>> bond_df = interpret_one_section(h, section="bond")
300
+ """
301
+ section = section.strip().lower().replace("-", "_").replace(" ", "_")
302
+ res = interpret_ffield_terms(handler, sections=[section], sep=sep)
303
+ if section not in res:
304
+ raise KeyError(f"Section {section!r} not found or not supported for interpretation.")
305
+ return res[section]
@@ -0,0 +1,79 @@
1
+ """
2
+ fort.13 (training error) analysis utilities.
3
+
4
+ This module provides helper functions for extracting force-field training
5
+ error values from a parsed ReaxFF ``fort.13`` file via ``Fort13Handler``.
6
+
7
+ Typical use cases include:
8
+
9
+ - retrieving total force-field error versus epoch
10
+ - extracting the error series for quick plotting or post-processing
11
+ """
12
+
13
+
14
+ from __future__ import annotations
15
+ from typing import List, Optional
16
+ import pandas as pd
17
+
18
+ from reaxkit.io.handlers.fort13_handler import Fort13Handler
19
+
20
+
21
+ def get_fort13_data(handler: Fort13Handler, epochs: Optional[List[int]] = None) -> pd.DataFrame:
22
+ """Extract total force-field error for all or selected epochs from ``fort.13``.
23
+
24
+ Works on
25
+ --------
26
+ Fort13Handler — ``fort.13``
27
+
28
+ Parameters
29
+ ----------
30
+ handler : Fort13Handler
31
+ Parsed ``fort.13`` handler.
32
+ epochs : list[int], optional
33
+ Epoch numbers to include. If None, all epochs are returned.
34
+
35
+ Returns
36
+ -------
37
+ pandas.DataFrame
38
+ Error table with columns: ``epoch`` and ``total_ff_error``.
39
+
40
+ Examples
41
+ --------
42
+ >>> from reaxkit.io.handlers.fort13_handler import Fort13Handler
43
+ >>> from reaxkit.analysis.per_file.fort13_analyzer import get_fort13_data
44
+ >>> h = Fort13Handler("fort.13")
45
+ >>> df = get_fort13_data(h, epochs=[1, 10, 50])
46
+ """
47
+ df = handler.dataframe()
48
+
49
+ if epochs is not None:
50
+ df = df[df["epoch"].isin(epochs)].reset_index(drop=True)
51
+
52
+ return df[["epoch", "total_ff_error"]].copy()
53
+
54
+
55
+ def _error_series_across_epochs(handler: Fort13Handler) -> List[float]:
56
+ """Return the total force-field error values across all epochs as a list.
57
+
58
+ Works on
59
+ --------
60
+ Fort13Handler — ``fort.13``
61
+
62
+ Parameters
63
+ ----------
64
+ handler : Fort13Handler
65
+ Parsed ``fort.13`` handler.
66
+
67
+ Returns
68
+ -------
69
+ list[float]
70
+ Total force-field error values in epoch order.
71
+
72
+ Examples
73
+ --------
74
+ >>> from reaxkit.io.handlers.fort13_handler import Fort13Handler
75
+ >>> from reaxkit.analysis.per_file.fort13_analyzer import _error_series_across_epochs
76
+ >>> h = Fort13Handler("fort.13")
77
+ >>> errs = _error_series_across_epochs(h)
78
+ """
79
+ return handler.dataframe()["total_ff_error"].tolist()