Skip to main content

holos_tda/
bifiltration.rs

1//! Finite multicritical bifiltrations and exact degree-Rips construction.
2//!
3//! A simplex carries the antichain of its minimal grades. The support of the
4//! simplex is the upward closure of that antichain. This representation keeps
5//! multicritical filtrations distinct from one-critical product grades.
6
7mod degree_rips;
8mod validation;
9
10#[cfg(test)]
11mod tests;
12
13pub use degree_rips::{DegreeRipsBifiltration, DegreeRipsParams};
14
15use validation::{
16    validate_axes, validate_face_support, validate_simplex_key, validate_vertex_keys,
17};
18
19use std::collections::BTreeMap;
20
21use crate::filtration::ComplexLimits;
22use crate::{Error, Result, SparseDistanceMatrix};
23
24/// One position in a finite two-parameter grid.
25///
26/// Both coordinates increase with the filtration. For degree-Rips, the
27/// second index addresses a strictly decreasing list of minimum degrees.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29pub struct Bigrade {
30    scale: usize,
31    density: usize,
32}
33
34impl Bigrade {
35    /// Construct a grid position from zero-based coordinate indices.
36    pub fn new(scale: usize, density: usize) -> Self {
37        Self { scale, density }
38    }
39
40    /// Scale-axis index.
41    pub fn scale(self) -> usize {
42        self.scale
43    }
44
45    /// Density-axis index.
46    pub fn density(self) -> usize {
47        self.density
48    }
49
50    /// Whether this grade precedes another in the product order.
51    pub fn precedes(self, other: Self) -> bool {
52        self.scale <= other.scale && self.density <= other.density
53    }
54}
55
56/// Minimal incomparable birth grades of one simplex.
57#[derive(Debug, Clone, PartialEq, Eq)]
58pub struct BirthAntichain {
59    grades: Vec<Bigrade>,
60}
61
62impl BirthAntichain {
63    /// Construct a nonempty canonical antichain.
64    pub fn new(mut grades: Vec<Bigrade>) -> Result<Self> {
65        grades.sort_unstable();
66        if grades.is_empty() {
67            return Err(Error::InvalidInput(
68                "a multicritical simplex needs at least one birth grade".into(),
69            ));
70        }
71        for (position, grade) in grades.iter().copied().enumerate() {
72            if grades
73                .iter()
74                .copied()
75                .enumerate()
76                .any(|(other, candidate)| other != position && candidate.precedes(grade))
77            {
78                return Err(Error::InvalidInput(
79                    "multicritical birth grades must be pairwise incomparable".into(),
80                ));
81            }
82        }
83        Ok(Self { grades })
84    }
85
86    /// Minimal grades in lexicographic order.
87    pub fn grades(&self) -> &[Bigrade] {
88        &self.grades
89    }
90
91    /// Whether the simplex occurs at one grid position.
92    pub fn supports(&self, grade: Bigrade) -> bool {
93        self.grades
94            .iter()
95            .copied()
96            .any(|birth| birth.precedes(grade))
97    }
98
99    fn from_candidates(candidates: impl IntoIterator<Item = Bigrade>) -> Result<Self> {
100        let mut minimal = Vec::<Bigrade>::new();
101        for grade in candidates {
102            if minimal.iter().copied().any(|birth| birth.precedes(grade)) {
103                continue;
104            }
105            minimal.retain(|birth| !grade.precedes(*birth));
106            minimal.push(grade);
107        }
108        Self::new(minimal)
109    }
110}
111
112/// One simplex in a finite multicritical bifiltration.
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct MulticriticalSimplex {
115    vertices: Vec<usize>,
116    births: BirthAntichain,
117}
118
119impl MulticriticalSimplex {
120    /// Construct a simplex. The enclosing bifiltration validates its key.
121    pub fn new(vertices: Vec<usize>, births: BirthAntichain) -> Self {
122        Self { vertices, births }
123    }
124
125    /// Stable vertices in ascending order.
126    pub fn vertices(&self) -> &[usize] {
127        &self.vertices
128    }
129
130    /// Minimal birth grades.
131    pub fn births(&self) -> &BirthAntichain {
132        &self.births
133    }
134
135    /// Simplex dimension.
136    pub fn dimension(&self) -> usize {
137        self.vertices.len().saturating_sub(1)
138    }
139}
140
141/// Resource limits for a finite multicritical bifiltration.
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
143#[non_exhaustive]
144pub struct BifiltrationLimits {
145    /// Largest accepted scale count.
146    pub max_scales: usize,
147    /// Largest accepted density-level count.
148    pub max_density_levels: usize,
149    /// Largest accepted total birth-grade count.
150    pub max_birth_grades: usize,
151    /// Per-dimension simplex limits.
152    pub complex: ComplexLimits,
153}
154
155impl Default for BifiltrationLimits {
156    fn default() -> Self {
157        Self {
158            max_scales: 100_000,
159            max_density_levels: 1_000_000,
160            max_birth_grades: 100_000_000,
161            complex: ComplexLimits::default(),
162        }
163    }
164}
165
166impl BifiltrationLimits {
167    fn simplex_limit(self, dimension: usize) -> usize {
168        match dimension {
169            0 => self.complex.max_vertices,
170            1 => self.complex.max_edges,
171            2 => self.complex.max_triangles,
172            _ => self.complex.max_higher_simplices,
173        }
174    }
175}
176
177/// A finite simplicial bifiltration represented by antichain births.
178#[derive(Debug, Clone, PartialEq, Eq)]
179pub struct MulticriticalBifiltration {
180    vertex_count: usize,
181    scale_bits: Vec<u64>,
182    minimum_degrees: Vec<usize>,
183    simplices: Vec<Vec<MulticriticalSimplex>>,
184}
185
186impl MulticriticalBifiltration {
187    /// Construct and validate a finite multicritical bifiltration.
188    pub fn new(
189        vertex_count: usize,
190        scales: Vec<f64>,
191        minimum_degrees: Vec<usize>,
192        mut simplices: Vec<Vec<MulticriticalSimplex>>,
193        limits: BifiltrationLimits,
194    ) -> Result<Self> {
195        let scale_bits = validate_axes(vertex_count, &scales, &minimum_degrees, limits)?;
196        if simplices.is_empty() {
197            simplices.push(Vec::new());
198        }
199        for dimension in &mut simplices {
200            dimension.sort_by(|left, right| left.vertices.cmp(&right.vertices));
201        }
202        let bifiltration = Self {
203            vertex_count,
204            scale_bits,
205            minimum_degrees,
206            simplices,
207        };
208        bifiltration.validate(limits)?;
209        Ok(bifiltration)
210    }
211
212    /// Number of labeled input vertices.
213    pub fn vertex_count(&self) -> usize {
214        self.vertex_count
215    }
216
217    /// Scale values in strict ascending order.
218    pub fn scales(&self) -> impl ExactSizeIterator<Item = f64> + '_ {
219        self.scale_bits.iter().copied().map(f64::from_bits)
220    }
221
222    /// Minimum vertex degrees in strict descending order.
223    pub fn minimum_degrees(&self) -> &[usize] {
224        &self.minimum_degrees
225    }
226
227    /// Simplices grouped by dimension and ordered by vertex key.
228    pub fn simplices(&self) -> &[Vec<MulticriticalSimplex>] {
229        &self.simplices
230    }
231
232    /// Largest valid grid position.
233    pub fn maximum_grade(&self) -> Bigrade {
234        Bigrade::new(self.scale_bits.len() - 1, self.minimum_degrees.len() - 1)
235    }
236
237    /// Materialize the simplices present at one grid position.
238    pub fn slice(&self, grade: Bigrade) -> Result<BifiltrationSlice> {
239        self.validate_grade(grade)?;
240        let simplices = self
241            .simplices
242            .iter()
243            .map(|dimension| {
244                dimension
245                    .iter()
246                    .filter(|simplex| simplex.births.supports(grade))
247                    .map(|simplex| simplex.vertices.clone())
248                    .collect::<Vec<_>>()
249            })
250            .collect::<Vec<_>>();
251        let active_vertices = simplices
252            .first()
253            .into_iter()
254            .flat_map(|vertices| vertices.iter().map(|simplex| simplex[0]))
255            .collect();
256        Ok(BifiltrationSlice {
257            grade,
258            active_vertices,
259            simplices,
260        })
261    }
262
263    fn validate_grade(&self, grade: Bigrade) -> Result<()> {
264        if grade.scale >= self.scale_bits.len() || grade.density >= self.minimum_degrees.len() {
265            return Err(Error::InvalidInput(
266                "bifiltration grade is outside the finite parameter grid".into(),
267            ));
268        }
269        Ok(())
270    }
271
272    fn validate(&self, limits: BifiltrationLimits) -> Result<()> {
273        let by_key = self.validate_simplices(limits)?;
274        validate_vertex_keys(self.vertex_count, &self.simplices[0])?;
275        validate_face_support(&by_key)
276    }
277
278    fn validate_simplices(
279        &self,
280        limits: BifiltrationLimits,
281    ) -> Result<BTreeMap<Vec<usize>, &BirthAntichain>> {
282        let mut births = 0usize;
283        let mut by_key = BTreeMap::<Vec<usize>, &BirthAntichain>::new();
284        for (dimension, simplices) in self.simplices.iter().enumerate() {
285            if simplices.len() > limits.simplex_limit(dimension) {
286                return Err(Error::InvalidInput(format!(
287                    "bifiltration dimension {dimension} exceeds its simplex limit"
288                )));
289            }
290            for simplex in simplices {
291                validate_simplex_key(simplex, dimension, self.vertex_count)?;
292                for grade in simplex.births.grades() {
293                    self.validate_grade(*grade)?;
294                }
295                births = births
296                    .checked_add(simplex.births.grades().len())
297                    .ok_or_else(|| {
298                        Error::InvalidInput("bifiltration birth-grade count overflows".into())
299                    })?;
300                if births > limits.max_birth_grades {
301                    return Err(Error::InvalidInput(
302                        "bifiltration birth-grade count exceeds its limit".into(),
303                    ));
304                }
305                if by_key
306                    .insert(simplex.vertices.clone(), &simplex.births)
307                    .is_some()
308                {
309                    return Err(Error::InvalidInput(format!(
310                        "bifiltration simplex {:?} occurs more than once",
311                        simplex.vertices
312                    )));
313                }
314            }
315        }
316        Ok(by_key)
317    }
318}
319
320/// One materialized value of a multicritical bifiltration.
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct BifiltrationSlice {
323    grade: Bigrade,
324    active_vertices: Vec<usize>,
325    simplices: Vec<Vec<Vec<usize>>>,
326}
327
328impl BifiltrationSlice {
329    /// Grid position of this slice.
330    pub fn grade(&self) -> Bigrade {
331        self.grade
332    }
333
334    /// Vertices present in the slice.
335    pub fn active_vertices(&self) -> &[usize] {
336        &self.active_vertices
337    }
338
339    /// Present simplices grouped by dimension.
340    pub fn simplices(&self) -> &[Vec<Vec<usize>>] {
341        &self.simplices
342    }
343
344    /// Build a zero-weight graph from the one-skeleton.
345    ///
346    /// The graph retains the original vertex count. Use it for positive-
347    /// dimensional flag cohomology. `active_vertices` records which isolated
348    /// vertices actually occur, so this graph alone does not represent H0.
349    pub fn h1_graph(&self, vertex_count: usize) -> Result<SparseDistanceMatrix> {
350        let edges = self
351            .simplices
352            .get(1)
353            .into_iter()
354            .flatten()
355            .map(|edge| (edge[0], edge[1], 0.0))
356            .collect::<Vec<_>>();
357        SparseDistanceMatrix::from_triplets(vertex_count, &edges)
358    }
359}