use std::collections::HashMap;
use harness_algebra::{
rings::Field,
tensors::dynamic::{
compute_quotient_basis,
matrix::{DynamicDenseMatrix, RowMajor},
vector::DynamicVector,
},
};
use super::*;
use crate::{
definitions::Topology,
homology::{Chain, Homology},
lattice::Lattice,
set::{Collection, Poset},
};
pub mod cubical;
pub mod simplicial;
pub use cubical::Cube;
pub use simplicial::Simplex;
pub type SimplicialComplex = Complex<Simplex>;
pub type CubicalComplex = Complex<Cube>;
pub trait ComplexElement: Clone + std::hash::Hash + Eq + PartialOrd + Ord {
fn dimension(&self) -> usize;
fn faces(&self) -> Vec<Self>;
fn boundary_with_orientations(&self) -> Vec<(Self, i32)>;
fn id(&self) -> Option<usize>;
fn same_content(&self, other: &Self) -> bool;
fn with_id(&self, new_id: usize) -> Self;
}
#[derive(Debug, Clone)]
pub struct Complex<T: ComplexElement> {
pub attachment_lattice: Lattice<usize>,
pub elements: HashMap<usize, T>,
pub next_id: usize,
}
impl<T: ComplexElement> Complex<T> {
pub fn new() -> Self {
Self {
attachment_lattice: Lattice::new(),
elements: HashMap::new(),
next_id: 0,
}
}
pub fn join_element(&mut self, element: T) -> T {
if let Some(existing) = self.find_equivalent_element(&element) {
return existing;
}
let element_with_id =
if element.id().is_some() && !self.elements.contains_key(&element.id().unwrap()) {
if element.id().unwrap() >= self.next_id {
self.next_id = element.id().unwrap() + 1;
}
element
} else {
let new_id = self.next_id;
self.next_id += 1;
element.with_id(new_id)
};
let mut face_ids = Vec::new();
for face in element_with_id.faces() {
let added_face = self.join_element(face);
face_ids.push(added_face.id().unwrap()); }
let element_id = element_with_id.id().unwrap();
self.attachment_lattice.add_element(element_id);
for face_id in face_ids {
self.attachment_lattice.add_relation(face_id, element_id);
}
self.elements.insert(element_id, element_with_id.clone());
element_with_id
}
fn find_equivalent_element(&self, element: &T) -> Option<T> {
self.elements.values().find(|existing| element.same_content(existing)).cloned()
}
pub fn get_element(&self, id: usize) -> Option<&T> { self.elements.get(&id) }
pub fn elements_of_dimension(&self, dimension: usize) -> Vec<T> {
self.elements.values().filter(|element| element.dimension() == dimension).cloned().collect()
}
pub fn max_dimension(&self) -> usize {
self.elements.values().map(ComplexElement::dimension).max().unwrap_or(0)
}
pub fn faces(&self, element: &T) -> Vec<T> {
element.id().map_or_else(Vec::new, |id| {
self
.attachment_lattice
.predecessors(id)
.into_iter()
.filter_map(|face_id| self.get_element(face_id).cloned())
.collect()
})
}
pub fn cofaces(&self, element: &T) -> Vec<T> {
element.id().map_or_else(Vec::new, |id| {
self
.attachment_lattice
.successors(id)
.into_iter()
.filter_map(|coface_id| self.get_element(coface_id).cloned())
.collect()
})
}
pub fn homology<F: Field + Copy>(&self, k: usize) -> Homology<F> {
let k_elements = {
let mut elements = self.elements_of_dimension(k);
elements.sort_unstable();
elements
};
if k_elements.is_empty() {
return Homology::trivial(k);
}
let cycles = if k == 0 {
let num_0_elements = k_elements.len();
let mut basis: Vec<DynamicVector<F>> = Vec::with_capacity(num_0_elements);
for i in 0..num_0_elements {
let mut v_data = vec![F::zero(); num_0_elements];
v_data[i] = F::one();
basis.push(DynamicVector::new(v_data));
}
basis
} else {
let boundary_k = self.get_boundary_matrix::<F>(k);
boundary_k.kernel()
};
let boundary_k_plus_1 = self.get_boundary_matrix::<F>(k + 1);
let boundaries = boundary_k_plus_1.image();
let quotient_basis_vectors = compute_quotient_basis(&boundaries, &cycles);
Homology {
dimension: k,
betti_number: quotient_basis_vectors.len(),
homology_generators: quotient_basis_vectors,
}
}
pub fn get_boundary_matrix<F: Field + Copy>(&self, k: usize) -> DynamicDenseMatrix<F, RowMajor>
where T: ComplexElement {
let codomain_basis = if k == 0 {
Vec::new()
} else {
let mut elements = self.elements_of_dimension(k - 1);
elements.sort_unstable();
elements
};
let domain_basis = {
let mut elements = self.elements_of_dimension(k);
elements.sort_unstable();
elements
};
let mut matrix = DynamicDenseMatrix::<F, RowMajor>::new();
if domain_basis.is_empty() {
for _ in 0..codomain_basis.len() {
matrix.append_row(DynamicVector::new(Vec::new()));
}
return matrix;
}
let basis_map_for_codomain: HashMap<&T, usize> =
codomain_basis.iter().enumerate().map(|(i, s)| (s, i)).collect();
let num_codomain_elements = codomain_basis.len();
for element_from_domain in &domain_basis {
let boundary_chain: Chain<Self, F> = self.boundary(element_from_domain);
let col_vector =
boundary_chain.to_coeff_vector(&basis_map_for_codomain, num_codomain_elements);
matrix.append_column(&col_vector);
}
matrix
}
}
impl<T: ComplexElement> Default for Complex<T> {
fn default() -> Self { Self::new() }
}
impl<T: ComplexElement> Collection for Complex<T> {
type Item = T;
fn contains(&self, point: &Self::Item) -> bool {
if let Some(id) = point.id() {
self.elements.contains_key(&id)
} else {
false
}
}
fn is_empty(&self) -> bool { self.elements.is_empty() }
}
impl<T: ComplexElement> Poset for Complex<T> {
fn leq(&self, a: &Self::Item, b: &Self::Item) -> Option<bool> {
match (a.id(), b.id()) {
(Some(id_a), Some(id_b)) => self.attachment_lattice.leq(&id_a, &id_b),
_ => None,
}
}
fn upset(&self, a: Self::Item) -> std::collections::HashSet<Self::Item> {
if let Some(id) = a.id() {
self
.attachment_lattice
.upset(id)
.into_iter()
.filter_map(|upset_id| self.get_element(upset_id).cloned())
.collect()
} else {
std::collections::HashSet::new()
}
}
fn downset(&self, a: Self::Item) -> std::collections::HashSet<Self::Item> {
if let Some(id) = a.id() {
self
.attachment_lattice
.downset(id)
.into_iter()
.filter_map(|downset_id| self.get_element(downset_id).cloned())
.collect()
} else {
std::collections::HashSet::new()
}
}
fn minimal_elements(&self) -> std::collections::HashSet<Self::Item> {
self
.attachment_lattice
.minimal_elements()
.into_iter()
.filter_map(|id| self.get_element(id).cloned())
.collect()
}
fn maximal_elements(&self) -> std::collections::HashSet<Self::Item> {
self
.attachment_lattice
.maximal_elements()
.into_iter()
.filter_map(|id| self.get_element(id).cloned())
.collect()
}
fn join(&self, a: Self::Item, b: Self::Item) -> Option<Self::Item> {
match (a.id(), b.id()) {
(Some(id_a), Some(id_b)) => self
.attachment_lattice
.join(id_a, id_b)
.and_then(|join_id| self.get_element(join_id).cloned()),
_ => None,
}
}
fn meet(&self, a: Self::Item, b: Self::Item) -> Option<Self::Item> {
match (a.id(), b.id()) {
(Some(id_a), Some(id_b)) => self
.attachment_lattice
.meet(id_a, id_b)
.and_then(|meet_id| self.get_element(meet_id).cloned()),
_ => None,
}
}
fn successors(&self, a: Self::Item) -> std::collections::HashSet<Self::Item> {
if let Some(id) = a.id() {
self
.attachment_lattice
.successors(id)
.into_iter()
.filter_map(|succ_id| self.get_element(succ_id).cloned())
.collect()
} else {
std::collections::HashSet::new()
}
}
fn predecessors(&self, a: Self::Item) -> std::collections::HashSet<Self::Item> {
if let Some(id) = a.id() {
self
.attachment_lattice
.predecessors(id)
.into_iter()
.filter_map(|pred_id| self.get_element(pred_id).cloned())
.collect()
} else {
std::collections::HashSet::new()
}
}
}
impl<T: ComplexElement> Topology for Complex<T> {
fn neighborhood(&self, item: &Self::Item) -> Vec<Self::Item> {
self.cofaces(item)
}
fn boundary<R: Ring + Copy>(&self, item: &Self::Item) -> Chain<Self, R> {
if item.dimension() == 0 {
return Chain::new(self);
}
let mut boundary_chain_items = Vec::new();
let mut boundary_chain_coeffs = Vec::new();
let faces_with_orientations = item.boundary_with_orientations();
for (face, orientation) in faces_with_orientations {
if let Some(complex_face) = self.find_equivalent_element(&face) {
let coeff = if orientation > 0 {
R::one()
} else if orientation < 0 {
-R::one()
} else {
continue; };
boundary_chain_items.push(complex_face);
boundary_chain_coeffs.push(coeff);
}
}
Chain::from_items_and_coeffs(self, boundary_chain_items, boundary_chain_coeffs)
}
}
#[cfg(test)]
mod tests {
use harness_algebra::algebras::boolean::Boolean;
use super::*;
#[test]
fn test_generic_complex_with_simplex() {
let mut complex: Complex<Simplex> = Complex::new();
let triangle = Simplex::new(2, vec![0, 1, 2]);
let added_triangle = complex.join_element(triangle);
assert_eq!(complex.elements_of_dimension(2).len(), 1);
assert_eq!(complex.elements_of_dimension(1).len(), 3);
assert_eq!(complex.elements_of_dimension(0).len(), 3);
assert!(complex.contains(&added_triangle));
let faces = complex.faces(&added_triangle);
assert_eq!(faces.len(), 3); }
#[test]
fn test_generic_complex_with_cube() {
let mut complex: Complex<Cube> = Complex::new();
let square = Cube::square([0, 1, 2, 3]);
let added_square = complex.join_element(square);
assert_eq!(complex.elements_of_dimension(2).len(), 1);
assert_eq!(complex.elements_of_dimension(1).len(), 4);
assert_eq!(complex.elements_of_dimension(0).len(), 4);
assert!(complex.contains(&added_square));
let faces = complex.faces(&added_square);
assert_eq!(faces.len(), 4); }
#[test]
fn test_automatic_id_assignment() {
let mut complex = SimplicialComplex::new();
let vertex1 = Simplex::new(0, vec![0]);
let vertex2 = Simplex::new(0, vec![1]);
let edge = Simplex::new(1, vec![0, 1]);
let added_vertex1 = complex.join_element(vertex1);
let added_vertex2 = complex.join_element(vertex2);
let added_edge = complex.join_element(edge);
assert!(complex.contains(&added_vertex1));
assert!(complex.contains(&added_vertex2));
assert!(complex.contains(&added_edge));
assert!(added_vertex1.id().is_some());
assert!(added_vertex2.id().is_some());
assert!(added_edge.id().is_some());
assert!(complex.leq(&added_vertex1, &added_edge).unwrap());
assert!(complex.leq(&added_vertex2, &added_edge).unwrap());
}
#[test]
fn test_element_deduplication() {
let mut complex = SimplicialComplex::new();
let simplex1 = Simplex::new(1, vec![0, 1]);
let simplex2 = Simplex::new(1, vec![0, 1]);
let added1 = complex.join_element(simplex1);
let added2 = complex.join_element(simplex2);
assert_eq!(added1.id(), added2.id());
assert_eq!(complex.elements_of_dimension(1).len(), 1); }
#[test]
fn test_complex_poset_operations() {
let mut complex = SimplicialComplex::new();
let triangle = Simplex::new(2, vec![0, 1, 2]);
let added_triangle = complex.join_element(triangle);
let vertices = complex.elements_of_dimension(0);
let edges = complex.elements_of_dimension(1);
let vertex = vertices.iter().find(|v| v.vertices() == [0]).unwrap().clone();
let edge = edges.iter().find(|e| e.vertices() == [0, 1]).unwrap().clone();
assert_eq!(complex.leq(&vertex, &edge), Some(true));
assert_eq!(complex.leq(&edge, &added_triangle), Some(true));
assert_eq!(complex.leq(&vertex, &added_triangle), Some(true));
assert_eq!(complex.leq(&added_triangle, &vertex), Some(false));
let vertex_upset = complex.upset(vertex.clone());
assert!(vertex_upset.contains(&vertex));
assert!(vertex_upset.contains(&edge));
assert!(vertex_upset.contains(&added_triangle));
let triangle_downset = complex.downset(added_triangle.clone());
assert!(triangle_downset.contains(&vertex));
assert!(triangle_downset.contains(&edge));
assert!(triangle_downset.contains(&added_triangle));
}
#[test]
fn test_complex_topology_operations() {
let mut complex = SimplicialComplex::new();
let edge = Simplex::new(1, vec![0, 1]);
let added_edge = complex.join_element(edge);
let vertices = complex.elements_of_dimension(0);
let vertex = vertices.iter().find(|v| v.vertices() == [0]).unwrap().clone();
let vertex_neighborhood = complex.neighborhood(&vertex);
assert_eq!(vertex_neighborhood.len(), 1);
assert!(vertex_neighborhood.contains(&added_edge));
let edge_neighborhood = complex.neighborhood(&added_edge);
assert_eq!(edge_neighborhood.len(), 0); }
#[test]
fn test_mixed_complex_operations() {
let mut simplicial_complex = SimplicialComplex::new();
let triangle = Simplex::from_vertices(vec![0, 1, 2]);
let added_triangle = simplicial_complex.join_element(triangle);
assert_eq!(simplicial_complex.elements_of_dimension(2).len(), 1);
assert_eq!(simplicial_complex.elements_of_dimension(1).len(), 3);
assert_eq!(simplicial_complex.elements_of_dimension(0).len(), 3);
let mut cubical_complex = CubicalComplex::new();
let square = Cube::square([0, 1, 2, 3]);
let added_square = cubical_complex.join_element(square);
assert_eq!(cubical_complex.elements_of_dimension(2).len(), 1);
assert_eq!(cubical_complex.elements_of_dimension(1).len(), 4);
assert_eq!(cubical_complex.elements_of_dimension(0).len(), 4);
assert!(simplicial_complex.contains(&added_triangle));
assert!(cubical_complex.contains(&added_square));
let triangle_faces = simplicial_complex.faces(&added_triangle);
let square_faces = cubical_complex.faces(&added_square);
assert_eq!(triangle_faces.len(), 3); assert_eq!(square_faces.len(), 4); }
#[test]
fn test_generic_homology_computation() {
let mut simplicial_complex = SimplicialComplex::new();
let edge1 = Simplex::new(1, vec![0, 1]);
let edge2 = Simplex::new(1, vec![1, 2]);
let edge3 = Simplex::new(1, vec![2, 0]);
simplicial_complex.join_element(edge1);
simplicial_complex.join_element(edge2);
simplicial_complex.join_element(edge3);
let h0 = simplicial_complex.homology::<Boolean>(0);
let h1 = simplicial_complex.homology::<Boolean>(1);
assert_eq!(h0.betti_number, 1); assert_eq!(h1.betti_number, 1);
let mut cubical_complex = CubicalComplex::new();
let edge1 = Cube::edge(0, 1);
let edge2 = Cube::edge(1, 2);
let edge3 = Cube::edge(2, 3);
let edge4 = Cube::edge(3, 0);
cubical_complex.join_element(edge1);
cubical_complex.join_element(edge2);
cubical_complex.join_element(edge3);
cubical_complex.join_element(edge4);
let h0_cube = cubical_complex.homology::<Boolean>(0);
let h1_cube = cubical_complex.homology::<Boolean>(1);
assert_eq!(h0_cube.betti_number, 1); assert_eq!(h1_cube.betti_number, 1);
assert_eq!(h0.betti_number, h0_cube.betti_number);
assert_eq!(h1.betti_number, h1_cube.betti_number);
}
#[test]
fn test_generic_homology_filled_shapes() {
let mut simplicial_complex = SimplicialComplex::new();
let triangle = Simplex::new(2, vec![0, 1, 2]);
simplicial_complex.join_element(triangle);
let h0 = simplicial_complex.homology::<Boolean>(0);
let h1 = simplicial_complex.homology::<Boolean>(1);
assert_eq!(h0.betti_number, 1); assert_eq!(h1.betti_number, 0);
let mut cubical_complex = CubicalComplex::new();
let square = Cube::square([0, 1, 2, 3]);
cubical_complex.join_element(square);
let h0_cube = cubical_complex.homology::<Boolean>(0);
let h1_cube = cubical_complex.homology::<Boolean>(1);
assert_eq!(h0_cube.betti_number, 1); assert_eq!(h1_cube.betti_number, 0); }
}