How-to: Parse eOn NEB Calculations#
Problem#
You have eOn NEB output (CON/DAT file pairs) and want to extract energies, RMSD coordinates, and gradients for plotting.
Solution#
Load Structures and Compute RMSD#
from pathlib import Path
from chemparseplot.parse.eon.neb import (
NebOverlayStructure,
load_structures_and_calculate_additional_rmsd,
)
bundle = load_structures_and_calculate_additional_rmsd(
con_file=Path("neb.con"),
additional_con=[
(Path("saddle.con"), "Saddle Point"),
(Path("minima.con"), "Local Minimum"),
],
ira_kmax=1.8,
sp_file=Path("sp.con"), # explicit saddle point (optional)
)
This returns:
bundle.atoms_list: ASE Atoms objects from the main trajectorybundle.additional_structures: list ofNebOverlayStructurerecordsbundle.saddle_point: explicit saddle-point overlay, orNone
for overlay in bundle.additional_structures:
print(overlay.label, overlay.r, overlay.p)
if bundle.saddle_point is not None:
print(
"Explicit saddle:",
bundle.saddle_point.label,
bundle.saddle_point.r,
bundle.saddle_point.p,
)
Landscape-style RMSD coordinates require IRA. If ira_mod is unavailable,
load_structures_and_calculate_additional_rmsd() and other landscape helpers
now raise ImportError instead of silently switching to order-dependent
alignment.
Aggregate Landscape Data from Multiple Steps#
eOn NEB runs produce one DAT/CON pair per optimization step. Aggregate them into a single Polars DataFrame:
from pathlib import Path
import ira_mod
from chemparseplot.parse.eon.neb import aggregate_neb_landscape_data
dat_paths = sorted(Path("neb_run").glob("*.dat"))
con_paths = sorted(Path("neb_run").glob("*.con"))
df = aggregate_neb_landscape_data(
all_dat_paths=dat_paths,
all_con_paths=con_paths,
y_data_column=1, # column index for energy in DAT files
ira_instance=ira_mod.IRA(), # provide a real IRA backend
ira_kmax=1.8,
)
print(df.columns) # ['r', 'p', 'grad_r', 'grad_p', 'z', 'step']
print(df.shape)
Use Parquet Caching#
Landscape aggregation is expensive (RMSD calculation per image). Cache the result:
df = aggregate_neb_landscape_data(
all_dat_paths=dat_paths,
all_con_paths=con_paths,
y_data_column=1,
ira_instance=ira_mod.IRA(),
cache_file=Path("landscape_cache.parquet"),
force_recompute=False, # set True to bypass cache
ira_kmax=1.8,
)
On subsequent runs, the function loads from landscape_cache.parquet and validates the schema (checks for p and grad_r columns).
Augment with External NEB Paths#
Add data from other NEB runs to improve landscape surface fits:
from ase.io import read as ase_read
ref_atoms = ase_read("reactant.con")
prod_atoms = ase_read("product.con")
df = aggregate_neb_landscape_data(
all_dat_paths=dat_paths,
all_con_paths=con_paths,
y_data_column=1,
ira_instance=ira_mod.IRA(),
cache_file=Path("landscape_cache.parquet"),
augment_dat="extra_run/*.dat",
augment_con="extra_run/*.con",
ref_atoms=ref_atoms,
prod_atoms=prod_atoms,
)
Augmented paths get step-1= in the DataFrame, distinguishing them from the primary path.
Compute 1D Profile RMSD#
For simple energy profiles (no landscape), compute RMSD from reactant only:
from chemparseplot.parse.eon.neb import compute_profile_rmsd
atoms_list = ase_read("neb.con", index=":")
df_profile = compute_profile_rmsd(
atoms_list,
cache_file=Path("profile_cache.parquet"),
force_recompute=False,
ira_kmax=1.8,
)
# df_profile has column: 'r' (RMSD from first frame)
Estimate RBF Smoothing#
Before fitting a landscape surface, estimate a smoothing parameter:
from chemparseplot.parse.eon.neb import estimate_rbf_smoothing
smoothing = estimate_rbf_smoothing(df)
print(f"Estimated smoothing: {smoothing:.4f}")
This calculates the median inter-image distance in (r, p) space for each step.
Data Format#
The aggregated DataFrame uses this schema:
Column |
Type |
Description |
|---|---|---|
|
|
RMSD from reactant (Angstrom) |
|
|
RMSD from product (Angstrom) |
|
|
Synthetic gradient in R direction |
|
|
Synthetic gradient in P direction |
|
|
Energy or eigenvalue |
|
|
Optimization step index (-1 for augmented) |
See Also#
In-memory ConFrame sequences (object plot)#
When frames already exist as readcon.ConFrame (for example
neb.path_frames() from pyeonclient, or a multi-frame CON read with
readcon), build plot series without re-reading job directories:
from chemparseplot.parse.eon.frame_series import (
energies_from_frames,
neb_path_arrays,
min_trajectory_from_frames,
dimer_trajectory_from_frames,
)
energies = energies_from_frames(frames)
path = neb_path_arrays(frames) # atoms_list, energies, optional NEB stamps
# min_traj = min_trajectory_from_frames(frames)
# dimer_traj = dimer_trajectory_from_frames(frames)
Wire into rgpycrumbs with:
from rgpycrumbs.eon import plot
plot(frames, kind="neb", plot_type="profile", output_file="1d.pdf")