mod funnel_bo

module funnel_bo

First-derivative interface for HMC-style samplers. Choosing which morphology to search next, by expected improvement.

The searches here fail by funnel, not by local optimization. At 75 points every failing run reaches the icosahedral plateau and stays; the relaxation is fine, the descent is fine, and the answer is in a region the chain never visits. Eight mechanisms built to make the chain leave a funnel were measured and none helped, because leaving is not the problem: the chain has nowhere better to be told to go.

That is a decision problem over a small space, and it is the shape Bayesian optimization is for. Not over the coordinates, where three hundred dimensions puts a Gaussian process out of reach, but over a structural descriptor: the share of points in each local environment, five numbers from crate::structure::ptm_fractions. A model of “how low does this morphology go” over those five numbers is cheap to fit and is exactly the surface the search is blind to.

What is modelled

For each distinct morphology the search has visited, the lowest energy found there. A Gaussian process over that gives a mean and a variance everywhere, including at morphologies never visited, and expected improvement turns the pair into a single number: how much better than the incumbent this morphology is likely to be, integrated over the model’s own uncertainty.

The point is what expected improvement does with a region that has never been sampled. Its mean reverts to the prior and its variance is large, so it scores highly; a region sampled repeatedly and found mediocre scores low however close it is to the incumbent. That is the opposite of what a bias does, and it is the missing half: a bias says where not to go, this says where to go instead.

Why a Gaussian process and not something larger

The observation count is the number of distinct morphologies, which is hundreds rather than millions, and the dimension is five. Exact inference is a Cholesky factorisation of a few-hundred-square matrix, done once per refit. Nothing here needs approximating.

Structs and Unions

struct FunnelModel

A Gaussian process over a structural descriptor.

Squared-exponential kernel with a single length scale, which is right when the inputs are fractions on a common scale, and a noise term that also keeps the factorisation well conditioned when two morphologies are nearly equal.

length_scale: f64

Kernel length scale, in units of the descriptor.

amplitude: f64

Signal standard deviation.

noise: f64

Observation noise standard deviation.

Implementations

impl FunnelModel

Functions

fn expected_improvement(&mut self, x: ArrayView1<f64>) -> f64

Expected improvement at a morphology, for a minimisation.

Zero where the model is confident nothing better lives, and large where the mean is low or the uncertainty is high. The second half is what makes this different from following the model’s mean: a morphology never visited scores on its variance alone, which is how a search reaches a funnel it has no evidence about.

fn incumbent(&self) -> Option<f64>

The best value seen, which is what improvement is measured against.

fn is_empty(&self) -> bool

Whether nothing has been observed.

fn len(&self) -> usize

Morphologies observed.

fn new(length_scale: f64, amplitude: f64, noise: f64) -> Self

A model over descriptors, with a length scale in descriptor units.

fn observe(&mut self, x: ArrayView1<f64>, y: f64)

Records the lowest energy found at a morphology.

A morphology already present is updated to the lower of the two rather than added again: the quantity modelled is how low a region goes, not how often it was sampled.

fn predict(&mut self, x: ArrayView1<f64>) -> (f64, f64)

Posterior mean and standard deviation at a morphology.