ruvector_math/homology/
mod.rs1mod distance;
27mod filtration;
28mod persistence;
29mod simplex;
30
31pub use distance::{BottleneckDistance, WassersteinDistance};
32pub use filtration::{AlphaComplex, Filtration, VietorisRips};
33pub use persistence::{BirthDeathPair, PersistenceDiagram, PersistentHomology};
34pub use simplex::{Simplex, SimplicialComplex};
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 {
115 points,
116 ambient_dim,
117 }
118 }
119
120 pub fn from_flat(data: &[f64], dim: usize) -> Self {
122 let points: Vec<Point> = data
123 .chunks(dim)
124 .map(|chunk| Point::new(chunk.to_vec()))
125 .collect();
126 Self {
127 points,
128 ambient_dim: dim,
129 }
130 }
131
132 pub fn len(&self) -> usize {
134 self.points.len()
135 }
136
137 pub fn is_empty(&self) -> bool {
139 self.points.is_empty()
140 }
141
142 pub fn distance_matrix(&self) -> Vec<f64> {
144 let n = self.points.len();
145 let mut dist = vec![0.0; n * n];
146
147 for i in 0..n {
148 for j in i + 1..n {
149 let d = self.points[i].distance(&self.points[j]);
150 dist[i * n + j] = d;
151 dist[j * n + i] = d;
152 }
153 }
154
155 dist
156 }
157
158 pub fn bounding_box(&self) -> Option<(Point, Point)> {
160 if self.points.is_empty() {
161 return None;
162 }
163
164 let dim = self.ambient_dim;
165 let mut min_coords = vec![f64::INFINITY; dim];
166 let mut max_coords = vec![f64::NEG_INFINITY; dim];
167
168 for p in &self.points {
169 for (i, &c) in p.coords.iter().enumerate() {
170 min_coords[i] = min_coords[i].min(c);
171 max_coords[i] = max_coords[i].max(c);
172 }
173 }
174
175 Some((Point::new(min_coords), Point::new(max_coords)))
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use super::*;
182
183 #[test]
184 fn test_betti_numbers() {
185 let betti = BettiNumbers::new(1, 2, 0);
186
187 assert_eq!(betti.total(), 3);
188 assert_eq!(betti.euler_characteristic(), -1);
189 }
190
191 #[test]
192 fn test_point_distance() {
193 let p1 = Point::new(vec![0.0, 0.0]);
194 let p2 = Point::new(vec![3.0, 4.0]);
195
196 assert!((p1.distance(&p2) - 5.0).abs() < 1e-10);
197 }
198
199 #[test]
200 fn test_point_cloud() {
201 let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2);
202
203 assert_eq!(cloud.len(), 3);
204 assert_eq!(cloud.ambient_dim, 2);
205 }
206
207 #[test]
208 fn test_distance_matrix() {
209 let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.0, 1.0], 2);
210 let dist = cloud.distance_matrix();
211
212 assert_eq!(dist.len(), 9);
213 assert!((dist[0 * 3 + 1] - 1.0).abs() < 1e-10); assert!((dist[0 * 3 + 2] - 1.0).abs() < 1e-10); }
216}