holos_tda/filtration/
simplex.rs1use std::collections::{BTreeMap, BTreeSet};
2
3use super::grade::{FiltrationError, FiltrationGrade, ScalarGrade, ScalarProjection};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct FilteredSimplex<G> {
8 vertices: Vec<usize>,
9 grade: G,
10}
11
12impl<G> FilteredSimplex<G> {
13 pub fn new(vertices: Vec<usize>, grade: G) -> Self {
15 Self { vertices, grade }
16 }
17
18 pub fn vertices(&self) -> &[usize] {
20 &self.vertices
21 }
22
23 pub fn dimension(&self) -> usize {
25 self.vertices.len().saturating_sub(1)
26 }
27
28 pub fn grade(&self) -> &G {
30 &self.grade
31 }
32}
33
34#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct FilteredSimplicialComplex<G> {
40 vertex_labels: Vec<usize>,
41 simplices: Vec<Vec<FilteredSimplex<G>>>,
42}
43
44impl<G: FiltrationGrade> FilteredSimplicialComplex<G> {
45 pub fn new(
47 vertex_labels: Vec<usize>,
48 mut simplices: Vec<Vec<FilteredSimplex<G>>>,
49 ) -> Result<Self, FiltrationError> {
50 if vertex_labels.windows(2).any(|pair| pair[0] >= pair[1]) {
51 return Err(FiltrationError::new(
52 "vertex labels must be strictly increasing",
53 ));
54 }
55 if simplices.is_empty() {
56 simplices.push(Vec::new());
57 }
58 for dimension in &mut simplices {
59 dimension.sort_by(|left, right| left.vertices.cmp(&right.vertices));
60 }
61 let complex = Self {
62 vertex_labels,
63 simplices,
64 };
65 complex.validate()?;
66 Ok(complex)
67 }
68
69 pub fn vertex_labels(&self) -> &[usize] {
71 &self.vertex_labels
72 }
73
74 pub fn max_dimension(&self) -> usize {
76 self.simplices.len().saturating_sub(1)
77 }
78
79 pub fn simplices(&self) -> &[Vec<FilteredSimplex<G>>] {
81 &self.simplices
82 }
83
84 pub fn simplex_count(&self) -> usize {
86 self.simplices.iter().map(Vec::len).sum()
87 }
88
89 pub fn project<P: ScalarProjection<G>>(
91 &self,
92 projection: &P,
93 ) -> Result<FilteredSimplicialComplex<ScalarGrade>, FiltrationError> {
94 let mut simplices = Vec::with_capacity(self.simplices.len());
95 for dimension in &self.simplices {
96 let mut projected = Vec::with_capacity(dimension.len());
97 for simplex in dimension {
98 projected.push(FilteredSimplex::new(
99 simplex.vertices.clone(),
100 projection.project(&simplex.grade)?,
101 ));
102 }
103 simplices.push(projected);
104 }
105 FilteredSimplicialComplex::new(self.vertex_labels.clone(), simplices)
106 }
107
108 fn validate(&self) -> Result<(), FiltrationError> {
109 let labels: BTreeSet<_> = self.vertex_labels.iter().copied().collect();
110 let mut grades = BTreeMap::<Vec<usize>, &G>::new();
111 for (dimension, simplices) in self.simplices.iter().enumerate() {
112 for simplex in simplices {
113 validate_simplex_key(simplex, dimension, &labels)?;
114 insert_simplex_grade(simplex, &mut grades)?;
115 }
116 }
117 validate_declared_vertices(&self.simplices[0], &self.vertex_labels)?;
118 validate_faces(&grades)
119 }
120}
121
122fn validate_simplex_key<G>(
123 simplex: &FilteredSimplex<G>,
124 dimension: usize,
125 labels: &BTreeSet<usize>,
126) -> Result<(), FiltrationError> {
127 if simplex.vertices.len() != dimension + 1 {
128 return Err(FiltrationError::new(format!(
129 "simplex {:?} is stored in dimension {dimension}",
130 simplex.vertices
131 )));
132 }
133 if simplex.vertices.windows(2).any(|pair| pair[0] >= pair[1]) {
134 return Err(FiltrationError::new(format!(
135 "simplex {:?} does not have a canonical vertex key",
136 simplex.vertices
137 )));
138 }
139 if simplex
140 .vertices
141 .iter()
142 .any(|vertex| !labels.contains(vertex))
143 {
144 return Err(FiltrationError::new(format!(
145 "simplex {:?} uses an unknown vertex label",
146 simplex.vertices
147 )));
148 }
149 Ok(())
150}
151
152fn insert_simplex_grade<'a, G>(
153 simplex: &'a FilteredSimplex<G>,
154 grades: &mut BTreeMap<Vec<usize>, &'a G>,
155) -> Result<(), FiltrationError> {
156 if grades
157 .insert(simplex.vertices.clone(), &simplex.grade)
158 .is_some()
159 {
160 return Err(FiltrationError::new(format!(
161 "simplex {:?} occurs more than once",
162 simplex.vertices
163 )));
164 }
165 Ok(())
166}
167
168fn validate_declared_vertices<G>(
169 vertices: &[FilteredSimplex<G>],
170 labels: &[usize],
171) -> Result<(), FiltrationError> {
172 let declared: Vec<_> = vertices.iter().map(|simplex| simplex.vertices[0]).collect();
173 if declared != labels {
174 return Err(FiltrationError::new(
175 "zero-dimensional simplices must match the vertex labels",
176 ));
177 }
178 Ok(())
179}
180
181fn validate_faces<G: FiltrationGrade>(
182 grades: &BTreeMap<Vec<usize>, &G>,
183) -> Result<(), FiltrationError> {
184 for (key, grade) in grades {
185 if key.len() > 1 {
186 validate_simplex_faces(key, *grade, grades)?;
187 }
188 }
189 Ok(())
190}
191
192fn validate_simplex_faces<G: FiltrationGrade>(
193 key: &[usize],
194 grade: &G,
195 grades: &BTreeMap<Vec<usize>, &G>,
196) -> Result<(), FiltrationError> {
197 for removed in 0..key.len() {
198 let mut face = key.to_vec();
199 face.remove(removed);
200 let face_grade = grades.get(&face).ok_or_else(|| {
201 FiltrationError::new(format!("simplex {key:?} is missing the face {face:?}"))
202 })?;
203 if !face_grade.precedes(grade) {
204 return Err(FiltrationError::new(format!(
205 "face {face:?} appears after its coface {key:?}"
206 )));
207 }
208 }
209 Ok(())
210}