1use crate::error::{Result, TransformError};
19use std::collections::{HashMap, HashSet};
20
21#[derive(Debug, Clone)]
25pub struct AlphaConfig {
26 pub max_alpha: f64,
28}
29
30impl Default for AlphaConfig {
31 fn default() -> Self {
32 Self {
33 max_alpha: f64::INFINITY,
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq)]
42pub struct Simplex {
43 pub vertices: Vec<usize>,
45 pub filtration_value: f64,
47}
48
49impl Simplex {
50 pub fn dimension(&self) -> usize {
52 self.vertices.len().saturating_sub(1)
53 }
54
55 pub fn boundary_faces(&self) -> Vec<Vec<usize>> {
60 if self.vertices.len() <= 1 {
61 return Vec::new();
62 }
63 (0..self.vertices.len())
64 .map(|i| {
65 self.vertices
66 .iter()
67 .enumerate()
68 .filter(|(j, _)| *j != i)
69 .map(|(_, &v)| v)
70 .collect::<Vec<usize>>()
71 })
72 .collect()
73 }
74}
75
76pub fn circumradius_2d(a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> f64 {
82 let ax = a[0] - c[0];
83 let ay = a[1] - c[1];
84 let bx = b[0] - c[0];
85 let by = b[1] - c[1];
86
87 let d = 2.0 * (ax * by - ay * bx);
88 if d.abs() < 1e-14 {
89 return f64::INFINITY; }
91
92 let ux = (by * (ax * ax + ay * ay) - ay * (bx * bx + by * by)) / d;
93 let uy = (ax * (bx * bx + by * by) - bx * (ax * ax + ay * ay)) / d;
94
95 (ux * ux + uy * uy).sqrt()
96}
97
98#[inline]
100fn dist2d(a: [f64; 2], b: [f64; 2]) -> f64 {
101 let dx = a[0] - b[0];
102 let dy = a[1] - b[1];
103 (dx * dx + dy * dy).sqrt()
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, Hash)]
110struct Triangle {
111 v: [usize; 3],
112}
113
114impl Triangle {
115 fn new(a: usize, b: usize, c: usize) -> Self {
116 let mut v = [a, b, c];
117 v.sort_unstable();
118 Self { v }
119 }
120}
121
122fn in_circumcircle(p: [f64; 2], a: [f64; 2], b: [f64; 2], c: [f64; 2]) -> bool {
124 let ax = a[0] - p[0];
126 let ay = a[1] - p[1];
127 let bx = b[0] - p[0];
128 let by = b[1] - p[1];
129 let cx = c[0] - p[0];
130 let cy = c[1] - p[1];
131
132 let det = ax * (by * (cx * cx + cy * cy) - cy * (bx * bx + by * by))
133 - ay * (bx * (cx * cx + cy * cy) - cx * (bx * bx + by * by))
134 + (ax * ax + ay * ay) * (bx * cy - by * cx);
135
136 let orientation = (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
139 if orientation > 0.0 {
140 det > 0.0
141 } else {
142 det < 0.0
143 }
144}
145
146fn bowyer_watson(points: &[[f64; 2]]) -> Vec<Triangle> {
151 let n = points.len();
152 if n < 3 {
153 return Vec::new();
154 }
155
156 let (mut min_x, mut min_y) = (f64::INFINITY, f64::INFINITY);
158 let (mut max_x, mut max_y) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
159 for p in points {
160 min_x = min_x.min(p[0]);
161 min_y = min_y.min(p[1]);
162 max_x = max_x.max(p[0]);
163 max_y = max_y.max(p[1]);
164 }
165 let dx = max_x - min_x;
166 let dy = max_y - min_y;
167 let delta_max = dx.max(dy).max(1.0);
168 let mid_x = (min_x + max_x) / 2.0;
169 let mid_y = (min_y + max_y) / 2.0;
170
171 let st0 = [mid_x - 20.0 * delta_max, mid_y - delta_max];
173 let st1 = [mid_x, mid_y + 20.0 * delta_max];
174 let st2 = [mid_x + 20.0 * delta_max, mid_y - delta_max];
175 let super_indices = [n, n + 1, n + 2];
176
177 let mut pts: Vec<[f64; 2]> = points.to_vec();
179 pts.push(st0);
180 pts.push(st1);
181 pts.push(st2);
182
183 let mut triangles: HashSet<Triangle> = HashSet::from([Triangle::new(
184 super_indices[0],
185 super_indices[1],
186 super_indices[2],
187 )]);
188
189 for (idx, &p) in points.iter().enumerate() {
190 let bad: Vec<Triangle> = triangles
192 .iter()
193 .filter(|t| in_circumcircle(p, pts[t.v[0]], pts[t.v[1]], pts[t.v[2]]))
194 .cloned()
195 .collect();
196
197 let mut edge_count: HashMap<(usize, usize), usize> = HashMap::new();
199 for t in &bad {
200 let edges = [(t.v[0], t.v[1]), (t.v[0], t.v[2]), (t.v[1], t.v[2])];
201 for (a, b) in edges {
202 let key = (a.min(b), a.max(b));
203 *edge_count.entry(key).or_insert(0) += 1;
204 }
205 }
206 let boundary: Vec<(usize, usize)> = edge_count
207 .into_iter()
208 .filter(|(_, cnt)| *cnt == 1)
209 .map(|(e, _)| e)
210 .collect();
211
212 for t in &bad {
214 triangles.remove(t);
215 }
216
217 for (a, b) in boundary {
219 triangles.insert(Triangle::new(idx, a, b));
220 }
221 }
222
223 triangles
225 .into_iter()
226 .filter(|t| !t.v.iter().any(|&v| v >= n))
227 .collect()
228}
229
230#[derive(Debug, Clone)]
234pub struct AlphaComplex {
235 pub points: Vec<[f64; 2]>,
237 pub simplices: Vec<Simplex>,
239}
240
241impl AlphaComplex {
242 pub fn new(points: &[[f64; 2]]) -> Self {
248 let n = points.len();
249 let mut simplex_map: HashMap<Vec<usize>, f64> = HashMap::new();
250
251 for i in 0..n {
253 simplex_map.insert(vec![i], 0.0);
254 }
255
256 if n >= 2 {
257 let triangles = bowyer_watson(points);
258
259 let mut edges: HashSet<(usize, usize)> = HashSet::new();
261 for t in &triangles {
262 edges.insert((t.v[0].min(t.v[1]), t.v[0].max(t.v[1])));
263 edges.insert((t.v[0].min(t.v[2]), t.v[0].max(t.v[2])));
264 edges.insert((t.v[1].min(t.v[2]), t.v[1].max(t.v[2])));
265 }
266
267 for (a, b) in &edges {
269 let d = dist2d(points[*a], points[*b]) / 2.0;
270 let key = vec![*a, *b];
271 let entry = simplex_map.entry(key).or_insert(f64::INFINITY);
272 if d < *entry {
273 *entry = d;
274 }
275 }
276
277 for t in &triangles {
279 let a = t.v[0];
280 let b = t.v[1];
281 let c = t.v[2];
282 let r = circumradius_2d(points[a], points[b], points[c]);
283 let e_ab = simplex_map
285 .get(&vec![a.min(b), a.max(b)])
286 .copied()
287 .unwrap_or(0.0);
288 let e_ac = simplex_map
289 .get(&vec![a.min(c), a.max(c)])
290 .copied()
291 .unwrap_or(0.0);
292 let e_bc = simplex_map
293 .get(&vec![b.min(c), b.max(c)])
294 .copied()
295 .unwrap_or(0.0);
296 let fv = r.max(e_ab).max(e_ac).max(e_bc);
297 simplex_map.insert(vec![a, b, c], fv);
298 }
299 }
300
301 let mut simplices: Vec<Simplex> = simplex_map
303 .into_iter()
304 .map(|(vertices, fv)| Simplex {
305 vertices,
306 filtration_value: fv,
307 })
308 .collect();
309
310 simplices.sort_by(|a, b| {
312 a.filtration_value
313 .partial_cmp(&b.filtration_value)
314 .unwrap_or(std::cmp::Ordering::Equal)
315 .then(a.vertices.len().cmp(&b.vertices.len()))
316 .then(a.vertices.cmp(&b.vertices))
317 });
318
319 Self {
320 points: points.to_vec(),
321 simplices,
322 }
323 }
324
325 pub fn filtration(&self, alpha: f64) -> Vec<Simplex> {
327 self.simplices
328 .iter()
329 .filter(|s| s.filtration_value <= alpha)
330 .cloned()
331 .collect()
332 }
333
334 pub fn persistence_pairs(&self) -> Vec<(f64, f64, usize)> {
338 compute_persistence_from_simplices(&self.simplices)
339 }
340}
341
342pub fn compute_persistence_from_simplices(simplices: &[Simplex]) -> Vec<(f64, f64, usize)> {
351 let n = simplices.len();
352
353 let mut simplex_index: HashMap<Vec<usize>, usize> = HashMap::new();
355 for (idx, s) in simplices.iter().enumerate() {
356 simplex_index.insert(s.vertices.clone(), idx);
357 }
358
359 let mut columns: Vec<Vec<usize>> = simplices
361 .iter()
362 .map(|s| {
363 let mut col: Vec<usize> = s
364 .boundary_faces()
365 .iter()
366 .filter_map(|face| simplex_index.get(face).copied())
367 .collect();
368 col.sort_unstable();
369 col
370 })
371 .collect();
372
373 let mut pivot_col: HashMap<usize, usize> = HashMap::new(); let mut pairs: Vec<(f64, f64, usize)> = Vec::new();
376 let mut paired: HashSet<usize> = HashSet::new();
377
378 for j in 0..n {
379 while let Some(&pivot) = columns[j].last() {
381 if let Some(&k) = pivot_col.get(&pivot) {
382 let col_k = columns[k].clone();
384 sym_diff_sorted(&mut columns[j], &col_k);
385 } else {
386 break;
387 }
388 }
389
390 if let Some(&pivot) = columns[j].last() {
391 pivot_col.insert(pivot, j);
393 let birth_idx = pivot;
394 let death_idx = j;
395 let dim = simplices[birth_idx].dimension();
396 pairs.push((
397 simplices[birth_idx].filtration_value,
398 simplices[death_idx].filtration_value,
399 dim,
400 ));
401 paired.insert(birth_idx);
402 paired.insert(death_idx);
403 }
404 }
405
406 pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
410 pairs
411}
412
413pub fn sym_diff_sorted(a: &mut Vec<usize>, b: &[usize]) {
415 let mut result: Vec<usize> = Vec::new();
416 let mut ai = 0;
417 let mut bi = 0;
418 while ai < a.len() && bi < b.len() {
419 match a[ai].cmp(&b[bi]) {
420 std::cmp::Ordering::Less => {
421 result.push(a[ai]);
422 ai += 1;
423 }
424 std::cmp::Ordering::Greater => {
425 result.push(b[bi]);
426 bi += 1;
427 }
428 std::cmp::Ordering::Equal => {
429 ai += 1;
431 bi += 1;
432 }
433 }
434 }
435 result.extend_from_slice(&a[ai..]);
436 result.extend_from_slice(&b[bi..]);
437 *a = result;
438}
439
440#[cfg(test)]
443mod tests {
444 use super::*;
445
446 fn equilateral_pts() -> Vec<[f64; 2]> {
448 vec![[0.0, 0.0], [1.0, 0.0], [0.5, 3_f64.sqrt() / 2.0]]
449 }
450
451 #[test]
452 fn test_circumradius_equilateral() {
453 let pts = equilateral_pts();
455 let r = circumradius_2d(pts[0], pts[1], pts[2]);
456 let expected = 1.0 / 3_f64.sqrt();
457 assert!(
458 (r - expected).abs() < 1e-10,
459 "circumradius={r}, expected={expected}"
460 );
461 }
462
463 #[test]
464 fn test_circumradius_right_triangle() {
465 let a = [0.0, 0.0];
467 let b = [3.0, 0.0];
468 let c = [0.0, 4.0];
469 let r = circumradius_2d(a, b, c);
470 assert!((r - 2.5).abs() < 1e-10, "circumradius={r}");
471 }
472
473 #[test]
474 fn test_filtration_threshold() {
475 let pts: Vec<[f64; 2]> = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.5, 0.5]];
477 let ac = AlphaComplex::new(&pts);
478
479 let f0 = ac.filtration(0.0);
481 assert!(f0.iter().all(|s| s.dimension() == 0));
482 assert_eq!(f0.len(), 5);
483
484 let f_inf = ac.filtration(f64::INFINITY);
486 assert!(!f_inf.is_empty());
487 assert!(f_inf.iter().filter(|s| s.dimension() == 0).count() >= 5);
489 }
490
491 #[test]
492 fn test_persistence_pairs_triangle() {
493 let pts = equilateral_pts();
494 let ac = AlphaComplex::new(&pts);
495 let pairs = ac.persistence_pairs();
496 for (birth, death, _dim) in &pairs {
499 assert!(birth <= death, "Invalid pair: birth={birth}, death={death}");
500 }
501 }
502
503 #[test]
504 fn test_persistence_pairs_not_empty_for_triangle() {
505 let pts = equilateral_pts();
506 let ac = AlphaComplex::new(&pts);
507 let pairs = ac.persistence_pairs();
508 assert!(!pairs.is_empty(), "Expected at least one persistence pair");
510 }
511
512 #[test]
513 fn test_five_point_cloud() {
514 let pts: Vec<[f64; 2]> = vec![[0.0, 0.0], [2.0, 0.0], [2.0, 2.0], [0.0, 2.0], [1.0, 1.0]];
515 let ac = AlphaComplex::new(&pts);
516 assert_eq!(
518 ac.simplices.iter().filter(|s| s.dimension() == 0).count(),
519 5
520 );
521 assert!(
523 ac.simplices.iter().any(|s| s.dimension() == 1),
524 "Expected edges"
525 );
526 }
527}