1use super::delaunay_2::{delaunay_triangulation_2, DelaunayError};
24use super::primitives::Point2;
25use super::voronoi_2::circumcenter;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum TdaError {
34 TooFewPoints { got: usize },
36 DelaunayFailed(DelaunayError),
38 BufferTooSmall { needed: usize, have: usize },
40}
41
42impl core::fmt::Display for TdaError {
43 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
44 match self {
45 Self::TooFewPoints { got } => write!(f, "tda: too few points: {got}"),
46 Self::DelaunayFailed(e) => write!(f, "tda: delaunay failed: {e:?}"),
47 Self::BufferTooSmall { needed, have } => {
48 write!(f, "tda: buffer too small, need {needed}, have {have}")
49 }
50 }
51 }
52}
53
54impl std::error::Error for TdaError {}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
62pub struct Simplex {
63 pub dim: u8,
65 pub v0: u32,
68 pub v1: u32,
69 pub v2: u32,
70 pub birth: u64, }
74
75#[derive(Debug, Clone, Copy, PartialEq)]
77pub struct PersistencePair {
78 pub dim: u8,
80 pub birth: f64,
82 pub death: f64,
84}
85
86pub fn alpha_filtration_2d(
99 points: &[Point2],
100 scratch_delaunay: &mut [u32],
101 out_triangles: &mut [[u32; 3]],
102 out_simplices: &mut [Simplex],
103) -> Result<usize, TdaError> {
104 if points.len() < 3 {
105 return Err(TdaError::TooFewPoints { got: points.len() });
106 }
107 let n = points.len();
108 let max_tris = 2 * n + 1;
109 if out_triangles.len() < max_tris {
110 return Err(TdaError::BufferTooSmall {
111 needed: max_tris,
112 have: out_triangles.len(),
113 });
114 }
115 let max_simplices = n + 3 * n + 2 * n;
117 if out_simplices.len() < max_simplices {
118 return Err(TdaError::BufferTooSmall {
119 needed: max_simplices,
120 have: out_simplices.len(),
121 });
122 }
123
124 let tri_count = delaunay_triangulation_2(points, scratch_delaunay, out_triangles)
126 .map_err(TdaError::DelaunayFailed)?;
127
128 let mut count = 0usize;
129
130 for i in 0..n {
132 out_simplices[count] = Simplex {
133 dim: 0,
134 v0: i as u32,
135 v1: 0,
136 v2: 0,
137 birth: 0.0f64.to_bits(),
138 };
139 count += 1;
140 }
141
142 let edge_start = count;
146 for t in 0..tri_count {
147 let [ia, ib, ic] = out_triangles[t];
148 for &(u, v) in &[(ia, ib), (ib, ic), (ia, ic)] {
149 let (a, b) = if u < v { (u, v) } else { (v, u) };
150 let mut found = false;
152 for e in edge_start..count {
153 if out_simplices[e].v0 == a && out_simplices[e].v1 == b {
154 found = true;
155 break;
156 }
157 }
158 if !found {
159 let pa = points[a as usize];
160 let pb = points[b as usize];
161 let half_len = ((pa.x - pb.x).powi(2) + (pa.y - pb.y).powi(2)).sqrt() / 2.0;
162 out_simplices[count] = Simplex {
163 dim: 1,
164 v0: a,
165 v1: b,
166 v2: 0,
167 birth: half_len.to_bits(),
168 };
169 count += 1;
170 }
171 }
172 }
173
174 for t in 0..tri_count {
176 let [ia, ib, ic] = out_triangles[t];
177 let a = points[ia as usize];
178 let b = points[ib as usize];
179 let c = points[ic as usize];
180 let cc = circumcenter(a, b, c);
181 let r = ((cc.x - a.x).powi(2) + (cc.y - a.y).powi(2)).sqrt();
182 out_simplices[count] = Simplex {
183 dim: 2,
184 v0: ia,
185 v1: ib,
186 v2: ic,
187 birth: r.to_bits(),
188 };
189 count += 1;
190 }
191
192 out_simplices[..count].sort_unstable();
194
195 Ok(count)
196}
197
198pub fn compute_persistence(
213 simplices: &[Simplex],
214 out_pairs: &mut [PersistencePair],
215) -> Result<usize, TdaError> {
216 if out_pairs.len() < simplices.len() {
217 return Err(TdaError::BufferTooSmall {
218 needed: simplices.len(),
219 have: out_pairs.len(),
220 });
221 }
222
223 let n = simplices.len();
229 let mut parent = [0u32; 1024]; if n > 1024 {
231 return Err(TdaError::BufferTooSmall {
234 needed: 1024,
235 have: n,
236 });
237 }
238
239 for i in 0..1024 {
241 parent[i] = i as u32;
242 }
243
244 fn find(parent: &mut [u32], x: u32) -> u32 {
245 let mut root = x;
246 while parent[root as usize] != root {
247 root = parent[root as usize];
248 }
249 let mut cur = x;
251 while parent[cur as usize] != root {
252 let next = parent[cur as usize];
253 parent[cur as usize] = root;
254 cur = next;
255 }
256 root
257 }
258
259 fn union(parent: &mut [u32], a: u32, b: u32) -> (u32, u32) {
260 let ra = find(parent, a);
261 let rb = find(parent, b);
262 if ra == rb {
263 return (ra, rb); }
265 let (new_root, old_root) = if ra < rb { (ra, rb) } else { (rb, ra) };
267 parent[old_root as usize] = new_root;
268 (new_root, old_root)
269 }
270
271 let mut pair_count = 0usize;
272 let mut component_births: Vec<(u32, f64)> = Vec::new(); let mut active_h1: Vec<(f64, usize)> = Vec::new();
274
275 for i in 0..n {
276 let s = simplices[i];
277 let birth = f64::from_bits(s.birth);
278
279 match s.dim {
280 0 => {
281 component_births.push((s.v0, birth));
283 }
284 1 => {
285 let (ra, rb) = union(&mut parent, s.v0, s.v1);
287 if ra == rb {
288 active_h1.push((birth, i));
290 } else {
291 let dead_root = if ra < rb { rb } else { ra };
294 if let Some(pos) = component_births.iter().position(|(r, _)| *r == dead_root) {
296 let (_, dead_birth) = component_births[pos];
297 if dead_birth >= birth {
299 } else {
301 out_pairs[pair_count] = PersistencePair {
302 dim: 0,
303 birth: dead_birth,
304 death: birth,
305 };
306 pair_count += 1;
307 }
308 component_births.remove(pos);
309 }
310 }
311 }
312 2 => {
313 let (va, vb, vc) = (s.v0, s.v1, s.v2);
315 let edges = [
316 (va.min(vb), va.max(vb)),
317 (vb.min(vc), vb.max(vc)),
318 (va.min(vc), va.max(vc)),
319 ];
320
321 if let Some(pos) = active_h1
324 .iter()
325 .enumerate()
326 .filter(|(_, &(hb, edge_idx))| {
327 hb <= birth && {
328 let se = simplices[edge_idx];
329 let e = (se.v0.min(se.v1), se.v0.max(se.v1));
330 edges.contains(&e)
331 }
332 })
333 .max_by(|(_, &(a, _)), (_, &(b, _))| {
334 a.partial_cmp(&b).unwrap_or(core::cmp::Ordering::Equal)
335 })
336 .map(|(pos, _)| pos)
337 {
338 let (h1_birth, _) = active_h1[pos];
339 out_pairs[pair_count] = PersistencePair {
340 dim: 1,
341 birth: h1_birth,
342 death: birth,
343 };
344 pair_count += 1;
345 active_h1.remove(pos);
346 }
347 }
348 _ => {}
349 }
350 }
351
352 for &(_, birth) in &component_births {
354 out_pairs[pair_count] = PersistencePair {
355 dim: 0,
356 birth,
357 death: f64::INFINITY,
358 };
359 pair_count += 1;
360 }
361
362 for &(birth, _) in &active_h1 {
364 out_pairs[pair_count] = PersistencePair {
365 dim: 1,
366 birth,
367 death: f64::INFINITY,
368 };
369 pair_count += 1;
370 }
371
372 out_pairs[..pair_count].sort_by(|a, b| {
374 a.dim
375 .cmp(&b.dim)
376 .then(
377 a.birth
378 .partial_cmp(&b.birth)
379 .unwrap_or(core::cmp::Ordering::Equal),
380 )
381 .then(
382 a.death
383 .partial_cmp(&b.death)
384 .unwrap_or(core::cmp::Ordering::Equal),
385 )
386 });
387
388 Ok(pair_count)
389}
390
391pub fn persistence_hash(pairs: &[PersistencePair]) -> u64 {
397 let mut hash: u64 = 0xcbf29ce484222325;
398 for p in pairs {
399 hash ^= p.dim as u64;
400 hash = hash.wrapping_mul(0x100000001b3);
401 hash ^= p.birth.to_bits();
402 hash = hash.wrapping_mul(0x100000001b3);
403 hash ^= p.death.to_bits();
404 hash = hash.wrapping_mul(0x100000001b3);
405 }
406 hash
407}
408
409#[cfg(test)]
414mod tests {
415 use super::*;
416
417 fn circle_points_jittered(n: usize, r: f64) -> Vec<Point2> {
418 (0..n)
419 .map(|i| {
420 let angle = 2.0 * core::f64::consts::PI * i as f64 / n as f64;
421 let r_jit = r + (i as f64 * 0.0001).sin() * 0.01;
422 Point2::new(r_jit * angle.cos(), r_jit * angle.sin())
423 })
424 .collect()
425 }
426
427 fn two_clusters() -> Vec<Point2> {
428 let mut pts = Vec::new();
429 for i in 0..10 {
431 let a = 2.0 * core::f64::consts::PI * i as f64 / 10.0;
432 pts.push(Point2::new(a.cos() * 0.5, a.sin() * 0.5));
433 }
434 for i in 0..10 {
436 let a = 2.0 * core::f64::consts::PI * i as f64 / 10.0;
437 pts.push(Point2::new(5.0 + a.cos() * 0.5, a.sin() * 0.5));
438 }
439 pts
440 }
441
442 #[test]
443 fn alpha_filtration_basic() {
444 let pts = circle_points_jittered(10, 1.0);
445 let n = pts.len();
446 let mut scratch = vec![0u32; n];
447 let mut tris = vec![[0u32; 3]; 2 * n + 1];
448 let mut simplices = vec![Simplex::default(); n + 3 * n + 2 * n];
449
450 let count = alpha_filtration_2d(&pts, &mut scratch, &mut tris, &mut simplices).unwrap();
451
452 assert!(count > n, "should have more than just vertices");
453 for i in 0..n {
455 assert_eq!(simplices[i].dim, 0, "vertex {i} should be dim 0");
456 }
457 }
458
459 #[test]
460 fn persistence_circle_has_one_h1() {
461 let pts = circle_points_jittered(15, 1.0);
464 let n = pts.len();
465 let mut scratch = vec![0u32; n];
466 let mut tris = vec![[0u32; 3]; 2 * n + 1];
467 let mut simplices = vec![Simplex::default(); n + 3 * n + 2 * n];
468
469 let count = alpha_filtration_2d(&pts, &mut scratch, &mut tris, &mut simplices).unwrap();
470
471 let mut pairs = vec![
472 PersistencePair {
473 dim: 0,
474 birth: 0.0,
475 death: 0.0
476 };
477 count
478 ];
479 let n_pairs = compute_persistence(&simplices[..count], &mut pairs).unwrap();
480
481 let h0_count = pairs[..n_pairs].iter().filter(|p| p.dim == 0).count();
483 let h1_count = pairs[..n_pairs].iter().filter(|p| p.dim == 1).count();
484
485 assert!(h0_count > 0, "should have H0 features");
487 assert!(h1_count > 0, "circle should have at least one H1 feature");
489 }
490
491 #[test]
492 fn persistence_two_clusters_two_h0() {
493 let pts = two_clusters();
494 let n = pts.len();
495 let mut scratch = vec![0u32; n];
496 let mut tris = vec![[0u32; 3]; 2 * n + 1];
497 let mut simplices = vec![Simplex::default(); n + 3 * n + 2 * n];
498
499 let count = alpha_filtration_2d(&pts, &mut scratch, &mut tris, &mut simplices).unwrap();
500
501 let mut pairs = vec![
502 PersistencePair {
503 dim: 0,
504 birth: 0.0,
505 death: 0.0
506 };
507 count
508 ];
509 let n_pairs = compute_persistence(&simplices[..count], &mut pairs).unwrap();
510
511 let h0_essential = pairs[..n_pairs]
513 .iter()
514 .filter(|p| p.dim == 0 && p.death == f64::INFINITY)
515 .count();
516 assert!(h0_essential >= 1, "should have at least 1 essential H0");
517 }
518
519 #[test]
520 fn persistence_determinism() {
521 let pts = circle_points_jittered(12, 1.0);
522 let n = pts.len();
523
524 let mut s1 = vec![0u32; n];
525 let mut t1 = vec![[0u32; 3]; 2 * n + 1];
526 let mut simp1 = vec![Simplex::default(); n + 3 * n + 2 * n];
527 let count1 = alpha_filtration_2d(&pts, &mut s1, &mut t1, &mut simp1).unwrap();
528 let mut pairs1 = vec![
529 PersistencePair {
530 dim: 0,
531 birth: 0.0,
532 death: 0.0
533 };
534 count1
535 ];
536 let np1 = compute_persistence(&simp1[..count1], &mut pairs1).unwrap();
537
538 let mut s2 = vec![0u32; n];
539 let mut t2 = vec![[0u32; 3]; 2 * n + 1];
540 let mut simp2 = vec![Simplex::default(); n + 3 * n + 2 * n];
541 let count2 = alpha_filtration_2d(&pts, &mut s2, &mut t2, &mut simp2).unwrap();
542 let mut pairs2 = vec![
543 PersistencePair {
544 dim: 0,
545 birth: 0.0,
546 death: 0.0
547 };
548 count2
549 ];
550 let np2 = compute_persistence(&simp2[..count2], &mut pairs2).unwrap();
551
552 assert_eq!(count1, count2);
553 assert_eq!(np1, np2);
554 assert_eq!(
555 persistence_hash(&pairs1[..np1]),
556 persistence_hash(&pairs2[..np2])
557 );
558 }
559
560 #[test]
561 fn alpha_filtration_too_few_points() {
562 let pts = vec![Point2::new(0.0, 0.0), Point2::new(1.0, 0.0)];
563 let mut scratch = vec![0u32; 2];
564 let mut tris = vec![[0u32; 3]; 5];
565 let mut simplices = vec![Simplex::default(); 10];
566 assert!(matches!(
567 alpha_filtration_2d(&pts, &mut scratch, &mut tris, &mut simplices),
568 Err(TdaError::TooFewPoints { .. })
569 ));
570 }
571}