Skip to main content

holos_tda/bipersistence/
queries.rs

1//! Comparable maps and generalized rectangle-rank queries.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use crate::{Error, Result};
6
7use super::linear::{LinearMap, SparseVector, checked_term_sum, negate, nullspace, rank};
8use super::{
9    Bigrade, BipersistenceMap, BipersistenceModule, BipersistenceRectangle, BipersistenceRegion,
10};
11
12impl BipersistenceModule {
13    /// Compute the exact map between comparable grades.
14    ///
15    /// The result composes cover maps. Checked commutative squares make the
16    /// result independent of the chosen monotone path.
17    pub fn map(&self, lower: Bigrade, upper: Bigrade) -> Result<BipersistenceMap> {
18        self.validate_comparable(lower, upper)?;
19        let upper_node = self.node(upper)?;
20        let mut composed = LinearMap::identity(upper_node.rank);
21        let mut current = upper;
22        while current.scale() > lower.scale() {
23            let next = Bigrade::new(current.scale() - 1, current.density());
24            let cover = self.cover_linear(next, current)?;
25            composed = LinearMap::compose(&cover, &composed, self.modulus)?;
26            current = next;
27        }
28        while current.density() > lower.density() {
29            let next = Bigrade::new(current.scale(), current.density() - 1);
30            let cover = self.cover_linear(next, current)?;
31            composed = LinearMap::compose(&cover, &composed, self.modulus)?;
32            current = next;
33        }
34        Ok(self.public_map(lower, upper, &composed))
35    }
36
37    /// Rank of the exact map between comparable grades.
38    pub fn map_rank(&self, lower: Bigrade, upper: Bigrade) -> Result<usize> {
39        Ok(self.map(lower, upper)?.rank)
40    }
41
42    /// Compute the generalized rank of one closed rectangle.
43    ///
44    /// The value is the rank of the canonical map from the diagram limit to
45    /// its colimit. The same value is obtained from the dual homology module.
46    pub fn rectangle_rank(&self, rectangle: BipersistenceRectangle) -> Result<usize> {
47        self.validate_comparable(rectangle.lower, rectangle.upper)?;
48        let region = BipersistenceRegion::new(rectangle_grades(rectangle))?;
49        self.region_rank(&region)
50    }
51
52    /// Compute the generalized rank of one connected finite region.
53    ///
54    /// The value is the rank of the canonical map from the diagram limit to
55    /// its colimit. Every comparable pair in the induced subposet contributes
56    /// its exact module map. A region with a minimum and maximum has the rank
57    /// of the map between those grades.
58    pub fn region_rank(&self, region: &BipersistenceRegion) -> Result<usize> {
59        self.validate_region(region)?;
60        let grades = region.grades();
61        let (offsets, ambient_rank) = self.region_offsets(grades)?;
62        if ambient_rank > self.limits.max_linear_variables {
63            return Err(Error::InvalidInput(format!(
64                "region direct-sum rank exceeds the limit {}",
65                self.limits.max_linear_variables
66            )));
67        }
68        if ambient_rank == 0 {
69            return Ok(0);
70        }
71        let (relations, equations) = self.region_relations(grades, &offsets)?;
72        let limit = nullspace(equations, ambient_rank, self.modulus);
73        let first = grades[0];
74        let first_offset = offsets[&first];
75        let first_rank = self.node(first)?.rank;
76        let images = limit_images(limit, first_offset, first_rank, self.modulus);
77        let relation_rank = rank(relations.clone(), ambient_rank, self.modulus);
78        let union_rank = rank(
79            relations.into_iter().chain(images).collect(),
80            ambient_rank,
81            self.modulus,
82        );
83        Ok(union_rank - relation_rank)
84    }
85
86    fn region_offsets(&self, grades: &[Bigrade]) -> Result<(BTreeMap<Bigrade, usize>, usize)> {
87        let mut offsets = BTreeMap::new();
88        let mut ambient_rank = 0usize;
89        for grade in grades {
90            offsets.insert(*grade, ambient_rank);
91            ambient_rank = ambient_rank
92                .checked_add(self.node(*grade)?.rank)
93                .ok_or_else(|| Error::InvalidInput("region direct-sum rank overflows".into()))?;
94        }
95        Ok((offsets, ambient_rank))
96    }
97
98    fn region_relations(
99        &self,
100        grades: &[Bigrade],
101        offsets: &BTreeMap<Bigrade, usize>,
102    ) -> Result<(Vec<SparseVector>, Vec<SparseVector>)> {
103        let mut relations = Vec::new();
104        let mut equations = Vec::new();
105        let mut coefficient_count = 0usize;
106        for (lower, upper) in comparable_pairs(grades) {
107            let map = self.map(lower, upper)?;
108            let linear = self.linear_from_public(&map)?;
109            let source_offset = offsets[&upper];
110            let target_offset = offsets[&lower];
111            append_map_relations(
112                &mut relations,
113                &linear,
114                source_offset,
115                target_offset,
116                self.modulus,
117                &mut coefficient_count,
118                self.limits,
119            )?;
120            append_map_equations(
121                &mut equations,
122                &linear,
123                source_offset,
124                target_offset,
125                self.modulus,
126                &mut coefficient_count,
127                self.limits,
128            )?;
129        }
130        Ok((relations, equations))
131    }
132
133    fn validate_region(&self, region: &BipersistenceRegion) -> Result<()> {
134        for &grade in region.grades() {
135            self.node(grade)?;
136        }
137        let mut reached = BTreeSet::from([region.grades()[0]]);
138        loop {
139            let before = reached.len();
140            for &grade in region.grades() {
141                if region.grades().iter().copied().any(|other| {
142                    reached.contains(&other) && (grade.precedes(other) || other.precedes(grade))
143                }) {
144                    reached.insert(grade);
145                }
146            }
147            if reached.len() == region.grades().len() {
148                return Ok(());
149            }
150            if reached.len() == before {
151                return Err(Error::InvalidInput(
152                    "a bipersistence region must have a connected comparability graph".into(),
153                ));
154            }
155        }
156    }
157}
158
159fn comparable_pairs(grades: &[Bigrade]) -> Vec<(Bigrade, Bigrade)> {
160    let mut pairs = Vec::new();
161    for (position, &lower) in grades.iter().enumerate() {
162        for &upper in &grades[position + 1..] {
163            if lower.precedes(upper) {
164                pairs.push((lower, upper));
165            }
166        }
167    }
168    pairs
169}
170
171fn rectangle_grades(rectangle: BipersistenceRectangle) -> Vec<Bigrade> {
172    let mut grades = Vec::new();
173    for scale in rectangle.lower.scale()..=rectangle.upper.scale() {
174        for density in rectangle.lower.density()..=rectangle.upper.density() {
175            grades.push(Bigrade::new(scale, density));
176        }
177    }
178    grades
179}
180
181fn append_map_relations(
182    relations: &mut Vec<SparseVector>,
183    linear: &LinearMap,
184    source_offset: usize,
185    target_offset: usize,
186    modulus: u32,
187    coefficient_count: &mut usize,
188    limits: super::BipersistenceLimits,
189) -> Result<()> {
190    for (source, column) in linear.columns.iter().enumerate() {
191        let mut relation = SparseVector::default();
192        relation.insert(source_offset + source, 1, modulus);
193        for (&target, &coefficient) in &column.0 {
194            relation.insert(
195                target_offset + target,
196                negate(coefficient, modulus),
197                modulus,
198            );
199        }
200        *coefficient_count = checked_term_sum(*coefficient_count, relation.len(), limits)?;
201        relations.push(relation);
202    }
203    Ok(())
204}
205
206fn append_map_equations(
207    equations: &mut Vec<SparseVector>,
208    linear: &LinearMap,
209    source_offset: usize,
210    target_offset: usize,
211    modulus: u32,
212    coefficient_count: &mut usize,
213    limits: super::BipersistenceLimits,
214) -> Result<()> {
215    for target in 0..linear.target_rank {
216        let mut equation = SparseVector::default();
217        equation.insert(target_offset + target, 1, modulus);
218        for (source, column) in linear.columns.iter().enumerate() {
219            if let Some(&coefficient) = column.0.get(&target) {
220                equation.insert(
221                    source_offset + source,
222                    negate(coefficient, modulus),
223                    modulus,
224                );
225            }
226        }
227        *coefficient_count = checked_term_sum(*coefficient_count, equation.len(), limits)?;
228        equations.push(equation);
229    }
230    Ok(())
231}
232
233fn limit_images(
234    limit: Vec<SparseVector>,
235    first_offset: usize,
236    first_rank: usize,
237    modulus: u32,
238) -> Vec<SparseVector> {
239    limit
240        .into_iter()
241        .map(|vector| {
242            let mut image = SparseVector::default();
243            for (&position, &coefficient) in vector.0.range(first_offset..first_offset + first_rank)
244            {
245                image.insert(position, coefficient, modulus);
246            }
247            image
248        })
249        .collect::<Vec<_>>()
250}