Nudged Elastic Band (NEB)¶
vibe-qc is growing a native Nudged Elastic Band driver for finding minimum-energy paths between reactant and product geometries. It is the right transition-state finder for systems where the reactive event is delocalised — most notably for surface catalysis (the flagship target is N₂ dissociation on Fe(100)), where a cluster model is a poor approximation of the actual catalytic surface and single-point TS searches struggle.
Status¶
NEB is landing across five increments on main. This page tracks
what is shippable today; the rest is signposted as roadmap.
Increment |
Status |
Public API surface |
|---|---|---|
1. Interpolators + dataclasses |
Available |
|
2. NEB driver (improved tangent + spring) |
Available |
|
3. Climbing image (CI-NEB) |
Available |
|
4. Periodic dispatch (BIPOLE + FD gradient) |
Available |
|
5. QVF |
Available |
|
+ Density warm-start (HANDOVER_PERIODIC_NEB M4) |
Available |
|
+ DFT+U (Dudarev) |
Available (molecular + periodic UHF/UKS) |
|
+ MACE backend (ML potential) |
Available (molecular + periodic; needs |
|
+ MSINDO backend (semiempirical INDO) |
Available (molecular) |
|
Increment 1 — path-construction primitives (available today)¶
Two interpolators construct an initial chain of images between a reactant and a product geometry. They do not run any SCF — they operate purely on atomic positions.
Linear Cartesian¶
from vibeqc import Atom, Molecule, interpolate_linear
reactant = Molecule([Atom(1, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 0.0, 1.4])])
product = Molecule([Atom(1, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 0.0, 4.0])])
path = interpolate_linear(reactant, product, n_images=5)
# len(path) == 7 (5 intermediate + the two endpoints)
Endpoints are returned as the original objects (no copy), so
path[0] is reactant and path[-1] is product.
IDPP (Image-Dependent Pair Potential)¶
Linear interpolation between two bonded structures often places atoms inside each other in the intermediate images. IDPP (Smidstrup, Pedersen, Stokbro, Jónsson 2014) constructs a smooth chain by minimising
S^(i)(R) = sum_{j<k} (d^(i)_{jk} - r_{jk}(R))^2 / r_{jk}(R)^4
for each intermediate image i, where d^(i)_{jk} is the linear
interpolation between reactant and product pair-distance matrices.
The 1/r⁴ weighting drives images away from atom-atom clashes
while still tracking the interpolated distance manifold.
from vibeqc import interpolate_idpp
path = interpolate_idpp(reactant, product, n_images=7)
IDPP is the recommended default starting path for NEB whenever reactant and product differ in bonding connectivity.
Dataclasses¶
from vibeqc import NEBImage, NEBPath
images = [NEBImage(system=s) for s in path]
neb = NEBPath(images=images, spring_constant=0.1)
neb.n_images # 7 (intermediate + endpoints)
neb.n_intermediate # 5
neb.energies() # np.ndarray of NaN (energies set by the driver)
NEBImage carries (system, energy, gradient, tangent) — the
energy/gradient/tangent slots stay None after interpolation; they
are populated by the NEB driver (Increment 2+).
Periodic systems¶
Both interpolators accept PeriodicSystem. The lattice must match
between reactant and product (variable-cell NEB is out of scope —
fix the cell to the reactant’s). Periodic IDPP uses minimum-image
pair distances, and the NEB driver’s tangent and spring use
minimum-image inter-image displacements, so a reactant and product
that differ by an atom hopping across the PBC — the surface
self-diffusion case — interpolate and relax along the short,
through-the-boundary path instead of being dragged across the cell.
The single-round minimum image is exact for orthorhombic cells and
the standard close approximation for mildly skewed ones.
interpolate_linear is a plain Cartesian straight line (no
wrapping); use IDPP for cross-boundary hops.
Increment 2 — NEB driver (available today, molecular only)¶
run_neb runs an improved-tangent NEB end to end: it computes
per-image SCFs (in parallel via joblib), assembles the NEB force
(tangent + spring + perpendicular-true-force projection), and
optimises with a damped quick-min outer loop (the same scheme ASE’s
MDMin uses).
from vibeqc import Atom, Molecule, run_neb
# H + H₂ → H₂ + H, collinear.
reactant = Molecule(
[Atom(1, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 0.0, 1.4]),
Atom(1, [0.0, 0.0, 4.4])],
0, 2, # neutral doublet
)
product = Molecule(
[Atom(1, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 0.0, 3.0]),
Atom(1, [0.0, 0.0, 4.4])],
0, 2,
)
result = run_neb(
reactant, product,
basis="sto-3g",
n_images=5,
method="UHF",
spring_constant=0.1,
interpolation="idpp",
max_iter=80,
conv_tol_force=2e-3,
)
print(result.converged, result.n_iter, result.max_force)
print("TS energy:", result.energies[result.transition_state_index])
ts = result.path.images[result.transition_state_index].system
NEBResult carries the final path, the per-image energies, a
boolean converged, the index of the highest-energy intermediate
image (transition_state_index), the number of outer iterations,
and the final maximum NEB force magnitude.
Parallelism¶
Per-image SCFs run inside a joblib.Parallel block per outer
iteration. n_jobs=-1 (default) uses every available core; pass
n_jobs=1 for serial. The endpoints are evaluated once and cached
— they don’t move while free_endpoints=False (the default).
Frozen atoms¶
Pass freeze_indices=[i, j, …] to zero the NEB force on the
listed atoms; their geometry stays at the endpoint position
throughout the optimisation. This is implemented at the NEB layer
(the per-image SCF still sees the full atom set, we just zero the
force components before the quick-min step). Useful for keeping
slab-substrate atoms fixed during surface NEB once Increment 4
lands.
Method support¶
method="RHF" | "UHF" | "RKS" | "UKS" — same set as
optimize_molecule. Pass the matching *_options (e.g.
uhf_options=UHFOptions()) to tune the SCF; default options work
for the textbook H + H₂ benchmark but more demanding systems will
likely want a higher max_iter on the SCF + maybe a tighter
conv_tol_grad.
Algorithmic notes¶
Improved tangent. The tangent at each intermediate image is determined by the energy ordering of its neighbours (eqs. 8–11 of Henkelman & Jónsson 2000). When the central image is the local energy extremum, the tangent is a weighted combination of the forward and backward differences with the magnitude of the larger energy difference upfront.
Outer loop. Quick-min (damped MD) with adaptive step. Each intermediate image carries a velocity; per step the velocity is projected onto the NEB force and zeroed if the projection is negative. Step size grows by 1.1 when aligned and resets on direction flips. Equivalent in spirit to ASE’s
MDMin. L-BFGS-B on the concatenated coordinate would also work but assumes the NEB force is conservative — it isn’t (the spring contribution depends only on the parallel projection of the displacement), so quick-min is the standard choice.Slow convergence tail. Near convergence the geometry can be essentially locked in (TS energy stable to mHa) while the spring-component max-force decays slowly. This is intrinsic to the quick-min / MDMin scheme; for tighter convergence increase
max_iter, or pre-relax the band with a coarserconv_tol_forceand finish the saddle with a quasi-Newton TS search.
Increment 3 — climbing image (available today)¶
Pass climbing_image=True to run_neb to promote the highest-
energy intermediate image to a “climbing” image partway through
the optimisation:
result = vibeqc.run_neb(
reactant, product, basis="sto-3g",
n_images=5, method="UHF",
climbing_image=True,
climbing_image_start_fraction=0.3, # default; warm-up fraction
conv_tol_force=1e-3, max_iter=80,
)
ts_image = result.path.images[result.path.climbing_image_index]
The CI image’s spring contribution is removed and the tangent- parallel component of its true force is inverted, so it climbs uphill along the path to the saddle while still relaxing perpendicular. Other images keep standard NEB dynamics, which keeps the path itself anchored at the right shape. Henkelman, Uberuaga, Jónsson 2000 is the original reference.
Warm-up. The highest-energy intermediate image is selected after
climbing_image_start_fraction * max_iteriterations of plain NEB and then fixed for the remainder of the run. The warm-up keeps the climbing selection from flipping between iterations before the band has found its rough shape; the selection lock keeps the climber from losing momentum to a later re-selection. Default fraction is 0.3.Accuracy. On the textbook H + H₂ → H₂ + H benchmark the climbing image lands at the symmetric saddle to ≤ 1e-4 bohr — six orders of magnitude tighter than the plain-NEB highest- energy image (which Inc 2’s test bounds at 0.05 bohr).
NEBResult.path.climbing_image_indexrecords which path image was promoted;transition_state_indexthen equals that index. Both areNoneon a plain (non-climbing) run.
The climbing image is the recommended saddle estimator from
run_neb: it’s accurate enough to seed a follow-up quasi-Newton
TS search (or in many cases to just report directly as the
saddle).
Increment 4 — periodic dispatch (available today, BIPOLE + FD gradient)¶
run_neb accepts PeriodicSystem endpoints. The path is laid
out exactly as in the molecular case (improved-tangent forces,
quick-min outer loop, optional climbing image); per-image SCFs
go through run_pbc_bipole_rhf/uhf/rks/uks and per-image
gradients through a central-difference fallback (the J^LR
reciprocal-Ewald contribution is still missing from the analytic
BIPOLE gradient — see
HANDOVER_PERIODIC_NEB.md).
import numpy as np
from vibeqc import (
Atom, PeriodicSystem, run_neb,
PeriodicRHFOptions, LatticeSumOptions,
)
L = np.diag([10.0, 10.0, 10.0])
reactant = PeriodicSystem(3, L, [
Atom(1, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 0.0, 1.4]),
])
product = PeriodicSystem(3, L, [
Atom(1, [0.0, 0.0, 0.0]),
Atom(1, [0.0, 0.0, 1.8]),
])
# Periodic SCF options. Use the same options object the
# non-NEB BIPOLE drivers take.
rhf_opts = PeriodicRHFOptions()
rhf_opts.max_iter = 50
result = run_neb(
reactant, product,
basis="sto-3g",
n_images=5,
method="RHF",
rhf_options=rhf_opts,
kpoints=(2, 2, 2), # Monkhorst-Pack mesh; passed to monkhorst_pack
fd_step_bohr=1e-3, # central-difference half-step
interpolation="idpp",
max_iter=20,
conv_tol_force=2e-3,
)
ts = result.path.images[result.transition_state_index].system
The FD-gradient surface. Per-image gradient cost is 6N + 1
BIPOLE SCFs (one per Cartesian degree of freedom, ± displaced,
plus the reference). For N = 10 atoms × 5 intermediate images × 50
outer iterations that’s ~15 000 BIPOLE SCFs total — slow, but
correct in the limit fd_step_bohr → 0. When the J^LR derivative
lands in the analytic BIPOLE gradient, the per-image cost drops to
2 SCFs (one for the energy + one for the analytic gradient).
kpoints accepts either a 3-tuple of ints (converted internally
via vibeqc.monkhorst_pack), a pre-built BlochKMesh, or a
KPoints object. None ⇒ Γ-only mesh (only useful as a
sanity-check; pick a real mesh for production).
Same lattice for both endpoints. Variable-cell NEB is out of scope — fix the cell to the reactant’s lattice. Endpoint lattices that don’t match raise.
Frozen substrate atoms. The same freeze_indices mechanism
works for surface NEB: pass the indices of the substrate atoms
you want to keep fixed. The frozen-atom forces are zeroed at the
NEB layer; the BIPOLE SCF still sees the full system.
Mixing molecular + periodic endpoints raises at the
type-dispatch guard inside run_neb. The endpoints must be the
same type.
Increment 5 — QVF reaction.path emitter (available today)¶
NEBResult carries a write_qvf(stem) method that emits a
vibe-view-renderable archive of the converged (or last-evaluated)
path:
result = vibeqc.run_neb(reactant, product, basis="sto-3g", ...)
qvf_path = result.write_qvf("my_neb_run")
# → Path('my_neb_run.qvf')
The archive contains three sections:
structure— the reactant geometry (single-frame fallback for viewers that don’t yet animatereaction.path).reaction.path— per-image coords + energies + reaction coordinate (cumulative arc length normalised to 0–1) + waypoint annotations (reactant/product/transition_state). The TS waypoint points at the climbing image when CI-NEB ran, else at the highest-energy intermediate.citations— BibTeX assembled withuses_neb=True(plususes_ci_neb=Truewhen the run was climbing-image). Always includes Henkelman+Jónsson 2000 (improved tangent) + Smidstrup 2014 (IDPP); CI-NEB adds Henkelman+Uberuaga+Jónsson 2000.
For periodic NEB the archive ships as QVF v2 automatically: the writer detects periodic frames and emits the per-frame lattice
dim on the
reaction.pathsection so vibe-view can draw the unit cell. See the “Periodic reaction paths (QVF v2)” subsection of the vibe-view guide for the schema details. Inc B (vibe-view renderer track) will add the cell-rendering + atom-wrapping logic to the renderer itself.
Density warm-start (available today)¶
run_neb reuses each image’s converged SCF density across outer
iterations: the density at outer iter N is fed in as the SCF
initial guess for the same image at outer iter N+1. The geometry
change between iterations is typically small, so the SCF
converges in many fewer iterations than from a cold SAD/Hcore
guess.
result = vq.run_neb(
reactant, product, basis="def2-svp",
method="UKS", functional="pbe",
n_images=7,
warm_start=True, # default; pass False to force cold SCFs (benchmarking)
)
The behaviour is bit-exact vs. cold-start — warm-start changes
only the initial guess, not the converged density. The NEB
trajectory (energies + forces + per-image positions) is identical
at any fixed max_iter budget regardless of whether warm_start
is on.
Coverage today¶
Method |
Molecular |
Periodic (BIPOLE) |
|---|---|---|
RHF |
yes |
yes |
UHF |
yes |
yes |
RKS |
yes |
yes |
UKS |
yes |
yes |
Periodic RKS / UKS warm-start was unblocked when the upstream
build_xc_periodic arg-order bug in
run_pbc_bipole_rks / run_pbc_bipole_uks got fixed alongside
Increment 4d-bipole UKS DFT+U. Same dispatch as RHF / UHF —
_evaluate_image_periodic hands the cached density (or
(α, β) blocks for open-shell) into the BIPOLE driver’s
warm-start kwarg per outer iteration.
Observed speedups¶
Molecular UKS on the H + H₂ → H₂ + H benchmark (3 intermediate images, 10 outer iters): ~2× wall-time reduction (~84 s → ~42 s). Open-shell DFT SCFs are the biggest win — expensive per iteration AND slow to converge from SAD/Hcore on a doublet manifold.
Periodic UHF on H₃-in-cubic-box (single SCF call): 76 SCF iters → 25 SCF iters, ~3× speedup.
Molecular RHF / RKS / periodic RHF: speedups in the flat-to-modest range on small systems — the cold SCF already converges in 3-5 iters at the test scale. Real surface NEB workloads should see 2-4× the maintainer originally scoped in
HANDOVER_PERIODIC_NEB.md§ M4.
Bonus: FD-gradient inner warm-start (periodic only)¶
Periodic NEB computes per-image gradients by central
differences (6N + 1 SCFs per image per outer iteration; see
Increment 4). Each of those 6N displaced SCFs additionally
warm-starts from the reference SCF’s converged density at the
same image — geometrically small perturbations
(default fd_step_bohr=1e-3) are essentially the same
electronic structure, so the displaced SCFs converge in a
couple of iterations instead of from cold. This stacking
multiplies the warm-start savings for periodic NEB
specifically.
When to turn warm-start off¶
Benchmarking the cold-start cost (
warm_start=False).Paranoid bisection when an unrelated convergence problem is suspected and you want to remove warm-start as a variable.
There’s no correctness reason to disable warm-start at production time — it’s bit-exact in both directions.
DFT+U (Hubbard correction; available today)¶
Pass dft_plus_u=[HubbardSite(...)] to run_neb to add the
Dudarev rotationally-invariant per-spin V_U potential to every
per-image SCF:
import vibeqc as vq
result = vq.run_neb(
reactant, product,
basis="def2-svp",
method="UKS", functional="pbe",
n_images=7,
dft_plus_u=[
# U_eff = 4 eV on the Fe 3d channel (atom index 0 in the
# cluster ordering).
vq.HubbardSite(atom_index=0, l=2, U_ev=4.0),
],
)
The kwarg accepts the same HubbardSite dataclass as
vibeqc.run_rhf / vibeqc.run_rks / vibeqc.run_uhf /
vibeqc.run_uks — eV inputs converted internally to Hartree,
(atom_index, l) → AO-group lookup done once per NEB call
(the AO grouping is geometry-invariant for a fixed basis +
atom ordering).
The Dudarev energy E_U = 2 Σ_A (U_eff/2) (tr n − tr n²)
folds into each image’s result.energy; the corresponding
e_dft_plus_u field of the per-image SCF result is the V_U
contribution alone. Combines cleanly with warm-start — the
converged density already encodes V_U, so feeding it back as
the next outer iter’s initial guess works the same way.
Coverage today:
Method |
Molecular |
Periodic |
|---|---|---|
RHF |
yes |
yes |
UHF |
yes |
yes |
RKS |
yes |
yes |
UKS |
yes |
yes |
All four periodic methods route dft_plus_u= through to the
matching BIPOLE driver via run_pbc_bipole_{rhf,uhf,rks,uks};
the per-spin Dudarev V_U Fock contribution lands in every
per-image BIPOLE SCF, and the FD-gradient finite differences
pick up ∂E_U/∂R correctly because the displaced SCFs carry
the same dft_plus_u= list. Closed-shell BIPOLE +U landed in
v0.9.0; the open-shell variants landed alongside Increment
4d-bipole.
Small-cell pathology warning. Periodic +U on diffuse AO
channels in tight boxes (e.g. H 1s in a 4-bohr cubic cell) can
exhibit an unphysically large +U Fock contribution: heavy
periodic AO image overlap pumps the AO projector and the
e_dft_plus_u contribution swings by orders of magnitude
relative to the molecular reference. The observed pathology on
the v0.9.0 NEB test fixture (H₂⁺ + U=4 eV on 1s): ΔE_TS ≈ -224
eV at L=4 bohr, vs. the physically-correct +0.27 eV at L≥8
bohr. Until the periodic +U projector is hardened against
small-cell image overlap, periodic +U recipes should stay at
cell sizes ≥ 2 × max AO decay length (~8 bohr for H 1s; much
larger for transition-metal d-channels).
Citations: when dft_plus_u is non-empty the per-image
SCFs route the Dudarev 1998 + Cococcioni-Gironcoli 2005
papers into the BibTeX assembled by NEBResult.write_qvf
(same routes.methods.dft_plus_u route the molecular SCF
runners use).
Invalid Hubbard sites raise at the NEB boundary: a
HubbardSite(atom_index, l, ...) pointing at a channel
absent from the basis raises ValueError before any SCF
runs — useful when the user picks the wrong l for a
minimal basis (e.g. l=1 on STO-3G H, which only has an
s-shell).
MACE backend — ML-potential reaction paths (available today)¶
Pass method="mace" to drive the band with a pre-trained
MACE machine-learned interatomic
potential instead of per-image SCFs. MACE returns analytic energy
and forces from a single forward pass — no SCF, no Gaussian basis,
no k-mesh — so a MACE-NEB is dramatically cheaper than the SCF path,
and for periodic bands it bypasses the 6N+1 finite-difference
gradient (Increment 4) entirely.
import vibeqc as vq
from vibeqc.mlip import MLIPOptions
# Molecular: an organic reaction path with the MACE-OFF23 model.
result = vq.run_neb(
reactant, product, # Molecule or PeriodicSystem endpoints
method="mace", # no basis / functional needed
n_images=7,
climbing_image=True,
mlip_options=MLIPOptions(model="medium-mpa-0"), # default: MIT MACE-MPA-0
conv_tol_force=5e-4,
)
ts = result.path.images[result.transition_state_index].system
The improved-tangent force, climbing image, frozen atoms, and the minimum-image handling for cross-boundary periodic hops all work exactly as for the SCF methods — only the per-image energy/force evaluation changes.
How it runs. The MACE model (torch weights) is loaded once and
its ASE calculator reused for every image and outer iteration;
constructing a fresh model per evaluation would reload the weights each
call. Because the loaded calculator is a live torch object, MACE-NEB
evaluates the band serially (n_jobs is forced to 1) rather than
pickling the model across worker processes — each evaluation is one
forward pass, so serial is cheap.
Energy scale. MACE energies are on a model-specific reference scale (each model subtracts its own per-element atomic energies) and are not comparable across models or to a vibe-qc total energy. They are meaningful for relative energetics — exactly what a reaction barrier is — so the MEP and barrier are well-defined, but don’t compare a MACE absolute energy to an SCF one.
Model selection + licensing. MLIPOptions(model=...) picks the
foundation model (default "medium-mpa-0", MIT, materials). The
organic off23-* models are under the Academic Software License
(academic, non-commercial) and are gated: selecting one raises
PermissionError unless you acknowledge the licence
(MLIPOptions(accept_academic_license=True) or VIBEQC_ACCEPT_ASL=1).
See the MLIP guide / vibeqc.mlip.mace for the model
registry.
Requirements. MACE is the optional [mace] extra (PyTorch + e3nn);
install with pip install 'vibe-qc[mace]'. It currently requires
Python ≤ 3.13 (its matscipy dependency ships no 3.14 wheel). A
run_neb(method="mace") on an interpreter without the extra raises a
clear, actionable ImportError.
Citations. A MACE-NEB run cites the MACE method paper (Batatia
2022) plus the foundation-model paper for the model actually used
(MACE-MP/MPA → Batatia 2024; MACE-OFF23 → Kovács 2023), alongside the
NEB papers — and not the Gaussian-integral library (no integrals
are evaluated). All of this lands in result.write_qvf(...)’s
references block automatically.
MSINDO backend — semiempirical reaction paths (available today)¶
Pass method="msindo" to drive the band with
MSINDO — vibe-qc’s own Bredow/Geudtner/Jug INDO engine
over Slater orbitals — instead of an SCF over a Gaussian basis. Like
MACE it needs no basis, functional, or k-mesh; unlike MACE it is
vibe-qc’s own quantum-chemistry method (not an external model), so its
energies are MSINDO total electronic energies and are directly
comparable run-to-run. MSINDO-NEB is molecular only (periodic MSINDO
is the Cyclic Cluster Model, a separate API — see the MSINDO
guide).
import vibeqc as vq
# Ammonia umbrella inversion: pyramidal NH3 flips through the planar
# D3h transition state. Endpoints carry the atomic numbers / charge /
# multiplicity; no basis or functional is passed.
result = vq.run_neb(
reactant, product, # Molecule endpoints (H–Br supported)
method="msindo",
n_images=5,
climbing_image=True,
conv_tol_force=1e-3,
)
ts = result.path.images[result.transition_state_index].system
The improved-tangent force, climbing image, frozen atoms, warm-start flag (a no-op here — MSINDO carries no SCF state across geometries), and the spring machinery are all shared with the SCF path; only the per-image energy/gradient evaluation changes.
Gradient + cost. MSINDO exposes a finite-difference nuclear
gradient (msindo_gradient_fd, central-differenced from the total
energy and oracle-validated to ≤ 1e-4 Ha/bohr against MSINDO’s analytic
CARTOPT ANALY gradient). Each image therefore costs 6N run_msindo
SCFs per outer iteration (3N Cartesian degrees of freedom, central
differences) — so keep MSINDO-NEB systems small. The FD half-step is
fd_step_bohr (default 1e-3 bohr, converted to the engine’s Angstrom).
Because the engine is a stateless pure-Python/NumPy function — no live
torch object like MACE — the band evaluates in parallel across images
(n_jobs honoured, default all cores), which is the main lever against
the FD cost.
Open shell + dispersion. Open-shell radicals route to MSINDO’s UHF
automatically (set the endpoint multiplicity); the s/p elements are
covered, open-shell d raises. Pass dispersion_params=D3BJParams(...)
to fold a D3-BJ correction (energy and gradient) into every image.
Citations. An MSINDO-NEB run cites the MSINDO method papers
(Ahlswede & Jug 1999, Parts I + II) and the NEB papers, plus the Pulay
DIIS paper (MSINDO’s SCF accelerator), and not the Gaussian-integral
library (MSINDO evaluates no Gaussian integrals) or any XC functional.
All of this lands in result.write_qvf(...)’s references block
automatically.
A complete, runnable example — endpoint relaxation, a converged
climbing-image band, and a Hessian check confirming the saddle has a
single imaginary mode — is at
examples/semiempirical/22_msindo_neb.py.
What is not in vibe-qc today¶
The dedicated periodic-NEB chat (
HANDOVER_PERIODIC_NEB.md) is the place where the J^LR analytic-gradient + periodic-NEB- specific optimisations get the focused attention they need. Therun_neb(PeriodicSystem, ...)path landed in Inc 4 + density warm-start lands the most impactful pieces; the dedicated chat will close the remaining gap (FD → analytic gradient).The vibe-view renderer’s lattice-box + atom-wrap logic for periodic reaction paths (QVF “Inc B”). The archive already carries everything the renderer needs; the rendering pass is scheduled separately.
Frame-to-frame anchored atom wrap in vibe-view’s renderer. The current modulo-1 wrap can produce a visual “jump” when an atom crosses a cell boundary mid-animation. Rare in practice for chemistry NEBs; cosmetic.
For TS finders that ship today see the geometry-optimization +
Hessian tooling. The relaxed-scan workflow (relaxed_scan /
ScanResult) can also provide an interpolated starting path that
NEB consumes.
Citations¶
Both NEB papers fire automatically whenever run_neb runs (via
the routes.drivers.neb route registered against
uses_neb=True):
Henkelman, G.; Jónsson, H. Improved tangent estimate in the nudged elastic band method for finding minimum energy paths and saddle points. J. Chem. Phys. 113, 9978 (2000). doi:10.1063/1.1323224
Smidstrup, S.; Pedersen, A.; Stokbro, K.; Jónsson, H. Improved initial guess for minimum energy path calculations. J. Chem. Phys. 140, 214106 (2014). doi:10.1063/1.4878664
For climbing_image=True, additionally:
Henkelman, G.; Uberuaga, B. P.; Jónsson, H. A climbing image nudged elastic band method for finding saddle points and minimum energy paths. J. Chem. Phys. 113, 9901 (2000). doi:10.1063/1.1329672
The matching routes.drivers.ci_neb route fires in addition to
the base routes.drivers.neb route when CI-NEB is enabled.