procrustes
Orthogonal Procrustes (Schönemann SVD) and brute-force signed-permutation alignment for Rust, built on faer.
Install
[]
= "0.1"
When to use
orthogonal— closed-form Schönemann SVD;O(M·K² + K³). Use whenever your alignment is a continuous rotation / reflection.signed_permutation— brute-forceO(K!·K)enumeration with optimal sign per permutation. Practical up to aboutK ≤ 10; forK ≳ 12decompose by hand. Use when columns are abstractly indexed (PLS components, ICA sources, eigenmaps) and you need a discrete match rather than a rotation.- TODO (planned, future minor version): Hungarian-algorithm
O(K³)fast path to lift theK ≤ 10ceiling. The cost matrixC[i, j] = -|⟨a[:, i], reference[:, j]⟩|is exactly a linear assignment problem after the closed-form sign reduction.
- TODO (planned, future minor version): Hungarian-algorithm
Convention
Both functions return a transform T such that a · T ≈ reference minimizes the Frobenius norm under their respective constraints. For orthogonal, T = R is a K×K orthogonal matrix. For signed_permutation, T = P · diag(signs) where P is the permutation encoded by assigned; equivalently, column k of a · T equals signs[k] · a[:, assigned[k]]. Matches SciPy's (A @ R) - B minimization convention in scipy.linalg.orthogonal_procrustes.
faer coupling
MatRef<'_, f64> and Mat<f64> appear in the public API. The crate re-exports them as procrustes::{Mat, MatRef}, so consumers do not need a separate faer dependency. The pin is faer = "0.24" — caret-equivalent within the minor series — so patch bumps unify with downstream ^0.24 users automatically. Until faer reaches 1.0, any faer minor bump (= breaking, pre-1.0) is a procrustes major bump: a Dependabot watcher proposes the upgrade in a PR, and the bit-parity test in tests/bit_parity.rs is the tripwire that flags faer-side numerical drift even when the API still compiles.
Example
Recover a known rotation from a rotated copy of a reference matrix:
use Mat;
let reference = from_fn;
// Apply a known 30° rotation in column space.
let theta = PI / 6.0;
let r0 = from_fn;
let a: = &reference * &r0;
// Recover R = R0ᵀ.
let aln = orthogonal.unwrap;
let residual = aln.residual_frobenius;
assert!;
For the discrete case, see signed_permutation in the API docs.
License
Dual-licensed under MIT OR Apache-2.0 at the user's option.
Paweł Lenartowicz — Freestyler Scientist · GitHub · ORCID