mod distance;
mod filtration;
mod persistence;
mod simplex;
pub use distance::{BottleneckDistance, WassersteinDistance};
pub use filtration::{AlphaComplex, Filtration, VietorisRips};
pub use persistence::{BirthDeathPair, PersistenceDiagram, PersistentHomology};
pub use simplex::{Simplex, SimplicialComplex};
#[derive(Debug, Clone, PartialEq)]
pub struct BettiNumbers {
pub b0: usize,
pub b1: usize,
pub b2: usize,
}
impl BettiNumbers {
pub fn new(b0: usize, b1: usize, b2: usize) -> Self {
Self { b0, b1, b2 }
}
pub fn total(&self) -> usize {
self.b0 + self.b1 + self.b2
}
pub fn euler_characteristic(&self) -> i64 {
self.b0 as i64 - self.b1 as i64 + self.b2 as i64
}
}
#[derive(Debug, Clone)]
pub struct Point {
pub coords: Vec<f64>,
}
impl Point {
pub fn new(coords: Vec<f64>) -> Self {
Self { coords }
}
pub fn dim(&self) -> usize {
self.coords.len()
}
pub fn distance(&self, other: &Point) -> f64 {
self.coords
.iter()
.zip(other.coords.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt()
}
pub fn distance_sq(&self, other: &Point) -> f64 {
self.coords
.iter()
.zip(other.coords.iter())
.map(|(a, b)| (a - b).powi(2))
.sum()
}
}
#[derive(Debug, Clone)]
pub struct PointCloud {
pub points: Vec<Point>,
pub ambient_dim: usize,
}
impl PointCloud {
pub fn new(points: Vec<Point>) -> Self {
let ambient_dim = points.first().map(|p| p.dim()).unwrap_or(0);
Self {
points,
ambient_dim,
}
}
pub fn from_flat(data: &[f64], dim: usize) -> Self {
let points: Vec<Point> = data
.chunks(dim)
.map(|chunk| Point::new(chunk.to_vec()))
.collect();
Self {
points,
ambient_dim: dim,
}
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
pub fn distance_matrix(&self) -> Vec<f64> {
let n = self.points.len();
let mut dist = vec![0.0; n * n];
for i in 0..n {
for j in i + 1..n {
let d = self.points[i].distance(&self.points[j]);
dist[i * n + j] = d;
dist[j * n + i] = d;
}
}
dist
}
pub fn bounding_box(&self) -> Option<(Point, Point)> {
if self.points.is_empty() {
return None;
}
let dim = self.ambient_dim;
let mut min_coords = vec![f64::INFINITY; dim];
let mut max_coords = vec![f64::NEG_INFINITY; dim];
for p in &self.points {
for (i, &c) in p.coords.iter().enumerate() {
min_coords[i] = min_coords[i].min(c);
max_coords[i] = max_coords[i].max(c);
}
}
Some((Point::new(min_coords), Point::new(max_coords)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_betti_numbers() {
let betti = BettiNumbers::new(1, 2, 0);
assert_eq!(betti.total(), 3);
assert_eq!(betti.euler_characteristic(), -1);
}
#[test]
fn test_point_distance() {
let p1 = Point::new(vec![0.0, 0.0]);
let p2 = Point::new(vec![3.0, 4.0]);
assert!((p1.distance(&p2) - 5.0).abs() < 1e-10);
}
#[test]
fn test_point_cloud() {
let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2);
assert_eq!(cloud.len(), 3);
assert_eq!(cloud.ambient_dim, 2);
}
#[test]
fn test_distance_matrix() {
let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2);
let dist = cloud.distance_matrix();
assert_eq!(dist.len(), 9);
assert!((dist[0 * 3 + 1] - 1.0).abs() < 1e-10); assert!((dist[0 * 3 + 2] - 1.0).abs() < 1e-10); }
}