mod spectral

module spectral

Collective variables from the spectrum of the visited landscape.

Every bias in this crate acts on a quantity computed from one configuration: crate::bias::WellTemperedBias on a linear projection of it, crate::bias::BasinBias on a fingerprint of it. Both are functions of a single point, and on a multi-funnel landscape neither separates funnels reliably. Measured over forty quenched 75-point Lennard-Jones minima, a Steinhardt parameter spans four deposition widths and the best learned projection twenty-two, and a bias on either fills the target funnel and its competitor together.

The object that carries the separation is not a configuration. It is the set of minima with the transitions observed between them, and a funnel is a poorly connected component of that graph. Component structure is spectral: the eigenvectors of the graph Laplacian belonging to the smallest non-zero eigenvalues vary slowly inside a component and quickly between them, so the second one, the Fiedler vector, is a coordinate on which two funnels lie apart by construction rather than by luck of projection.

Two measurements make this the right object rather than a nice idea.

The shape distance between such minima is bimodal with an empty middle: a hop lands at either 0.000, having returned, or at a median 1.910, and two independent minima never come closer than 1.663. A distance matrix with that structure is block structured, which is where a spectral partition is exact and where a merge radius or a kernel width has nothing to trade off. Three radius calibrations and a shape-space kernel failed here for that reason.

From the structure a search settles into, none of 1800 single moves reaches anything lower, so escape is a sequence of accepted uphill hops rather than a gap one move crosses. A quantity defined on the hop graph describes such a sequence; a quantity defined on one structure cannot.

The eigenproblem is solved by cyclic Jacobi rather than by pulling in a LAPACK binding. The matrix is the visited-basin graph, orders of magnitude smaller than the relaxations that produced it, and Jacobi is accurate for small symmetric matrices and has no dependency.

Functions

fn laplacian_embedding(weights: ArrayView2<f64>, n_vectors: usize) -> Result<(Array1<f64>, Array2<f64>), SpectralError>

Eigenvectors of the normalised Laplacian, smallest non-trivial first.

Uses L = I - D^-1/2 W D^-1/2 and returns the random-walk eigenvectors, by applying the D^-1/2 scaling, so the result is a function on nodes rather than on the symmetrised operator.

The constant eigenvector belonging to eigenvalue zero is dropped: it says nothing about how the graph divides.

fn symmetric_eigen(a: ArrayView2<f64>, max_sweeps: usize) -> (Array1<f64>, Array2<f64>)

Eigenvalues and eigenvectors of a symmetric matrix, ascending.

Cyclic Jacobi: repeatedly annihilate the largest off-diagonal entry by a plane rotation. Converges for any real symmetric matrix and is accurate for small ones, which is what a visited-basin graph is.

Returns (values, vectors) with eigenvector k in column k.

Enums

enum SpectralError

Reasons an embedding cannot be produced.

TooFewNodes(usize)

Fewer nodes than a partition needs.

IsolatedNodes(usize)

Nodes with no edges, for which the normalised Laplacian is undefined.

Traits implemented

impl std::fmt::Display for SpectralError
impl std::error::Error for SpectralError

Structs and Unions

struct DiffusionDirection

The leading diffusion direction of an archive of minima, with a Nystrom extension for proposing from any structure.

The archive of visited minima is a point cloud on the landscape’s low-dimensional backbone. Its diffusion map (Coifman and Lafon, doi:10.1016/j.acha.2006.04.006) is the spectral embedding of the row-normalised kernel; the leading nontrivial eigenvector orders the archive along its principal connectivity direction, the same object the sketch-map literature draws for these landscapes, computed here by power iteration on the small dense kernel. The Nystrom extension evaluates that coordinate at a structure outside the archive, so a proposal can step along the backbone rather than isotropically. Rational spectral filtering over the sparse kernel replaces the dense pass when the archive outgrows it; at the archive sizes a charged run accumulates, dense is exact and cheaper.

Implementations

impl DiffusionDirection

Functions

fn coordinate(&self, x: &[f64]) -> f64

The diffusion coordinate of an arbitrary structure, by the Nystrom extension: the kernel-weighted average of the anchor coordinates.

fn fit(structures: &[Vec<f64>]) -> Option<Self>

Fits the leading diffusion coordinate of structures.

Returns None below four anchors, where a direction is not defined.

struct SpectralBias<F: Fingerprint>

Well-tempered bias deposited on the Fiedler coordinate of the hop graph.

Keyed through a Fingerprint like crate::bias::BasinBias, so it acts on the same exact basin identity that works on this landscape, and deposits on a continuous coordinate so that filling spreads over a funnel rather than over one basin at a time. That combination is the point: identity supplies the resolution, the spectrum supplies the generalisation, and neither a hand-picked order parameter nor a distance threshold is involved.

A basin with no spectral position yet, because it is newly seen or not yet connected, reads as the origin. That is a real position rather than a missing value: an unconnected basin is not known to belong to either side of any partition.

w0: f64

Deposited height per visit before well tempering.

gamma: f64

Well-tempered factor; must exceed one.

sigma: f64

Gaussian width on the spectral coordinate.

refit_every: usize

Hops between recomputations of the embedding.

min_nodes: usize

Connected nodes required before an embedding is attempted.

refits: usize

Embeddings computed.

last_error: Option<SpectralError>

Why the last attempt failed, if it did.

Implementations

impl<F: Fingerprint> SpectralBias<F>

Functions

fn coordinate(&self, basin: usize) -> Array1<f64>

Spectral position of a basin, or the origin when it has none.

fn n_edges(&self) -> usize

Hops recorded.

fn n_placed(&self) -> usize

Basins carrying a spectral position.

fn new(fingerprint: F, merge_radius: f64, w0: f64, gamma: f64, sigma: f64) -> Self

Bias keyed by fingerprint, merging basins within merge_radius.

fn refit(&mut self) -> bool

Recomputes the embedding. Returns whether it succeeded.

A failure is recorded rather than leaving stale coordinates silently in place, because a coordinate that stopped updating is a bias acting on a graph the run has moved past.

impl<F: Fingerprint> SpectralBias<F>

Functions

fn visit(&mut self, x: ArrayView1<f64>, temp: f64)

Records that the chain moved to the basin holding x, and deposits.

The graph is built from what the chain actually does, so this is the entry point a driver calls rather than Bias::deposit alone: the coordinate does not exist until the transitions that define it do.

struct TransitionGraph

Basins visited, and the transitions observed between them.

Implementations

impl TransitionGraph

Functions

fn adjacency(&self) -> (Vec<usize>, Array2<f64>)

Adjacency over the connected nodes, with the basin ids they belong to.

Nodes seen but never connected are dropped rather than kept with a zero degree, which would make the normalised Laplacian undefined.

fn is_empty(&self) -> bool

True when no node has been seen.

fn len(&self) -> usize

Nodes seen.

fn n_edges(&self) -> usize

Edges recorded.

fn record(&mut self, from: usize, to: usize, weight: f64)

Records a hop.

A hop that returns to the same basin marks the node as present but adds no edge: it says nothing about connectivity between components, and counting it would load the diagonal and flatten the spectrum. Given the measured return rate near a deep minimum, roughly 19 proposals in 20, including them would leave the graph describing little else.