ruvector_math/homology/
mod.rs1mod simplex;
27mod filtration;
28mod persistence;
29mod distance;
30
31pub use simplex::{Simplex, SimplicialComplex};
32pub use filtration::{Filtration, VietorisRips, AlphaComplex};
33pub use persistence::{PersistenceDiagram, PersistentHomology, BirthDeathPair};
34pub use distance::{BottleneckDistance, WassersteinDistance};
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct BettiNumbers {
39 pub b0: usize,
41 pub b1: usize,
43 pub b2: usize,
45}
46
47impl BettiNumbers {
48 pub fn new(b0: usize, b1: usize, b2: usize) -> Self {
50 Self { b0, b1, b2 }
51 }
52
53 pub fn total(&self) -> usize {
55 self.b0 + self.b1 + self.b2
56 }
57
58 pub fn euler_characteristic(&self) -> i64 {
60 self.b0 as i64 - self.b1 as i64 + self.b2 as i64
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct Point {
67 pub coords: Vec<f64>,
68}
69
70impl Point {
71 pub fn new(coords: Vec<f64>) -> Self {
73 Self { coords }
74 }
75
76 pub fn dim(&self) -> usize {
78 self.coords.len()
79 }
80
81 pub fn distance(&self, other: &Point) -> f64 {
83 self.coords
84 .iter()
85 .zip(other.coords.iter())
86 .map(|(a, b)| (a - b).powi(2))
87 .sum::<f64>()
88 .sqrt()
89 }
90
91 pub fn distance_sq(&self, other: &Point) -> f64 {
93 self.coords
94 .iter()
95 .zip(other.coords.iter())
96 .map(|(a, b)| (a - b).powi(2))
97 .sum()
98 }
99}
100
101#[derive(Debug, Clone)]
103pub struct PointCloud {
104 pub points: Vec<Point>,
106 pub ambient_dim: usize,
108}
109
110impl PointCloud {
111 pub fn new(points: Vec<Point>) -> Self {
113 let ambient_dim = points.first().map(|p| p.dim()).unwrap_or(0);
114 Self { points, ambient_dim }
115 }
116
117 pub fn from_flat(data: &[f64], dim: usize) -> Self {
119 let points: Vec<Point> = data
120 .chunks(dim)
121 .map(|chunk| Point::new(chunk.to_vec()))
122 .collect();
123 Self { points, ambient_dim: dim }
124 }
125
126 pub fn len(&self) -> usize {
128 self.points.len()
129 }
130
131 pub fn is_empty(&self) -> bool {
133 self.points.is_empty()
134 }
135
136 pub fn distance_matrix(&self) -> Vec<f64> {
138 let n = self.points.len();
139 let mut dist = vec![0.0; n * n];
140
141 for i in 0..n {
142 for j in i + 1..n {
143 let d = self.points[i].distance(&self.points[j]);
144 dist[i * n + j] = d;
145 dist[j * n + i] = d;
146 }
147 }
148
149 dist
150 }
151
152 pub fn bounding_box(&self) -> Option<(Point, Point)> {
154 if self.points.is_empty() {
155 return None;
156 }
157
158 let dim = self.ambient_dim;
159 let mut min_coords = vec![f64::INFINITY; dim];
160 let mut max_coords = vec![f64::NEG_INFINITY; dim];
161
162 for p in &self.points {
163 for (i, &c) in p.coords.iter().enumerate() {
164 min_coords[i] = min_coords[i].min(c);
165 max_coords[i] = max_coords[i].max(c);
166 }
167 }
168
169 Some((Point::new(min_coords), Point::new(max_coords)))
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_betti_numbers() {
179 let betti = BettiNumbers::new(1, 2, 0);
180
181 assert_eq!(betti.total(), 3);
182 assert_eq!(betti.euler_characteristic(), -1);
183 }
184
185 #[test]
186 fn test_point_distance() {
187 let p1 = Point::new(vec![0.0, 0.0]);
188 let p2 = Point::new(vec![3.0, 4.0]);
189
190 assert!((p1.distance(&p2) - 5.0).abs() < 1e-10);
191 }
192
193 #[test]
194 fn test_point_cloud() {
195 let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2);
196
197 assert_eq!(cloud.len(), 3);
198 assert_eq!(cloud.ambient_dim, 2);
199 }
200
201 #[test]
202 fn test_distance_matrix() {
203 let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2);
204 let dist = cloud.distance_matrix();
205
206 assert_eq!(dist.len(), 9);
207 assert!((dist[0 * 3 + 1] - 1.0).abs() < 1e-10); assert!((dist[0 * 3 + 2] - 1.0).abs() < 1e-10); }
210}