Skip to main content

holos_tda/program_wire/
model.rs

1use std::fmt;
2
3use crate::{
4    AtlasDecodeLimits, CertificateLimits, Diagram, PersistenceProgram, RipsParams,
5    SparseDistanceMatrix,
6};
7
8use super::{decode, encode, verification};
9
10/// Failure while producing, decoding, or checking a program artifact.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct ProgramArtifactError {
13    message: String,
14}
15
16impl ProgramArtifactError {
17    pub(super) fn new(message: impl Into<String>) -> Self {
18        Self {
19            message: message.into(),
20        }
21    }
22
23    /// Description of the violated artifact rule.
24    pub fn message(&self) -> &str {
25        &self.message
26    }
27}
28
29impl fmt::Display for ProgramArtifactError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "program artifact: {}", self.message)
32    }
33}
34
35impl std::error::Error for ProgramArtifactError {}
36
37/// Decoder limits applied before program collections are allocated.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub struct ProgramDecodeLimits {
41    /// Largest accepted envelope in bytes.
42    pub max_bytes: usize,
43    /// Largest accepted vertex count.
44    pub max_vertices: usize,
45    /// Largest accepted cyclic-atom count.
46    pub max_atoms: usize,
47    /// Largest accepted total atom vertex count.
48    pub max_atom_vertices: usize,
49    /// Largest accepted total atom edge count.
50    pub max_atom_edges: usize,
51    /// Largest accepted diagram bar count.
52    pub max_bars: usize,
53    /// Largest accepted total nested atlas bytes.
54    pub max_atlas_bytes: usize,
55    /// Limits for each nested atlas envelope.
56    pub atlas: AtlasDecodeLimits,
57}
58
59impl Default for ProgramDecodeLimits {
60    fn default() -> Self {
61        Self {
62            max_bytes: 1 << 30,
63            max_vertices: 1_000_000,
64            max_atoms: 50_000_000,
65            max_atom_vertices: 100_000_000,
66            max_atom_edges: 100_000_000,
67            max_bars: 100_000_000,
68            max_atlas_bytes: 1 << 30,
69            atlas: AtlasDecodeLimits::default(),
70        }
71    }
72}
73
74/// One cyclic atom and its nested atlas.
75#[derive(Debug, Clone)]
76pub struct ProgramAtomArtifact {
77    pub(super) id: usize,
78    pub(super) vertices: Vec<usize>,
79    pub(super) edges: Vec<crate::EdgeKey>,
80    pub(super) atlas: crate::AtlasArtifact,
81}
82
83impl ProgramAtomArtifact {
84    /// Position in the complete articulation decomposition.
85    pub fn id(&self) -> usize {
86        self.id
87    }
88
89    /// Original labeled vertices.
90    pub fn vertices(&self) -> &[usize] {
91        &self.vertices
92    }
93
94    /// Original labeled edges.
95    pub fn edges(&self) -> &[crate::EdgeKey] {
96        &self.edges
97    }
98
99    /// Nested atlas.
100    pub fn atlas(&self) -> &crate::AtlasArtifact {
101        &self.atlas
102    }
103}
104
105/// Input binding, articulation program, and nested atom atlases.
106#[derive(Debug, Clone)]
107pub struct ProgramArtifact {
108    pub(super) vertex_count: usize,
109    pub(super) threshold: Option<f64>,
110    pub(super) modulus: u32,
111    pub(super) input_digest: [u8; 32],
112    pub(super) diagram: Diagram,
113    pub(super) atoms: Vec<ProgramAtomArtifact>,
114}
115
116impl ProgramArtifact {
117    /// Capture the current state of a compiled program.
118    ///
119    /// Persistence reduction does not run. The artifact binds the graph
120    /// most recently supplied to the program.
121    pub fn from_program(
122        program: &PersistenceProgram,
123    ) -> std::result::Result<Self, ProgramArtifactError> {
124        Self::capture(program.current_graph(), program)
125    }
126
127    /// Produce a compositional program artifact.
128    pub fn build(
129        input: &SparseDistanceMatrix,
130        params: &RipsParams,
131        certificate_limits: CertificateLimits,
132    ) -> std::result::Result<Self, ProgramArtifactError> {
133        Self::compile(input, params, certificate_limits).map(|(artifact, _)| artifact)
134    }
135
136    /// Produce an artifact and retain its ready-to-update program.
137    pub fn compile(
138        input: &SparseDistanceMatrix,
139        params: &RipsParams,
140        certificate_limits: CertificateLimits,
141    ) -> std::result::Result<(Self, PersistenceProgram), ProgramArtifactError> {
142        let program = PersistenceProgram::compile(input, params, certificate_limits)
143            .map_err(|error| ProgramArtifactError::new(error.to_string()))?;
144        let artifact = Self::capture(input, &program)?;
145        Ok((artifact, program))
146    }
147
148    pub(crate) fn capture(
149        input: &SparseDistanceMatrix,
150        program: &PersistenceProgram,
151    ) -> std::result::Result<Self, ProgramArtifactError> {
152        let atoms = program
153            .states()
154            .iter()
155            .map(|state| ProgramAtomArtifact {
156                id: state.info_index,
157                vertices: state.vertices.clone(),
158                edges: state.edges.clone(),
159                atlas: state.artifact.clone(),
160            })
161            .collect();
162        let artifact = Self {
163            vertex_count: input.len(),
164            threshold: program.params().threshold,
165            modulus: program.params().modulus,
166            input_digest: verification::program_graph_digest(input, program.params().threshold),
167            diagram: program.result().diagram.clone(),
168            atoms,
169        };
170        artifact.check_structure(ProgramDecodeLimits::default())?;
171        Ok(artifact)
172    }
173
174    /// Diagram carried by the program.
175    pub fn diagram(&self) -> &Diagram {
176        &self.diagram
177    }
178
179    /// Number of vertices bound to the program.
180    pub fn vertex_count(&self) -> usize {
181        self.vertex_count
182    }
183
184    /// Fixed filtration threshold.
185    pub fn threshold(&self) -> Option<f64> {
186        self.threshold
187    }
188
189    /// Prime coefficient modulus.
190    pub fn modulus(&self) -> u32 {
191        self.modulus
192    }
193
194    /// Cyclic atoms and their nested proofs.
195    pub fn atoms(&self) -> &[ProgramAtomArtifact] {
196        &self.atoms
197    }
198
199    /// Encode the canonical `HOLOSPRG` version 1 envelope.
200    pub fn encode(&self) -> std::result::Result<Vec<u8>, ProgramArtifactError> {
201        self.check_structure(ProgramDecodeLimits::default())?;
202        let nested = encode::encode_atlases(&self.atoms)?;
203        let mut out = Vec::new();
204        encode::encode_program_header(&mut out, self)?;
205        encode::encode_bars(&mut out, &self.diagram.bars)?;
206        for (atom, atlas) in self.atoms.iter().zip(nested) {
207            encode::encode_atom(&mut out, atom, &atlas)?;
208        }
209        Ok(out)
210    }
211
212    /// Decode and structurally validate a bounded program envelope.
213    pub fn decode(
214        bytes: &[u8],
215        limits: ProgramDecodeLimits,
216        certificate_limits: CertificateLimits,
217    ) -> std::result::Result<Self, ProgramArtifactError> {
218        decode::check_envelope_size(bytes, limits.max_bytes)?;
219        let mut reader = super::primitives::Reader::new(bytes);
220        let header = decode::decode_program_header(&mut reader, limits)?;
221        decode::check_minimum_record_bytes(&reader, header.bar_count, header.atom_count)?;
222        let bars = decode::decode_bars(&mut reader, header.bar_count)?;
223        let atoms =
224            decode::decode_atoms(&mut reader, header.atom_count, limits, certificate_limits)?;
225        decode::check_no_trailing_bytes(&reader)?;
226        let artifact = Self {
227            vertex_count: header.vertex_count,
228            threshold: header.threshold,
229            modulus: header.modulus,
230            input_digest: header.input_digest,
231            diagram: Diagram { bars },
232            atoms,
233        };
234        artifact.check_structure(limits)?;
235        Ok(artifact)
236    }
237
238    /// Verify all atom proofs and reconstruct the program.
239    pub fn verify(
240        &self,
241        input: &SparseDistanceMatrix,
242        certificate_limits: CertificateLimits,
243    ) -> std::result::Result<PersistenceProgram, ProgramArtifactError> {
244        self.check_structure(ProgramDecodeLimits::default())?;
245        verification::check_input_binding(self, input)?;
246        let mut params = RipsParams::new(1).with_modulus(self.modulus);
247        params.threshold = self.threshold;
248        let (_, blocks) = crate::factorization::program_blocks(input, self.threshold)
249            .map_err(|error| ProgramArtifactError::new(error.to_string()))?;
250        let infos = crate::program::atom_infos(input, &blocks);
251        let topology: Vec<_> = input
252            .edges()
253            .map(|(u, v, _)| crate::EdgeKey::new(u, v))
254            .collect();
255        let states =
256            verification::verify_atom_states(self, input, &infos, &topology, certificate_limits)?;
257        let result = crate::program::compose_result(input, &params, &states)
258            .map_err(|error| ProgramArtifactError::new(error.to_string()))?;
259        verification::check_composed_diagram(&result.diagram, &self.diagram)?;
260        Ok(PersistenceProgram::from_verified_parts(
261            input,
262            &params,
263            certificate_limits,
264            infos,
265            states,
266            result,
267        ))
268    }
269
270    fn check_structure(
271        &self,
272        limits: ProgramDecodeLimits,
273    ) -> std::result::Result<(), ProgramArtifactError> {
274        verification::check_program_limits(self, limits)?;
275        verification::check_program_diagram(&self.diagram)?;
276        verification::check_program_atoms(self, limits)?;
277        Ok(())
278    }
279}