use std::{
collections::{HashMap, HashSet},
iter::Sum,
marker::PhantomData,
};
use harness_algebra::rings::Field;
use itertools::Itertools;
#[cfg(feature = "parallel")]
use crate::filtration::ParallelFiltration;
use crate::{
cloud::Cloud,
complexes::{Complex, Simplex, SimplicialComplex},
filtration::Filtration,
homology::Homology,
prelude::MetricSpace,
};
pub struct VietorisRips<const N: usize, F, O> {
_phantom: PhantomData<[F; N]>, _output_space: PhantomData<O>, }
impl<const N: usize, F, O> VietorisRips<N, F, O> {
pub const fn new() -> Self { Self { _phantom: PhantomData, _output_space: PhantomData } }
}
impl<const N: usize, F: Field + Copy + Sum<F> + PartialOrd> VietorisRips<N, F, Complex<Simplex>> {
pub fn build_complex(&self, cloud: &Cloud<N, F>, epsilon: F) -> SimplicialComplex {
let mut complex = SimplicialComplex::new();
let points_vec = cloud.points_ref();
if points_vec.is_empty() {
return complex;
}
for i in 0..points_vec.len() {
complex.join_element(Simplex::new(0, vec![i]));
}
for dim_k in 1..points_vec.len() {
for vertex_indices in (0..points_vec.len()).combinations(dim_k + 1) {
let mut form_simplex = true;
for edge_node_indices in vertex_indices.iter().copied().combinations(2) {
let p1_idx = edge_node_indices[0];
let p2_idx = edge_node_indices[1];
let point_a = points_vec[p1_idx];
let point_b = points_vec[p2_idx];
if Cloud::<N, F>::distance(point_a, point_b) > epsilon * epsilon {
form_simplex = false;
break;
}
}
if form_simplex {
complex.join_element(Simplex::new(dim_k, vertex_indices));
}
}
}
complex
}
}
impl<const N: usize, F> Default for VietorisRips<N, F, SimplicialComplex> {
fn default() -> Self { Self::new() }
}
impl<const N: usize, F: Field + Copy + Sum<F> + PartialOrd> Filtration
for VietorisRips<N, F, Complex<Simplex>>
{
type InputParameter = F;
type InputSpace = Cloud<N, F>;
type OutputParameter = ();
type OutputSpace = Complex<Simplex>;
fn build(
&self,
cloud: &Self::InputSpace,
epsilon: Self::InputParameter,
_output_param: &(),
) -> Self::OutputSpace {
self.build_complex(cloud, epsilon)
}
}
#[cfg(feature = "parallel")]
impl<const N: usize, F> ParallelFiltration for VietorisRips<N, F, SimplicialComplex>
where
F: Field + Copy + Sum<F> + PartialOrd + Send + Sync,
Cloud<N, F>: Sync,
{
}
impl<const N: usize, F, R> Filtration for VietorisRips<N, F, Homology<R>>
where
F: Field + Copy + Sum<F> + PartialOrd,
R: Field + Copy,
{
type InputParameter = F;
type InputSpace = Cloud<N, F>;
type OutputParameter = HashSet<usize>;
type OutputSpace = HashMap<usize, Homology<R>>;
fn build(
&self,
input: &Self::InputSpace,
param: Self::InputParameter, output_param: &Self::OutputParameter, ) -> Self::OutputSpace {
let complex_builder = VietorisRips::<N, F, Complex<Simplex>>::new();
let complex = complex_builder.build_complex(input, param);
let mut homology_groups = HashMap::new();
for dim in output_param {
let homology_group = complex.homology(*dim); homology_groups.insert(*dim, homology_group);
}
homology_groups
}
}
#[cfg(feature = "parallel")]
impl<const N: usize, F, R> ParallelFiltration for VietorisRips<N, F, Homology<R>>
where
F: Field + Copy + Sum<F> + PartialOrd + Send + Sync,
R: Field + Copy + Send + Sync,
Cloud<N, F>: Sync,
Homology<R>: Send,
{
}
#[cfg(test)]
mod tests {
use harness_algebra::{
algebras::boolean::Boolean, modular, prime_field, tensors::fixed::FixedVector,
};
use super::*;
#[test]
fn test_vietoris_rips_empty_cloud() {
let cloud: Cloud<2, f64> = Cloud::new(vec![]);
let vr = VietorisRips::<2, f64, SimplicialComplex>::new();
let complex = vr.build(&cloud, 0.5, &());
assert!(complex.elements_of_dimension(0).is_empty());
}
#[test]
fn test_vietoris_rips_single_point() {
let points = vec![FixedVector([0.0, 0.0])];
let cloud = Cloud::new(points);
let vr = VietorisRips::<2, f64, SimplicialComplex>::new();
let complex = vr.build(&cloud, 0.5, &());
let simplices_dim_0 = complex.elements_of_dimension(0);
assert_eq!(simplices_dim_0.len(), 1);
assert_eq!(simplices_dim_0[0].vertices(), &[0]);
assert!(complex.elements_of_dimension(1).is_empty());
}
#[test]
fn test_vietoris_rips_two_points() {
let p1 = FixedVector([0.0, 0.0]);
let p2 = FixedVector([1.0, 0.0]);
let cloud = Cloud::new(vec![p1, p2]);
let vr = VietorisRips::<2, f64, SimplicialComplex>::new();
let complex_no_edge = vr.build(&cloud, 0.5, &()); assert_eq!(complex_no_edge.elements_of_dimension(0).len(), 2);
assert!(complex_no_edge.elements_of_dimension(1).is_empty());
let complex_with_edge = vr.build(&cloud, 1.5, &());
assert_eq!(complex_with_edge.elements_of_dimension(0).len(), 2);
let simplices_dim_1 = complex_with_edge.elements_of_dimension(1);
assert_eq!(simplices_dim_1.len(), 1);
assert_eq!(simplices_dim_1[0].vertices(), &[0, 1]);
}
#[test]
fn test_vietoris_rips_triangle() {
let p0 = FixedVector([0.0, 0.0]);
let p1 = FixedVector([1.0, 0.0]);
let p2 = FixedVector([0.5, 0.866]);
let cloud = Cloud::new(vec![p0, p1, p2]);
let vr = VietorisRips::<2, f64, SimplicialComplex>::new();
let complex = vr.build(&cloud, 1.1, &());
assert_eq!(complex.elements_of_dimension(0).len(), 3);
assert_eq!(complex.elements_of_dimension(1).len(), 3);
assert_eq!(complex.elements_of_dimension(2).len(), 1);
assert_eq!(complex.elements_of_dimension(2)[0].vertices(), &[0, 1, 2]);
}
modular!(Mod7, u32, 7);
prime_field!(Mod7);
#[test]
fn test_compute_homology_filtration_basic() {
let p0 = FixedVector([0.0, 0.0]);
let p1 = FixedVector([1.0, 0.0]);
let cloud: Cloud<2, f64> = Cloud::new(vec![p0, p1]);
let vr_builder = VietorisRips::<2, f64, Homology<Mod7>>::new();
let epsilons = vec![0.5, 1.5]; let dims = HashSet::from([0, 1]);
let mut homology_results_vec = Vec::new();
for &epsilon in &epsilons {
homology_results_vec.push(vr_builder.build(&cloud, epsilon, &dims));
}
let homology_results = &homology_results_vec;
assert_eq!(homology_results.len(), 2);
let homology_at_eps0_5 = &homology_results[0];
let h0_eps0_5 = homology_at_eps0_5.get(&0).unwrap();
assert_eq!(h0_eps0_5.dimension, 0);
assert_eq!(h0_eps0_5.betti_number, 2, "H0(eps=0.5): Expected 2 connected components");
let h1_eps0_5 = homology_at_eps0_5.get(&1).unwrap();
assert_eq!(h1_eps0_5.dimension, 1);
assert_eq!(h1_eps0_5.betti_number, 0, "H1(eps=0.5): Expected 0 1-cycles");
let homology_at_eps1_5 = &homology_results[1];
let h0_eps1_5 = homology_at_eps1_5.get(&0).unwrap();
assert_eq!(h0_eps1_5.dimension, 0);
assert_eq!(h0_eps1_5.betti_number, 1, "H0(eps=1.5): Expected 1 connected component");
let h1_eps1_5 = homology_at_eps1_5.get(&1).unwrap();
assert_eq!(h1_eps1_5.dimension, 1);
assert_eq!(
h1_eps1_5.betti_number, 0,
"H1(eps=1.5): Expected 0 1-cycles (edge is contractible)"
);
}
#[cfg(feature = "parallel")]
#[test]
fn test_compute_homology_filtration_parallel_triangle() {
use crate::filtration::ParallelFiltration;
let p0 = FixedVector([0.0, 0.0]);
let p1 = FixedVector([1.0, 0.0]);
let p2 = FixedVector([0.5, 0.8660254]);
let cloud = Cloud::new(vec![p0, p1, p2]);
let vr_builder = VietorisRips::<2, f64, Homology<Boolean>>::new();
let epsilons = vec![0.5, 1.1];
let dims = HashSet::from([0, 1, 2]);
let homology_results = vr_builder.build_parallel(&cloud, epsilons, &dims);
assert_eq!(homology_results.len(), 2);
let homology_at_eps0_5 = &homology_results[0];
let h0_eps0_5 = homology_at_eps0_5.get(&0).unwrap();
assert_eq!(h0_eps0_5.dimension, 0);
assert_eq!(h0_eps0_5.betti_number, 3, "H0(eps=0.5) for triangle points");
let h1_eps0_5 = homology_at_eps0_5.get(&1).unwrap();
assert_eq!(h1_eps0_5.betti_number, 0, "H1(eps=0.5) for triangle points");
let h2_eps0_5 = homology_at_eps0_5.get(&2).unwrap();
assert_eq!(h2_eps0_5.betti_number, 0, "H2(eps=0.5) for triangle points");
let homology_at_eps1_1 = &homology_results[1];
let h0_eps1_1 = homology_at_eps1_1.get(&0).unwrap();
assert_eq!(h0_eps1_1.dimension, 0);
assert_eq!(h0_eps1_1.betti_number, 1, "H0(eps=1.1) for filled triangle");
let h1_eps1_1 = homology_at_eps1_1.get(&1).unwrap();
assert_eq!(h1_eps1_1.betti_number, 0, "H1(eps=1.1) for filled triangle");
let h2_eps1_1 = homology_at_eps1_1.get(&2).unwrap();
assert_eq!(h2_eps1_1.betti_number, 0, "H2(eps=1.1) for filled triangle");
}
}