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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
trainset (ReaxFF training-set) analysis utilities.
|
|
3
|
+
|
|
4
|
+
This module provides helper functions for extracting metadata and
|
|
5
|
+
human-readable annotations from ReaxFF ``trainset`` files via
|
|
6
|
+
``TrainsetHandler``.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- listing unique group comments defined across training sections
|
|
11
|
+
- auditing how training targets are grouped and documented
|
|
12
|
+
- preparing summaries of training-set structure for reporting
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
import pandas as pd
|
|
20
|
+
|
|
21
|
+
from reaxkit.io.handlers.trainset_handler import TrainsetHandler
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_trainset_group_comments(handler: TrainsetHandler, *, sort: bool = False) -> pd.DataFrame:
|
|
25
|
+
"""
|
|
26
|
+
Collect unique group comments from a ReaxFF training-set file.
|
|
27
|
+
|
|
28
|
+
Each group comment is returned together with the training section
|
|
29
|
+
it belongs to.
|
|
30
|
+
|
|
31
|
+
Works on
|
|
32
|
+
--------
|
|
33
|
+
TrainsetHandler — ``trainset``
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
handler : TrainsetHandler
|
|
38
|
+
Parsed trainset handler with metadata and section tables.
|
|
39
|
+
sort : bool, default=False
|
|
40
|
+
If True, sort the result by ``section`` and ``group_comment``.
|
|
41
|
+
If False, preserve the original appearance order.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
pandas.DataFrame
|
|
46
|
+
Table with columns:
|
|
47
|
+
``section`` — training section name
|
|
48
|
+
``group_comment`` — unique group annotation text
|
|
49
|
+
|
|
50
|
+
Examples
|
|
51
|
+
--------
|
|
52
|
+
>>> from reaxkit.io.handlers.trainset_handler import TrainsetHandler
|
|
53
|
+
>>> from reaxkit.analysis.per_file.trainset_analyzer import get_trainset_group_comments
|
|
54
|
+
>>> h = TrainsetHandler("trainset")
|
|
55
|
+
>>> df = get_trainset_group_comments(h, sort=True)
|
|
56
|
+
"""
|
|
57
|
+
meta: dict[str, Any] = handler.metadata()
|
|
58
|
+
tables: dict[str, pd.DataFrame] = meta.get("tables", {})
|
|
59
|
+
|
|
60
|
+
rows: list[dict[str, str]] = []
|
|
61
|
+
|
|
62
|
+
for section_name, df in tables.items():
|
|
63
|
+
if "group_comment" not in df.columns:
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
# keep only non-empty comments
|
|
67
|
+
series = df["group_comment"].astype(str).str.strip()
|
|
68
|
+
series = series[series != ""]
|
|
69
|
+
|
|
70
|
+
for gc in series.unique():
|
|
71
|
+
rows.append(
|
|
72
|
+
{
|
|
73
|
+
"section": section_name.lower(), # or keep as-is
|
|
74
|
+
"group_comment": gc,
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
result = pd.DataFrame(rows).drop_duplicates()
|
|
79
|
+
|
|
80
|
+
# Apply sort only if requested
|
|
81
|
+
if sort and not result.empty:
|
|
82
|
+
result = result.sort_values(["section", "group_comment"], ignore_index=True)
|
|
83
|
+
|
|
84
|
+
return result
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""
|
|
2
|
+
vels / moldyn.vel / molsav analysis utilities.
|
|
3
|
+
|
|
4
|
+
This module provides a unified interface for accessing atomic coordinates,
|
|
5
|
+
velocities, and accelerations stored in ReaxFF velocity output files via
|
|
6
|
+
``VelsHandler``.
|
|
7
|
+
|
|
8
|
+
Typical use cases include:
|
|
9
|
+
|
|
10
|
+
- retrieving atomic coordinates or velocities for selected atoms
|
|
11
|
+
- extracting acceleration histories for diagnostics or plotting
|
|
12
|
+
- accessing file metadata (timestep, atom count, sections present)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Literal, Sequence
|
|
19
|
+
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
from reaxkit.io.handlers.vels_handler import VelsHandler
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
VelsKey = Literal[
|
|
26
|
+
"metadata",
|
|
27
|
+
"coordinates",
|
|
28
|
+
"velocities",
|
|
29
|
+
"accelerations",
|
|
30
|
+
"prev_accelerations",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_vels_data(
|
|
35
|
+
handler: VelsHandler,
|
|
36
|
+
key: VelsKey,
|
|
37
|
+
*,
|
|
38
|
+
atoms: Sequence[int] | None = None,
|
|
39
|
+
) -> pd.DataFrame | dict:
|
|
40
|
+
"""
|
|
41
|
+
Retrieve metadata or a selected atomic table from a velocity output file.
|
|
42
|
+
|
|
43
|
+
Works on
|
|
44
|
+
--------
|
|
45
|
+
VelsHandler — ``vels`` / ``moldyn.vel`` / ``molsav``
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
handler : VelsHandler
|
|
50
|
+
Parsed velocity file handler.
|
|
51
|
+
key : {"metadata", "coordinates", "velocities", "accelerations", "prev_accelerations"}
|
|
52
|
+
Section to retrieve:
|
|
53
|
+
- ``metadata``: file-level information (returned as a dict)
|
|
54
|
+
- ``coordinates``: atomic positions
|
|
55
|
+
- ``velocities``: atomic velocities
|
|
56
|
+
- ``accelerations``: atomic accelerations
|
|
57
|
+
- ``prev_accelerations``: accelerations from the previous step
|
|
58
|
+
atoms : sequence of int, optional
|
|
59
|
+
1-based atom indices to include. If None, all atoms are returned.
|
|
60
|
+
Ignored when ``key="metadata"``.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
pandas.DataFrame or dict
|
|
65
|
+
If ``key="metadata"``, returns a metadata dictionary.
|
|
66
|
+
Otherwise, returns a DataFrame with one row per atom containing the
|
|
67
|
+
requested quantities and an ``atom_index`` column.
|
|
68
|
+
|
|
69
|
+
Examples
|
|
70
|
+
--------
|
|
71
|
+
>>> from reaxkit.io.handlers.vels_handler import VelsHandler
|
|
72
|
+
>>> from reaxkit.analysis.per_file.vels_analyzer import get_vels_data
|
|
73
|
+
>>> h = VelsHandler("moldyn.vel")
|
|
74
|
+
>>> v = get_vels_data(h, "velocities", atoms=[1, 2, 3])
|
|
75
|
+
>>> meta = get_vels_data(h, "metadata")
|
|
76
|
+
"""
|
|
77
|
+
if key == "metadata":
|
|
78
|
+
return handler.metadata()
|
|
79
|
+
|
|
80
|
+
if key == "coordinates":
|
|
81
|
+
df = handler.section_df(handler.SECTION_COORDS).copy()
|
|
82
|
+
elif key == "velocities":
|
|
83
|
+
df = handler.section_df(handler.SECTION_VELS).copy()
|
|
84
|
+
elif key == "accelerations":
|
|
85
|
+
df = handler.section_df(handler.SECTION_ACCELS).copy()
|
|
86
|
+
elif key == "prev_accelerations":
|
|
87
|
+
df = handler.section_df(handler.SECTION_PREV_ACCELS).copy()
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f"Unknown vels key: {key}")
|
|
90
|
+
|
|
91
|
+
if atoms:
|
|
92
|
+
# 1-based indices expected (matches how handler stores atom_index)
|
|
93
|
+
df = df[df["atom_index"].isin(list(atoms))].copy()
|
|
94
|
+
|
|
95
|
+
return df.reset_index(drop=True)
|
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xmolout trajectory analysis utilities.
|
|
3
|
+
|
|
4
|
+
This module provides atomistic and trajectory-level analysis tools for
|
|
5
|
+
ReaxFF ``xmolout`` files via ``XmoloutHandler``.
|
|
6
|
+
|
|
7
|
+
It supports extraction of atom properties, trajectories, simulation box
|
|
8
|
+
information, displacement metrics, atom-type mappings, and radial
|
|
9
|
+
distribution functions (RDFs) using multiple backends.
|
|
10
|
+
|
|
11
|
+
Typical use cases include:
|
|
12
|
+
|
|
13
|
+
- exporting per-atom coordinates or properties across frames
|
|
14
|
+
- building atom trajectories in long or wide format
|
|
15
|
+
- tracking box dimensions and thermodynamic scalars over time
|
|
16
|
+
- computing mean-squared displacement (MSD)
|
|
17
|
+
- computing total or partial RDFs and RDF-derived properties
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
from typing import Iterable, Optional, Sequence, Union, Dict, Any, List
|
|
23
|
+
import numpy as np
|
|
24
|
+
import pandas as pd
|
|
25
|
+
|
|
26
|
+
from reaxkit.io.handlers.xmolout_handler import XmoloutHandler
|
|
27
|
+
from reaxkit.utils.frame_utils import select_frames as _df_select
|
|
28
|
+
|
|
29
|
+
from reaxkit.analysis.composed.RDF_analyzer import (
|
|
30
|
+
rdf_using_freud as _rdf_freud_many,
|
|
31
|
+
rdf_using_ovito as _rdf_ovito_many,
|
|
32
|
+
rdf_property_over_frames as _rdf_props,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# ==========================================================
|
|
36
|
+
# === non-RDF helpers (atom tables/trajectories/box) ===
|
|
37
|
+
# ==========================================================
|
|
38
|
+
|
|
39
|
+
FrameSel = Optional[Union[Sequence[int], range, slice]]
|
|
40
|
+
AtomSel = Optional[Union[Sequence[int], slice]]
|
|
41
|
+
|
|
42
|
+
BASE_ATOM_COLS = ("atom_type", "x", "y", "z")
|
|
43
|
+
|
|
44
|
+
def _frame_table(xh: XmoloutHandler, i: int) -> pd.DataFrame:
|
|
45
|
+
"""Internal helper: return a DataFrame for a specific frame index from an XmoloutHandler.
|
|
46
|
+
|
|
47
|
+
If pre-parsed frames are cached in `xh._frames`, retrieves directly from cache.
|
|
48
|
+
Otherwise, loads the frame via `xh.frame(i)` and constructs a DataFrame with:
|
|
49
|
+
- atom_type
|
|
50
|
+
- x, y, z coordinates
|
|
51
|
+
|
|
52
|
+
Used internally by higher-level utilities (e.g., atom table extraction,
|
|
53
|
+
per-frame analysis, or visualization routines).
|
|
54
|
+
"""
|
|
55
|
+
if hasattr(xh, "_frames") and i < len(xh._frames):
|
|
56
|
+
return xh._frames[i]
|
|
57
|
+
fr = xh.frame(i)
|
|
58
|
+
return pd.DataFrame({
|
|
59
|
+
"atom_type": fr["atom_types"],
|
|
60
|
+
"x": fr["coords"][:, 0], "y": fr["coords"][:, 1], "z": fr["coords"][:, 2],
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
def _get_xmolout_data_per_atom(
|
|
64
|
+
xh: XmoloutHandler,
|
|
65
|
+
*,
|
|
66
|
+
frames: FrameSel = None,
|
|
67
|
+
every: int = 1,
|
|
68
|
+
atoms: AtomSel = None,
|
|
69
|
+
atom_types: Optional[Sequence[str]] = None,
|
|
70
|
+
extra_cols: Optional[Sequence[str]] = None,
|
|
71
|
+
include_xyz: bool = True,
|
|
72
|
+
format: str = "long",
|
|
73
|
+
) -> pd.DataFrame:
|
|
74
|
+
"""Extract per-atom properties across selected frames.
|
|
75
|
+
|
|
76
|
+
Works on
|
|
77
|
+
--------
|
|
78
|
+
XmoloutHandler — ``xmolout``
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
xh : XmoloutHandler
|
|
83
|
+
Parsed xmolout handler.
|
|
84
|
+
frames : int, slice, or sequence of int, optional
|
|
85
|
+
Frame indices to include.
|
|
86
|
+
every : int, default=1
|
|
87
|
+
Subsample frames by taking every Nth frame.
|
|
88
|
+
atoms : sequence of int or slice, optional
|
|
89
|
+
Atom indices to include (0-based internally, exported as 1-based).
|
|
90
|
+
atom_types : sequence of str, optional
|
|
91
|
+
Atom types to include (e.g. ``["Al", "N"]``).
|
|
92
|
+
extra_cols : sequence of str, optional
|
|
93
|
+
Additional per-atom columns to include if present.
|
|
94
|
+
include_xyz : bool, default=True
|
|
95
|
+
Whether to include ``x``, ``y``, ``z`` coordinates.
|
|
96
|
+
format : {"long", "wide"}, default="long"
|
|
97
|
+
Output format.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
pandas.DataFrame
|
|
102
|
+
Atom-level table in long or wide format, including frame and iteration
|
|
103
|
+
metadata.
|
|
104
|
+
|
|
105
|
+
Examples
|
|
106
|
+
--------
|
|
107
|
+
>>> df = _get_xmolout_data_per_atom(xh, frames=slice(0, 10), atom_types=["O"])
|
|
108
|
+
"""
|
|
109
|
+
df_sim = xh.dataframe()
|
|
110
|
+
sub_df = _df_select(df_sim, frames)
|
|
111
|
+
fidx_all = list(sub_df.index)[::max(1, int(every))]
|
|
112
|
+
|
|
113
|
+
rows: list[dict[str, Any]] = []
|
|
114
|
+
for i in fidx_all:
|
|
115
|
+
ft = _frame_table(xh, i)
|
|
116
|
+
if atoms is not None:
|
|
117
|
+
if isinstance(atoms, slice):
|
|
118
|
+
atom_sel = list(range(*atoms.indices(len(ft))))
|
|
119
|
+
else:
|
|
120
|
+
atom_sel = [int(a) for a in atoms if 0 <= int(a) < len(ft)]
|
|
121
|
+
elif atom_types:
|
|
122
|
+
tset = {str(t) for t in atom_types}
|
|
123
|
+
atom_sel = [j for j, t in enumerate(ft["atom_type"].astype(str)) if t in tset]
|
|
124
|
+
else:
|
|
125
|
+
atom_sel = list(range(len(ft)))
|
|
126
|
+
|
|
127
|
+
extras_here = [c for c in ft.columns if c not in BASE_ATOM_COLS]
|
|
128
|
+
wanted = extras_here if extra_cols is None else [c for c in extra_cols if c in ft.columns]
|
|
129
|
+
vals_cols = (["x", "y", "z"] if include_xyz else []) + wanted
|
|
130
|
+
|
|
131
|
+
for j in atom_sel:
|
|
132
|
+
rec = {
|
|
133
|
+
"frame_index": int(i),
|
|
134
|
+
"iter": int(df_sim.iloc[i]["iter"]) if "iter" in df_sim.columns else int(i),
|
|
135
|
+
"atom_id": int(j) + 1, #1-based atom format
|
|
136
|
+
"atom_type": str(ft.at[j, "atom_type"]),
|
|
137
|
+
}
|
|
138
|
+
for c in vals_cols:
|
|
139
|
+
rec[c] = ft.at[j, c] if c in ft.columns else np.nan
|
|
140
|
+
rows.append(rec)
|
|
141
|
+
|
|
142
|
+
out = pd.DataFrame(rows).sort_values(["frame_index", "atom_id"]).reset_index(drop=True)
|
|
143
|
+
|
|
144
|
+
if format == "long":
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
if format == "wide":
|
|
148
|
+
id_cols = ["frame_index", "iter"]
|
|
149
|
+
value_cols = [c for c in out.columns if c not in (id_cols + ["atom_id", "atom_type"])]
|
|
150
|
+
wide = out[id_cols + ["atom_id"] + value_cols].pivot(index=id_cols, columns="atom_id", values=value_cols)
|
|
151
|
+
wide.columns = [f"{col}[{aid}]" for (col, aid) in wide.columns.to_flat_index()]
|
|
152
|
+
return wide.reset_index().sort_values("frame_index").reset_index(drop=True)
|
|
153
|
+
|
|
154
|
+
raise ValueError("format must be 'long' or 'wide'")
|
|
155
|
+
|
|
156
|
+
def get_unit_cell_dimensions_across_frames(
|
|
157
|
+
xh: XmoloutHandler,
|
|
158
|
+
*,
|
|
159
|
+
frames: FrameSel = None,
|
|
160
|
+
every: int = 1,
|
|
161
|
+
) -> pd.DataFrame:
|
|
162
|
+
"""
|
|
163
|
+
Extract simulation box dimensions and scalar quantities per frame.
|
|
164
|
+
|
|
165
|
+
Works on
|
|
166
|
+
--------
|
|
167
|
+
XmoloutHandler — ``xmolout``
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
xh : XmoloutHandler
|
|
172
|
+
Parsed xmolout handler.
|
|
173
|
+
frames : int, slice, or sequence of int, optional
|
|
174
|
+
Frame indices to include.
|
|
175
|
+
every : int, default=1
|
|
176
|
+
Subsample frames by taking every Nth frame.
|
|
177
|
+
|
|
178
|
+
Returns
|
|
179
|
+
-------
|
|
180
|
+
pandas.DataFrame
|
|
181
|
+
Table with columns such as ``a``, ``b``, ``c``, ``alpha``, ``beta``,
|
|
182
|
+
``gamma``, ``E_pot``, and ``num_of_atoms``.
|
|
183
|
+
|
|
184
|
+
Examples
|
|
185
|
+
--------
|
|
186
|
+
>>> df = get_unit_cell_dimensions_across_frames(xh)
|
|
187
|
+
"""
|
|
188
|
+
df_sim = xh.dataframe()
|
|
189
|
+
sub_df = _df_select(df_sim, frames)
|
|
190
|
+
fidx = list(sub_df.index)[::max(1, int(every))]
|
|
191
|
+
|
|
192
|
+
if df_sim.empty:
|
|
193
|
+
return pd.DataFrame(columns=["frame_index", "iter", "a", "b", "c",
|
|
194
|
+
"alpha", "beta", "gamma", "E_pot", "num_of_atoms"])
|
|
195
|
+
|
|
196
|
+
out = df_sim.iloc[fidx].copy().reset_index(drop=True)
|
|
197
|
+
out.insert(0, "frame_index", fidx)
|
|
198
|
+
wanted = ["frame_index", "iter", "a", "b", "c", "alpha", "beta", "gamma", "E_pot", "num_of_atoms"]
|
|
199
|
+
return out[[c for c in wanted if c in out.columns]]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def get_atom_trajectories(
|
|
203
|
+
xh: XmoloutHandler,
|
|
204
|
+
*,
|
|
205
|
+
frames: FrameSel = None,
|
|
206
|
+
every: int = 1,
|
|
207
|
+
atoms: AtomSel = None, # indices or slice
|
|
208
|
+
atom_types: Optional[Sequence[str]] = None,
|
|
209
|
+
dims: Sequence[str] = ("x", "y", "z"),
|
|
210
|
+
format: str = "long", # 'long' or 'wide'
|
|
211
|
+
) -> pd.DataFrame:
|
|
212
|
+
"""Extract atomic trajectories across frames.
|
|
213
|
+
|
|
214
|
+
Works on
|
|
215
|
+
--------
|
|
216
|
+
XmoloutHandler — ``xmolout``
|
|
217
|
+
|
|
218
|
+
Parameters
|
|
219
|
+
----------
|
|
220
|
+
xh : XmoloutHandler
|
|
221
|
+
Parsed xmolout handler.
|
|
222
|
+
frames : int, slice, or sequence of int, optional
|
|
223
|
+
Frame indices to include.
|
|
224
|
+
every : int, default=1
|
|
225
|
+
Subsample frames by taking every Nth frame.
|
|
226
|
+
atoms : sequence of int or slice, optional
|
|
227
|
+
Atom indices to include.
|
|
228
|
+
atom_types : sequence of str, optional
|
|
229
|
+
Atom types to include.
|
|
230
|
+
dims : sequence of {"x", "y", "z"}, default=("x","y","z")
|
|
231
|
+
Coordinate components to extract.
|
|
232
|
+
format : {"long", "wide"}, default="long"
|
|
233
|
+
Output format.
|
|
234
|
+
|
|
235
|
+
Returns
|
|
236
|
+
-------
|
|
237
|
+
pandas.DataFrame
|
|
238
|
+
Atom trajectories in long or wide format.
|
|
239
|
+
|
|
240
|
+
Examples
|
|
241
|
+
--------
|
|
242
|
+
>>> df = get_atom_trajectories(xh, atoms=[1, 2], dims=("z",))
|
|
243
|
+
"""
|
|
244
|
+
dims = tuple(d for d in dims if d in ("x", "y", "z"))
|
|
245
|
+
if not dims:
|
|
246
|
+
raise ValueError("dims must include at least one of 'x','y','z'")
|
|
247
|
+
|
|
248
|
+
df_sim = xh.dataframe()
|
|
249
|
+
sub_df = _df_select(df_sim, frames)
|
|
250
|
+
fidx = list(sub_df.index)[::max(1, int(every))]
|
|
251
|
+
|
|
252
|
+
rows: List[Dict[str, Any]] = []
|
|
253
|
+
for i in fidx:
|
|
254
|
+
fr = xh.frame(i)
|
|
255
|
+
coords = fr["coords"]
|
|
256
|
+
|
|
257
|
+
# selection
|
|
258
|
+
if atoms is not None:
|
|
259
|
+
if isinstance(atoms, slice):
|
|
260
|
+
atom_sel = list(range(*atoms.indices(coords.shape[0])))
|
|
261
|
+
else:
|
|
262
|
+
atom_sel = [int(a) for a in atoms if 0 <= int(a) < coords.shape[0]]
|
|
263
|
+
elif atom_types:
|
|
264
|
+
tset = {str(t) for t in atom_types}
|
|
265
|
+
atom_sel = [j for j, t in enumerate(fr["atom_types"]) if str(t) in tset]
|
|
266
|
+
else:
|
|
267
|
+
atom_sel = list(range(coords.shape[0]))
|
|
268
|
+
|
|
269
|
+
for j in atom_sel:
|
|
270
|
+
rec = {
|
|
271
|
+
"frame_index": int(i),
|
|
272
|
+
"iter": int(fr.get("iter", i)),
|
|
273
|
+
"atom_id": int(j) + 1,
|
|
274
|
+
"atom_type": str(fr["atom_types"][j]),
|
|
275
|
+
}
|
|
276
|
+
if "x" in dims: rec["x"] = float(coords[j, 0])
|
|
277
|
+
if "y" in dims: rec["y"] = float(coords[j, 1])
|
|
278
|
+
if "z" in dims: rec["z"] = float(coords[j, 2])
|
|
279
|
+
rows.append(rec)
|
|
280
|
+
|
|
281
|
+
out = pd.DataFrame(rows).sort_values(["frame_index", "atom_id"]).reset_index(drop=True)
|
|
282
|
+
if format == "long":
|
|
283
|
+
return out
|
|
284
|
+
|
|
285
|
+
if format == "wide":
|
|
286
|
+
val_cols = [d for d in ("x", "y", "z") if d in dims]
|
|
287
|
+
to_pivot = out[["frame_index", "iter", "atom_id"] + val_cols]
|
|
288
|
+
wide = to_pivot.pivot(index=["frame_index", "iter"], columns="atom_id", values=val_cols)
|
|
289
|
+
wide.columns = [f"{d}[{aid}]" for (d, aid) in wide.columns.to_flat_index()]
|
|
290
|
+
return wide.reset_index().sort_values("frame_index").reset_index(drop=True)
|
|
291
|
+
|
|
292
|
+
raise ValueError("format must be 'long' or 'wide'")
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def get_mean_squared_displacement(
|
|
296
|
+
xh: XmoloutHandler,
|
|
297
|
+
*,
|
|
298
|
+
frames: FrameSel = None,
|
|
299
|
+
every: int = 1,
|
|
300
|
+
atoms: AtomSel = None,
|
|
301
|
+
atom_types: Optional[Sequence[str]] = None,
|
|
302
|
+
dims: Sequence[str] = ("x", "y", "z"),
|
|
303
|
+
origin: str = "first", # 'first' or an int frame index inside selection
|
|
304
|
+
) -> pd.DataFrame:
|
|
305
|
+
"""Compute per-atom mean-squared displacement (MSD) without PBC unwrapping.
|
|
306
|
+
|
|
307
|
+
Works on
|
|
308
|
+
--------
|
|
309
|
+
XmoloutHandler — ``xmolout``
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
xh : XmoloutHandler
|
|
314
|
+
Parsed xmolout handler.
|
|
315
|
+
frames : int, slice, or sequence of int, optional
|
|
316
|
+
Frames over which MSD is computed.
|
|
317
|
+
every : int, default=1
|
|
318
|
+
Subsample frames by taking every Nth frame.
|
|
319
|
+
atoms : sequence of int, optional
|
|
320
|
+
Atom indices to include.
|
|
321
|
+
atom_types : sequence of str, optional
|
|
322
|
+
Atom types to include.
|
|
323
|
+
dims : sequence of {"x","y","z"}, default=("x","y","z")
|
|
324
|
+
Coordinate components used for displacement.
|
|
325
|
+
origin : {"first"} or int, default="first"
|
|
326
|
+
Reference frame for displacement.
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
pandas.DataFrame
|
|
331
|
+
Long-format table with columns ``frame_index``, ``iter``,
|
|
332
|
+
``atom_id``, and ``msd``.
|
|
333
|
+
|
|
334
|
+
Examples
|
|
335
|
+
--------
|
|
336
|
+
>>> df = get_mean_squared_displacement(xh, atom_types=["O"])
|
|
337
|
+
"""
|
|
338
|
+
dims = tuple(d for d in dims if d in ("x", "y", "z"))
|
|
339
|
+
if not dims:
|
|
340
|
+
raise ValueError("dims must include at least one of 'x','y','z'")
|
|
341
|
+
|
|
342
|
+
df_sim = xh.dataframe()
|
|
343
|
+
sub_df = _df_select(df_sim, frames)
|
|
344
|
+
fidx = list(sub_df.index)[::max(1, int(every))]
|
|
345
|
+
if not fidx:
|
|
346
|
+
return pd.DataFrame(columns=["frame_index", "iter", "atom_id", "msd"])
|
|
347
|
+
|
|
348
|
+
# Choose reference frame in the selection
|
|
349
|
+
ref_frame = fidx[0] if origin == "first" else int(origin)
|
|
350
|
+
if ref_frame not in fidx:
|
|
351
|
+
raise ValueError("origin must be 'first' or a frame index inside the selected frames")
|
|
352
|
+
|
|
353
|
+
# Build selection of atoms using the reference frame
|
|
354
|
+
fr0 = xh.frame(ref_frame)
|
|
355
|
+
coords0 = fr0["coords"]
|
|
356
|
+
|
|
357
|
+
if atoms is not None:
|
|
358
|
+
# interpret `atoms` as 1-based indices
|
|
359
|
+
if isinstance(atoms, slice):
|
|
360
|
+
# if user passes a slice, assume 0-based like Python
|
|
361
|
+
sel = list(range(*atoms.indices(coords0.shape[0])))
|
|
362
|
+
else:
|
|
363
|
+
sel = []
|
|
364
|
+
for a in atoms:
|
|
365
|
+
ai = int(a)
|
|
366
|
+
if 1 <= ai <= coords0.shape[0]:
|
|
367
|
+
sel.append(ai - 1) # convert 1-based -> 0-based
|
|
368
|
+
elif atom_types:
|
|
369
|
+
tset = {str(t) for t in atom_types}
|
|
370
|
+
sel = [j for j, t in enumerate(fr0["atom_types"]) if str(t) in tset]
|
|
371
|
+
else:
|
|
372
|
+
sel = list(range(coords0.shape[0]))
|
|
373
|
+
|
|
374
|
+
if not sel:
|
|
375
|
+
return pd.DataFrame(columns=["frame_index", "iter", "atom_id", "msd"])
|
|
376
|
+
|
|
377
|
+
axes = {"x": 0, "y": 1, "z": 2}
|
|
378
|
+
use_cols = [axes[d] for d in dims]
|
|
379
|
+
|
|
380
|
+
sel_idx = np.asarray(sel, dtype=int) # 0-based indices
|
|
381
|
+
atom_ids = (sel_idx + 1).tolist() # 1-based atom ids
|
|
382
|
+
r0 = coords0[sel_idx[:, None], use_cols].astype(float) # (n_sel, len(dims))
|
|
383
|
+
|
|
384
|
+
rows: List[Dict[str, Any]] = []
|
|
385
|
+
for i in fidx:
|
|
386
|
+
fr = xh.frame(i)
|
|
387
|
+
coords = fr["coords"][sel_idx[:, None], use_cols].astype(float)
|
|
388
|
+
dr = coords - r0 # (n_sel, len(dims))
|
|
389
|
+
sq = np.sum(dr * dr, axis=1) # per-atom MSD, shape (n_sel,)
|
|
390
|
+
|
|
391
|
+
iter_val = int(fr.get("iter", i))
|
|
392
|
+
for atom_id, msd_val in zip(atom_ids, sq):
|
|
393
|
+
rows.append(
|
|
394
|
+
{
|
|
395
|
+
"frame_index": int(i),
|
|
396
|
+
"iter": iter_val,
|
|
397
|
+
"atom_id": int(atom_id),
|
|
398
|
+
"msd": float(msd_val),
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
pd.DataFrame(rows)
|
|
404
|
+
.sort_values(["frame_index", "atom_id"])
|
|
405
|
+
.reset_index(drop=True)
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def get_atom_type_mapping(
|
|
411
|
+
xh: XmoloutHandler,
|
|
412
|
+
frame: int = 0,
|
|
413
|
+
) -> Dict[str, Any]:
|
|
414
|
+
"""Return atom-type mappings for a given frame.
|
|
415
|
+
|
|
416
|
+
Works on
|
|
417
|
+
--------
|
|
418
|
+
XmoloutHandler — ``xmolout``
|
|
419
|
+
|
|
420
|
+
Parameters
|
|
421
|
+
----------
|
|
422
|
+
xh : XmoloutHandler
|
|
423
|
+
Parsed xmolout handler.
|
|
424
|
+
frame : int, default=0
|
|
425
|
+
Frame index to inspect.
|
|
426
|
+
|
|
427
|
+
Returns
|
|
428
|
+
-------
|
|
429
|
+
dict
|
|
430
|
+
Mapping containing:
|
|
431
|
+
- ``types``: sorted unique atom types
|
|
432
|
+
- ``type_to_indices``: atom indices per type (1-based)
|
|
433
|
+
- ``index_to_type``: per-atom type list
|
|
434
|
+
|
|
435
|
+
Examples
|
|
436
|
+
--------
|
|
437
|
+
>>> m = get_atom_type_mapping(xh)
|
|
438
|
+
>>> m["types"]
|
|
439
|
+
"""
|
|
440
|
+
fr = xh.frame(int(frame))
|
|
441
|
+
types = [str(t) for t in fr["atom_types"]]
|
|
442
|
+
uniq = sorted(set(types))
|
|
443
|
+
type_to_indices: Dict[str, List[int]] = {t: [] for t in uniq}
|
|
444
|
+
for idx, t in enumerate(types, start=1): # start=1 for 1-based numbering
|
|
445
|
+
type_to_indices[t].append(idx)
|
|
446
|
+
return {
|
|
447
|
+
"types": uniq,
|
|
448
|
+
"type_to_indices": type_to_indices,
|
|
449
|
+
"index_to_type": types,
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
# ==========================================================
|
|
454
|
+
# ========== Single public entry for RDF & properties ======
|
|
455
|
+
# ==========================================================
|
|
456
|
+
|
|
457
|
+
def get_radial_dist_fnc(
|
|
458
|
+
xh: XmoloutHandler,
|
|
459
|
+
*,
|
|
460
|
+
backend: str = "freud", # 'freud' or 'ovito'
|
|
461
|
+
frames: Optional[Iterable[int]] = None, # frame indices; None => all
|
|
462
|
+
types_a: Optional[Iterable[str]] = None,
|
|
463
|
+
types_b: Optional[Iterable[str]] = None,
|
|
464
|
+
r_max: Optional[float] = None, # OVITO accepts float; FREUD accepts Optional[float]
|
|
465
|
+
bins: int = 200,
|
|
466
|
+
property: Optional[str] = None, # None => return RDF curve; else 'first_peak'|'dominant_peak'|'area'|'excess_area'
|
|
467
|
+
average: bool = True, # for curves: average across frames
|
|
468
|
+
return_stack: bool = False # for curves: if average=False, optionally return list of per-frame g(r)
|
|
469
|
+
):
|
|
470
|
+
"""Compute radial distribution functions (RDFs) or RDF-derived properties.
|
|
471
|
+
|
|
472
|
+
Works on
|
|
473
|
+
--------
|
|
474
|
+
XmoloutHandler — ``xmolout``
|
|
475
|
+
|
|
476
|
+
Parameters
|
|
477
|
+
----------
|
|
478
|
+
xh : XmoloutHandler
|
|
479
|
+
Parsed xmolout handler.
|
|
480
|
+
backend : {"freud", "ovito"}, default="freud"
|
|
481
|
+
RDF backend to use.
|
|
482
|
+
frames : iterable of int, optional
|
|
483
|
+
Frame indices to include.
|
|
484
|
+
types_a, types_b : iterable of str, optional
|
|
485
|
+
Atom types defining a partial RDF.
|
|
486
|
+
r_max : float, optional
|
|
487
|
+
Maximum radius cutoff.
|
|
488
|
+
bins : int, default=200
|
|
489
|
+
Number of RDF bins.
|
|
490
|
+
property : str, optional
|
|
491
|
+
RDF-derived quantity (e.g. ``first_peak``, ``dominant_peak``,
|
|
492
|
+
``area``, ``excess_area``).
|
|
493
|
+
average : bool, default=True
|
|
494
|
+
Average RDF curves across frames.
|
|
495
|
+
return_stack : bool, default=False
|
|
496
|
+
Return per-frame RDF curves when not averaging.
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
numpy.ndarray, tuple, or pandas.DataFrame
|
|
501
|
+
RDF curves or per-frame RDF-derived properties.
|
|
502
|
+
|
|
503
|
+
Examples
|
|
504
|
+
--------
|
|
505
|
+
>>> r, g = get_radial_dist_fnc(xh, types_a=["Al"], types_b=["N"])
|
|
506
|
+
>>> df = get_radial_dist_fnc(xh, property="first_peak")
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
if property is None:
|
|
511
|
+
if backend.lower() == "freud":
|
|
512
|
+
return _rdf_freud_many(
|
|
513
|
+
xh, frames=frames, types_a=types_a, types_b=types_b,
|
|
514
|
+
r_max=r_max, bins=bins, average=average, return_stack=return_stack
|
|
515
|
+
)
|
|
516
|
+
elif backend.lower() == "ovito":
|
|
517
|
+
return _rdf_ovito_many(
|
|
518
|
+
xh, frames=frames, r_max=float(r_max or 4.0), bins=bins,
|
|
519
|
+
types_a=types_a, types_b=types_b, average=average, return_stack=return_stack
|
|
520
|
+
)
|
|
521
|
+
else:
|
|
522
|
+
raise ValueError("backend must be 'freud' or 'ovito'")
|
|
523
|
+
|
|
524
|
+
# property mode
|
|
525
|
+
return _rdf_props(
|
|
526
|
+
xh, backend=backend, frames=frames, property=property,
|
|
527
|
+
r_max=r_max, bins=bins, types_a=types_a, types_b=types_b
|
|
528
|
+
)
|