Roll down from a first-order saddle#
A two-atom double well has a saddle at
the origin and minima at \(x = \pm 1\). IrcSession kicks along
the imaginary mode, then projects each inner step onto the
mass-weighted sphere from rgmin (IrcTrust). Forward and
reverse leave the col in opposite directions.
What you will need#
A checkout of rgsaddle with its pinned rgmin dependency.
The surface#
use ndarray::{Array1, ArrayView1};
use rgsaddle::{PointSurface, SaddleError};
struct DoubleWell;
impl PointSurface for DoubleWell {
fn eval(&self, x: ArrayView1<f64>) -> Result<(f64, Array1<f64>), SaddleError> {
let t = x[0];
let energy = (t * t - 1.0) * (t * t - 1.0);
let mut g = Array1::zeros(x.len());
g[0] = 4.0 * t * (t * t - 1.0);
Ok((energy, g))
}
}
Both branches#
use ndarray::Array1;
use rgsaddle::{IrcConfig, IrcDirection, IrcSession};
let saddle = Array1::zeros(6);
let masses = Array1::from(vec![1.0, 1.0]);
let seed = Array1::from(vec![1.0, 0.1, 0.0, 0.0, 0.0, 0.0]);
let mut session = IrcSession::from_surface(
IrcConfig { dx: 0.2, ..IrcConfig::default() },
saddle,
masses,
seed,
IrcDirection::Forward,
&DoubleWell,
)
.unwrap();
let _fwd = session.step(&DoubleWell).unwrap();
let x_fwd = session.position()[0];
session.set_direction(IrcDirection::Reverse);
let _rev = session.step(&DoubleWell).unwrap();
let x_rev = session.position()[0];
assert!(x_fwd * x_rev < 0.0);
from_surface estimates the imaginary mode with matrix-free
Lanczos (finite-difference H v). It does not form a dense
Hessian or invoke a dense eigensolver.
The resulting path#
Lanczos evaluates Hessian actions at the origin and returns the negative-curvature direction.
The first
stepkicked along that mode onto the mass-weighted sphere of radiusdx.set_direction(Reverse)restored the saddle and flipped the kick. The two accepted points sit on opposite sides of the col.
The host owns the loop after that. run(surface, max_steps) is
a convenience over step until at_minimum. reset drops quasi-Newton
history at a model-update boundary, as for
BandSession.