mod cluster_hopping

module cluster_hopping

Basin hopping over quenched minima with a basin-keyed bias. Basin hopping over quenched minima with a bias keyed on basin identity.

Cluster global optimisation happens on the quenched landscape, E_q(x) = E(local_min(x)), not on the raw surface: the funnel structure is only visible after relaxation, and a search on the raw surface finds nothing on a 38-atom Lennard-Jones cluster.

Three things then decide whether a funnel can be left.

The relaxation is where the budget goes. A full one costs a few hundred charged evaluations and most trials land nowhere near the incumbent, so trials are screened by a short relaxation first and only promoted when they land within Config::screen_margin of the incumbent. Measured on LJ38, screening took basin discovery from 27 to 327 at a fixed charge.

The bias is keyed on basin identity rather than on a collective variable. A variable has to be chosen correctly or it cannot see the competition: on LJ75 the Marks decahedron and the structures a search settles into differ by 0.023 in the fourth Steinhardt parameter, narrower than any usable deposition width, so biasing on it fills both competitors at once.

The moves have to change the packing rather than perturb it, which is what crate::movekernel::SurfaceRelocate, crate::movekernel::ShellRotate and crate::movekernel::Symmetrise do, and which of them pays at a given stage is decided online rather than fixed.

Types

type Grad<'a>

A borrow of one, for a caller that has a gradient to lend.

The two lifetimes are separate on purpose. Tying the trait object’s lifetime to the borrow makes the pair invariant, so a caller that holds a gradient and wants to lend it to a sequence of inner runs cannot reborrow it: it has one gradient and can hand it over once.

type GradFn<'g>

Gradient of the objective, charged to the ledger by the caller.

Optional because only the soft-mode escape needs it: everything else in this driver works from relaxations alone.

type Relax<'a>

Relaxes x, charging every evaluation, and stopping when the budget ends.

The relaxation is supplied by the caller because the objective, its gradient and the minimiser are the caller’s: this module owns the search, not the numerics under it.

type Settle<'a>

Partial relaxation of the listed atoms in the frozen environment.

Arguments: ledger, structure, moved atom indices, descent steps. Returns the settled structure. The callee owns the objective and therefore the honest fractional price; it charges through Ledger::charge_frac.

Functions

fn optimize(cfg: &Config, ledger: &mut Ledger, relax: Relax<'_>, seed: u64) -> Outcome

Convenience entry point seeding its own start.

fn optimize_with_gradient<'g>(cfg: &Config, ledger: &mut Ledger, relax: Relax<'_>, grad: Option<&mut GradFn<'g>>, seed: u64) -> Outcome

As optimize, with a gradient for the soft-mode escape.

fn optimize_with_settle<'g>(cfg: &Config, ledger: &mut Ledger, relax: Relax<'_>, grad: Option<&mut GradFn<'g>>, settle: Option<Settle<'_>>, seed: u64) -> Outcome

As optimize_with_gradient, with a settle stage for the staged quench.

fn random_cluster<R: Rng + ?Sized>(n: usize, density: f64, min_sep: f64, rng: &mut R) -> Array1<f64>

Seeds a non-overlapping configuration at liquid-like density.

Uniform draws over a container overlap almost surely at the sizes of interest, and a relaxation cannot recover from that.

fn random_cluster_in_radius<R: Rng + ?Sized>(n: usize, radius: f64, min_sep: f64, rng: &mut R) -> Array1<f64>
fn repack_rigid_groups<R: Rng + ?Sized>(template: ArrayView1<f64>, groups: &[Vec<usize>], length_scale: f64, rng: &mut R) -> Array1<f64>

Seeds a non-overlapping configuration inside a declared sphere radius. Re-place rigid groups on a new sphere, keeping each group’s internals.

The shell dimensions are dimensionless molecular-preset coefficients multiplied by the caller’s declared length_scale.

fn run<R: Rng + ?Sized>(cfg: &Config, start: ArrayView1<f64>, ledger: &mut Ledger, relax: Relax<'_>, rng: &mut R) -> Outcome

Runs the driver until the ledger is spent.

start is a starting configuration and relax performs a relaxation of the requested number of steps, charging the ledger.

fn run_with_bias<'g, R: Rng + ?Sized>(cfg: &Config, start: ArrayView1<f64>, ledger: &mut Ledger, relax: Relax<'_>, grad: Option<&mut GradFn<'g>>, bias: &mut BasinBias<ClusterFingerprint>, rng: &mut R) -> Outcome

As run_with_gradient, with a bias supplied by the caller and left behind when the run ends.

For a caller running several chains under one budget. The well-tempered bias is a memory of the landscape rather than of the chain that walked it, and a funnel one chain has filled is filled for the next one. Rebuilding it per chain throws that away, which is not a small effect: at 75 points the crossing takes on the order of a hundred thousand hops of accumulation, and a bank of sixteen chains each starting from an empty bias solved 2 seeds in 8 where one long chain solved 9 in 16.

Only for a single-rung run. Replica exchange gives each rung its own bias by construction and there is nothing for one external bias to be.

fn run_with_gradient<'g, R: Rng + ?Sized>(cfg: &Config, start: ArrayView1<f64>, ledger: &mut Ledger, relax: Relax<'_>, grad: Option<&mut GradFn<'g>>, rng: &mut R) -> Outcome

Runs from start with an optional charged gradient.

fn run_with_gradient_settle<'g, R: Rng + ?Sized>(cfg: &Config, start: ArrayView1<f64>, ledger: &mut Ledger, relax: Relax<'_>, grad: Option<&mut GradFn<'g>>, settle: Option<Settle<'_>>, rng: &mut R) -> Outcome

As run, with a gradient for the soft-mode escape.

Without one, Config::minima_hopping falls back to scaling the ordinary displacement, which carries the same feedback law and is what Goedecker reports as strictly weaker.

Enums

enum ClusterFingerprint

Descriptor for basin keying, matched to the metric that will compare it.

A sorted distance spectrum is permutation and rotation invariant already, so Euclidean distance on it is a usable if scale-broken notion of sameness. A shape metric quotients out those symmetries itself and needs the coordinates, so the two cannot be mixed.

Spectrum(SortedPairs)

Sorted pairwise distances, compared by Euclidean distance.

Coordinates

Coordinates, for a metric that does its own matching.

Sites(SiteEnergies)

Sorted per-point pair energies, keying on how well each point is bound rather than on how far apart the points are.

Canonical(Box<crate::shape::CanonicalOrder>)

Coordinates put in a canonical order against a fixed reference, so Euclidean distance between two of them is a shape distance.

Implementations

impl ClusterFingerprint

Functions

fn for_keying(n_points: usize, shape_keyed: bool) -> Self

The descriptor a given keying requires.

fn of(n_points: usize, keying: Keying) -> Self

The descriptor for a named keying, without a reference.

Keying::Canonical needs one and falls back to the distance spectrum here; callers that want it should use ClusterFingerprint::of_with.

fn of_with(n_points: usize, keying: Keying, reference: &Array1<f64>) -> Self

The descriptor for a named keying, against reference.

enum Keying

Which descriptor a run keys basins on.

Named rather than a boolean because there are now three and the choice is the lever: at 75 points the merge radius on a distance spectrum is sharply sensitive, 13 seeds in 24 at 0.7 against 0 in 8 at 0.95, and a descriptor that separates distinct structures more cleanly is what would widen that.

Distances

Sorted pairwise distances.

Shape

Coordinates, matched by a shape metric.

Sites

Sorted per-point pair energies.

Canonical

Coordinates canonically ordered against a fixed reference.

The only keying here that does not throw correspondence away. Sorting buys invariance by discarding which point holds which value; a canonical order keeps it, so two structures with the same multiset of distances and a different arrangement separate.

It is also what makes shape keying affordable. A shape distance costs an IRA call, so keying on it directly pays one call per basin comparison and a bias holding thousands of basins cannot be queried at hop rate. Matching each structure once against a reference costs one call per hop and leaves every comparison Euclidean.

Structs and Unions

struct Config

Driver settings.

n_points: usize

Points in a state; the state length must be 3 * n_points.

length_scale: f64

Declared coordinate length scale.

energy_scale: f64

Declared objective-energy scale.

move_library: MoveLibrary

Proposal library hosted by this configuration.

neighbour_cutoff: f64

Separation below which two points count as neighbours.

symmetrise_cutoff: f64

Pair cutoff used by the symmetry proposal.

temperature: f64

Metropolis temperature on the quenched chain.

bias_height: f64

Height of a fresh bias deposit.

bias_gamma: f64

Well-tempered bias factor; must exceed one.

merge_radius: f64

Distance below which two states are the same basin.

Its units are those of whichever metric keys the bias. Against a sorted distance spectrum compared by Euclidean distance it is a number in descriptor space with no physical meaning; against a shape distance it is a length.

theta: f64

Design point for the budget-window temperature, as a fraction of the sphere-model descent boundary. Must lie strictly below two.

budget_window: bool

Set the temperature by the budget-window law rather than holding it.

allocate_moves: bool

Choose the move kernel by discounted Thompson allocation.

adaptive_height: bool

Set the deposit height from the escape gaps the chain observes.

max_hops: Option<usize>

Hops a single run may take before returning, when set.

Used by the replica ladder to advance one chain by a slice; a plain run leaves it unset and stops only when the ledger does.

replicas: usize

Replicas run on a temperature ladder, with periodic swaps.

One is the plain chain. Above one, the driver runs a ladder and offers swaps through crate::exchange::Exchange, which is the crate’s own operator and satisfies detailed balance by construction.

This is the standard non-local mechanism for a multi-funnel landscape and the measurements here say why it is the right one to reach for: no single move from the plateau reaches anything lower, so a cold chain cannot leave it, while a hot chain crosses freely and finds nothing precise. A swap moves a hot chain’s crossing down to a cold chain that can polish it, which neither temperature achieves alone.

swap_period: usize

Hops between swap attempts.

minima_hopping: bool

Drive the escape scale and the acceptance threshold from the history, after Goedecker’s minima hopping, instead of a Metropolis temperature.

Revisiting a known minimum makes the next escape harder rather than the current basin less attractive, which is a different use of the same history the bias keeps. The transition region between funnels is left crossable, which Goedecker argues is why flooding it is the wrong response to a revisit.

This is the scaled-move form: the escape scale multiplies the move amplitude and the acceptance threshold replaces Metropolis. Soft-mode climbs are not taken every hop under this flag; they are the separate Config::escape_on_stall path. Measured: activating every hop under a gradient cost ~687 charged evaluations per hop on LJ38 and bought 291 hops from 200k, which is not a search. The controller and the climb are complementary and must stay separable.

escape_lanczos_steps: usize

Lanczos steps for the soft-mode escape.

Each costs two gradient evaluations, charged. Eight resolves the softest mode of a cluster well enough to displace along, against about forty charged evaluations for the relaxation that follows.

escape_epsilon: f64

Finite-difference step for the Hessian-vector product.

escape_amplitude: f64

Distance moved along the softest mode per climbing step.

escape_overshoot: f64

Push past the saddle, in units of the climbing step, before the feedback scale multiplies it.

escape_max_climb: usize

Climbing steps before a climb is abandoned.

escape_on_stall: bool

Climb out of the basin when the chain stops improving.

The escape and the plain chain have opposite economics and this is how they are combined. A climb is a guaranteed way out of a funnel and costs 637 charged evaluations against 30 for an ordinary hop, so running one every hop buys 471 hops where the plain chain buys a hundred thousand and loses LJ38 outright. Running one only when the chain has stopped improving costs a few per cent and supplies the one thing a biased random walk has no mechanism for: leaving a funnel on purpose.

escape_stall_patience: usize

Smallest number of hops without improvement before a climb is triggered.

A floor, not the trigger. The trigger is escape_stall_factor times the longest quiet stretch this chain has already survived, so a climb fires only when the chain is stuck longer than it has ever been stuck before.

A fixed patience cannot be set. Traced on 75 points, the runs that succeed cross at 42 and 55 per cent of the way in, after 1500 to 1900 basins, and go tens of thousands of hops between improvements on the way. A patience of 400 fires about 180 climbs into that and the chain never accumulates: the arm scored 2 seeds in 8 against 9 in 16 without it.

escape_stall_factor: f64

Multiple of the longest quiet stretch so far that counts as stuck.

track_funnels: bool

Track the funnel partition the search’s own transitions imply.

A stall is currently detected from energy: so many hops without a new best. That conflates two situations a search should treat differently, a chain polishing inside a region it can leave, and a chain that cannot leave at all. The transition graph tells them apart: when the accepted hops split into two parts with few edges between them and the chain sits in one, that is a funnel and not slow progress.

Steer with spectral (Fiedler) well-tempered bias on the hop graph.

When true, accepted hops build a transition graph on basin identity (the same fingerprint as the per-basin bias; with ira and Keying::Canonical that identity is IRA, not SortedPairs). The second eigenvector of the normalised Laplacian is the continuous CV for an extra well-tempered bias (crate::spectral::SpectralBias). That is the algorithm: identity supplies resolution, the spectrum supplies the funnel coordinate — no hand-chosen Q4. Refits cost an eigendecomposition of a matrix the size of the basin count, on a schedule (Config::funnel_period). See crate::funnel_spectral and crate::spectral.

funnel_period: usize

Accepted hops between Laplacian refits / Fiedler updates.

symmetrise_on_stall: bool

Symmetrise onto the symmetry the structure nearly has, on a stall.

Oakley, Johnston and Wales report the mean first encounter time for the 98-point cluster, whose global minimum is tetrahedral, improving by more than seventyfold under a scheme of this kind. That is the case this driver is weakest on: 3 seeds in 8 at twelve million evaluations against 8 in 8 at 75 points.

Applied when the chain is stuck rather than as an allocator arm, because it is not a perturbation competing with the others. It either finds an approximate symmetry and lands the structure on it, or finds none and leaves the chain alone. See crate::symmetrise.

symmetry_tolerance: f64

Largest deviation at which an approximate symmetry is worth using.

symmetry_merge_radius: f64

Coordinate-space radius used to merge points after symmetrisation.

symmetrise_patience: usize

Hops without improvement before a symmetrisation is considered.

Separate from the escape patience because the two answer different questions: an escape is for a chain that cannot leave, this is for a chain that has stopped finding anything and may be near a symmetric answer without being on it.

angular_moves: bool

Wales and Doye’s angular move, applied when a point is loose.

“If the highest pair energy rose above a fraction R of the lowest pair energy then an angular move was employed for the atom in question with all other atoms fixed” (J. Phys. Chem. A 101, 5111). This is the move their unbiased search used to reach the decahedral minima at 75 and 102 points, and it is not in this crate’s library: the nearest thing, surface relocation, picks the least-coordinated point rather than the worst-bound one and places it near the surface rather than at the far edge.

It replaces the allocator’s choice on the steps where it fires, rather than being one arm among many, because the criterion decides when it is the right move.

angular_target: f64

Acceptance rate the pair-energy ratio is tuned to.

“R was adjusted to give an acceptance ratio for angular moves of 0.5 and generally converged to between 0.40 and 0.44.”

keying: Keying

Which descriptor basins are keyed on.

Takes precedence over shape_keyed, which stays for callers that only need the two-way choice.

contextual_moves: bool

Choose the move from where the chain is standing.

The allocator learns one success rate per move, which is the right model when a move has a rate and the wrong one when it has a precondition. The angular move is the clear case: it is not applied at a frequency, it is applied when a point crosses a pair-energy criterion, and a rate learned across the times it was and was not appropriate describes no situation the chain is ever in. See crate::contextual.

contextual_floor: f64

Rate at which the contextual allocator picks uniformly regardless.

bayes_screen: bool

Decide whether to finish a relaxation from a posterior rather than from a fixed margin.

The margin screen is the one mechanism here measured to be worth having, at 13 seeds in 24 against 2 in 8 without it, and what it does is spend numerical effort where it is likely to pay. That is a decision under uncertainty about a quantity not yet computed, and a constant is a poor way to make it. See crate::screen.

flat_histogram: bool

Accept against the density of minima rather than against the energy.

The Metropolis rule targets g(E~) exp(-E~ / T) in quenched energy, and on a multi-funnel landscape g decides the outcome: the chain sits in whichever funnel holds the most minima. Weighting by 1 / g makes the sampled energy histogram flat, so the deep and rare energies get the same share of the run as the shallow and abundant ones. See crate::dos.

flat_sweep: usize

Trials between weight refreshes. The weight is frozen across a sweep so each sweep is an exact chain for its own target rather than an adaptive one whose invariance has to be argued.

flat_quantile: f64

Quantile of a sweep’s visited energies that sets the cut below which the target is flat. Lower is greedier: the flat region shrinks toward the deepest energies the chain has reached.

statistical_temperature: bool

Take the temperature from the entropy’s own slope rather than from a constant.

The Metropolis rule and the basin bias both measure well and both stand; what this replaces is the one hand-set number they sit on. See crate::dos::DensityOfStates::temperature.

energy_bias: bool

Deposit a well-tempered bias in quenched energy.

The per-basin bias fills the basin the chain stands in, and the trap is a funnel holding exponentially many basins. Energy separates the funnel where a coordinate length cannot. All scales come from the run’s own quenched-energy distribution. See crate::dos::EnergyBias.

depth_reward: bool

Reward move arms by the depth they reach, not by acceptance.

See crate::allocate::DepthAllocator.

soft_perturb: bool

Perturb in the soft subspace of the incumbent’s own curvature.

An isotropic step in 3n dimensions puts nearly all of its norm on stiff directions, and the quench relaxes those components straight back into the basin they came from: only the projection onto the low-curvature subspace survives. Confining the draw to that subspace with per-mode thermal amplitudes is the local Gaussian N(0, T H^{-1}) truncated to the modes that carry displacement at T, computed matrix-free by shifted Lanczos and charged to the ledger like any other work. Nothing here mentions a morphology: the subspace is the structure’s own.

soft_modes: usize

Soft modes kept in the subspace.

soft_steps: usize

Lanczos steps per subspace computation.

cov_perturb: bool

Perturb from a covariance learned from this run’s accepted moves.

The soft-subspace arm computes the directions that matter from the Hessian and pays charged evaluations for them. This arm learns the same object free: accepted minimum-to-minimum displacements concentrate in the directions basins actually connect along, so their shrunk empirical covariance, (1 - gamma) sigma0^2 I + gamma S, is a proposal fitted to the run’s own successes. Shrinkage toward isotropy covers the cold start, and the weight ramps with evidence, which is the Ledoit-Wolf compromise rather than a schedule. Sampling needs no factorisation: sqrt(1-gamma) sigma0 z0 + sqrt(gamma/m) sum z_i d_i has exactly the mixture covariance. Nothing morphological enters; the buffer is this run’s history.

staged_quench: bool

Stage the quench: settle the moved atoms in the frozen environment before the screening relaxation.

The measured-productive moves displace one to three atoms while every trial pays a full-system screen. A k-atom settle costs k of the n(n-1)/2 pair rows per evaluation, charged fractionally through Ledger::charge_frac, so the cheap stage absorbs the descent the full screen would otherwise spend whole evaluations on.

settle_iters: usize

Descent steps in the settle stage.

group_cutoff: f64

Inter-group contact cutoff for the molecular library.

covalent_cutoff: f64

Bonding cutoff below which two atoms are one molecule, for deriving the groups from the structure’s own connectivity each hop. Used only when no species are declared; with species the bond-matrix rule over covalent radii replaces it.

species: Option<Vec<u32>>

Atomic numbers, one per point. With these set the connectivity uses the species-aware bond matrix, which a single length cannot replace on a system holding more than one element.

bond_tolerance: f64

Bond-matrix tolerance on the covalent radii sum.

frozen: Option<Vec<bool>>

Frozen mask, one flag per point. A frozen point is environment: groups made entirely of frozen points never enter the move library, and the structure is not recentred or contained, since the frozen frame IS the frame. The caller’s objective is expected to return zero force on frozen points so the quench leaves them where they stand.

active_region: Option<(Vec<usize>, usize)>

Dynamic active region: seed atoms and the number of bond-matrix neighbour shells around them that stay mobile, recomputed from the current structure each hop. Everything outside the region is treated as frozen for that hop, so the mobile patch follows the seeds. Requires species.

bayes_exploration: f64

Trials relaxed regardless of the posterior, to keep the model’s training set from being censored by the rule it trains.

bayes_threshold: f64

Posterior probability of improvement above which a trial is relaxed.

bayes_warmup: usize

Observations before the posterior is consulted at all.

tabu_on_stall: bool

Forbid the funnel the chain is stuck in, rather than making it expensive.

Wales and Doye record the lockout directly: once the lowest icosahedral minimum is reached at 75 points, the decahedron is never found later in that run. Two responses were measured here and both failed. A well-tempered bias raises the potential where the chain has been, and runs that fail register as many basins as runs that succeed, so the filling is not what decides it. Restarting the walker from a random configuration failed too, nineteen times per run: a random start descends into the icosahedral funnel again because that funnel’s basin of attraction is far wider. The lockout is entropic and a soft penalty cannot outrun it.

This rejects outright. Structures within the merge radius of a quarantined one are refused whatever their energy, so the chain cannot return to a funnel it has been declared stuck in. The ledger still records them, so a quarantine that turns out to cover the answer costs the search nothing it had already found.

tabu_capacity: usize

Quarantined structures held at once, oldest dropped first.

restart_on_stall: bool

Restart the walker from a fresh configuration on a stall, keeping the bias.

What is stuck is the walker, not the landscape memory. Traced at 75 points, a run that fails stops improving at 2 to 26 per cent of the way in and spends the rest inside the icosahedral funnel, while the runs that succeed cross at 42 to 91 per cent; so a chain that has not crossed early is unlikely to, and the thing worth keeping from its remaining budget is what it has already filled in.

Different from the climb, which moves the walker a short way and leaves it in the same funnel, and from a bank, which splits the budget. This spends nothing and discards nothing: the bias the old chain built is what steers the new one away from where the old one was.

calibrate_radius: bool

Set the merge radius from how far an accepted hop actually reaches.

A radius chosen by hand does not transfer: one calibrated at 38 points is wrong at 75, and one calibrated in a sorted-distance spectrum is wrong in a shape metric. Two structures are the same basin when a single accepted hop can carry the chain between them, and the search reports that step length for free. See crate::calibrate.

calibrate_quantile: f64

Quantile of the accepted-hop step length the radius tracks.

calibrate_warmup: u64

Accepted hops required before the calibrated radius is used.

bias_by_rung: bool

Scale the deposit height with rung temperature.

A bias pushes a chain out of where it sits and a low temperature keeps it in, so a cold rung carrying a full bias is evicted from good basins and cannot return. Measured on LJ75, that inverts the ladder: the coldest rung held -391.3 while the hottest held -396.0, where a working ladder has the deepest structure at the cold end.

Scaling the height by the rung’s temperature ratio leaves the coldest rung nearly a plain hopping chain, which polishes, and the hottest carrying the full bias, which crosses. The swap then moves a crossing down to a chain that can refine it, which is the division of labour the ladder exists for.

ladder_top: f64

Hottest temperature on the ladder, as a multiple of temperature.

return_screen: bool

Abandon a trial whose short relaxation is heading back to the current basin, before paying for the full one.

The energy screen passes a returning trial, because a perturbation that falls straight back carries the incumbent’s energy and looks like a success. Near a deep minimum roughly nineteen proposals in twenty return, so most of the budget buys relaxations into the basin the chain already occupies. Measured on the shape distance after a partial relaxation, returns and escapes separate cleanly: 0.160 against 1.846 with 97 per cent of pairs ordered correctly at thirty iterations.

soap_class_residual: bool

SOAP hop uses the 555→421 / fcc-prototype oracle. Off (recommended) is the observed-cloud residual 2p μ.

soap_hop: bool

Offer the SOAP pullback arm. Recommended leaves this on. Off is the control that asks whether the arm does any work.

return_polish: usize

Extra relaxation steps on a returning trial when return_screen is on.

Zero (the recommended default) skips the full quench entirely, which is how a hop opts out of return polish. A positive value finishes every returning trial in that hop at a fraction of relax_steps so a near-incumbent that is actually a new isomer can still settle.

return_polish_after: usize

Ledger spend that must be reached before return_polish fires.

Zero polishes every returning trial. A positive value keeps the first part of the hop as skip-return and only finishes returns after that many charged evaluations, so one chain can cover both the ico GM and the later Marks funnel.

path_on_stall: bool

Attempt a multi-step path between funnels when hopping stalls.

Basin hopping searches to depth one, and from the structure a 75-point search settles into none of 1800 single moves reaches anything lower. A path relaxes images between the current structure and a structurally different archive member, so the corridor between two funnels is examined rather than jumped.

stall_patience: usize

Hops without improvement before a path is attempted.

path_images: usize

Images relaxed along a path.

anneal_diversity: bool

Anneal the merge radius from wide to narrow across the budget.

The threshold that decides when two structures are one basin is a temperature rather than a setting, and the only published method that solves the hard cluster sizes reliably anneals it. Held fixed, it is the quantity three separate calibrations here failed to pin down.

diversity_floor: f64

Fraction of the starting radius the annealed threshold falls to.

Bounded below by what basin identity needs, which is not what a population diversity threshold needs. A merge radius under the distance a single hop covers, 0.4766 on 75-point minima, stops recognising a structure already visited: annealing 0.7 down to 0.07 took a run from 250 basins at 25 revisits to 4423 at 2.6, and the best found from -396.282 to -394.629.

height_revisits: f64

Revisits a basin should take before the accumulated bias clears the escape gap, when the height is adaptive.

shape_keyed: bool

Key basins on IRA shape distance rather than on the descriptor.

Measured on LJ38 at 400 thousand charged evaluations: keying on the descriptor solves 1 seed in 8. The threshold there has to absorb relabelling and rotation, which is what makes it untransferable between sizes and what three separate calibrations failed to pin down.

screen_margin: f64

How far above the incumbent a screened trial may land and still be promoted to a full relaxation.

screen_steps: usize

Relaxation steps in the screening pass. Calibrated by sweep on the corrected relaxer, LJ38 at 4e5 charged evaluations, four seeds each:

steps

solved

charged per hop

hops

6

0/4

11

149392

10

0/4

16

94412

15

1/4

21

66437

25

4/4

33

49728

40

4/4

47

33396

Three times the hops buys nothing when the quench is short. The chain moves on the transformed landscape, and a screened energy that has not reached its basin is not a point on it, so a proposal is compared against the incumbent on a quantity that is not the one being minimised. 25 is the knee: 40 solves as often and costs 1.4 times as much per hop, 15 costs less and solves once in four.

This is the same wall the adaptive screening quench hit from the other side. There the extrapolated energy was wrong by 1e4 at the step where its rule fired; here a genuinely shorter quench is simply not enough quench. Both say the screening pass is the quench rather than overhead around it.

adaptive_screen: bool

Whether the screening pass stops on a decision instead of screen_steps.

The fixed length is where the budget goes: measured on 38 points, 89 to 92 per cent of charged evaluations were spent screening, against 8 per cent on the relaxations that screening exists to avoid. Every mechanism in this crate that tried to change where the chain goes was measured and failed; this one changes what a hop costs, which is the axis the only successful mechanism so far, the return screen, also moved.

record_gradient: f64

Gradient below which a structure may be recorded as the run’s best.

Loose enough that a genuine minimum passes, since a quenched cluster comes back near 1e-6, and tight enough to bar a partial quench, which comes back near 1e-1 or worse.

surrogate_tolerance: f64

Predictive spread, in units of the temperature, above which the first stage abstains rather than deciding.

See crate::delayed::Surrogate::predict_at.

delayed_acceptance: bool

Whether acceptance is delayed behind a learned surrogate.

A first stage decides on a surrogate for the quenched energy, costing one evaluation and no gradient, and only survivors are quenched; a second stage subtracts the surrogate difference back out. The composite step is reversible with respect to the true target for any surrogate, so a poor one costs acceptance rate rather than correctness. This is what the screen was reaching for and does not have. See crate::delayed.

construct_width: usize

Candidates built and scored per growth proposal.

Costs no charged evaluations, since scoring is structural.

probe_screen: bool

Whether to score the quench extrapolation without acting on it.

Runs the screening pass to its full length and records what an adaptive stop would have claimed, which is the only way to separate “the model is wrong” from “the model is right and the search needs the precision”.

quench_warmup: usize

Descent steps before the quench predictor may speak.

The first steps of a quench from a perturbed cluster are nowhere near the quadratic region: atoms sit close enough that energies run to 1e5, and a log-linear fit through three such decrements extrapolates a tail that has nothing to do with the basin. Measured, a stop at step 4 missed the full pass by 1.0e4 on a landscape whose minima are 0.5 apart.

quench_confidence: f64

Standard deviations of separation a verdict needs.

relax_steps: usize

Relaxation steps in the full pass.

container: f64

Container half-width, applied when a move is generated.

min_separation: f64

Closest approach enforced before a trial is relaxed.

Implementations

impl Config

Functions

fn derived(n_points: usize) -> Self

Recommended flags, with the two hand-set scalars replaced by derived ones: budget-window temperature (θ = 1/2 inside the descent window) and the cost-asymmetric Bayes screen τ = (R-S)/(2R-S) from crate::screen::cost_asymmetric_threshold.

This is not the measured recommended configuration. Hit rates for this stack are not claimed until a campaign records them.

fn for_cluster(n_points: usize) -> Self
fn for_molecular(species: Vec<u32>, groups: Vec<Vec<usize>>, energy_scale: f64) -> Self

Species-aware molecular preset with rigid groups.

fn packing_cna_applies(&self) -> bool

CNA 555 / Ih packing diagnostics apply only to a monoatomic cluster. A molecule or a slab has species or a frozen frame; those are not Honeycutt-Andersen environments.

fn proposal_kernel(&self) -> ClusterProposal

Proposal mixture for use by the general crate::sampler::Sampler.

Settings for n_points at the campaign’s measured defaults. The measured configuration: the stack every layer of which beat or matched its paired control across four cluster morphologies.

Composed surface relocations paying one acceptance test (LJ75 49/144 against 17/144, Bayes factor 3104 with the arm allocator), Normal-Gamma Thompson allocation rewarded by depth, and tabu on stall (LJ98 40/72 against 20/72, Bayes factor 43.8). Neutral where its mechanisms are not needed: 55/72 against 55/72 on the 38-point double funnel and 47-48 of 48 on the 55-point single funnel. Reference GMIN at matched potential-call budgets: 37/48, 0/48, 0/48.

Config::for_cluster remains the plain Wales-Doye protocol, kept as the comparison baseline; this is what a caller who wants answers should start from.

LeanBurst includes the SOAP pullback (analytic (J^{+}) of stacked local power spectra). The hop target is the observed-cloud residual 2p μ, the same map used on molecules and slabs: partitioned by observed species, never by a CNA class or an fcc prototype. Thompson allocates SOAP with surface, single, burst and sym. The return screen and stall symmetrisation are on; Ih-dominated stalls withhold symmetrise rather than invent a missing packing.

Measured allocation and stall controls over a molecular move library.

SOAP is the same observed-cloud residual as the cluster hop: 2p μ within each observed atomic number. No CNA, no fcc prototype. A slab sets active_region; frozen atoms stay as SOAP neighbours and do not move.

fn start_radius(&self) -> f64

Radius of the preset’s initial cluster sphere.

fn with_scales(n_points: usize, length_scale: f64, energy_scale: f64) -> Self

Lennard-Jones preset expressed against declared physical scales.

struct Ledger

Work ledger: every objective or gradient evaluation is charged.

A relaxation inside a move spends the same budget as a proposal does, which is the accounting that makes methods with different internal structure comparable. Published cluster success rates are quoted per hopping step, with the relaxation inside each step uncounted.

best: f64

Lowest objective value seen.

best_state: Option<Array1<f64>>

State attaining Ledger::best.

Implementations

impl Ledger

Functions

fn budget(&self) -> usize

Charged evaluations the ledger was created with.

fn charge(&mut self) -> bool

Charges one unit, returning false when the budget is gone.

fn charge_frac(&mut self, frac: f64) -> bool

Charges a fraction of one evaluation, for work that touches a subset of the system.

A k-atom partial evaluation computes k of the n(n-1)/2 pair rows, so its honest price is a fraction of a full evaluation. The fraction accumulates as exact debt and is settled into whole charged units as it crosses one: deterministic, auditable, and never cheaper than the work done because the residue is still owed when the run ends.

fn charge_many(&mut self, n: usize) -> bool

Charges n units at once, returning false when the budget ran out partway.

For a caller that ran work against a sub-ledger and is settling up. The alternative, handing the real ledger to the inner run, makes any budget arithmetic inside it see the whole campaign’s budget rather than the slice it was given.

fn new(budget: usize) -> Self

Creates a ledger with budget charged evaluations.

fn record(&mut self, value: f64, state: ArrayView1<f64>)

Records a value and its state when it improves the incumbent.

fn remaining(&self) -> usize

Charged evaluations remaining.

fn spent(&self) -> usize

Charged evaluations spent.

struct Outcome

What a run produced.

best: f64

Lowest quenched value found.

best_state: Option<Array1<f64>>

State attaining it.

final_state: Option<Array1<f64>>

Live chain at the end of the run, which a later hop can continue.

hops: usize

Hops taken.

screened_out: usize

Trials rejected by screening before a full relaxation.

basins: usize

Distinct basins registered.

charged: usize

Charged evaluations spent.

returned: usize

Trials abandoned because their partial relaxation was going home.

escape_scale: f64

Escape scale at the end of the run, when the controller is used.

escape_threshold: f64

Acceptance threshold at the end of the run.

visit_counts: (usize, usize, usize)

Quenches classified as a return, a known basin and a new one.

soft_perturbs: usize

Soft-subspace perturbations proposed.

soft_subspaces: usize

Soft-subspace recomputations paid for.

soft_escapes: usize

Proposals made along the softest mode.

soft_crossed: usize

Of those, the ones whose climb reached a saddle.

improvements: Vec<(usize, usize, usize, f64)>

Hop, charged evaluations spent, basin count and value at each new global best.

This is what a first-encounter time is computed from, and it is the statistic worth reporting. A success rate at a fixed budget is the same quantity through an arbitrary threshold: above the budget it saturates and says nothing about the margin, below it censors and says nothing about how near the failures came. The work to first reach a target is a property of the method rather than of a budget someone chose, which is why the literature quotes mean first encounter times.

The charged count is the part that makes it comparable. Hops are not: two arms with different screening spend different amounts per hop, and this campaign has arms ranging from 26 to 637 charged evaluations per hop.

Capped on the number of records rather than on the hops: a run that improves ten thousand times is descending, and the tail of that is not what anyone is asking about.

merge_radius: f64

Merge radius at the end of the run, calibrated or as configured.

mean_step: f64

Mean accepted-hop step length, which the radius is a quantile of.

angular: (usize, usize, f64)

Angular moves attempted, and the ratio they settled at.

contextual: (Vec<usize>, usize)

Picks per move under the contextual allocator, and choices it forced.

screen: (usize, usize, usize, usize)

Screen decisions: made, relaxed, forced by the exploration floor, and observations the model was fitted on.

tabu: (usize, usize)

Funnels quarantined, and proposals refused for landing in one.

funnel: Option<(usize, usize, f64)>

The funnel partition at the end of the run: parts, and how separated.

A connectivity near zero means the search’s transitions split into two nearly disconnected sets, which is what a funnel boundary looks like from the inside.

symmetrised: (usize, f64)

Symmetrisations attempted, and the energy they gained.

restarts: usize

Restarts triggered by a stall.

stall_escapes: usize

Climbs triggered by a stall.

stall_escape_gain: f64

Energy gained by those that landed lower than where they left.

soft_lambda: f64

Mean softest eigenvalue over those proposals.

rungs: Vec<(f64, usize, f64)>

Per-rung temperature, basin count and best energy.

What says whether a ladder is doing its job rather than merely swapping: a hot rung should register many basins and a poor energy, a cold rung few basins and a deep one. A ladder where every rung looks alike is a ladder whose spread is too narrow to be worth its cost.

swaps_tried: usize

Swap attempts between adjacent replicas.

accepted: usize

Hops the acceptance rule took, before any veto.

unconverged_records: usize

Structures barred from the ledger for not being minima.

arms: Vec<(String, usize, usize, f64)>

Per-arm draws, accepts and best quenched value, in library order.

delayed: Option<(usize, usize, usize, usize)>

Delayed acceptance: first stages run, first-stage rejections (each a quench not paid), second stages run, and second-stage rejections (the surrogate’s mistakes).

swaps_accepted: usize

Swaps accepted.

paths: usize

Paths attempted after a stall.

path_escapes: usize

Paths that produced a structure outside the starting basin.

Nearly always all of them, and so not worth much on its own: an image interpolated towards a different structure differs from the start by construction. The useful count is path_improvements.

path_improvements: usize

Paths that produced a structure lower than the chain was standing on.

path_gain: f64

Total depth gained from paths, in energy units.