1use super::{Filtration, Simplex, BettiNumbers};
6use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone, PartialEq)]
10pub struct BirthDeathPair {
11 pub dimension: usize,
13 pub birth: f64,
15 pub death: Option<f64>,
17}
18
19impl BirthDeathPair {
20 pub fn finite(dimension: usize, birth: f64, death: f64) -> Self {
22 Self {
23 dimension,
24 birth,
25 death: Some(death),
26 }
27 }
28
29 pub fn essential(dimension: usize, birth: f64) -> Self {
31 Self {
32 dimension,
33 birth,
34 death: None,
35 }
36 }
37
38 pub fn persistence(&self) -> f64 {
40 match self.death {
41 Some(d) => d - self.birth,
42 None => f64::INFINITY,
43 }
44 }
45
46 pub fn is_essential(&self) -> bool {
48 self.death.is_none()
49 }
50
51 pub fn midpoint(&self) -> f64 {
53 match self.death {
54 Some(d) => (self.birth + d) / 2.0,
55 None => f64::INFINITY,
56 }
57 }
58}
59
60#[derive(Debug, Clone)]
62pub struct PersistenceDiagram {
63 pub pairs: Vec<BirthDeathPair>,
65 pub max_dim: usize,
67}
68
69impl PersistenceDiagram {
70 pub fn new() -> Self {
72 Self {
73 pairs: Vec::new(),
74 max_dim: 0,
75 }
76 }
77
78 pub fn add(&mut self, pair: BirthDeathPair) {
80 self.max_dim = self.max_dim.max(pair.dimension);
81 self.pairs.push(pair);
82 }
83
84 pub fn pairs_of_dim(&self, d: usize) -> impl Iterator<Item = &BirthDeathPair> {
86 self.pairs.iter().filter(move |p| p.dimension == d)
87 }
88
89 pub fn betti_at(&self, t: f64) -> BettiNumbers {
91 let mut b0 = 0;
92 let mut b1 = 0;
93 let mut b2 = 0;
94
95 for pair in &self.pairs {
96 let alive = pair.birth <= t && pair.death.map(|d| d > t).unwrap_or(true);
97 if alive {
98 match pair.dimension {
99 0 => b0 += 1,
100 1 => b1 += 1,
101 2 => b2 += 1,
102 _ => {}
103 }
104 }
105 }
106
107 BettiNumbers::new(b0, b1, b2)
108 }
109
110 pub fn total_persistence(&self) -> f64 {
112 self.pairs
113 .iter()
114 .filter(|p| !p.is_essential())
115 .map(|p| p.persistence())
116 .sum()
117 }
118
119 pub fn average_persistence(&self) -> f64 {
121 let finite: Vec<f64> = self
122 .pairs
123 .iter()
124 .filter(|p| !p.is_essential())
125 .map(|p| p.persistence())
126 .collect();
127
128 if finite.is_empty() {
129 0.0
130 } else {
131 finite.iter().sum::<f64>() / finite.len() as f64
132 }
133 }
134
135 pub fn filter_by_persistence(&self, min_persistence: f64) -> Self {
137 Self {
138 pairs: self
139 .pairs
140 .iter()
141 .filter(|p| p.persistence() >= min_persistence)
142 .cloned()
143 .collect(),
144 max_dim: self.max_dim,
145 }
146 }
147
148 pub fn feature_counts(&self) -> Vec<usize> {
150 let mut counts = vec![0; self.max_dim + 1];
151 for pair in &self.pairs {
152 if pair.dimension <= self.max_dim {
153 counts[pair.dimension] += 1;
154 }
155 }
156 counts
157 }
158}
159
160impl Default for PersistenceDiagram {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166pub struct PersistentHomology {
168 columns: Vec<Option<HashSet<usize>>>,
170 pivot_to_col: HashMap<usize, usize>,
172 birth_times: Vec<f64>,
174 dimensions: Vec<usize>,
176}
177
178impl PersistentHomology {
179 pub fn compute(filtration: &Filtration) -> PersistenceDiagram {
181 let mut ph = Self {
182 columns: Vec::new(),
183 pivot_to_col: HashMap::new(),
184 birth_times: Vec::new(),
185 dimensions: Vec::new(),
186 };
187
188 ph.run(filtration)
189 }
190
191 fn run(&mut self, filtration: &Filtration) -> PersistenceDiagram {
192 let n = filtration.simplices.len();
193 if n == 0 {
194 return PersistenceDiagram::new();
195 }
196
197 let simplex_to_idx: HashMap<&Simplex, usize> = filtration
199 .simplices
200 .iter()
201 .enumerate()
202 .map(|(i, fs)| (&fs.simplex, i))
203 .collect();
204
205 self.columns = Vec::with_capacity(n);
207 self.birth_times = filtration.simplices.iter().map(|fs| fs.birth).collect();
208 self.dimensions = filtration.simplices.iter().map(|fs| fs.simplex.dim()).collect();
209
210 for fs in &filtration.simplices {
212 let boundary = self.boundary(&fs.simplex, &simplex_to_idx);
213 self.columns.push(if boundary.is_empty() {
214 None
215 } else {
216 Some(boundary)
217 });
218 }
219
220 self.reduce();
222
223 self.extract_pairs()
225 }
226
227 fn boundary(&self, simplex: &Simplex, idx_map: &HashMap<&Simplex, usize>) -> HashSet<usize> {
229 let mut boundary = HashSet::new();
230 for face in simplex.faces() {
231 if let Some(&idx) = idx_map.get(&face) {
232 boundary.insert(idx);
233 }
234 }
235 boundary
236 }
237
238 fn reduce(&mut self) {
240 let n = self.columns.len();
241
242 for j in 0..n {
243 while let Some(pivot) = self.get_pivot(j) {
245 if let Some(&other) = self.pivot_to_col.get(&pivot) {
246 self.add_columns(j, other);
248 } else {
249 self.pivot_to_col.insert(pivot, j);
251 break;
252 }
253 }
254 }
255 }
256
257 fn get_pivot(&self, col: usize) -> Option<usize> {
259 self.columns[col].as_ref().and_then(|c| c.iter().max().copied())
260 }
261
262 fn add_columns(&mut self, dst: usize, src: usize) {
264 let src_col = self.columns[src].clone();
265 if let (Some(ref mut dst_col), Some(ref src_col)) = (&mut self.columns[dst], &src_col) {
266 let mut new_col = HashSet::new();
268 for &idx in dst_col.iter() {
269 if !src_col.contains(&idx) {
270 new_col.insert(idx);
271 }
272 }
273 for &idx in src_col.iter() {
274 if !dst_col.contains(&idx) {
275 new_col.insert(idx);
276 }
277 }
278 if new_col.is_empty() {
279 self.columns[dst] = None;
280 } else {
281 *dst_col = new_col;
282 }
283 }
284 }
285
286 fn extract_pairs(&self) -> PersistenceDiagram {
288 let n = self.columns.len();
289 let mut diagram = PersistenceDiagram::new();
290 let mut paired = HashSet::new();
291
292 for (&pivot, &col) in &self.pivot_to_col {
294 let birth = self.birth_times[pivot];
295 let death = self.birth_times[col];
296 let dim = self.dimensions[pivot];
297
298 if death > birth {
299 diagram.add(BirthDeathPair::finite(dim, birth, death));
300 }
301
302 paired.insert(pivot);
303 paired.insert(col);
304 }
305
306 for j in 0..n {
308 if !paired.contains(&j) && self.columns[j].is_none() {
309 let dim = self.dimensions[j];
310 let birth = self.birth_times[j];
311 diagram.add(BirthDeathPair::essential(dim, birth));
312 }
313 }
314
315 diagram
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322 use crate::homology::{PointCloud, VietorisRips};
323
324 #[test]
325 fn test_birth_death_pair() {
326 let finite = BirthDeathPair::finite(0, 0.0, 1.0);
327 assert_eq!(finite.persistence(), 1.0);
328 assert!(!finite.is_essential());
329
330 let essential = BirthDeathPair::essential(0, 0.0);
331 assert!(essential.is_essential());
332 assert_eq!(essential.persistence(), f64::INFINITY);
333 }
334
335 #[test]
336 fn test_persistence_diagram() {
337 let mut diagram = PersistenceDiagram::new();
338 diagram.add(BirthDeathPair::essential(0, 0.0));
339 diagram.add(BirthDeathPair::finite(0, 0.0, 1.0));
340 diagram.add(BirthDeathPair::finite(1, 0.5, 2.0));
341
342 assert_eq!(diagram.pairs.len(), 3);
343
344 let betti = diagram.betti_at(0.75);
345 assert_eq!(betti.b0, 2); assert_eq!(betti.b1, 1); }
348
349 #[test]
350 fn test_persistent_homology_simple() {
351 let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0], 2);
353 let rips = VietorisRips::new(1, 2.0);
354 let filtration = rips.build(&cloud);
355
356 let diagram = PersistentHomology::compute(&filtration);
357
358 let h0_pairs: Vec<_> = diagram.pairs_of_dim(0).collect();
362 assert!(!h0_pairs.is_empty());
363 }
364
365 #[test]
366 fn test_persistent_homology_triangle() {
367 let cloud = PointCloud::from_flat(&[0.0, 0.0, 1.0, 0.0, 0.5, 0.866], 2);
369 let rips = VietorisRips::new(2, 2.0);
370 let filtration = rips.build(&cloud);
371
372 let diagram = PersistentHomology::compute(&filtration);
373
374 let h0_count = diagram.pairs_of_dim(0).count();
376 assert!(h0_count > 0);
377 }
378
379 #[test]
380 fn test_filter_by_persistence() {
381 let mut diagram = PersistenceDiagram::new();
382 diagram.add(BirthDeathPair::finite(0, 0.0, 0.1));
383 diagram.add(BirthDeathPair::finite(0, 0.0, 1.0));
384 diagram.add(BirthDeathPair::essential(0, 0.0));
385
386 let filtered = diagram.filter_by_persistence(0.5);
387 assert_eq!(filtered.pairs.len(), 2); }
389
390 #[test]
391 fn test_feature_counts() {
392 let mut diagram = PersistenceDiagram::new();
393 diagram.add(BirthDeathPair::finite(0, 0.0, 1.0));
394 diagram.add(BirthDeathPair::finite(0, 0.0, 1.0));
395 diagram.add(BirthDeathPair::finite(1, 0.0, 1.0));
396
397 let counts = diagram.feature_counts();
398 assert_eq!(counts[0], 2);
399 assert_eq!(counts[1], 1);
400 }
401}