mod minima_hopping

module minima_hopping

History-conditioned escape feedback, after Goedecker’s minima hopping.

Goedecker, J. Chem. Phys. 120, 9911 (2004).

The campaign’s own measurements say what is missing. From the structure a 75-point search settles into, none of 1800 single moves across the whole kernel set reaches anything lower, so crossing needs a sequence of accepted uphill quenches and a reason to accept them that does not destroy descent. A continuous collective variable cannot supply that reason: the Q4 gap between the two funnels is 0.023, under any usable deposition width.

This crate already keeps a history of visited basins and uses it to deposit bias. Minima hopping uses a history for something else: to scale the next escape. Revisiting a known minimum makes the next attempt more violent rather than the current one less attractive.

That difference is the whole mechanism and it is worth stating precisely, because the two are complementary and must stay separable:

escape feedback

basin bias

what a revisit raises

the escape scale

the potential on that basin

acceptance

adaptive threshold on the energy rise

Metropolis on F + V

transition regions

left crossable

filled if revisited

Goedecker argues against flooding transition regions for exactly this reason: the region between funnels has to stay crossable, so the response to a revisit should be a harder push rather than a higher potential where the chain needs to pass.

The escape scale grows geometrically while a chain revisits, which is the guarantee that no funnel is permanent: a chain that keeps returning keeps escalating until it leaves. Schoenborn, Goedecker, Roy and Oganov, J. Chem. Phys. 130, 144108 (2009), multiply the known-minimum update by 1 + c ln(N) for visit count N, and report that this finds the LJ75 Marks decahedron where a cut-and-splice evolutionary algorithm does not.

Functions

fn nve_escape<F, R>(start: ArrayView1<f64>, initial_kinetic: f64, config: &MdEscapeConfig, evaluate: &mut F, rng: &mut R) -> Result<MdEscapeReport, SaddleError>
where
    F: for<'a> FnMut(ArrayView1<'a, f64>) -> Option<(f64, Array1<f64>)> + Send,
    R: Rng + ?Sized

Run the NVE escape of minima hopping through rgsaddle::SamdSession.

The Gaussian initial velocity is projected onto geometry and rescaled to exactly initial_kinetic. With an infinite SAMD thermostat time, the BDP rescale is the identity and the delegated velocity-Verlet trajectory is microcanonical. The host loop stops after potential_minima local minima in the potential-energy trace, as in the Goedecker algorithm.

fn nve_escape_seeded<F, R>(start: ArrayView1<f64>, initial_kinetic: f64, seed_direction: Option<ArrayView1<f64>>, config: &MdEscapeConfig, evaluate: &mut F, rng: &mut R) -> Result<MdEscapeReport, SaddleError>
where
    F: for<'a> FnMut(ArrayView1<'a, f64>) -> Option<(f64, Array1<f64>)> + Send,
    R: Rng + ?Sized

Same escape as nve_escape, with the launch velocity seeded by seed_direction when that vector has the right length.

The seed is the covering displacement. Softening, when the config asks for it, mixes the seed toward the softest direction. A small Gaussian part remains so two nearby covers do not collapse onto one trajectory. None is the unbiased Gaussian launch.

Enums

enum MdEscapeGeometry

Geometry on which an MD escape evolves.

Euclidean

Unconstrained Euclidean coordinates of any dimension.

RigidQuotient

Cartesian coordinates modulo rigid translation and rotation.

enum Visit

What a quench was, relative to the history.

Same

The basin the chain was already in.

Known

A basin the history has seen before, but not the current one.

New

A basin not in the history.

Structs and Unions

struct EscapeFeedback

Escape scale and acceptance threshold, both driven by the history.

Defaults are Goedecker’s: the escape scale grows by 1.05 on a revisit and shrinks by the same factor on a discovery, and the acceptance threshold moves the other way, so roughly half of proposals are accepted without a temperature being chosen.

beta_same: f64

Growth on returning to the current basin.

beta_known: f64

Growth on reaching a basin already in the history.

beta_new: f64

Shrink on reaching a new basin.

visits_coeff: f64

Enhanced feedback: the known-basin growth is multiplied by 1 + visits_coeff * ln(visits), so a repeatedly seen basin is escaped harder than one seen once (Schönborn et al.).

alpha_accept: f64

Threshold multiplier on acceptance; below one.

alpha_reject: f64

Threshold multiplier on rejection; above one.

escape_ceiling: f64

Ceiling on the escape scale.

A streak of revisits grows the scale geometrically, and an unbounded scale eventually proposes a structure so scattered that its relaxation costs far more than an ordinary one. The ceiling keeps the charged cost of a proposal bounded without removing the escalation.

The default of four is set by that cost, not by taste. At a ceiling of sixty-four an LJ38 run paid 138 charged evaluations per hop against 39 without the controller, so the same budget bought 2173 hops rather than 7775 and the run solved 1 seed in 8 where the plain chain solved 8. Escalation that prices itself out of the budget is not escalation.

escape_floor: f64

Floor on the escape scale, so a run of discoveries cannot drive it to zero and freeze the search.

n_same: usize

Counts of each outcome, for reporting.

n_known: usize

Quenches that landed in a known other basin.

n_new: usize

Quenches that opened a new basin.

Implementations

impl EscapeFeedback

Functions

fn accept(&mut self, delta: f64) -> bool

Whether to accept a move of energy rise delta, updating the threshold.

The rule is Goedecker’s: accept when the rise is under the threshold, then move the threshold so the acceptance rate sits near a half. No temperature appears, which is the point: a Metropolis temperature cold enough to polish cannot cross and one hot enough to cross cannot polish, while this threshold adapts to whichever the chain is currently failing at.

fn classify(&self, current: Option<usize>, reached: usize) -> Visit

Classifies a quench without recording it.

fn discovery_rate(&self) -> f64

Fraction of quenches that were discoveries.

A run whose fraction is near zero is revisiting and should be escalating; one near one is wandering and should be settling. Reported because it says whether the feedback is doing anything.

fn escape(&self) -> f64

Current escape scale.

fn known_basins(&self) -> usize

Basins in the history.

fn new(escape: f64, threshold: f64) -> Self

Controller starting at escape and threshold, with Goedecker’s rates.

fn observe(&mut self, current: Option<usize>, reached: usize) -> Visit

Records a quench and updates the escape scale.

Returns what the quench was. The scale rises on a revisit and falls on a discovery, which is the feedback: a chain that keeps returning escalates until it leaves, and one that keeps finding new structures settles down to explore them.

fn register_initial(&mut self, basin: usize)

Register the starting minimum without applying escape feedback.

The first minimum is part of the history even though no escape has reached it. Recording it leaves both adaptive controls at their stated initial values and makes a later return classify as known.

fn threshold(&self) -> f64

Current acceptance threshold.

fn visits(&self, basin: usize) -> u32

Times basin has been recorded.

struct MdEscapeConfig

Short NVE escape controls for minima hopping.

dt: f64

Velocity-Verlet step in the surface’s reduced time units.

potential_minima: usize

Potential-energy minima crossed before stopping the trajectory.

maximum_steps: usize

Hard cap on Verlet steps when the requested crossings are not reached.

geometry: MdEscapeGeometry

Coordinate geometry used for velocity projection and retraction.

softening: Option<VelocitySofteningConfig>

Published finite modified-dimer softening of the Gaussian direction.

minimum_rise: f64

A time-series minimum counts only after the energy has fallen by at least this much from the highest point since the previous counted minimum. Zero keeps every wiggle. A positive value is the barrier the escape has to cross before it is allowed to stop.

max_rms: f64

Stop once the root-mean-square displacement from the launch reaches this value. Infinite disables the cap.

min_well_rms: f64

A potential minimum counts as the end of the escape only when its root-mean-square displacement from the launch is at least this large. Zero keeps every minimum.

Traits implemented

impl Default for MdEscapeConfig
struct MdEscapeReport

Endpoint and accounting evidence from one MD escape.

position: Array1<f64>

Last point on the MD trajectory.

steps: usize

Verlet steps completed.

potential_minima: usize

Potential-energy minima detected along the trajectory.

energy: f64

Potential energy at the last point.

kinetic: f64

Kinetic energy at the last point.

softening_evaluations: usize

Force evaluations used to soften the launch direction.

far_position: Array1<f64>

Geometry farthest from the launch, by root-mean-square displacement.

far_rms: f64

Root-mean-square displacement of Self::far_position from the launch.