#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ChainComplex {
pub num_vertices: usize,
pub edges: Vec<(u32, u32)>,
pub triangles: Vec<(u32, u32, u32)>,
}
#[allow(dead_code)]
pub fn build_chain_complex(num_verts: usize, indices: &[u32]) -> ChainComplex {
use std::collections::HashSet;
let tc = indices.len() / 3;
let mut edge_set: HashSet<(u32, u32)> = HashSet::new();
let mut tris = Vec::with_capacity(tc);
for t in 0..tc {
let a = indices[t * 3];
let b = indices[t * 3 + 1];
let c = indices[t * 3 + 2];
tris.push((a, b, c));
for &(x, y) in &[(a, b), (b, c), (c, a)] {
let e = if x < y { (x, y) } else { (y, x) };
edge_set.insert(e);
}
}
let mut edges: Vec<(u32, u32)> = edge_set.into_iter().collect();
edges.sort();
ChainComplex { num_vertices: num_verts, edges, triangles: tris }
}
#[allow(dead_code)]
pub fn chain_vertex_count(cc: &ChainComplex) -> usize {
cc.num_vertices
}
#[allow(dead_code)]
pub fn chain_edge_count(cc: &ChainComplex) -> usize {
cc.edges.len()
}
#[allow(dead_code)]
pub fn chain_triangle_count(cc: &ChainComplex) -> usize {
cc.triangles.len()
}
#[allow(dead_code)]
pub fn chain_euler_characteristic(cc: &ChainComplex) -> i64 {
cc.num_vertices as i64 - cc.edges.len() as i64 + cc.triangles.len() as i64
}
#[allow(dead_code)]
pub fn triangle_boundary(tri: (u32, u32, u32)) -> [(u32, u32); 3] {
let (a, b, c) = tri;
let mut edges = [
if a < b { (a, b) } else { (b, a) },
if b < c { (b, c) } else { (c, b) },
if c < a { (c, a) } else { (a, c) },
];
edges.sort();
edges
}
#[allow(dead_code)]
pub fn edge_boundary(e: (u32, u32)) -> [u32; 2] {
[e.0, e.1]
}
#[allow(dead_code)]
pub fn boundary_squared_is_zero(cc: &ChainComplex) -> bool {
for &tri in &cc.triangles {
let edges = triangle_boundary(tri);
let mut vertex_count = std::collections::HashMap::new();
for e in &edges {
let verts = edge_boundary(*e);
for &v in &verts {
*vertex_count.entry(v).or_insert(0u32) += 1;
}
}
for &count in vertex_count.values() {
if count % 2 != 0 {
return false;
}
}
}
true
}
#[allow(dead_code)]
pub fn betti_0_estimate(cc: &ChainComplex) -> usize {
let n = cc.num_vertices;
if n == 0 { return 0; }
let mut parent: Vec<usize> = (0..n).collect();
fn find(parent: &mut [usize], x: usize) -> usize {
if parent[x] != x {
parent[x] = find(parent, parent[x]);
}
parent[x]
}
for &(a, b) in &cc.edges {
let ra = find(&mut parent, a as usize);
let rb = find(&mut parent, b as usize);
if ra != rb {
parent[ra] = rb;
}
}
let mut roots = std::collections::HashSet::new();
for i in 0..n {
roots.insert(find(&mut parent, i));
}
roots.len()
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_tri() -> ChainComplex {
build_chain_complex(3, &[0, 1, 2])
}
fn tetra() -> ChainComplex {
build_chain_complex(4, &[0, 1, 2, 0, 3, 1, 0, 2, 3, 1, 3, 2])
}
#[test]
fn test_vertex_count() {
assert_eq!(chain_vertex_count(&simple_tri()), 3);
}
#[test]
fn test_edge_count_triangle() {
assert_eq!(chain_edge_count(&simple_tri()), 3);
}
#[test]
fn test_triangle_count() {
assert_eq!(chain_triangle_count(&simple_tri()), 1);
}
#[test]
fn test_euler_triangle() {
assert_eq!(chain_euler_characteristic(&simple_tri()), 1);
}
#[test]
fn test_euler_tetrahedron() {
let cc = tetra();
assert_eq!(chain_euler_characteristic(&cc), 2);
}
#[test]
fn test_boundary_squared_zero() {
assert!(boundary_squared_is_zero(&simple_tri()));
assert!(boundary_squared_is_zero(&tetra()));
}
#[test]
fn test_triangle_boundary() {
let edges = triangle_boundary((0, 1, 2));
assert_eq!(edges.len(), 3);
}
#[test]
fn test_edge_boundary() {
let verts = edge_boundary((3, 7));
assert_eq!(verts, [3, 7]);
}
#[test]
fn test_betti_0_single_component() {
assert_eq!(betti_0_estimate(&simple_tri()), 1);
}
#[test]
fn test_betti_0_two_components() {
let cc = build_chain_complex(6, &[0, 1, 2, 3, 4, 5]);
assert_eq!(betti_0_estimate(&cc), 2);
}
}