use std::{collections::HashMap, fmt::Debug, hash::Hash};
use harness_algebra::tensors::dynamic::{
block::BlockMatrix, matrix::RowMajor, vector::DynamicVector,
};
use super::*;
use crate::{
complexes::{Complex, ComplexElement},
definitions::Topology,
set::Poset,
};
pub struct Sheaf<T: Topology, C: Category> {
pub space: T,
pub restrictions: HashMap<(T::Item, T::Item), C::Morphism>,
}
impl<T, C> Sheaf<T, C>
where
T: Topology + Poset,
T::Item: Hash + Eq + Clone + Debug,
C: Category + Clone + PartialEq + Debug,
C::Morphism: Clone + Debug,
{
pub fn new(space: T, restrictions: HashMap<(T::Item, T::Item), C::Morphism>) -> Self {
assert!(
restrictions.iter().all(|(k, _v)| space.leq(&k.0, &k.1) == Some(true)),
"Restriction map defined for a pair (parent, child) where parent is not less than or equal \
to child, or they are incomparable."
);
Self { space, restrictions }
}
pub fn restrict(
&self,
parent_target_item: &T::Item,
child_source_item: &T::Item,
section_data_on_child: C,
) -> C {
assert!(
self.space.leq(parent_target_item, child_source_item) == Some(true),
"Cannot restrict: parent_target_item is not less than or equal to child_source_item, or \
items are incomparable."
);
let restriction_map = self
.restrictions
.get(&(parent_target_item.clone(), child_source_item.clone()))
.unwrap_or_else(|| {
panic!("Restriction map not found for ({parent_target_item:?}, {child_source_item:?})")
});
C::apply(restriction_map.clone(), section_data_on_child)
}
pub fn is_global_section(&self, section: &HashMap<T::Item, C>) -> bool {
for ((parent_item, child_item), restriction_map) in &self.restrictions {
let Some(parent_data) = section.get(parent_item) else {
return false;
};
let Some(child_data) = section.get(child_item) else {
return false;
};
let restricted_parent_data = C::apply(restriction_map.clone(), parent_data.clone());
if restricted_parent_data != *child_data {
return false;
}
}
true
}
}
use harness_algebra::tensors::dynamic::matrix::DynamicDenseMatrix;
impl<T: ComplexElement, F: Field + Copy> Sheaf<Complex<T>, DynamicVector<F>>
where T: Hash + Eq + Clone + Debug
{
pub fn coboundary(&self, dimension: usize) -> BlockMatrix<F, RowMajor> {
let k_elements = {
let mut elements = self.space.elements_of_dimension(dimension);
elements.sort_unstable();
elements
};
let k_plus_1_elements = {
let mut elements = self.space.elements_of_dimension(dimension + 1);
elements.sort_unstable();
elements
};
if k_elements.is_empty() || k_plus_1_elements.is_empty() {
return BlockMatrix::new(vec![], vec![]);
}
let mut col_block_sizes = Vec::new();
for k_element in &k_elements {
let stalk_dim = self
.restrictions
.iter()
.find_map(|((from, to), matrix)| {
if from.same_content(k_element) {
Some(matrix.num_cols()) } else if to.same_content(k_element) {
Some(matrix.num_rows()) } else {
None
}
})
.unwrap_or(1); col_block_sizes.push(stalk_dim);
}
let mut row_block_sizes = Vec::new();
for k_plus_1_element in &k_plus_1_elements {
let stalk_dim = self
.restrictions
.iter()
.find_map(|((from, to), matrix)| {
if from.same_content(k_plus_1_element) {
Some(matrix.num_cols()) } else if to.same_content(k_plus_1_element) {
Some(matrix.num_rows()) } else {
None
}
})
.unwrap_or(1); row_block_sizes.push(stalk_dim);
}
let mut block_matrix = BlockMatrix::new(row_block_sizes, col_block_sizes);
for (row_idx, k_plus_1_element) in k_plus_1_elements.iter().enumerate() {
for (col_idx, k_element) in k_elements.iter().enumerate() {
let boundary_with_orientations = k_plus_1_element.boundary_with_orientations();
if let Some((_, orientation_coeff)) =
boundary_with_orientations.iter().find(|(face, _)| face.same_content(k_element))
{
if let Some(restriction_matrix) =
self.restrictions.get(&(k_element.clone(), k_plus_1_element.clone()))
{
let signed_matrix = if *orientation_coeff > 0 {
restriction_matrix.clone()
} else if *orientation_coeff < 0 {
let mut negated = restriction_matrix.clone();
for i in 0..negated.num_rows() {
for j in 0..negated.num_cols() {
let val = *negated.get_component(i, j);
negated.set_component(i, j, -val);
}
}
negated
} else {
DynamicDenseMatrix::<F, RowMajor>::zeros(
block_matrix.row_block_sizes()[row_idx],
block_matrix.col_block_sizes()[col_idx],
)
};
block_matrix.set_block(row_idx, col_idx, signed_matrix);
}
}
}
}
block_matrix
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::float_cmp)]
use harness_algebra::tensors::dynamic::{
matrix::{DynamicDenseMatrix, RowMajor},
vector::DynamicVector,
};
use super::*;
use crate::complexes::{Cube, CubicalComplex, Simplex, SimplicialComplex};
fn simplicial_complex_1d() -> (
SimplicialComplex,
HashMap<(Simplex, Simplex), DynamicDenseMatrix<f64, RowMajor>>,
Simplex,
Simplex,
Simplex,
) {
let mut cc = SimplicialComplex::new();
let v0 = Simplex::new(0, vec![0]);
let v1 = Simplex::new(0, vec![1]);
let e01 = Simplex::new(1, vec![0, 1]);
let v0 = cc.join_element(v0);
let v1 = cc.join_element(v1);
let e01 = cc.join_element(e01);
let restrictions = HashMap::from([
((v0.clone(), e01.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![1.0, 2.0]));
mat
}),
((v1.clone(), e01.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![2.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 2.0]));
mat
}),
]);
(cc, restrictions, v0, v1, e01)
}
#[test]
fn test_simplicial_sheaf_global_section_1d() {
let (cc, restrictions, v1, v2, e1) = simplicial_complex_1d();
let sheaf = Sheaf::<SimplicialComplex, DynamicVector<f64>>::new(cc, restrictions);
let section = HashMap::from([
(v1.clone(), DynamicVector::<f64>::new(vec![2.0])), (v2.clone(), DynamicVector::<f64>::new(vec![1.0, 2.0])), (e1.clone(), DynamicVector::<f64>::new(vec![2.0, 4.0])), ]);
assert!(sheaf.is_global_section(§ion));
let section = HashMap::from([
(v1.clone(), DynamicVector::<f64>::new(vec![1.0])), (v2.clone(), DynamicVector::<f64>::new(vec![1.0, 2.0])), (e1.clone(), DynamicVector::<f64>::new(vec![2.0, 4.0])), ]);
assert!(!sheaf.is_global_section(§ion));
let section = HashMap::from([
(v1.clone(), DynamicVector::<f64>::new(vec![2.0])), (v2.clone(), DynamicVector::<f64>::new(vec![1.0, 2.0])), (e1.clone(), DynamicVector::<f64>::new(vec![1.0, 2.0])), ]);
assert!(!sheaf.is_global_section(§ion));
let section = HashMap::from([
(v1, DynamicVector::<f64>::new(vec![2.0])), (v2, DynamicVector::<f64>::new(vec![3.0, 3.0])), (e1, DynamicVector::<f64>::new(vec![2.0, 4.0])), ]);
assert!(!sheaf.is_global_section(§ion));
}
#[test]
fn test_simplicial_sheaf_coboundary_1d() {
let (cc, restrictions, ..) = simplicial_complex_1d();
let sheaf = Sheaf::<SimplicialComplex, DynamicVector<f64>>::new(cc, restrictions);
let coboundary = sheaf.coboundary(0);
assert_eq!(coboundary.block_structure(), (1, 2));
assert_eq!(coboundary.row_block_sizes(), &[2]); assert_eq!(coboundary.col_block_sizes(), &[1, 2]);
let block_00 = coboundary.get_block(0, 0).expect("Block (0,0) should exist");
assert_eq!(block_00.num_rows(), 2);
assert_eq!(block_00.num_cols(), 1);
assert_eq!(*block_00.get_component(0, 0), -1.0);
assert_eq!(*block_00.get_component(1, 0), -2.0);
let block_01 = coboundary.get_block(0, 1).expect("Block (0,1) should exist");
assert_eq!(block_01.num_rows(), 2);
assert_eq!(block_01.num_cols(), 2);
assert_eq!(*block_01.get_component(0, 0), 2.0);
assert_eq!(*block_01.get_component(0, 1), 0.0);
assert_eq!(*block_01.get_component(1, 0), 0.0);
assert_eq!(*block_01.get_component(1, 1), 2.0);
let flattened = coboundary.flatten();
assert_eq!(flattened.num_rows(), 2);
assert_eq!(flattened.num_cols(), 3);
assert_eq!(*flattened.get_component(0, 0), -1.0);
assert_eq!(*flattened.get_component(0, 1), 2.0);
assert_eq!(*flattened.get_component(0, 2), 0.0);
assert_eq!(*flattened.get_component(1, 0), -2.0);
assert_eq!(*flattened.get_component(1, 1), 0.0);
assert_eq!(*flattened.get_component(1, 2), 2.0);
println!("Coboundary matrix:");
println!("{coboundary}");
let coboundary = sheaf.coboundary(1);
println!("{coboundary}");
assert_eq!(coboundary.block_structure(), (0, 0));
}
fn simplicial_complex_2d() -> (
SimplicialComplex,
HashMap<(Simplex, Simplex), DynamicDenseMatrix<f64, RowMajor>>,
Simplex,
Simplex,
Simplex,
Simplex,
Simplex,
Simplex,
Simplex,
) {
let mut cc = SimplicialComplex::new();
let v0 = Simplex::new(0, vec![0]); let v1 = Simplex::new(0, vec![1]); let v2 = Simplex::new(0, vec![2]); let e01 = Simplex::new(1, vec![0, 1]); let e02 = Simplex::new(1, vec![0, 2]); let e12 = Simplex::new(1, vec![1, 2]);
let f012 = Simplex::new(2, vec![0, 1, 2]);
let v0 = cc.join_element(v0);
let v1 = cc.join_element(v1);
let v2 = cc.join_element(v2);
let e01 = cc.join_element(e01);
let e02 = cc.join_element(e02);
let e12 = cc.join_element(e12);
let f012 = cc.join_element(f012);
let restrictions = HashMap::from([
((v0.clone(), e01.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![1.0, 2.0]));
mat
}),
((v1.clone(), e01.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![1.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 1.0]));
mat
}),
((v0.clone(), e02.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![1.0, 0.0]));
mat
}),
((v2.clone(), e02.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![1.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((v1.clone(), e12.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![2.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 2.0]));
mat
}),
((v2.clone(), e12.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![2.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 2.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((e01.clone(), f012.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![2.0, 0.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat
}),
((e02.clone(), f012.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![2.0, 0.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 1.0, 0.0]));
mat
}),
((e12.clone(), f012.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_column(&DynamicVector::<f64>::new(vec![1.0, 0.0, 0.0]));
mat.append_column(&DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat
}),
]);
(cc, restrictions, v0, v1, v2, e01, e02, e12, f012)
}
#[test]
fn test_simplicial_sheaf_global_section_2d() {
let (cc, restrictions, v0, v1, v2, e01, e02, e12, f012) = simplicial_complex_2d();
let sheaf = Sheaf::<SimplicialComplex, DynamicVector<f64>>::new(cc, restrictions);
let section = HashMap::from([
(v0, DynamicVector::<f64>::new(vec![1.0])), (v1, DynamicVector::<f64>::new(vec![1.0, 2.0])), (v2, DynamicVector::<f64>::new(vec![1.0, 2.0, 3.0])), (e01, DynamicVector::<f64>::new(vec![1.0, 2.0])), (e02, DynamicVector::<f64>::new(vec![1.0, 0.0])), (e12, DynamicVector::<f64>::new(vec![2.0, 4.0])), (f012, DynamicVector::<f64>::new(vec![2.0, 0.0, 0.0])), ]);
assert!(sheaf.is_global_section(§ion));
}
#[test]
fn test_simplicial_sheaf_coboundary_2d() {
let (cc, restrictions, ..) = simplicial_complex_2d();
let sheaf = Sheaf::<SimplicialComplex, DynamicVector<f64>>::new(cc, restrictions);
let coboundary = sheaf.coboundary(0);
println!("{coboundary}");
assert_eq!(coboundary.block_structure(), (3, 3));
let coboundary = sheaf.coboundary(1);
println!("{coboundary}");
assert_eq!(coboundary.block_structure(), (1, 3));
let coboundary = sheaf.coboundary(2);
println!("{coboundary}");
assert_eq!(coboundary.block_structure(), (0, 0));
}
fn cubical_complex_2d(
) -> (CubicalComplex, HashMap<(Cube, Cube), DynamicDenseMatrix<f64, RowMajor>>) {
let mut cc = CubicalComplex::new();
let v00 = Cube::vertex(0);
let v10 = Cube::vertex(1);
let v01 = Cube::vertex(2);
let v11 = Cube::vertex(3);
let e_h1 = Cube::edge(0, 1);
let e_h2 = Cube::edge(2, 3);
let e_v1 = Cube::edge(0, 2);
let e_v2 = Cube::edge(1, 3);
let square = Cube::square([0, 1, 2, 3]);
let v00 = cc.join_element(v00); let v10 = cc.join_element(v10); let v01 = cc.join_element(v01); let v11 = cc.join_element(v11); let e_h1 = cc.join_element(e_h1); let e_h2 = cc.join_element(e_h2); let e_v1 = cc.join_element(e_v1); let e_v2 = cc.join_element(e_v2); let square = cc.join_element(square);
let restrictions = HashMap::from([
((v00.clone(), e_h1.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.5])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((v10.clone(), e_h1.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 1.0]));
mat
}),
((v01.clone(), e_h2.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat
}),
((v11.clone(), e_h2.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 1.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 1.0]));
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0, 0.0]));
mat
}),
((v00, e_v1.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![2.0, 1.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((v01, e_v1.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat
}),
((v10, e_v2.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 1.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((v11, e_v2.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 1.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat
}),
((e_h1, square.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 1.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((e_h2, square.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 1.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0, 0.0]));
mat
}),
((e_v1, square.clone()), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![1.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0]));
mat
}),
((e_v2, square), {
let mut mat = DynamicDenseMatrix::<f64, RowMajor>::new();
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 1.0, 0.0])); mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat.append_row(DynamicVector::<f64>::new(vec![0.0, 0.0, 0.0]));
mat
}),
]);
(cc, restrictions)
}
#[test]
fn test_cubical_sheaf_coboundary_2d() {
let (cc, restrictions) = cubical_complex_2d();
let sheaf = Sheaf::<CubicalComplex, DynamicVector<f64>>::new(cc, restrictions);
println!("=== 2D Cubical Sheaf Analysis ===");
let coboundary_0 = sheaf.coboundary(0);
println!("\n0-dimensional coboundary (vertices → edges):");
println!("{coboundary_0}");
assert_eq!(coboundary_0.block_structure().0, 4); assert_eq!(coboundary_0.block_structure().1, 4);
assert_eq!(coboundary_0.row_block_sizes(), &[2, 2, 3, 3]);
assert_eq!(coboundary_0.col_block_sizes(), &[2, 2, 3, 3]);
let coboundary_1 = sheaf.coboundary(1);
println!("\n1-dimensional coboundary (edges → faces):");
println!("{coboundary_1}");
assert_eq!(coboundary_1.block_structure().0, 1); assert_eq!(coboundary_1.block_structure().1, 4);
assert_eq!(coboundary_1.row_block_sizes(), &[4]);
assert_eq!(coboundary_1.col_block_sizes(), &[2, 2, 3, 3]);
let coboundary_2 = sheaf.coboundary(2);
println!("\n2-dimensional coboundary (faces → 3-cubes, should be empty):");
println!("{coboundary_2}");
assert_eq!(coboundary_2.block_structure(), (0, 0));
}
}