Skip to main content

holos_tda/bipersistence/
atlas.rs

1//! Class-extension fibers and connected atlas regions.
2
3use std::collections::{BTreeMap, BTreeSet, VecDeque};
4
5use crate::Result;
6
7use super::linear::{affine_solution, coordinate_vector, public_terms};
8use super::{Bigrade, BipersistenceModule, BipersistenceTerm};
9
10/// Classification of the affine extension fiber at one grid node.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
12pub enum ClassExtensionKind {
13    /// The base class has one extension.
14    Unique,
15    /// The base class has an affine family of extensions.
16    Ambiguous,
17    /// The base class has no extension.
18    NoExtension,
19}
20
21/// Exact extension fiber of one base class at one grid node.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct ClassExtension {
24    /// Target grid node.
25    pub grade: Bigrade,
26    /// Classification of the affine fiber.
27    pub kind: ClassExtensionKind,
28    /// Unique extension or one canonical affine base point.
29    pub class: Vec<BipersistenceTerm>,
30    /// Canonical basis for the affine direction.
31    pub ambiguity: Vec<Vec<BipersistenceTerm>>,
32}
33
34/// Connected region with one extension classification and ambiguity rank.
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct ClassExtensionRegion {
37    /// Stable zero-based region position.
38    pub region_index: usize,
39    /// Shared extension classification.
40    pub kind: ClassExtensionKind,
41    /// Dimension of the affine direction.
42    pub ambiguity_rank: usize,
43    /// Region grades in lexicographic order.
44    pub grades: Vec<Bigrade>,
45}
46
47/// Exact class-extension atlas over an upper parameter cone.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct CohomologyClassAtlas {
50    /// Grade at which the selected class is defined.
51    pub base_grade: Bigrade,
52    /// Nonzero class in the base node's canonical basis.
53    pub base_class: Vec<BipersistenceTerm>,
54    /// Extension fiber at every grade above the base.
55    pub extensions: Vec<ClassExtension>,
56    /// Four-neighbor connected regions of equal fiber type and dimension.
57    pub regions: Vec<ClassExtensionRegion>,
58}
59
60impl BipersistenceModule {
61    /// Compute all extensions of one nonzero class over its upper parameter cone.
62    pub fn class_atlas(
63        &self,
64        base_grade: Bigrade,
65        base_class: &[BipersistenceTerm],
66    ) -> Result<CohomologyClassAtlas> {
67        self.validate_grade(base_grade)?;
68        let base = coordinate_vector(
69            base_class,
70            self.node(base_grade)?.rank,
71            self.modulus(),
72            "bipersistence base class coordinates are not canonical",
73        )?;
74        if base.is_zero() {
75            return Err(crate::Error::InvalidInput(
76                "a bipersistence class atlas needs a nonzero base class".into(),
77            ));
78        }
79        let mut extensions = Vec::new();
80        for scale in base_grade.scale()..self.scale_count() {
81            for density in base_grade.density()..self.density_count() {
82                let grade = Bigrade::new(scale, density);
83                extensions.push(self.extension_at_grade(base_grade, grade, &base)?);
84            }
85        }
86        let regions = extension_regions(
87            base_grade,
88            self.scale_count(),
89            self.density_count(),
90            &extensions,
91        );
92        Ok(CohomologyClassAtlas {
93            base_grade,
94            base_class: public_terms(&base),
95            extensions,
96            regions,
97        })
98    }
99
100    fn extension_at_grade(
101        &self,
102        base_grade: Bigrade,
103        grade: Bigrade,
104        base: &super::linear::SparseVector,
105    ) -> Result<ClassExtension> {
106        let map = self.map(base_grade, grade)?;
107        let linear = self.linear_from_public(&map)?;
108        let (kind, class, ambiguity) = match affine_solution(&linear.columns, base, self.modulus())
109        {
110            None => (ClassExtensionKind::NoExtension, Vec::new(), Vec::new()),
111            Some((particular, kernel)) if kernel.is_empty() => (
112                ClassExtensionKind::Unique,
113                public_terms(&particular),
114                Vec::new(),
115            ),
116            Some((particular, kernel)) => (
117                ClassExtensionKind::Ambiguous,
118                public_terms(&particular),
119                kernel.iter().map(public_terms).collect(),
120            ),
121        };
122        Ok(ClassExtension {
123            grade,
124            kind,
125            class,
126            ambiguity,
127        })
128    }
129}
130
131fn extension_regions(
132    base: Bigrade,
133    scale_count: usize,
134    density_count: usize,
135    extensions: &[ClassExtension],
136) -> Vec<ClassExtensionRegion> {
137    let by_grade = extensions
138        .iter()
139        .map(|extension| (extension.grade, extension))
140        .collect::<BTreeMap<_, _>>();
141    let mut visited = BTreeSet::new();
142    let mut regions = Vec::new();
143    for extension in extensions {
144        if !visited.insert(extension.grade) {
145            continue;
146        }
147        let label = (extension.kind, extension.ambiguity.len());
148        let mut queue = VecDeque::from([extension.grade]);
149        let mut grades = Vec::new();
150        while let Some(grade) = queue.pop_front() {
151            grades.push(grade);
152            for neighbor in grade_neighbors(grade, base, scale_count, density_count) {
153                let candidate = by_grade[&neighbor];
154                if (candidate.kind, candidate.ambiguity.len()) == label && visited.insert(neighbor)
155                {
156                    queue.push_back(neighbor);
157                }
158            }
159        }
160        grades.sort_unstable();
161        regions.push(ClassExtensionRegion {
162            region_index: regions.len(),
163            kind: label.0,
164            ambiguity_rank: label.1,
165            grades,
166        });
167    }
168    regions
169}
170
171fn grade_neighbors(
172    grade: Bigrade,
173    base: Bigrade,
174    scale_count: usize,
175    density_count: usize,
176) -> Vec<Bigrade> {
177    let mut neighbors = Vec::with_capacity(4);
178    if grade.scale() > base.scale() {
179        neighbors.push(Bigrade::new(grade.scale() - 1, grade.density()));
180    }
181    if grade.scale() + 1 < scale_count {
182        neighbors.push(Bigrade::new(grade.scale() + 1, grade.density()));
183    }
184    if grade.density() > base.density() {
185        neighbors.push(Bigrade::new(grade.scale(), grade.density() - 1));
186    }
187    if grade.density() + 1 < density_count {
188        neighbors.push(Bigrade::new(grade.scale(), grade.density() + 1));
189    }
190    neighbors
191}