Skip to main content

holos_tda/relative_interface/
builder.rs

1use std::collections::BTreeSet;
2
3use crate::certificate::{CertificateError, CertificateLimits, ChangeColumn};
4use crate::field::{MODULUS_LIMIT, is_prime};
5use crate::filtration::{ComplexLimits, FilteredSimplicialComplex, FlagComplexParams, ScalarGrade};
6use crate::{Diagram, RipsParams, SparseDistanceMatrix, rips_persistence_sparse};
7
8use super::cancellation::{cancel_relative, simplex_boundary};
9use super::chain::check_chain_complex;
10use super::composition::{check_composition_children, merge_child_cells};
11use super::digest::{certificate_digest, diagrams_equal, source_digest};
12use super::model::{
13    InterfaceCancellation, InterfaceCell, RelativeInterfaceCertificate, RelativeInterfaceWork,
14};
15use super::reduction::reduce_core;
16use super::validation::{
17    canonical_vertices, cell_order, check_protected_cells, count_cells, enforce_cell_limits,
18    validate_parameters,
19};
20
21impl RelativeInterfaceCertificate {
22    /// Build a relative core from one filtered flag complex.
23    ///
24    /// `protected_vertices` induces the separator subcomplex that every
25    /// cancellation fixes. Vertex labels are the input positions.
26    pub fn build(
27        input: &SparseDistanceMatrix,
28        params: &RipsParams,
29        protected_vertices: &[usize],
30        limits: CertificateLimits,
31    ) -> Result<Self, CertificateError> {
32        let labels: Vec<_> = (0..input.len()).collect();
33        Self::build_labeled(input, &labels, params, protected_vertices, limits)
34    }
35
36    /// Build a relative core with explicit global vertex labels.
37    ///
38    /// `labels[local]` names each input vertex. Labels must be strictly
39    /// increasing. Independent child cores identify a shared separator during
40    /// [`Self::compose`].
41    pub fn build_labeled(
42        input: &SparseDistanceMatrix,
43        labels: &[usize],
44        params: &RipsParams,
45        protected_vertices: &[usize],
46        limits: CertificateLimits,
47    ) -> Result<Self, CertificateError> {
48        validate_parameters(input, labels, params, protected_vertices, limits)?;
49        let complex = FilteredSimplicialComplex::from_flag_graph(
50            input,
51            labels,
52            FlagComplexParams {
53                max_dimension: params.max_dim + 1,
54                threshold: params.threshold,
55                limits: ComplexLimits {
56                    max_vertices: limits.max_vertices,
57                    max_edges: limits.max_edges,
58                    max_triangles: limits.max_triangles,
59                    max_higher_simplices: limits.max_higher_simplices,
60                },
61            },
62        )
63        .map_err(|error| CertificateError::new(error.to_string()))?;
64        let certificate = Self::build_complex(
65            &complex,
66            params.max_dim,
67            params.modulus,
68            protected_vertices,
69            limits,
70        )?;
71        let expected = rips_persistence_sparse(input, params)
72            .map_err(|error| CertificateError::new(error.to_string()))?;
73        if !diagrams_equal(&expected, certificate.diagram()) {
74            return Err(CertificateError::new(format!(
75                "relative interface differs from the compute engine: expected {:?}, got {:?}",
76                expected.bars,
77                certificate.diagram().bars
78            )));
79        }
80        Ok(certificate)
81    }
82
83    /// Build a relative core from an explicit scalar filtered complex.
84    ///
85    /// The complex must contain cells through dimension `max_dim + 1`.
86    /// The filtration need not come from a Vietoris-Rips flag construction.
87    pub fn build_complex(
88        complex: &FilteredSimplicialComplex<ScalarGrade>,
89        max_dim: usize,
90        modulus: u32,
91        protected_vertices: &[usize],
92        limits: CertificateLimits,
93    ) -> Result<Self, CertificateError> {
94        if max_dim > limits.max_dimension
95            || u64::from(modulus) >= MODULUS_LIMIT
96            || !is_prime(modulus as u64)
97        {
98            return Err(CertificateError::new(
99                "relative interface dimension or coefficient field is invalid",
100            ));
101        }
102        if complex.simplices().len() != max_dim + 2 {
103            return Err(CertificateError::new(format!(
104                "relative interface needs simplex dimensions zero through {}, got zero through {}",
105                max_dim + 1,
106                complex.max_dimension()
107            )));
108        }
109        let labels: BTreeSet<_> = complex.vertex_labels().iter().copied().collect();
110        if protected_vertices
111            .iter()
112            .any(|vertex| !labels.contains(vertex))
113        {
114            return Err(CertificateError::new(
115                "protected vertex is outside the interface scope",
116            ));
117        }
118        let mut cells = Vec::with_capacity(complex.simplices().len());
119        for dimension in complex.simplices() {
120            let mut output = Vec::with_capacity(dimension.len());
121            for simplex in dimension {
122                output.push(InterfaceCell {
123                    vertices: simplex.vertices().to_vec(),
124                    value: simplex.grade().value(),
125                    boundary: if simplex.dimension() == 0 {
126                        Vec::new()
127                    } else {
128                        simplex_boundary(simplex.vertices(), modulus)
129                    },
130                });
131            }
132            output.sort_by(cell_order);
133            cells.push(output);
134        }
135        Self::from_cells(cells, max_dim, modulus, protected_vertices, limits)
136    }
137
138    /// Compose child cores by identifying cells with equal labeled vertices.
139    ///
140    /// Equal cells must have bit-identical filtration values and boundaries.
141    /// The protected vertices are the separator retained for the next
142    /// composition level.
143    pub fn compose(
144        children: &[&Self],
145        protected_vertices: &[usize],
146        limits: CertificateLimits,
147    ) -> Result<Self, CertificateError> {
148        for child in children {
149            child.verify(limits)?;
150        }
151        Self::compose_trusted(children, protected_vertices, limits)
152    }
153
154    pub(crate) fn compose_trusted(
155        children: &[&Self],
156        protected_vertices: &[usize],
157        limits: CertificateLimits,
158    ) -> Result<Self, CertificateError> {
159        let first = check_composition_children(children)?;
160        let cells = merge_child_cells(children, first.max_dim)?;
161        Self::from_cells(
162            cells,
163            first.max_dim,
164            first.modulus,
165            protected_vertices,
166            limits,
167        )
168    }
169
170    fn from_cells(
171        input_cells: Vec<Vec<InterfaceCell>>,
172        max_dim: usize,
173        modulus: u32,
174        protected_vertices: &[usize],
175        limits: CertificateLimits,
176    ) -> Result<Self, CertificateError> {
177        let protected_vertices = canonical_vertices(protected_vertices)?;
178        let input_count = count_cells(&input_cells);
179        enforce_cell_limits(&input_cells, limits)?;
180        check_chain_complex(&input_cells, max_dim, modulus, limits)?;
181        let protected: BTreeSet<_> = protected_vertices.iter().copied().collect();
182        let (core_cells, cancellations) =
183            cancel_relative(input_cells.clone(), &protected, modulus)?;
184        check_protected_cells(&input_cells, &core_cells, &protected)?;
185        check_chain_complex(&core_cells, max_dim, modulus, limits)?;
186        let (columns, diagram, reduction_additions) = reduce_core(&core_cells, modulus, limits)?;
187        let work = RelativeInterfaceWork {
188            input_cells: input_count,
189            cancellations: cancellations.len(),
190            core_cells: count_cells(&core_cells),
191            reduction_additions,
192        };
193        let digest = certificate_digest(
194            max_dim,
195            modulus,
196            &protected_vertices,
197            &core_cells,
198            &columns,
199            &diagram,
200        );
201        Ok(Self {
202            max_dim,
203            modulus,
204            protected_vertices,
205            input_cells,
206            cancellations,
207            core_cells,
208            columns,
209            diagram,
210            digest,
211            work,
212        })
213    }
214
215    /// Highest homology dimension carried by this interface.
216    pub fn max_dim(&self) -> usize {
217        self.max_dim
218    }
219
220    /// Prime coefficient modulus.
221    pub fn modulus(&self) -> u32 {
222        self.modulus
223    }
224
225    /// Vertices whose induced subcomplex is fixed by every cancellation.
226    pub fn protected_vertices(&self) -> &[usize] {
227        &self.protected_vertices
228    }
229
230    /// Input cells grouped by dimension.
231    pub fn input_cells(&self) -> &[Vec<InterfaceCell>] {
232        &self.input_cells
233    }
234
235    /// Checked cancellation trace in execution order.
236    pub fn cancellations(&self) -> &[InterfaceCancellation] {
237        &self.cancellations
238    }
239
240    /// Retained interface core grouped by dimension.
241    pub fn core_cells(&self) -> &[Vec<InterfaceCell>] {
242        &self.core_cells
243    }
244
245    /// Change-of-basis columns grouped by positive source dimension.
246    pub fn graded_columns(&self) -> &[Vec<ChangeColumn>] {
247        &self.columns
248    }
249
250    /// Diagram derived from the retained core.
251    pub fn diagram(&self) -> &Diagram {
252        &self.diagram
253    }
254
255    /// Content identifier of the checked core and reduction.
256    pub fn digest(&self) -> &[u8; 32] {
257        &self.digest
258    }
259
260    /// Content identifier of the pre-cancellation filtered chain complex.
261    ///
262    /// Equal cores from different source complexes have different source
263    /// identifiers. Dynamic proof nodes use both this digest and [`Self::digest`].
264    pub fn source_digest(&self) -> [u8; 32] {
265        source_digest(
266            self.max_dim,
267            self.modulus,
268            &self.protected_vertices,
269            &self.input_cells,
270        )
271    }
272
273    /// Exact cell and reduction work for this interface.
274    pub fn work(&self) -> RelativeInterfaceWork {
275        self.work
276    }
277}