Skip to main content

sheaf_coherence/
section.rs

1use crate::error::SheafError;
2use crate::laplacian::SheafLaplacian;
3use crate::sheaf::CellularSheaf;
4
5/// A (possibly approximate) global section of a cellular sheaf.
6///
7/// A global section assigns a vector to each stalk such that
8/// restriction maps are satisfied on every edge. Equivalently,
9/// it lies in the kernel of the sheaf Laplacian.
10#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
11pub struct GlobalSection {
12    /// Stalk values at each node.
13    pub values: Vec<Vec<f64>>,
14    /// True if this is an exact global section (residual ≈ 0).
15    pub is_exact: bool,
16    /// The residual ||L_F x||.
17    pub residual: f64,
18}
19
20impl GlobalSection {
21    /// Build a section from per-node values and check exactness.
22    pub fn new(sheaf: &CellularSheaf, values: Vec<Vec<f64>>, tol: f64) -> Result<Self, SheafError> {
23        sheaf.validate()?;
24        if values.len() != sheaf.node_count() {
25            return Err(SheafError::InvalidNode(values.len()));
26        }
27        for (i, v) in values.iter().enumerate() {
28            if v.len() != sheaf.stalk_dims[i] {
29                return Err(SheafError::BeliefDimensionMismatch {
30                    agent: format!("node_{i}"),
31                    expected: sheaf.stalk_dims[i],
32                    got: v.len(),
33                });
34            }
35        }
36
37        let lap = SheafLaplacian::from_sheaf(sheaf)?;
38        let flat = values.iter().flatten().copied().collect::<Vec<_>>();
39        let residual = lap.residual_norm(&flat);
40        let is_exact = residual < tol;
41
42        Ok(Self {
43            values,
44            is_exact,
45            residual,
46        })
47    }
48
49    /// Try to find a nontrivial global section by solving L_F x = 0
50    /// using inverse iteration toward the smallest eigenvalue.
51    pub fn find(sheaf: &CellularSheaf, max_iter: usize, tol: f64) -> Result<Self, SheafError> {
52        sheaf.validate()?;
53        let lap = SheafLaplacian::from_sheaf(sheaf)?;
54
55        let (eigenvalue, flat) = lap.smallest_eigenvalue(max_iter, tol);
56
57        // Split flat vector back into per-stalk values
58        let mut values = Vec::new();
59        let mut offset = 0;
60        for &d in &sheaf.stalk_dims {
61            values.push(flat[offset..offset + d].to_vec());
62            offset += d;
63        }
64
65        let residual = lap.residual_norm(&flat);
66        let is_exact = eigenvalue.abs() < tol;
67
68        Ok(Self {
69            values,
70            is_exact,
71            residual,
72        })
73    }
74
75    /// Flatten per-node values into a single vector.
76    pub fn flatten(&self) -> Vec<f64> {
77        self.values.iter().flatten().copied().collect()
78    }
79}