Skip to main content

persistence_agent/
barcode.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use crate::boundary::BoundaryMatrix;
3use crate::error::PersistenceError;
4use crate::vietoris_rips::VietorisRipsComplex;
5
6fn serialize_death<S: Serializer>(death: &f64, s: S) -> Result<S::Ok, S::Error> {
7    if death.is_infinite() {
8        s.serialize_str("inf")
9    } else {
10        s.serialize_f64(*death)
11    }
12}
13
14fn deserialize_death<'de, D: Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
15    use serde::de;
16    struct DeathVisitor;
17    impl<'de> de::Visitor<'de> for DeathVisitor {
18        type Value = f64;
19        fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str("f64 or \"inf\"") }
20        fn visit_f64<E: de::Error>(self, v: f64) -> Result<f64, E> { Ok(v) }
21        fn visit_str<E: de::Error>(self, v: &str) -> Result<f64, E> {
22            if v == "inf" { Ok(f64::INFINITY) } else { Err(E::custom("expected \"inf\"")) }
23        }
24    }
25    d.deserialize_any(DeathVisitor)
26}
27
28/// A single bar in a persistence barcode.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct PersistencePair {
31    pub birth: f64,
32    #[serde(serialize_with = "serialize_death", deserialize_with = "deserialize_death")]
33    pub death: f64,
34    pub dimension: usize,
35}
36
37impl PersistencePair {
38    pub fn persistence(&self) -> f64 {
39        self.death - self.birth
40    }
41}
42
43/// The full persistence barcode for all dimensions.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct Barcode {
46    pub pairs: Vec<PersistencePair>,
47    /// Betti curve sampled at each unique filtration value:
48    /// (epsilon, [β₀, β₁, β₂, …])
49    pub betti_curve: Vec<(f64, Vec<usize>)>,
50}
51
52impl Barcode {
53    /// Compute the barcode from a Vietoris-Rips complex.
54    pub fn compute(vr: &VietorisRipsComplex) -> Result<Self, PersistenceError> {
55        let mut bm = BoundaryMatrix::build(vr)?;
56        let low_map = bm.reduce();
57
58        let n = vr.n_simplices();
59        let mut pairs = Vec::new();
60
61        for (&row, &col) in &low_map {
62            let birth_dim = vr.simplex_dimension(row);
63            // birth simplex is `row`, death simplex is `col`
64            let birth_eps = vr.filtration_values[row];
65            let death_eps = vr.filtration_values[col];
66            pairs.push(PersistencePair {
67                birth: birth_eps,
68                death: death_eps,
69                dimension: birth_dim,
70            });
71        }
72
73        // Remove dead code: unpaired columns logic was unused
74
75        // Find unpaired birth simplices: those that never appear as `low(j)` for any j
76        let mut is_low_of_some_col = vec![false; n];
77        for &row in low_map.keys() {
78            is_low_of_some_col[row] = true;
79        }
80
81        for (i, is_low) in is_low_of_some_col.iter().enumerate() {
82            if vr.simplex_dimension(i) == 0 && !is_low {
83                // Vertex that never got paired — persists to infinity
84                pairs.push(PersistencePair {
85                    birth: vr.filtration_values[i],
86                    death: f64::INFINITY,
87                    dimension: 0,
88                });
89            }
90        }
91
92        // Sort by (dimension, birth)
93        pairs.sort_by(|a, b| {
94            a.dimension
95                .cmp(&b.dimension)
96                .then_with(|| a.birth.partial_cmp(&b.birth).unwrap_or(std::cmp::Ordering::Equal))
97        });
98
99        // Compute Betti curve
100        let max_dim = vr.max_dimension;
101        let mut thresholds = vr.filtration_values.to_vec();
102        thresholds.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
103        thresholds.dedup_by(|a, b| (*a - *b).abs() < 1e-12);
104
105        let betti_curve = compute_betti_curve(&pairs, &thresholds, max_dim);
106
107        Ok(Self { pairs, betti_curve })
108    }
109
110    /// Pairs filtered by dimension.
111    pub fn pairs_of_dimension(&self, dim: usize) -> Vec<&PersistencePair> {
112        self.pairs.iter().filter(|p| p.dimension == dim).collect()
113    }
114
115    /// Maximum persistence value among finite pairs.
116    pub fn max_persistence(&self) -> f64 {
117        self.pairs
118            .iter()
119            .filter(|p| p.death.is_finite())
120            .map(|p| p.persistence())
121            .fold(0.0_f64, f64::max)
122    }
123
124    /// Persistence entropy: Shannon entropy of normalized persistence values.
125    pub fn persistence_entropy(&self) -> f64 {
126        let persistences: Vec<f64> = self
127            .pairs
128            .iter()
129            .filter(|p| p.death.is_finite() && p.persistence() > 1e-12)
130            .map(|p| p.persistence())
131            .collect();
132        if persistences.is_empty() {
133            return 0.0;
134        }
135        let total: f64 = persistences.iter().sum();
136        if total < 1e-12 {
137            return 0.0;
138        }
139        persistences
140            .iter()
141            .map(|&p| {
142                let q = p / total;
143                if q > 1e-12 {
144                    -q * q.ln()
145                } else {
146                    0.0
147                }
148            })
149            .sum()
150    }
151
152    /// Betti numbers at the given epsilon value.
153    pub fn betti_numbers_at(&self, eps: f64) -> Vec<usize> {
154        if self.betti_curve.is_empty() {
155            return vec![];
156        }
157        // Find the last entry with epsilon <= eps
158        let mut result = vec![0usize; self.betti_curve[0].1.len()];
159        for &(e, ref bettis) in &self.betti_curve {
160            if e > eps {
161                break;
162            }
163            result = bettis.clone();
164        }
165        result
166    }
167}
168
169fn compute_betti_curve(
170    pairs: &[PersistencePair],
171    thresholds: &[f64],
172    max_dim: usize,
173) -> Vec<(f64, Vec<usize>)> {
174    let n_dims = max_dim + 1;
175    let mut curve = Vec::with_capacity(thresholds.len());
176
177    for &eps in thresholds {
178        let mut bettis = vec![0usize; n_dims];
179        for pair in pairs {
180            if pair.dimension < n_dims {
181                let alive = pair.birth <= eps + 1e-12
182                    && (pair.death > eps - 1e-12 || !pair.death.is_finite());
183                if alive {
184                    bettis[pair.dimension] += 1;
185                }
186            }
187        }
188        curve.push((eps, bettis));
189    }
190
191    curve
192}