use std::collections::{BinaryHeap, HashMap};
use num_traits::{NumCast, One, ToPrimitive, Zero, real::Real as _};
use super::{Euclidean, Point, Real, TangentBundle};
pub trait GroupPresentation: std::fmt::Debug {
type Word: IntoIterator<Item = (usize, bool), IntoIter: ExactSizeIterator>
+ Clone
+ std::fmt::Debug;
type Relations<'a>: IntoIterator<Item = &'a Self::Word, IntoIter: ExactSizeIterator>
+ std::fmt::Debug
where
<Self as GroupPresentation>::Word: 'a,
Self: 'a;
fn n_generators(&self) -> usize;
fn relations(&self) -> Self::Relations<'_>;
fn check_exactly_equal(&self, other: &impl GroupPresentation) -> bool {
if self.n_generators() != other.n_generators() {
return false;
}
let self_iter = self.relations().into_iter();
let other_iter = other.relations().into_iter();
if self_iter.len() != other_iter.len() {
return false;
}
self_iter.zip(other_iter).all(|(a, b)| {
let a_iter = a.clone().into_iter();
let b_iter = b.clone().into_iter();
a_iter.len() == b_iter.len() && a_iter.zip(b_iter).all(|(x, y)| x == y)
})
}
}
pub struct NerveTopology {
pub n_generators: usize,
pub edge_gen: HashMap<(usize, usize), (usize, bool)>,
pub relations: Vec<Vec<(usize, bool)>>,
pub abel: Abelianisation,
}
#[derive(Debug, Clone)]
pub struct Abelianisation {
live: Vec<usize>,
live_invariants: Vec<i64>,
gen_images: Vec<Vec<i64>>,
invariants: Vec<i64>,
}
impl Abelianisation {
pub fn from_relations(n: usize, relations: Vec<Vec<i64>>) -> Self {
let m = relations.len();
let mut mat = vec![vec![0i64; m]; n];
for (j, col) in relations.iter().enumerate() {
for (i, row) in mat.iter_mut().enumerate().take(n) {
row[j] = col[i];
}
}
let mut row_transform: Vec<Vec<i64>> = (0..n)
.map(|i| (0..n).map(|j| <i64 as From<_>>::from(i == j)).collect())
.collect();
diagonalise(&mut mat, &mut row_transform);
let mut invariants: Vec<i64> = (0..n)
.map(|i| if i < m { mat[i][i].abs() } else { 0 })
.collect();
make_divisibility_chain(&mut invariants);
let live: Vec<usize> = (0..n).filter(|&i| invariants[i] != 1).collect();
let live_invariants: Vec<i64> = live.iter().map(|&i| invariants[i]).collect();
let gen_images: Vec<Vec<i64>> = (0..n)
.map(|g| {
let mut w: Vec<i64> = live.iter().map(|&i| row_transform[i][g]).collect();
for (wi, &d) in w.iter_mut().zip(&live_invariants) {
if d != 0 {
*wi = wi.rem_euclid(d);
}
}
w
})
.collect();
Self {
live,
live_invariants,
gen_images,
invariants,
}
}
pub fn identity(&self) -> Vec<i64> {
vec![0; self.live.len()]
}
pub fn extend(&self, key: &[i64], edge: Option<(usize, bool)>) -> Vec<i64> {
let mut out = key.to_vec();
let Some((idx, inverted)) = edge else {
return out;
};
for ((o, g), &d) in out
.iter_mut()
.zip(&self.gen_images[idx])
.zip(&self.live_invariants)
{
*o += if inverted { -g } else { *g };
if d != 0 {
*o = o.rem_euclid(d);
}
}
out
}
pub fn free_rank(&self) -> usize {
self.invariants.iter().filter(|&&d| d == 0).count()
}
pub fn torsion(&self) -> Vec<i64> {
self.invariants.iter().copied().filter(|&d| d > 1).collect()
}
pub fn is_finite(&self) -> bool {
self.free_rank() == 0
}
}
fn diagonalise(mat: &mut [Vec<i64>], row_transform: &mut [Vec<i64>]) {
let n = mat.len();
if n == 0 {
return;
}
let m = mat[0].len();
for t in 0..n.min(m) {
loop {
let Some((pi, pj)) = (t..n)
.flat_map(|i| (t..m).map(move |j| (i, j)))
.filter(|&(i, j)| mat[i][j] != 0)
.min_by_key(|&(i, j)| mat[i][j].abs())
else {
return; };
mat.swap(t, pi);
row_transform.swap(t, pi);
for row in mat.iter_mut() {
row.swap(t, pj);
}
let pivot = mat[t][t];
let mut dirty = false;
for i in (t + 1)..n {
let q = mat[i][t] / pivot;
if q != 0 {
for j in t..m {
mat[i][j] -= q * mat[t][j];
}
for j in 0..n {
row_transform[i][j] -= q * row_transform[t][j];
}
}
if mat[i][t] != 0 {
dirty = true; }
}
for j in (t + 1)..m {
let q = mat[t][j] / pivot;
if q != 0 {
for row in mat.iter_mut().take(n).skip(t) {
row[j] -= q * row[t];
}
}
if mat[t][j] != 0 {
dirty = true;
}
}
if !dirty {
break;
}
}
}
}
fn make_divisibility_chain(inv: &mut [i64]) {
let r = inv.iter().filter(|&&d| d != 0).count();
let nonzero = &mut inv[..r];
let mut changed = true;
while changed {
changed = false;
for i in 0..r.saturating_sub(1) {
let (a, b) = (nonzero[i], nonzero[i + 1]);
if b % a != 0 {
let g = gcd(a, b);
nonzero[i] = g;
nonzero[i + 1] = a / g * b; changed = true;
}
}
}
}
fn gcd(a: i64, b: i64) -> i64 {
let (mut a, mut b) = (a.abs(), b.abs());
while b != 0 {
(a, b) = (b, a % b);
}
a
}
#[derive(Debug, Clone, Copy)]
pub struct StaticWord(pub &'static [(usize, bool)]);
impl IntoIterator for StaticWord {
type Item = (usize, bool);
type IntoIter = std::iter::Copied<std::slice::Iter<'static, (usize, bool)>>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter().copied()
}
}
#[derive(Debug)]
pub struct StaticGroupPresentation {
pub n_generators: usize,
pub flat_words: &'static [(usize, bool)],
pub words: &'static [StaticWord],
}
impl StaticGroupPresentation {
pub const fn n_relations(&self) -> usize {
self.words.len()
}
}
impl GroupPresentation for StaticGroupPresentation {
type Word = StaticWord;
type Relations<'a> = &'a [StaticWord];
fn n_generators(&self) -> usize {
self.n_generators
}
fn relations(&self) -> Self::Relations<'_> {
self.words
}
}
#[macro_export]
macro_rules! group_presentation {
(
$vis:vis $name:ident,
n_generators = $n:expr,
relations = [ $( [ $( ($g:expr, $inv:expr) ),* $(,)? ] ),* $(,)? ]
) => {
$vis static $name: $crate::traits::StaticGroupPresentation = {
const FLAT: &[(usize, bool)] = &[
$( $( ($g, $inv) ),* ),*
];
const LENS: &[usize] = &[ $( [ $( ($g, $inv) ),* ].len() ),* ];
const fn offsets() -> [usize; LENS.len() + 1] {
let mut out = [0usize; LENS.len() + 1];
let mut i = 0;
while i < LENS.len() {
out[i + 1] = out[i] + LENS[i];
i += 1;
}
out
}
const OFFSETS: [usize; LENS.len() + 1] = offsets();
const fn build_words() -> [$crate::traits::StaticWord; LENS.len()] {
let mut out = [$crate::traits::StaticWord(&[]); LENS.len()];
let mut i = 0;
while i < LENS.len() {
let start = OFFSETS[i];
let end = OFFSETS[i + 1];
let (_, rest) = FLAT.split_at(start);
let (word, _) = rest.split_at(end - start);
out[i] = $crate::traits::StaticWord(word);
i += 1;
}
out
}
static WORDS: [$crate::traits::StaticWord; LENS.len()] = build_words();
$crate::traits::StaticGroupPresentation {
n_generators: $n,
flat_words: FLAT,
words: &WORDS,
}
};
};
}
mod nodes_cache {
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::{Mutex, OnceLock};
fn registry() -> &'static Mutex<HashMap<(TypeId, TypeId), &'static (dyn Any + Send + Sync)>> {
type Registry =
OnceLock<Mutex<HashMap<(TypeId, TypeId), &'static (dyn Any + Send + Sync)>>>;
static REGISTRY: Registry = OnceLock::new();
REGISTRY.get_or_init(|| Mutex::new(HashMap::new()))
}
fn slot_for<Caller: 'static + ?Sized, T: 'static + Send + Sync>()
-> &'static OnceLock<&'static T> {
let key = (TypeId::of::<Caller>(), TypeId::of::<T>());
let mut map = registry().lock().unwrap();
let entry = map.entry(key).or_insert_with(|| {
let slot: &'static OnceLock<&'static T> = Box::leak(Box::new(OnceLock::new()));
slot as &'static (dyn Any + Send + Sync)
});
entry
.downcast_ref::<OnceLock<&'static T>>()
.expect("TypeId collision — should be impossible")
}
pub fn get_or_build<Caller: 'static + ?Sized, T: 'static + Send + Sync>(
build: impl FnOnce() -> T,
) -> &'static T {
slot_for::<Caller, T>().get_or_init(|| Box::leak(Box::new(build())))
}
pub fn get_or_build_slice<Caller: 'static + ?Sized, B: 'static + Send + Sync>(
build: impl FnOnce() -> Vec<B>,
) -> &'static [B] {
get_or_build::<Caller, Box<[B]>>(|| build().into_boxed_slice())
}
}
pub trait BuildNodes<B: 'static + Send + Sync> {
fn build_nodes() -> Vec<B>;
}
pub trait Nodes<B: 'static + Send + Sync>: BuildNodes<B> + 'static {
fn nodes() -> &'static [B] {
nodes_cache::get_or_build_slice::<Self, B>(Self::build_nodes)
}
}
impl<B: 'static + Send + Sync, T: BuildNodes<B> + 'static + Send + Sync> Nodes<B> for T {}
#[inline]
fn scalar<F: Real>(x: usize) -> F {
<F as NumCast>::from(x).expect("usize is representable in the scalar field")
}
#[inline]
fn half<F: Real>() -> F {
(F::one() + F::one()).recip()
}
#[derive(Debug, Clone)]
pub struct Basin<P, F> {
pub path: Vec<P>,
pub length: F,
pub witness: Vec<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GeodesicCertificate<F> {
pub bound_asserted: bool,
pub search_exhaustive: bool,
pub straightening_result: StraighteningResult<F>,
}
impl<F> GeodesicCertificate<F> {
pub fn is_global(&self) -> bool {
self.bound_asserted
&& self.search_exhaustive
&& matches!(self.straightening_result, StraighteningResult::Success)
}
}
#[derive(Debug, Clone)]
pub struct Geodesic<P, F> {
pub path: Option<Vec<P>>,
pub length: F,
pub certificate: GeodesicCertificate<F>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum StraighteningResult<F> {
Success,
Stalled(usize),
ArithmeticFloor(F),
NotConverged,
NotConnected,
MaxRescues,
}
#[derive(Debug, Clone)]
pub struct ArcPoly<P, F> {
pub pts: Vec<P>,
pub cum: Vec<F>,
}
impl<P, F: Real> ArcPoly<P, F> {
pub fn total(&self) -> F {
self.cum.last().copied().unwrap_or_else(F::zero)
}
}
fn free_reduce(word: Vec<(usize, bool)>) -> Vec<(usize, bool)> {
let mut reduced: Vec<(usize, bool)> = Vec::new();
for letter in word {
if let Some(&last) = reduced.last()
&& last == (letter.0, !letter.1)
{
reduced.pop();
continue;
}
reduced.push(letter);
}
reduced
}
pub trait NerveComplexParameters<
P: Point,
V: Euclidean<F: 'static + Send + Sync>,
T: TangentBundle<P, V> + Point,
B: Bounded<T, P, V> + 'static + Send + Sync,
>: Nodes<B>
{
fn overestimation_bound() -> Option<(V::F, V::F)> {
None
}
fn max_candidate_paths() -> usize {
512
}
fn max_frontier() -> usize {
1 << 20
}
fn max_rescues() -> usize {
64
}
fn max_straightening_iterations(n: usize) -> usize {
(64 + 4 * n.saturating_mul(n)).min(100_000)
}
fn max_samples() -> usize {
64
}
fn max_canonical_generators() -> usize {
7
}
fn prefix_smoothing_sweeps() -> usize {
2
}
fn max_basins_per_class() -> usize {
8
}
fn get_neighbors(i: usize) -> impl Iterator<Item = usize> {
let nodes = Self::nodes();
let inode = &nodes[i];
let ibase = inode.base_point();
nodes.iter().enumerate().filter_map(move |(j, jnode)| {
if j == i {
return None;
}
let half = (V::F::one() + V::F::one()).recip();
match (
inode.as_ref().to_local(&jnode.base_point()),
jnode.as_ref().to_local(&ibase),
) {
(Some(v_ij), Some(v_ji))
if inode.sdf(&(v_ij * half)) < V::F::zero()
&& jnode.sdf(&(v_ji * half)) < V::F::zero() =>
{
Some(j)
}
_ => None,
}
})
}
}
impl<
P: Point,
V: Euclidean<F: 'static + Send + Sync>,
T: TangentBundle<P, V> + Point,
B: Bounded<T, P, V> + 'static + Send + Sync,
C: NerveComplexParameters<P, V, T, B>,
> NerveComplex<P, V, T, B> for C
{
}
pub trait NerveComplex<
P: Point,
V: Euclidean<F: 'static + Send + Sync>,
T: TangentBundle<P, V> + Point,
B: Bounded<T, P, V> + 'static + Send + Sync,
>: NerveComplexParameters<P, V, T, B>
{
fn topology() -> &'static NerveTopology {
nodes_cache::get_or_build::<Self, NerveTopology>(Self::build_topology)
}
fn build_topology() -> NerveTopology {
let n = Self::nodes().len();
let neighbors: Vec<Vec<usize>> = (0..n).map(|i| Self::get_neighbors(i).collect()).collect();
let mut parent: Vec<Option<usize>> = vec![None; n];
let mut seen = vec![false; n];
let mut queue = std::collections::VecDeque::from([0usize]);
seen[0] = true;
while let Some(u) = queue.pop_front() {
for &v in &neighbors[u] {
if !seen[v] {
seen[v] = true;
parent[v] = Some(u);
queue.push_back(v);
}
}
}
debug_assert!(
seen.iter().all(|&s| s),
"nerve is disconnected: `fundamental_group` would report π₁ of \
component zero, silently"
);
let mut edge_gen: HashMap<(usize, usize), (usize, bool)> = HashMap::new();
let mut n_generators = 0usize;
for i in 0..n {
for &j in &neighbors[i] {
if i < j && parent[j] != Some(i) && parent[i] != Some(j) {
edge_gen.insert((i, j), (n_generators, false));
edge_gen.insert((j, i), (n_generators, true));
n_generators += 1;
}
}
}
let mut relations: Vec<Vec<(usize, bool)>> = Vec::new();
let mut columns: Vec<Vec<i64>> = Vec::new();
for i in 0..n {
for &j in &neighbors[i] {
for &k in &neighbors[j] {
if !(i < j && j < k && neighbors[i].contains(&k)) {
continue;
}
let word: Vec<(usize, bool)> = [(i, j), (j, k), (k, i)]
.iter()
.filter_map(|e| edge_gen.get(e).copied())
.collect();
let word = free_reduce(word);
if word.is_empty() {
continue;
}
let mut col = vec![0i64; n_generators];
for &(g, inverted) in &word {
col[g] += if inverted { -1 } else { 1 };
}
if col.iter().any(|&x| x != 0) {
columns.push(col);
}
relations.push(word);
}
}
}
let abel = Abelianisation::from_relations(n_generators, columns);
NerveTopology {
n_generators,
edge_gen,
relations,
abel,
}
}
fn adjacency() -> &'static Vec<Vec<(usize, V::F)>> {
nodes_cache::get_or_build::<Self, Vec<Vec<(usize, V::F)>>>(Self::build_adjacency)
}
fn build_adjacency() -> Vec<Vec<(usize, V::F)>> {
let n = Self::nodes().len();
let mut adj: Vec<Vec<(usize, V::F)>> = vec![Vec::new(); n];
for i in 0..n {
for j in Self::get_neighbors(i) {
let Some(w) = Self::edge_weight(i, j) else {
panic!("adjacent nodes cannot see each other: 2ρ exceeds injectivity radius");
};
if !adj[i].iter().any(|&(k, _)| k == j) {
adj[i].push((j, w));
}
if !adj[j].iter().any(|&(k, _)| k == i) {
adj[j].push((i, w));
}
}
}
adj
}
fn homology() -> (
&'static Abelianisation,
&'static HashMap<(usize, usize), (usize, bool)>,
) {
let t = Self::topology();
(&t.abel, &t.edge_gen)
}
fn fundamental_group() -> impl GroupPresentation {
let topology = Self::topology();
let n_generators = topology.n_generators;
fn invert(w: &[(usize, bool)]) -> Vec<(usize, bool)> {
w.iter().rev().map(|&(g, inv)| (g, !inv)).collect()
}
fn cyclic_reduce(mut w: Vec<(usize, bool)>) -> Vec<(usize, bool)> {
w = free_reduce(w);
while w.len() >= 2 {
let (f, l) = (w[0], w[w.len() - 1]);
if f.0 == l.0 && f.1 != l.1 {
w.remove(0);
w.pop();
w = free_reduce(w);
} else {
break;
}
}
w
}
fn canonical_relator(w: &[(usize, bool)]) -> Vec<(usize, bool)> {
let mut best: Option<Vec<(usize, bool)>> = None;
for cand in [w.to_vec(), invert(w)] {
for r in 0..cand.len().max(1) {
let mut rot = cand.clone();
rot.rotate_left(r % cand.len().max(1));
if best.as_ref().is_none_or(|b| rot < *b) {
best = Some(rot);
}
}
}
best.unwrap_or_default()
}
fn substitute(
w: &[(usize, bool)],
g: usize,
replacement: &[(usize, bool)],
) -> Vec<(usize, bool)> {
let inv_rep = invert(replacement);
let mut out = Vec::new();
for &(x, inv) in w {
if x == g {
out.extend(if inv {
inv_rep.clone()
} else {
replacement.to_vec()
});
} else {
out.push((x, inv));
}
}
free_reduce(out)
}
let mut alive: Vec<bool> = vec![true; n_generators];
let mut rels: Vec<Vec<(usize, bool)>> = topology
.relations
.iter()
.cloned()
.map(cyclic_reduce)
.filter(|w| !w.is_empty())
.collect();
loop {
let mut seen = std::collections::HashSet::new();
rels.retain(|w| seen.insert(canonical_relator(w)));
rels.sort_by_key(|w| w.len());
type Action = Option<(usize, Vec<(usize, bool)>, usize)>;
let mut action: Action = None;
'search: for (ri, r) in rels.iter().enumerate() {
let mut counts = std::collections::HashMap::new();
for &(g, _) in r {
*counts.entry(g).or_insert(0usize) += 1;
}
for (pos, &(g, inv)) in r.iter().enumerate() {
if counts[&g] == 1 {
let mut rest: Vec<(usize, bool)> = Vec::new();
rest.extend_from_slice(&r[pos + 1..]);
rest.extend_from_slice(&r[..pos]);
let repl = if inv {
free_reduce(rest)
} else {
invert(&rest)
};
action = Some((g, repl, ri));
break 'search;
}
}
}
match action {
Some((g, repl, ri)) => {
rels.remove(ri);
alive[g] = false;
rels = rels
.iter()
.map(|w| cyclic_reduce(substitute(w, g, &repl)))
.filter(|w| !w.is_empty())
.collect();
}
None => break,
}
}
let mut remap = std::collections::HashMap::new();
for (g, &a) in alive.iter().enumerate() {
if a {
let idx = remap.len();
remap.insert(g, idx);
}
}
let relations: Vec<Vec<(usize, bool)>> = rels
.iter()
.map(|w| {
let w: Vec<(usize, bool)> = w.iter().map(|&(g, i)| (remap[&g], i)).collect();
let inv_count = w.iter().filter(|&&(_, i)| i).count();
if inv_count * 2 > w.len() {
invert(&w)
} else {
w
}
})
.collect();
let n_generators = remap.len();
if n_generators > Self::max_canonical_generators() {
return FundamentalGroupPresentation {
n_generators,
relations,
};
}
fn canonicalize_presentation(
n_generators: usize,
relators: &[Vec<(usize, bool)>],
) -> (usize, Vec<Vec<(usize, bool)>>) {
let mut perm: Vec<usize> = (0..n_generators).collect();
let mut best: Option<Vec<Vec<(usize, bool)>>> = None;
loop {
for invert_mask in 0u32..(1 << n_generators) {
let relabel_one = |g: usize, inv: bool| -> (usize, bool) {
let new_g = perm[g];
let flip = (invert_mask >> g) & 1 == 1;
(new_g, inv ^ flip)
};
let mut relabeled: Vec<Vec<(usize, bool)>> = relators
.iter()
.map(|w| {
let relabeled_word: Vec<(usize, bool)> =
w.iter().map(|&(g, inv)| relabel_one(g, inv)).collect();
canonical_relator(&relabeled_word)
})
.collect();
relabeled.sort();
if best.as_ref().is_none_or(|b| relabeled < *b) {
best = Some(relabeled);
}
}
if !next_permutation(&mut perm) {
break;
}
}
(n_generators, best.unwrap_or_default())
}
fn next_permutation(perm: &mut [usize]) -> bool {
if perm.len() < 2 {
return false;
}
let mut i = perm.len() - 1;
while i > 0 && perm[i - 1] >= perm[i] {
i -= 1;
}
if i == 0 {
return false;
}
let mut j = perm.len() - 1;
while perm[j] <= perm[i - 1] {
j -= 1;
}
perm.swap(i - 1, j);
perm[i..].reverse();
true
}
let (n_generators, relations) = canonicalize_presentation(n_generators, &relations);
#[derive(Debug, PartialEq)]
struct FundamentalGroupPresentation {
n_generators: usize,
relations: Vec<Vec<(usize, bool)>>,
}
impl GroupPresentation for FundamentalGroupPresentation {
type Word = Vec<(usize, bool)>;
type Relations<'a> = &'a [Vec<(usize, bool)>];
fn n_generators(&self) -> usize {
self.n_generators
}
fn relations(&self) -> Self::Relations<'_> {
&self.relations
}
}
FundamentalGroupPresentation {
n_generators,
relations,
}
}
fn arc_poly(pts: Vec<P>) -> Option<ArcPoly<P, V::F>> {
let mut cum = Vec::with_capacity(pts.len());
cum.push(V::F::zero());
for w in pts.windows(2) {
let d = Self::hop(&w[0], &w[1])?;
cum.push(*cum.last().expect("nonempty") + d);
}
Some(ArcPoly { pts, cum })
}
fn sample(ap: &ArcPoly<P, V::F>, t: V::F) -> Option<P> {
let total = ap.total();
if !(total > V::F::zero()) {
return ap.pts.first().cloned();
}
let target = total * t;
let key = target;
let (mut lo, mut hi) = (0usize, ap.cum.len() - 1);
while hi - lo > 1 {
let mid = lo + (hi - lo) / 2;
if ap.cum[mid] <= key {
lo = mid;
} else {
hi = mid;
}
}
let seg = ap.cum[hi] - ap.cum[lo];
let s = if seg > V::F::zero() {
(target - ap.cum[lo]) / seg
} else {
V::F::zero()
};
let chart = T::chart_at(&ap.pts[lo]);
let v = chart.to_local(&ap.pts[hi])?;
Some(chart.to_global(v * s))
}
fn n_samples(total: V::F, rho: V::F) -> Option<usize> {
let n = (total / rho).ceil().to_usize()?;
(n <= Self::max_samples()).then(|| n.max(2))
}
fn base_point_of(i: usize) -> P {
Self::nodes()[i].base_point()
}
fn edge_weight(i: usize, j: usize) -> Option<V::F> {
let target = Self::base_point_of(j);
Self::nodes()[i]
.as_ref()
.to_local(&target)
.map(|v| v.norm())
}
fn dijkstra(adj: &[Vec<(usize, V::F)>], sources: &[(usize, V::F)]) -> Vec<Option<V::F>> {
let n = adj.len();
let mut dist: Vec<Option<V::F>> = vec![None; n];
let mut done = vec![false; n];
for &(s, d0) in sources {
if s < n && dist[s].is_none_or(|d| d0 < d) {
dist[s] = Some(d0);
}
}
for _ in 0..n {
let mut chosen: Option<(usize, V::F)> = None;
for v in 0..n {
if done[v] {
continue;
}
let Some(d) = dist[v] else { continue };
if chosen.is_none_or(|(_, best)| d < best) {
chosen = Some((v, d));
}
}
let Some((u, du)) = chosen else { break };
done[u] = true;
for &(w, wt) in &adj[u] {
let relaxed = du + wt;
if dist[w].is_none_or(|dw| relaxed < dw) {
dist[w] = Some(relaxed);
}
}
}
dist
}
fn hop(a: &P, b: &P) -> Option<V::F> {
let ca = T::chart_at(a);
if let Some(v) = ca.to_local(b) {
return Some(v.norm());
}
let cb = T::chart_at(b);
cb.to_local(a).map(|v| v.norm())
}
fn midpoint(a: &P, b: &P) -> Option<P> {
let ca = T::chart_at(a);
if let Some(v) = ca.to_local(b) {
return Some(ca.to_global(v * half()));
}
let cb = T::chart_at(b);
cb.to_local(a).map(|v| cb.to_global(v * half()))
}
fn polyline_length(pts: &[P]) -> Option<V::F> {
let mut total = V::F::zero();
for w in pts.windows(2) {
total = total + Self::hop(&w[0], &w[1])?;
}
Some(total)
}
fn relax(a: &P, b: &P, c: &P) -> Option<(P, V::F)> {
let chart = T::chart_at(b);
let va = chart.to_local(a)?;
let vc = chart.to_local(c)?;
let delta = (va + vc) * half();
Some((chart.to_global(delta), delta.norm()))
}
fn relax_sweep(pts: &mut [P]) -> Result<(V::F, V::F), StraighteningResult<V::F>> {
if pts.len() < 3 {
let len = Self::polyline_length(pts).ok_or(StraighteningResult::NotConnected)?;
return Ok((V::F::zero(), len));
}
let mut worst = V::F::zero();
let mut total = V::F::zero();
let last = pts.len() - 2;
for i in 1..pts.len() - 1 {
let (left, rest) = pts.split_at_mut(i);
let (mid, right) = rest.split_at_mut(1);
let chart = T::chart_at(&mid[0]);
let va = chart
.to_local(&left[i - 1])
.ok_or(StraighteningResult::Stalled(i))?;
let vc = chart
.to_local(&right[0])
.ok_or(StraighteningResult::Stalled(i))?;
total = total + va.norm();
if i == last {
total = total + vc.norm();
}
let delta = (va + vc) * half();
let kink = delta.norm();
mid[0] = chart.to_global(delta);
if kink > worst {
worst = kink;
}
}
Ok((worst, total))
}
fn rescue(mut pts: Vec<P>, i: usize) -> Vec<P> {
let mid_bc = Self::midpoint(&pts[i], &pts[i + 1])
.expect("polyline_length succeeded, so adjacent vertices are mutually visible");
let mid_ab = Self::midpoint(&pts[i - 1], &pts[i])
.expect("polyline_length succeeded, so adjacent vertices are mutually visible");
pts.insert(i + 1, mid_bc);
pts.insert(i, mid_ab);
pts
}
fn relax_to_convergence(pts: &mut Vec<P>) -> Result<V::F, StraighteningResult<V::F>> {
let eps = V::F::epsilon();
let iters = Self::max_straightening_iterations(pts.len());
let segments = scalar::<V::F>(pts.len().saturating_sub(1).max(1));
let mut prev = Self::polyline_length(pts).ok_or(StraighteningResult::NotConnected)?;
let length =
|pts| Self::polyline_length(pts).ok_or(StraighteningResult::<V::F>::NotConnected);
let mut converged = false;
for _ in 0..iters {
let (kink, len) = Self::relax_sweep(pts)?;
let h = len / segments;
if kink < eps.sqrt() * h {
converged = true;
break;
}
if !(eps * len * scalar(16) < (prev - len).abs()) {
return Err(match length(pts) {
Ok(x) => StraighteningResult::ArithmeticFloor(x),
Err(e) => e,
});
}
prev = len;
}
if !converged {
return Err(StraighteningResult::NotConverged);
}
length(pts)
}
fn straighten(mut pts: Vec<P>) -> Result<(Vec<P>, V::F), StraighteningResult<V::F>> {
let mut rescues = 0;
let length = loop {
match Self::relax_to_convergence(&mut pts) {
Ok(len) => break len,
Err(StraighteningResult::Stalled(x)) => {
rescues += 1;
if rescues > Self::max_rescues() {
return Err(StraighteningResult::MaxRescues);
}
pts = Self::rescue(pts, x);
}
Err(e) => return Err(e),
}
};
Ok((pts, length))
}
fn locate_all(p: &P) -> Vec<(usize, V::F)> {
Self::nodes()
.iter()
.enumerate()
.filter_map(|(i, node)| {
let v = node.as_ref().to_local(p)?;
(node.sdf(&v) < V::F::zero()).then(|| (i, v.norm()))
})
.collect()
}
fn covering_radius() -> Option<V::F> {
let (kappa, c) = Self::overestimation_bound()?;
Some(c / (scalar::<V::F>(2) * (V::F::one() + kappa)))
}
fn smooth(mut pts: Vec<P>, sweeps: usize) -> Option<Vec<P>> {
let mut rescues = 0usize;
let mut done = 0usize;
while done < sweeps {
match Self::relax_sweep(&mut pts) {
Ok(_) => done += 1,
Err(StraighteningResult::Stalled(i)) => {
rescues += 1;
if rescues > Self::max_rescues() {
return None;
}
pts = Self::rescue(pts, i);
}
Err(_) => return None,
}
}
Some(pts)
}
fn smoothed_prefix(p: &P, nodes: &[usize]) -> Option<ArcPoly<P, V::F>> {
let pts: Vec<P> = std::iter::once(p.clone())
.chain(nodes.iter().map(|&k| Self::base_point_of(k)))
.collect();
let pts = Self::smooth(pts, Self::prefix_smoothing_sweeps())?;
Self::arc_poly(pts)
}
fn same_basin(a: &ArcPoly<P, V::F>, b: &ArcPoly<P, V::F>, rho: V::F) -> Option<bool> {
let longer = if a.total() > b.total() {
a.total()
} else {
b.total()
};
let n = Self::n_samples(longer, rho)?;
for k in 1..n {
let t = scalar::<V::F>(k) / scalar::<V::F>(n);
let d = Self::hop(&Self::sample(a, t)?, &Self::sample(b, t)?)?;
if !(d < rho) {
return Some(false);
}
}
Some(true)
}
fn provably_same_basin(a: &ArcPoly<P, V::F>, b: &ArcPoly<P, V::F>, rho: V::F) -> bool {
Self::same_basin(a, b, rho).unwrap_or(false)
}
fn basins(
p: &P,
q: &P,
) -> Option<(
Result<Basin<P, V::F>, V::F>,
bool,
StraighteningResult<V::F>,
)> {
#[derive(Clone, Copy)]
struct Entry<F: Real> {
f: F,
acc: F,
tip: u32,
key: u32,
complete: bool,
}
impl<F: Real> PartialEq for Entry<F> {
fn eq(&self, o: &Self) -> bool {
self.cmp(o).is_eq()
}
}
impl<F: Real> Eq for Entry<F> {}
impl<F: Real> PartialOrd for Entry<F> {
fn partial_cmp(&self, o: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(o))
}
}
impl<F: Real> Ord for Entry<F> {
fn cmp(&self, o: &Self) -> std::cmp::Ordering {
o.f.partial_cmp(&self.f).unwrap() }
}
struct Arena(Vec<(u32, Option<u32>)>);
impl Arena {
fn push(&mut self, node: usize, parent: Option<u32>) -> u32 {
self.0.push((node as u32, parent));
(self.0.len() - 1) as u32
}
fn node(&self, tip: u32) -> usize {
self.0[tip as usize].0 as usize
}
fn contains(&self, mut tip: u32, v: usize) -> bool {
loop {
let (node, parent) = self.0[tip as usize];
if node as usize == v {
return true;
}
match parent {
Some(par) => tip = par,
None => return false,
}
}
}
fn path(&self, mut tip: u32) -> Vec<usize> {
let mut out = Vec::new();
loop {
let (node, parent) = self.0[tip as usize];
out.push(node as usize);
match parent {
Some(par) => tip = par,
None => break,
}
}
out.reverse();
out
}
}
struct Keys<'a> {
abel: &'a Abelianisation,
tab: Vec<Vec<i64>>,
ids: HashMap<Vec<i64>, u32>,
trans: HashMap<(u32, u32), u32>,
}
impl<'a> Keys<'a> {
fn new(abel: &'a Abelianisation) -> Self {
let id = abel.identity();
Self {
abel,
tab: vec![id.clone()],
ids: HashMap::from([(id, 0u32)]),
trans: HashMap::new(),
}
}
fn intern(&mut self, k: Vec<i64>) -> u32 {
if let Some(&id) = self.ids.get(&k) {
return id;
}
let id = self.tab.len() as u32;
self.tab.push(k.clone());
self.ids.insert(k, id);
id
}
fn step(&mut self, key: u32, edge: Option<(usize, bool)>) -> u32 {
let Some((g, inverted)) = edge else {
return key;
};
let tag = (g as u32) * 2 + <u32 as From<_>>::from(inverted);
if let Some(&next) = self.trans.get(&(key, tag)) {
return next;
}
let extended = self.abel.extend(&self.tab[key as usize], edge);
let next = self.intern(extended);
self.trans.insert((key, tag), next);
next
}
}
let sources = Self::locate_all(p);
let targets = Self::locate_all(q);
if sources.is_empty() || targets.is_empty() {
debug_assert!(
false,
"covering invariant violated: point outside every domain"
);
return None;
}
let adj = Self::adjacency();
let to_dst = Self::dijkstra(adj, &targets);
let mut leg_q: Vec<Option<V::F>> = vec![None; adj.len()];
for &(j, d) in &targets {
leg_q[j] = Some(d);
}
let graph_opt = sources
.iter()
.filter_map(|&(i, leg)| to_dst[i].map(|d| leg + d))
.reduce(|a, b| if b < a { b } else { a });
let Some(graph_opt) = graph_opt else {
debug_assert!(
T::chart_at(p).to_local(q).is_none(),
"nerve disconnects points joined by a geodesic"
);
return None;
};
let (kappa, c) = Self::overestimation_bound().unwrap_or((V::F::one(), V::F::zero()));
let rho = Self::covering_radius();
let fudge = V::F::one() + V::F::epsilon() * scalar(8);
let static_budget = graph_opt * kappa * fudge + c;
let (abel, edge_gen) = Self::homology();
let mut keys = Keys::new(abel);
let mut arena = Arena(Vec::new());
let mut visited: HashMap<(u32, u32), Vec<ArcPoly<P, V::F>>> = HashMap::new();
let mut completed: Vec<ArcPoly<P, V::F>> = Vec::new();
let mut best: Option<Result<Basin<P, V::F>, V::F>> = None;
let mut straighten_result = StraighteningResult::Success;
let mut straightened = 0usize;
let mut exhaustive = true;
if let Some(v) = T::chart_at(p).to_local(q) {
let length = v.norm();
best = Some(Ok(Basin {
path: vec![p.clone(), q.clone()],
length,
witness: Vec::new(),
}));
}
let mut heap: BinaryHeap<Entry<V::F>> = BinaryHeap::new();
for &(i, leg) in &sources {
let Some(h) = to_dst[i] else { continue };
let f = leg + h;
heap.push(Entry {
f,
acc: leg,
tip: arena.push(i, None),
key: 0,
complete: false,
});
}
'search: while let Some(Entry {
f,
acc,
tip,
key,
complete,
}) = heap.pop()
{
let ceiling = match best {
Some(ref b) => {
let b = match b {
Ok(b) => b.length,
Err(len) => *len,
};
let dynamic = b * kappa * fudge + c;
if dynamic < static_budget {
dynamic
} else {
static_budget
}
}
None => static_budget,
};
if ceiling < f {
break; }
let u = arena.node(tip);
let nodes = arena.path(tip);
if complete {
let raw: Vec<P> = std::iter::once(p.clone())
.chain(nodes.iter().map(|&k| Self::base_point_of(k)))
.chain(std::iter::once(q.clone()))
.collect();
let smoothed = Self::smooth(raw.clone(), Self::prefix_smoothing_sweeps())
.and_then(Self::arc_poly);
let seen = match (&smoothed, rho) {
(Some(ap), Some(r)) => completed
.iter()
.any(|old| Self::provably_same_basin(old, ap, r)),
_ => false, };
if seen {
continue;
}
let start = smoothed.as_ref().map_or(raw, |ap| ap.pts.clone());
let set_best = |best: &mut Option<Result<Basin<P, V::F>, V::F>>,
v: Result<Basin<P, V::F>, V::F>| {
let length = match v {
Ok(ref b) => b.length,
Err(len) => len,
};
match best {
Some(Ok(b)) => {
if length < b.length {
*best = Some(v)
}
}
Some(Err(blen)) => {
if length <= *blen {
*best = Some(v)
}
}
None => *best = Some(v),
}
};
match Self::straighten(start) {
Ok((pts, length)) => {
if let Some(ap) = smoothed {
completed.push(ap);
}
let basin = Basin {
path: pts,
length,
witness: nodes,
};
set_best(&mut best, Ok(basin));
}
Err(StraighteningResult::ArithmeticFloor(len)) => set_best(&mut best, Err(len)),
Err(e) => straighten_result = e,
}
straightened += 1;
if straightened >= Self::max_candidate_paths() {
exhaustive = false;
break;
}
continue;
}
if let Some(r) = rho {
let Some(ap) = Self::smoothed_prefix(p, &nodes) else {
continue; };
let slot = visited.entry((u as u32, key)).or_default();
if slot
.iter()
.any(|old| Self::provably_same_basin(old, &ap, r))
{
continue; }
if slot.len() < Self::max_basins_per_class() {
slot.push(ap);
}
}
if let Some(leg) = leg_q[u] {
let total = acc + leg;
heap.push(Entry {
f: total,
acc: total,
tip,
key,
complete: true,
});
}
for &(v, w) in &adj[u] {
if arena.contains(tip, v) {
continue; }
let Some(h_v) = to_dst[v] else { continue };
if heap.len() >= Self::max_frontier() {
exhaustive = false;
break 'search;
}
let acc2 = acc + w;
let f2 = acc2 + h_v;
if f2 > ceiling * (V::F::one() + V::F::epsilon() * scalar(64)) {
continue;
}
heap.push(Entry {
f: f2,
acc: acc2,
tip: arena.push(v, Some(tip)),
key: keys.step(key, edge_gen.get(&(u, v)).copied()),
complete: false,
});
}
}
if let Some(ref b) = best {
let b = match b {
Ok(b) => b.length,
Err(len) => *len,
};
debug_assert!(
!(graph_opt > kappa * b + c),
"overestimation bound violated: graph_opt {graph_opt:?} > κ·{b:?} + C"
);
}
match best {
Some(b) => Some((b, exhaustive, straighten_result)),
None => None,
}
}
fn geodesic_path(p: &P, q: &P) -> Option<Geodesic<P, V::F>> {
let (basin, exhaustive, straightening_result) = Self::basins(p, q)?;
let certificate = GeodesicCertificate {
bound_asserted: Self::overestimation_bound().is_some(),
search_exhaustive: exhaustive,
straightening_result,
};
let (path, length) = match basin {
Ok(b) => (Some(b.path), b.length),
Err(len) => (None, len),
};
Some(Geodesic {
path,
length,
certificate,
})
}
fn geodesic_distance(p: &P, q: &P) -> Option<V::F> {
Self::geodesic_path(p, q).and_then(|g| {
if g.certificate.is_global() {
Some(g.length)
} else {
None
}
})
}
fn geodesic_distance_uncertified(p: &P, q: &P) -> Option<V::F> {
Self::geodesic_path(p, q).map(|g| g.length)
}
}
pub trait Bounded<T: TangentBundle<P, V>, P: Point, V: Euclidean>:
TangentBundle<P, V> + From<T> + AsRef<T>
{
fn sdf(&self, v: &V) -> V::F;
}
#[macro_export]
macro_rules! impl_tangent_bundle_via_bounded {
($chart:ty, $ambient:ty, $manifold:ty, $v:ty, $($generics:tt)*) => {
impl<$($generics)*> Chart<$manifold, $v> for $chart {
fn to_local(&self, p: &$manifold) -> Option<$v> {
<$ambient as Chart<$manifold, $v>>::to_local(self.as_ref(), p)
.filter(|v| self.sdf(v) < <$v as $crate::traits::Vector>::F::zero())
}
fn to_global(&self, c: $v) -> $manifold {
<$ambient as Chart<$manifold, $v>>::to_global(self.as_ref(), c)
}
fn chart_at(p: &$manifold) -> Self {
Self::from(<$ambient as Chart<$manifold, $v>>::chart_at(p))
}
}
impl<$($generics)*> $crate::traits::ExpMap<$manifold, $v> for $chart {
fn base_point(&self) -> $manifold {
<$ambient as $crate::traits::ExpMap<$manifold, $v>>::base_point(
<$chart as AsRef<$ambient>>::as_ref(self)
)
}
}
impl<$($generics)*> $crate::traits::TangentBundle<$manifold, $v> for $chart {}
};
}