#[must_use]
pub fn betti_persistence_cpu(mask: &[u32], n: u32) -> (u32, u32, u32) {
let n_us = n as usize;
if n == 0 {
return (0, 0, 0);
}
let mut parent: Vec<u32> = (0..n).collect();
let mut rank: Vec<u32> = vec![0; n_us];
fn find(parent: &mut [u32], mut x: u32) -> u32 {
while parent[x as usize] != x {
let p = parent[x as usize];
parent[x as usize] = parent[p as usize];
x = parent[x as usize];
}
x
}
fn union(parent: &mut [u32], rank: &mut [u32], a: u32, b: u32) -> bool {
let ra = find(parent, a);
let rb = find(parent, b);
if ra == rb {
return false;
}
let (ra_rank, rb_rank) = (rank[ra as usize], rank[rb as usize]);
match ra_rank.cmp(&rb_rank) {
std::cmp::Ordering::Less => parent[ra as usize] = rb,
std::cmp::Ordering::Greater => parent[rb as usize] = ra,
std::cmp::Ordering::Equal => {
parent[rb as usize] = ra;
rank[ra as usize] = ra_rank + 1;
}
}
true
}
let mut edges: u32 = 0;
let mut tree_edges: u32 = 0;
for i in 0..n_us {
for j in (i + 1)..n_us {
if mask.get(i * n_us + j).copied().unwrap_or(0) == 0 {
continue;
}
edges = edges.saturating_add(1);
if union(&mut parent, &mut rank, i as u32, j as u32) {
tree_edges = tree_edges.saturating_add(1);
}
}
}
let mut roots = std::collections::BTreeSet::new();
for v in 0..n {
roots.insert(find(&mut parent, v));
}
let b0 = roots.len() as u32;
let b1 = edges - tree_edges;
(b0, b1, edges)
}
#[cfg(test)]
mod tests {
use super::*;
fn empty_mask(n: u32) -> Vec<u32> {
vec![0u32; (n * n) as usize]
}
fn add_edge(mask: &mut [u32], n: u32, i: u32, j: u32) {
let n_us = n as usize;
mask[(i as usize) * n_us + (j as usize)] = 1;
mask[(j as usize) * n_us + (i as usize)] = 1;
}
#[test]
fn empty_graph_has_b0_n_b1_zero() {
let n = 5;
let mask = empty_mask(n);
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (5, 0, 0));
}
#[test]
fn n_zero_returns_all_zero() {
assert_eq!(betti_persistence_cpu(&[], 0), (0, 0, 0));
}
#[test]
fn tree_has_b1_zero() {
let n = 4;
let mut mask = empty_mask(n);
add_edge(&mut mask, n, 0, 1);
add_edge(&mut mask, n, 1, 2);
add_edge(&mut mask, n, 2, 3);
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (1, 0, 3));
}
#[test]
fn triangle_has_b1_one() {
let n = 3;
let mut mask = empty_mask(n);
add_edge(&mut mask, n, 0, 1);
add_edge(&mut mask, n, 1, 2);
add_edge(&mut mask, n, 0, 2);
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (1, 1, 3));
}
#[test]
fn two_triangles_share_no_edge_has_b1_two() {
let n = 6;
let mut mask = empty_mask(n);
for (a, b) in [(0, 1), (1, 2), (0, 2), (3, 4), (4, 5), (3, 5)] {
add_edge(&mut mask, n, a, b);
}
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (2, 2, 6));
}
#[test]
fn k4_has_b1_three() {
let n = 4;
let mut mask = empty_mask(n);
for i in 0..n {
for j in (i + 1)..n {
add_edge(&mut mask, n, i, j);
}
}
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (1, 3, 6));
}
#[test]
fn tree_plus_isolated_vertex() {
let n = 4;
let mut mask = empty_mask(n);
add_edge(&mut mask, n, 0, 1);
add_edge(&mut mask, n, 1, 2);
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (2, 0, 2));
}
#[test]
fn cycle_then_attach_chord_adds_cycle() {
let n = 4;
let mut mask = empty_mask(n);
add_edge(&mut mask, n, 0, 1);
add_edge(&mut mask, n, 1, 2);
add_edge(&mut mask, n, 2, 3);
add_edge(&mut mask, n, 3, 0);
add_edge(&mut mask, n, 0, 2);
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (1, 2, 5));
}
#[test]
fn matches_euler_characteristic_identity() {
let n = 7;
let mut mask = empty_mask(n);
let edges = [(0, 1), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (4, 6)];
for (a, b) in edges {
add_edge(&mut mask, n, a, b);
}
let (b0, b1, e) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, e), (2, 2, 7));
}
#[test]
fn symmetric_mask_is_required() {
let n = 3;
let mut mask = empty_mask(n);
mask[0] = 1;
mask[4] = 1; mask[8] = 1; add_edge(&mut mask, n, 0, 1);
let (b0, b1, edges) = betti_persistence_cpu(&mask, n);
assert_eq!((b0, b1, edges), (2, 0, 1));
}
#[test]
fn larger_random_graph_consistent() {
let n = 10;
let mut mask = empty_mask(n);
let edges = [
(0, 1),
(1, 2),
(0, 2), (2, 3),
(3, 4),
(4, 5),
(5, 3), (5, 6),
(6, 7),
(7, 8),
(8, 9),
(9, 6), ];
for (a, b) in edges {
add_edge(&mut mask, n, a, b);
}
let (b0, b1, e) = betti_persistence_cpu(&mask, n);
assert_eq!(b0, 1);
assert_eq!(b1, 3);
assert_eq!(e, 12);
}
}