Skip to content

feat(saddle): follow the intrinsic reaction coordinate after a saddle - #868

Merged
HaoZeke merged 6 commits into
TheochemUI:developfrom
HaoZeke:feat/rgsaddle-irc
Nov 2, 2026
Merged

HaoZeke merged 6 commits into
TheochemUI:developfrom
HaoZeke:feat/rgsaddle-irc

Conversation

@HaoZeke

@HaoZeke HaoZeke commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

A converged saddle does not say which two minima it joins.
Process search checks it with two minimizations displaced along the
mode.
These can slide into a minimum the saddle does not connect.
irc_after = true in [Saddle Search] follows the intrinsic reaction
coordinate (IRC) with rgsaddle.
The path goes downhill from the saddle in both directions, and the eOn
minimizer relaxes both ends.

Six commits on develop 630ccad2 add the path, the relaxed ends, a
dimer fix and the config.yaml keys, 29 files, +1762/-10.
A diff against main also shows develop's own commits that are not on
main yet.

2f464bd1 follows the intrinsic reaction coordinate, in 8 files with
+430.
cf269a18 describes the saddle irc keys in the schema, in 3 files with
+67.
417c404f covers the irc_after ends and default bytes, in 3 files with
+235.
bf0f5ae4 drops the torque stop from the improved rgsaddle dimer, in 6
files with +35/-10.
f36b286d adds a how-to for the reaction path after a saddle, in 11
files with +970.
dff34824 adds the saddle irc keys to config.yaml, in 1 file with +25.

Rebase onto develop

The series now sits on the develop commit that refuses prfo_refine
before the first force call.
The rebase had conflicts in two files.
SaddleSearchJob.cpp includes both PrfoRefine.h and XtsciIrc.h.
Its run still calls requirePrfoRefine for a min-mode search.
runPrepared keeps the IRC check as it was.
In client/meson.build, test_prfo_refine stays outside the rgsaddle
block, where develop moved it.
test_rgsaddle_irc stays inside that block.
No commit became empty.

The sixth commit adds the four irc keys to eon/config.yaml.
The schema drift test compares that file with the schema, and it failed
from the second commit on.

Keys and outputs

The keys are
irc_after, irc_step_size, irc_converged_force and irc_max_steps.
The first is off by default, and the last defaults to 1000.
The step defaults to 0.1 amu^1/2 Angstroms, where amu is the atomic mass
unit.
The force threshold defaults to 0.05
electronvolts/Angstrom (eV/Angstrom).
Sella's IRC stops its path there, and so does the rgsaddle session.
A branch ends at a minimum when rgsaddle reports one.
rgsaddle does so only after the first step and where the largest atomic
force is at or below the threshold.
The job writes irc.con, irc.dat, irc_min1.con and irc_min2.con,
plus seven results.dat entries.
With the option off, a saddle search writes the same files as before.

The IRC needs rgsaddle C application binary interface (ABI) minor 14 or
newer.
Both the build and the library loaded at run time must meet it.
The rgsaddle crate stayed at 0.2.2 from minor 5 to 18.
So eOn checks RGSADDLE_ABI_MINOR and the ABI version the library
reports, not the pkg-config version.
Other builds refuse irc_after before the saddle search starts.

Dimer rotation

The fourth commit fixes the dimer search itself, and it would apply
without the IRC.
With rgsaddle, eOn hands both of its dimers to XtsciMinMode.
That class passed
[Dimer] torque_min, 0.1 by default, to rgsaddle as rotation_tol.
rgsaddle 0.3.0 and newer read a rotation_tol of 1e-4 or more as the
classic torque window.
Once the rotational force is below 1, that window stops a rotation after
its first step.
So a request for the improved dimer ran a classic one.
On the Pt island of examples/akmc-pt, develop against rgsaddle main
fe6bed89 ends at another saddle.
It stops at -1774.804074 eV after 449 force calls, against -1775.190088
eV on the pin.

The commit passes rotation_tol = 0 when [Dimer] improved is true.
Lanczos keeps torque_min, since it reads rotation_tol as its
residual stop.
The improved dimer then rotates until
converged_angle or rotations_max, as eOn's own ImprovedDimer does.
It needs no ABI change, so it applies to the pin 06c55c5 and to the
releases 0.3.0 to 0.3.2.
With it, the pin and main find the same saddle on all five inputs
below, the island included.

Four of the five saddles that the pinned rgsaddle finds move.
LJ7 and LJ13 are the 7- and 13-atom Lennard-Jones clusters.
The numbers below compare develop and this series, both against the pin.

For the Morse Pt adatom the saddle energy is -1462.008706 eV, the force
calls go from 38 to 46, and the eigenvalue goes from -1.013461 to
-1.010819.
For LJ7 the saddle energy is -15.283414 eV, the force calls go from 748
to 750, and the eigenvalue goes from -9.880619 to -9.880954.
For the Pt island the saddle energy is -1775.190088 eV, the force calls
go from 471 to 486, and the eigenvalue goes from -0.612079 to -0.613662.
For the how-to input the saddle energy is -1462.008706 eV, the force
calls go from 32 to 39, and the eigenvalue goes from -1.010681 to
-1.010010.

The energies agree to six decimals, and each moved saddle lies within
1e-4 Angstroms of develop's.
LJ13 keeps its saddle and its 8 force calls.
Two job tests bound the force calls of the Morse Pt search.
Under WITH_RGSADDLE the bounds rise from 39 to 47 and from 69 to 75,
and the expected eigenvalue becomes -1.010819.
The bounds without rgsaddle stay as they were.
Once rgsaddle releases C ABI minor 26, a separate follow-up will set the
torque window through rgsaddle_minmode_set_torque_window.

The pinned rgsaddle

The rgsaddle that continuous integration (CI) pins, 06c55c5, can
report a minimum beside the saddle.
On the Morse Pt saddle at the defaults it stops the branch along the
mode 0.0152 Angstroms from the saddle.
The largest force there is 0.017 eV/Angstrom, already below 0.05.
It can also run a branch to irc_max_steps with its force below the
threshold.
LJ13 at step 0.5 shows this.
The LJ13 step-0.5 numbers come from runs on an earlier revision of this
series.
Its eOn code differs from this tip only in rebases, one local variable
name, and comment, docs and config.yaml text.
The how-to shows the first case.
It uses step 0.5 and threshold 0.01, the cheapest of its runs that trace
both branches with the pin.
The schema note for irc_converged_force says so too.

The fix is one rgsaddle commit on main fe6bed89, not yet opened as a
pull request.
It stops a branch on the force and on the curvature along the launch
mode.
eOn needs no change for it, because eOn reads only the converged status.
With it, the LJ13 step-0.5 case reaches the minimum on both branches at
both thresholds.
On the Pt island the pin stops both branches about one step from the
saddle.
The relaxed ends still reach the right minima there.
The island path with the fix runs slowly, a separate known issue in the
rgsaddle IRC stop.
The LJ13 and island sentences on the fix also come from runs on that
earlier revision.

Tests

On the tip, meson test --suite eon passes 77 tests against the pin and
74 without rgsaddle.
test_prfo_refine now runs in both builds, as on develop.
Only the pinned build has test_rgsaddle_band, test_rgsaddle_irc and
test_rgsaddle_neb.
The Python tests give 789 passed and 17 skipped, with 206 subtests
passed.
The eon-schema tests give 56 passed and 1 failed,
test_published_tree_matches_generator.
Develop 630ccad2 fails it the same way.
Its published index.json, instanton.json and
nudged-elastic-band.json lack the develop keys tunnel_wall_points
and symmetrize.
This series does not touch those keys, and its own irc keys match the
generator.

One nudged elastic band (NEB) test compares a band with Subversion (SVN)
reference data.
Against rgsaddle main that test, NEB LJ matches SVN reference, fails
its status == 0 check.
Develop fails it the same way against main, and passes it against the
pin.
It is the only failure against rgsaddle 0.3.2, on the tip and on develop
alike.
The failure comes from rgsaddle e253dab, which changed the
climbing-image rule of the band.
A pending rgsaddle change, not yet on main, keeps the climb off an
endpoint that slopes into the band.
This series does not touch the band.

test_rgsaddle_irc follows the IRC from the LJ13 saddle that the NEB
test refines.
It checks that each relaxed end matches one of the two NEB minima.
The [irc] job cases check three things.
With irc_after off, setting the irc keys leaves every returned file
unchanged.
A build without the IRC refuses the option.
On the Morse Pt saddle the IRC leaves saddle.con, mode.dat and the
saddle force calls unchanged.
The two job cases with raised bounds pass with and without rgsaddle.

Docs

docs/source/tutorials/systems/irc_after_saddle.md is a how-to on the
Morse Pt saddle.
Its log line, results.dat lines and figure come from the run in
data/irc_pt/.
A table of six runs shows how step size and threshold trade path detail
for force calls.
A cost section compares the IRC with the displaced minimizations on four
systems.
These are the Morse Pt adatom saddle, LJ7, LJ13 and the Pt island of
examples/akmc-pt.
On all four the IRC reached the same minima and used more force calls.
On the island, with 175 free atoms, the path took about 120 s for 16
force calls.
The user guide gains a short IRC section, and a towncrier fragment
records the keys.
docs/source/user_guide/optimizer.md and a second fragment record the
dimer change.

A converged saddle does not say which minima it joins.
Process search runs two minimizations from points displaced along the
mode.
Either minimization can slide into a minimum the saddle does not
connect.
rgsaddle exports an intrinsic reaction coordinate (IRC) session from C
application binary interface (ABI) minor 14.
The continuous integration (CI) pin 06c55c5 is minor 18.

irc_after in [Saddle Search] follows that IRC from a converged saddle
with a negative eigenvalue.
It walks both directions over the free atoms and relaxes both ends with
the eOn minimizer.
It writes seven entries in results.dat and the files irc.con, irc.dat,
irc_min1.con and irc_min2.con.
Each branch gets its own rgsaddle session.
A session halves its step after every rejected trial and keeps the
shorter step through rgsaddle_irc_set_direction.
The rgsaddle crate stayed at 0.2.2 from C ABI minor 5 to 18.
For that reason the build reads RGSADDLE_ABI_MINOR and the ABI version
of the loaded library, not the pkg-config version.
A build without the session refuses irc_after before the search starts.

The step defaults to 0.1 amu^1/2 Angstroms, where amu is the atomic mass
unit.
The force threshold defaults to 0.05 electronvolts/Angstrom.
Sella's IRC stops its path there, and so does the rgsaddle session.
The [Optimizer] tolerance that relaxes the ends stays separate.
eOn passes the threshold to the session and has it compare the largest
atomic force.
A branch stops at a minimum when rgsaddle reports one.
rgsaddle reports a minimum only after the first step and at a force at
or below that threshold, so eOn adds no test of its own.

On the Morse Pt saddle of the systems tutorials the pinned rgsaddle
stops both branches at the defaults after a few steps.
The ends lie 0.0077 and 0.0116 Angstroms from the saddle, where the
forces are already under 0.05.
eOn counts both branches, and the minimizer relaxes them into the two
minima.
On the 7- and 13-atom Lennard-Jones clusters the path and the ends take
259 and 464 force calls at 0.05, and 259 and 492 at 0.01.
At step 0.5 and threshold 0.05 the 13-atom branch along the mode stops
after 144 steps.
Its end lies 0.49 step lengths from the saddle in mass-weighted
distance.

The option is off by default.
With it off the client reads four more keys and writes the same bytes as
before.
The schema documents the four irc keys of [Saddle Search] with their
units, defaults, outputs and build requirement.
irc_converged_force defaults to 0.05, the path stop of the intrinsic
reaction coordinate (IRC) in Sella.
Its note says that a branch ends at a minimum when rgsaddle reports one.
It also says that the rgsaddle pinned by continuous integration can stop
a branch beside the saddle.
The generated JavaScript Object Notation (JSON) Schema follows.
The 13-atom Lennard-Jones (LJ13) test refines the saddle of a nudged
elastic band (NEB) and follows the intrinsic reaction coordinate (IRC)
both ways.
It checks that each relaxed end matches exactly one of the two NEB
minima.
It also checks that energies fall monotonically from the saddle.
Each branch must reach the minimum flag at the default threshold and end
within 1e-3 electronvolts of its relaxed end.
With one session shared by both branches the second branch ends 0.10 or
0.14 electronvolts above its end, at thresholds 0.05 and 0.01.

The job tests check three things.
With irc_after off, setting the irc keys leaves the bytes of every
returned file unchanged.
A build without the IRC refuses the option.
On the Morse Pt saddle the IRC leaves saddle.con, mode.dat and the
saddle force calls unchanged.
There it relaxes into the reactant and writes one irc.con frame per
irc.dat row.
With rgsaddle, eOn runs min_mode_method = dimer through XtsciMinMode
whatever [Dimer] improved says.
XtsciMinMode set rotation_tol to torque_min, 0.1 by default.
eOn's own improved dimer has no torque stop.
Its rotation ends on converged_angle or after rotations_max.
The rgsaddle session ends a rotation once the rotational force is at or
below rotation_tol.
From 0.3.0 rgsaddle also reads a rotation_tol of 1e-4 or more as the
classic torque window.
Once the rotational force is below 1, that window stops a rotation after
its first step.
So the default improved request got a classic dimer.

The improved dimer now passes rotation_tol = 0.
The classic dimer and Lanczos still pass torque_min.
It needs no rgsaddle change and no new C application binary interface
(ABI) minor.
Every minor that eOn reads has rotation_tol, so the change applies to
the continuous integration (CI) pin 06c55c5 and to the releases 0.3.0 to
0.3.2 alike.

The runs cover five saddle searches against the CI pin.
They are the Morse Pt saddle, the 7- and 13-atom Lennard-Jones clusters
(LJ7 and LJ13), the Pt island of examples/akmc-pt and the how-to input.
Four of the five saddles move, with energies equal to six decimals.
The Morse Pt search takes 46 force calls instead of 38, LJ7 750 instead
of 748, the island 486 instead of 471 and the how-to input 39 instead of
32.
Their eigenvalues go from -1.013461, -9.880619, -0.612079 and -1.010681
to -1.010819, -9.880954, -0.613662 and -1.010010.
LJ13 keeps its saddle.con and mode.dat.
Each moved saddle lies within 1e-4 Angstroms of the old one.
Against rgsaddle main fe6bed89 the five searches give the same
saddle.con and mode.dat as against the pin.
Develop against main ends the island search at another saddle, at
-1774.804074 electronvolts (eV) after 449 force calls.
With this change it ends at -1775.190088 eV, as with the pin.

Two job tests bound the Morse Pt force calls by the Subversion (SVN)
reference, 39 for the saddle search and 69 for the process search.
With rgsaddle these searches now take 47 and 75, and the bounds under
WITH_RGSADDLE move to those counts.
The rgsaddle eigenvalue of the saddle search test moves to -1.010819.
eOn's own dimers do not change, and their bounds stay.
The how-to runs irc_after, which follows the intrinsic reaction
coordinate (IRC), on the Morse Pt saddle of the systems tutorials.
It shows the exact config, copy commands from the checkout root and the
output of the run.
A table of six runs shows how step size and force threshold trade path
detail for force calls.
Four of them trace both branches with the pinned rgsaddle.
The config sets step 0.5 and threshold 0.01, the cheapest of those four.
The page says which output comes from the committed run and which from
the other runs.
At the default threshold of 0.05, one branch stops next to the saddle at
step 0.1 and at step 0.5.
The pinned rgsaddle reports a minimum there, so eOn counts the branch.
The figure comes from the run, and its irc.dat, results.dat and plotting
script sit next to it.
The user guide gains a short IRC section with references.
It credits Sella's QuasiNewtonIRC for the step on the sphere.
It also says that a branch ends where rgsaddle reports a minimum.
A towncrier fragment records the new keys and their defaults.
@github-actions

github-actions Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

eOn Documentation Preview

Download: documentation.zip

Unzip and open index.html to view.

@codecov

codecov Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 22.22222% with 84 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
client/SaddleSearchJob.cpp 8.33% 77 Missing ⚠️
client/XtsciIrc.cpp 22.22% 7 Missing ⚠️

📢 Thoughts on this report? Let us know!

Commit cf269a1 added four irc keys to the [Saddle Search] schema.
Without them in eon/config.yaml, test_schema_matches_config_yaml failed.
The keys now appear in config.yaml with the defaults of the schema.
Those are off, 0.1, 0.05 and 1000, the same as in the client options.
Their comments repeat the descriptions in the schema.
@HaoZeke HaoZeke changed the title feat(saddle): follow the intrinsic reaction coordinate after a saddle search feat(saddle): follow the intrinsic reaction coordinate after a saddle Nov 2, 2026
@HaoZeke
HaoZeke merged commit c8db9a8 into TheochemUI:develop Nov 2, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant