mod shape

module shape

pyo3 module entry point exposed as anneal._core. Shape matching through the IRA library, behind the ira feature.

crate::bias::BasinBias asks whether two states are the same basin, and crate::bias::SortedPairs answers with a descriptor whose threshold has to be found empirically and does not transfer between system sizes. For point sets there is an exact answer: the optimal permutation and rigid motion between them, and the Hausdorff distance under that match.

IRA computes it (Gunde, Salles, Hemeryck and Martin-Samos, Comput Phys Commun 280, 108431, 2022). Its threshold is a length rather than a tuned number in descriptor space, so the same value works whatever the system: measured on a 38-point Lennard-Jones cluster, a permuted and rotated copy returns 0.0, the same basin jittered by 0.02 returns 0.054, and a different basin returns 1.58.

The library is Fortran with a C-bound interface, linked here as libira. Build it and point IRA_LIB_DIR at the result, or leave the feature off.

Functions

fn axis_deviation(coords: ArrayView1<f64>, axis: [f64; 3], order: usize) -> Result<f64, ShapeError>

Smallest deviation over the rotations of an order-fold axis.

A structure is scored against a symmetry it need not possess, so the result is a distance from that symmetry rather than a test for it. Minimising over the non-trivial powers keeps the quantity a property of the axis rather than of which power happens to be tried.

fn match_shapes(a: ArrayView1<f64>, b: ArrayView1<f64>, kmax_factor: f64) -> Result<Match, ShapeError>

Optimal match between two flattened (n, 3) point sets.

kmax_factor bounds how far IRA searches for candidate frames; 1.8 is the value the library’s examples use for equal-sized structures.

fn rotation_matrix(axis: [f64; 3], angle: f64) -> [f64; 9]

Rotation by angle about a unit axis, row-major, for symmetry_deviation.

fn symmetry_deviation(coords: ArrayView1<f64>, matrix: &[f64; 9]) -> Result<f64, ShapeError>

Deviation of a structure from invariance under one operation.

matrix is a three by three orthogonal operation in row-major order. Returns the Hausdorff distance between the structure and its image, which is zero when the operation is an exact symmetry and grows smoothly with the departure from it.

The continuous quantity is the useful one. A point group is a discrete answer that reads C1 for every structure a search on this landscape visits, so a bias on it deposits on a constant; the deviation under a chosen operation separates structures that no group label distinguishes.

fn symmetry_pair(coords: ArrayView1<f64>, matrix: &[f64; 9]) -> Result<(f64, Vec<usize>), ShapeError>

Hausdorff deviation and the permutation realising the image.

perm[i] is the point that matrix sends onto i when the library returns a bijection. The residual x[i] R x[perm[i]] is the Cartesian leftover of that symmetry.

Enums

enum ShapeError

Error from the shape-matching library.

NonBijectivePermutation

The library returned success with a permutation that is not a bijection, so the correspondence it reports cannot be used.

Observed against libira at 3cb0c29: matching a twelve-point structure to a relabelled copy of itself returns cerr = 0, the correct distance of zero and the correct identity rotation, with the permutation [4,5,6,7,8,9,10,0,0,1,2,3], where index 0 is assigned twice and index 11 never. The distance is computed inside the library and is right; the correspondence is not.

Reported rather than repaired: a permutation with a duplicate has no unique completion, and guessing one would put points in each other’s places silently. Callers that only need the distance are unaffected.

SizeMismatch(usize, usize)

Structures hold different numbers of points, so no permutation exists.

Library(i32)

IRA reported a non-zero status.

NotThreeDimensional(usize)

A coordinate buffer whose length is not a multiple of three.

Empty

An empty structure, which has no symmetry to measure.

Traits implemented

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

Structs and Unions

struct CanonicalOrder

A canonical atom order and frame, taken against a fixed reference.

Keying basins on shape has been priced out by the comparison, not by the descriptor. A shape distance between two structures costs an IRA call, so a bias holding a few thousand basins pays one call per comparison and a run that does a hundred thousand hops cannot finish.

The permutation IRA already returns is what removes that cost, and it was being discarded. Matching each structure once against a single reference gives an ordering and a frame in which corresponding points occupy corresponding slots; after that, comparing two structures is Euclidean distance on the aligned coordinates. One call per hop rather than one per basin.

The idea is the one readcon uses for atom identity: the .con format groups atoms by element and so reorders them, and rather than pretending order does not matter it stores the pre-grouping index “so the original sequence can be reconstructed after any number of read/write cycles”. Carry the permutation; do not quotient it away.

What this buys over sorting

A sorted descriptor destroys correspondence: it says which values occur, not which point holds which. Two structures with the same multiset of site energies and a different arrangement are identical to it. Under a canonical order the arrangement survives, so the descriptor separates them, and the merge radius becomes a root-mean-square displacement, which is a length and transfers between sizes.

kmax_factor: f64

Search breadth passed to IRA.

Implementations

impl CanonicalOrder

Functions

fn canonicalise(&self, x: ArrayView1<f64>) -> Option<Array1<f64>>

x reordered and rigidly moved onto the reference frame.

None when IRA cannot match, which for equal-sized structures means the call failed rather than that no match exists; the caller should fall back to an order-free descriptor rather than treat it as a new basin.

fn n_points(&self) -> usize

Points the reference holds.

fn new(reference: Array1<f64>, kmax_factor: f64) -> Self

A canonicaliser against reference, which fixes the frame for the run.

Any structure of the right size will do; what matters is that it does not change, since the ordering is only canonical relative to it.

struct IraMetric

Distance between two states under optimal permutation and rigid motion.

kmax_factor: f64

Search breadth passed to IRA.

Implementations

impl IraMetric

Functions

fn distance(&self, a: ArrayView1<f64>, b: ArrayView1<f64>) -> f64

Distance, or f64::INFINITY when the structures cannot be matched.

Traits implemented

impl Default for IraMetric
struct IraPassthrough

Fingerprint that defers to shape matching.

Fingerprint returns a vector and crate::bias::BasinBias compares those by euclidean distance, which cannot express “optimal over all permutations”. The state is therefore passed through unchanged and IraMetric is used where a metric is wanted instead.

struct Match

Result of matching two point sets.

distance: f64

Hausdorff distance under the optimal permutation and rigid motion.

rotation: [f64; 9]

Row-major 3x3 rotation taking the second structure onto the first.

translation: [f64; 3]

Translation applied after the rotation.

permutation: Option<Vec<usize>>

Permutation carrying the second structure’s points onto the first’s, when the library returns a usable one.

None when what came back is not a bijection. The distance and the rigid motion are computed inside the library and stay trustworthy in that case; only the correspondence is lost, so a caller that needs the distance is unaffected and a caller that needs the ordering finds out rather than being handed a mapping that puts points in each other’s places.