use crate::error::{GraphError, Result};
use scirs2_core::ndarray::Array2;
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone)]
pub struct SimplicialComplex {
simplices: BTreeMap<usize, BTreeSet<Vec<usize>>>,
}
impl Default for SimplicialComplex {
fn default() -> Self {
Self::new()
}
}
impl SimplicialComplex {
pub fn new() -> Self {
SimplicialComplex {
simplices: BTreeMap::new(),
}
}
pub fn add_simplex(&mut self, mut simplex: Vec<usize>) {
simplex.sort_unstable();
simplex.dedup();
self.add_simplex_internal(simplex);
}
fn add_simplex_internal(&mut self, simplex: Vec<usize>) {
let dim = simplex.len().saturating_sub(1);
let set = self.simplices.entry(dim).or_insert_with(BTreeSet::new);
if set.contains(&simplex) {
return; }
set.insert(simplex.clone());
if simplex.len() > 1 {
for i in 0..simplex.len() {
let mut face = simplex.clone();
face.remove(i);
self.add_simplex_internal(face);
}
}
}
pub fn max_dim(&self) -> Option<usize> {
self.simplices.keys().next_back().copied()
}
pub fn simplices_of_dim(&self, dim: usize) -> Vec<Vec<usize>> {
self.simplices
.get(&dim)
.map(|s| s.iter().cloned().collect())
.unwrap_or_default()
}
pub fn total_simplices(&self) -> usize {
self.simplices.values().map(|s| s.len()).sum()
}
pub fn num_simplices(&self, dim: usize) -> usize {
self.simplices.get(&dim).map(|s| s.len()).unwrap_or(0)
}
pub fn boundary_matrix(&self, dim: usize) -> Array2<i8> {
if dim == 0 {
let n0 = self.num_simplices(0);
return Array2::zeros((1, n0.max(1)));
}
let chains_high = self.simplices_of_dim(dim);
let chains_low = self.simplices_of_dim(dim - 1);
if chains_high.is_empty() || chains_low.is_empty() {
let rows = chains_low.len().max(1);
let cols = chains_high.len().max(1);
return Array2::zeros((rows, cols));
}
let low_index: BTreeMap<Vec<usize>, usize> = chains_low
.iter()
.enumerate()
.map(|(i, s)| (s.clone(), i))
.collect();
let rows = chains_low.len();
let cols = chains_high.len();
let mut mat = Array2::<i8>::zeros((rows, cols));
for (j, sigma) in chains_high.iter().enumerate() {
for k in 0..sigma.len() {
let mut face = sigma.clone();
face.remove(k);
if let Some(&i) = low_index.get(&face) {
let sign = if k % 2 == 0 { 1i8 } else { -1i8 };
mat[[i, j]] = sign;
}
}
}
mat
}
pub fn betti_numbers(&self) -> Vec<usize> {
let max_dim = match self.max_dim() {
Some(d) => d,
None => return Vec::new(),
};
let mut ranks: Vec<usize> = vec![0; max_dim + 2];
for d in 0..=(max_dim + 1) {
let mat = self.boundary_matrix(d);
ranks[d] = matrix_rank_i8(&mat);
}
let mut betti = Vec::new();
for k in 0..=max_dim {
let n_k = self.num_simplices(k);
let ker_k = n_k.saturating_sub(ranks[k]);
let im_k1 = ranks[k + 1];
betti.push(ker_k.saturating_sub(im_k1));
}
betti
}
pub fn euler_characteristic(&self) -> i64 {
self.simplices
.iter()
.map(|(&dim, set)| {
let sign: i64 = if dim % 2 == 0 { 1 } else { -1 };
sign * set.len() as i64
})
.sum()
}
pub fn vietoris_rips(points: &Array2<f64>, epsilon: f64) -> Self {
let n = points.nrows();
let mut sc = SimplicialComplex::new();
if n == 0 {
return sc;
}
for i in 0..n {
sc.add_simplex(vec![i]);
}
let mut dist = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in (i + 1)..n {
let d = euclidean_distance(points.row(i).as_slice().unwrap_or(&[]),
points.row(j).as_slice().unwrap_or(&[]));
dist[i][j] = d;
dist[j][i] = d;
}
}
let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
for i in 0..n {
for j in (i + 1)..n {
if dist[i][j] <= epsilon {
adj[i].push(j);
adj[j].push(i);
}
}
}
let mut all_cliques: Vec<Vec<usize>> = Vec::new();
bron_kerbosch(&adj, vec![], (0..n).collect(), vec![], &mut all_cliques);
for clique in all_cliques {
sc.add_simplex(clique);
}
sc
}
pub fn cech_complex(points: &Array2<f64>, radius: f64) -> Self {
let n = points.nrows();
let d = points.ncols();
let mut sc = SimplicialComplex::new();
if n == 0 {
return sc;
}
let max_simplex = (d + 2).min(n);
for i in 0..n {
sc.add_simplex(vec![i]);
}
for i in 0..n {
for j in (i + 1)..n {
let pts = vec![i, j];
if miniball_radius(points, &pts) <= radius {
sc.add_simplex(pts);
}
}
}
for size in 3..=max_simplex {
enumerate_subsets(n, size, &mut |subset| {
if miniball_radius(points, subset) <= radius {
sc.add_simplex(subset.to_vec());
}
});
}
sc
}
pub fn nerve_complex(cover: &[Vec<usize>]) -> Self {
let mut sc = SimplicialComplex::new();
let k = cover.len();
if k == 0 {
return sc;
}
for i in 0..k {
sc.add_simplex(vec![i]);
}
for size in 2..=k {
enumerate_subsets(k, size, &mut |subset| {
let mut inter: Vec<usize> = cover[subset[0]].clone();
for &idx in &subset[1..] {
inter.retain(|x| cover[idx].contains(x));
if inter.is_empty() {
return;
}
}
if !inter.is_empty() {
sc.add_simplex(subset.to_vec());
}
});
}
sc
}
}
fn euclidean_distance(a: &[f64], b: &[f64]) -> f64 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).powi(2))
.sum::<f64>()
.sqrt()
}
fn miniball_radius(points: &Array2<f64>, indices: &[usize]) -> f64 {
match indices.len() {
0 => 0.0,
1 => 0.0,
2 => {
let d = points.ncols();
let mut sq = 0.0f64;
for k in 0..d {
let diff = points[[indices[0], k]] - points[[indices[1], k]];
sq += diff * diff;
}
sq.sqrt() / 2.0
}
_ => {
let d = points.ncols();
let mut max_dist = 0.0f64;
let mut p1 = indices[0];
let mut p2 = indices[1];
for &i in indices {
for &j in indices {
if i == j {
continue;
}
let mut sq = 0.0f64;
for k in 0..d {
let diff = points[[i, k]] - points[[j, k]];
sq += diff * diff;
}
if sq > max_dist {
max_dist = sq;
p1 = i;
p2 = j;
}
}
}
let mut centre: Vec<f64> = (0..d)
.map(|k| (points[[p1, k]] + points[[p2, k]]) / 2.0)
.collect();
let mut radius = max_dist.sqrt() / 2.0;
for &i in indices {
let mut sq = 0.0f64;
for k in 0..d {
let diff = points[[i, k]] - centre[k];
sq += diff * diff;
}
let dist = sq.sqrt();
if dist > radius {
let new_radius = (radius + dist) / 2.0;
let alpha = (dist - radius) / (2.0 * dist);
for k in 0..d {
centre[k] += alpha * (points[[i, k]] - centre[k]);
}
radius = new_radius;
}
}
radius
}
}
}
fn enumerate_subsets<F: FnMut(&[usize])>(n: usize, k: usize, f: &mut F) {
let mut subset = vec![0usize; k];
for i in 0..k {
subset[i] = i;
}
loop {
f(&subset);
let mut i = k;
loop {
if i == 0 {
return;
}
i -= 1;
if subset[i] < n - k + i {
subset[i] += 1;
for j in (i + 1)..k {
subset[j] = subset[j - 1] + 1;
}
break;
}
}
}
}
fn bron_kerbosch(
adj: &[Vec<usize>],
r: Vec<usize>,
mut p: Vec<usize>,
mut x: Vec<usize>,
result: &mut Vec<Vec<usize>>,
) {
if p.is_empty() && x.is_empty() {
if !r.is_empty() {
result.push(r);
}
return;
}
let pivot = {
let all: Vec<usize> = p.iter().chain(x.iter()).copied().collect();
*all.iter()
.max_by_key(|&&u| p.iter().filter(|&&v| adj[u].contains(&v)).count())
.unwrap_or(&all[0])
};
let candidates: Vec<usize> = p
.iter()
.copied()
.filter(|&v| !adj[pivot].contains(&v))
.collect();
for v in candidates {
let mut r_new = r.clone();
r_new.push(v);
let p_new: Vec<usize> = p
.iter()
.copied()
.filter(|&u| adj[v].contains(&u))
.collect();
let x_new: Vec<usize> = x
.iter()
.copied()
.filter(|&u| adj[v].contains(&u))
.collect();
bron_kerbosch(adj, r_new, p_new, x_new, result);
p.retain(|&u| u != v);
x.push(v);
}
}
fn matrix_rank_i8(mat: &Array2<i8>) -> usize {
let (rows, cols) = (mat.nrows(), mat.ncols());
if rows == 0 || cols == 0 {
return 0;
}
let mut m: Vec<Vec<i64>> = (0..rows)
.map(|i| (0..cols).map(|j| mat[[i, j]] as i64).collect())
.collect();
let mut rank = 0usize;
let mut pivot_row = 0usize;
for col in 0..cols {
let pivot = (pivot_row..rows).find(|&r| m[r][col] != 0);
if let Some(p) = pivot {
m.swap(pivot_row, p);
let piv = m[pivot_row][col];
for r in 0..rows {
if r == pivot_row {
continue;
}
let factor = m[r][col];
if factor == 0 {
continue;
}
for c in 0..cols {
m[r][c] = m[r][c] * piv - factor * m[pivot_row][c];
}
let g = row_gcd(&m[r]);
if g > 1 {
for c in 0..cols {
m[r][c] /= g;
}
}
}
pivot_row += 1;
rank += 1;
}
}
rank
}
fn row_gcd(row: &[i64]) -> i64 {
row.iter()
.filter(|&&x| x != 0)
.map(|x| x.unsigned_abs())
.fold(0u64, gcd) as i64
}
fn gcd(a: u64, b: u64) -> u64 {
if b == 0 {
a
} else {
gcd(b, a % b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_add_simplex_closure() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1, 2]);
assert_eq!(sc.num_simplices(0), 3);
assert_eq!(sc.num_simplices(1), 3);
assert_eq!(sc.num_simplices(2), 1);
}
#[test]
fn test_euler_characteristic_triangle_surface() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1]);
sc.add_simplex(vec![1, 2]);
sc.add_simplex(vec![0, 2]);
assert_eq!(sc.euler_characteristic(), 0);
}
#[test]
fn test_euler_characteristic_filled_triangle() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1, 2]);
assert_eq!(sc.euler_characteristic(), 1);
}
#[test]
fn test_betti_numbers_point() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0]);
let b = sc.betti_numbers();
assert_eq!(b[0], 1);
}
#[test]
fn test_betti_numbers_edge() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1]);
let b = sc.betti_numbers();
assert_eq!(b[0], 1);
assert_eq!(b.get(1).copied().unwrap_or(0), 0);
}
#[test]
fn test_betti_numbers_hollow_triangle() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1]);
sc.add_simplex(vec![1, 2]);
sc.add_simplex(vec![0, 2]);
let b = sc.betti_numbers();
assert_eq!(b[0], 1);
assert_eq!(b.get(1).copied().unwrap_or(0), 1);
}
#[test]
fn test_betti_numbers_filled_triangle() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1, 2]);
let b = sc.betti_numbers();
assert_eq!(b[0], 1);
assert_eq!(b.get(1).copied().unwrap_or(0), 0);
}
#[test]
fn test_betti_two_components() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0]);
sc.add_simplex(vec![1]);
let b = sc.betti_numbers();
assert_eq!(b[0], 2);
}
#[test]
fn test_boundary_matrix_dim0() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0]);
let mat = sc.boundary_matrix(0);
assert_eq!(mat.nrows(), 1);
}
#[test]
fn test_boundary_matrix_dim1() {
let mut sc = SimplicialComplex::new();
sc.add_simplex(vec![0, 1]);
let mat = sc.boundary_matrix(1);
assert_eq!(mat.nrows(), 2); assert_eq!(mat.ncols(), 1); let entries: Vec<i8> = vec![mat[[0, 0]], mat[[1, 0]]];
assert!(entries.contains(&1));
assert!(entries.contains(&-1));
}
#[test]
fn test_vietoris_rips_collinear() {
use scirs2_core::ndarray::array;
let pts = array![[0.0_f64, 0.0], [1.0, 0.0], [2.0, 0.0]];
let sc = SimplicialComplex::vietoris_rips(&pts, 1.5);
assert_eq!(sc.num_simplices(1), 2);
assert_eq!(sc.num_simplices(2), 0);
}
#[test]
fn test_vietoris_rips_triangle() {
use scirs2_core::ndarray::array;
let pts = array![
[0.0_f64, 0.0],
[1.0, 0.0],
[0.5, 0.866_025_403_784_438_6]
];
let sc = SimplicialComplex::vietoris_rips(&pts, 1.0001);
assert_eq!(sc.num_simplices(0), 3);
assert_eq!(sc.num_simplices(1), 3);
assert_eq!(sc.num_simplices(2), 1);
}
#[test]
fn test_nerve_complex_basic() {
let cover = vec![vec![0, 1], vec![1, 2], vec![2, 3]];
let sc = SimplicialComplex::nerve_complex(&cover);
assert_eq!(sc.num_simplices(0), 3);
assert_eq!(sc.num_simplices(1), 2);
assert_eq!(sc.num_simplices(2), 0);
}
#[test]
fn test_nerve_triple_overlap() {
let cover = vec![vec![0, 1], vec![0, 2], vec![0, 3]];
let sc = SimplicialComplex::nerve_complex(&cover);
assert_eq!(sc.num_simplices(2), 1);
}
#[test]
fn test_cech_complex_two_points() {
use scirs2_core::ndarray::array;
let pts = array![[0.0_f64], [1.0]];
let sc = SimplicialComplex::cech_complex(&pts, 0.6); assert_eq!(sc.num_simplices(1), 1);
}
#[test]
fn test_cech_complex_too_small_radius() {
use scirs2_core::ndarray::array;
let pts = array![[0.0_f64], [1.0]];
let sc = SimplicialComplex::cech_complex(&pts, 0.4); assert_eq!(sc.num_simplices(1), 0);
}
}