Skip to main content

holos_tda/trajectory/
model.rs

1use std::fmt;
2
3use crate::{
4    AtlasArtifact, AtlasDecodeLimits, AtlasEvaluation, CertificateLimits, RipsParams,
5    SparseDistanceMatrix, TopologyEvent, UpdateMode,
6};
7
8use super::{decode, encode, verification};
9
10/// Failure while producing, decoding, or checking a trajectory artifact.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct TrajectoryError {
13    message: String,
14}
15
16impl TrajectoryError {
17    pub(super) fn new(message: impl Into<String>) -> Self {
18        Self {
19            message: message.into(),
20        }
21    }
22
23    /// Description of the violated trajectory rule.
24    pub fn message(&self) -> &str {
25        &self.message
26    }
27}
28
29impl fmt::Display for TrajectoryError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        write!(f, "trajectory artifact: {}", self.message)
32    }
33}
34
35impl std::error::Error for TrajectoryError {}
36
37/// Decoder limits applied before trajectory collections are allocated.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub struct TrajectoryDecodeLimits {
41    /// Largest accepted envelope in bytes.
42    pub max_bytes: usize,
43    /// Largest accepted update count.
44    pub max_steps: usize,
45    /// Largest vertex count in one graph.
46    pub max_vertices: usize,
47    /// Largest total edge count across all graphs.
48    pub max_total_edges: usize,
49    /// Largest total event count.
50    pub max_total_events: usize,
51    /// Largest nested atlas envelope in bytes.
52    pub max_atlas_bytes: usize,
53}
54
55impl Default for TrajectoryDecodeLimits {
56    fn default() -> Self {
57        Self {
58            max_bytes: 1 << 30,
59            max_steps: 10_000_000,
60            max_vertices: 1_000_000,
61            max_total_edges: 200_000_000,
62            max_total_events: 100_000_000,
63            max_atlas_bytes: 1 << 30,
64        }
65    }
66}
67
68/// One graph and its transition from the preceding atlas region.
69#[derive(Debug, Clone)]
70pub struct TrajectoryStep {
71    pub(super) input: SparseDistanceMatrix,
72    pub(super) mode: UpdateMode,
73    pub(super) events: Vec<TopologyEvent>,
74    pub(super) checkpoint: Option<AtlasArtifact>,
75}
76
77impl TrajectoryStep {
78    /// Graph evaluated at this step.
79    pub fn input(&self) -> &SparseDistanceMatrix {
80        &self.input
81    }
82
83    /// Transition from the prior atlas region.
84    pub fn mode(&self) -> UpdateMode {
85        self.mode
86    }
87
88    /// Events declared at this step.
89    pub fn events(&self) -> &[TopologyEvent] {
90        &self.events
91    }
92
93    /// Atlas checkpoint at a region boundary.
94    pub fn checkpoint(&self) -> Option<&AtlasArtifact> {
95        self.checkpoint.as_ref()
96    }
97}
98
99/// Graphs, events, and proofs for a persistence trajectory.
100#[derive(Debug, Clone)]
101pub struct TrajectoryArtifact {
102    pub(super) initial_input: SparseDistanceMatrix,
103    pub(super) initial_atlas: AtlasArtifact,
104    pub(super) steps: Vec<TrajectoryStep>,
105}
106
107impl TrajectoryArtifact {
108    /// Compile and certify a sequence of sparse weighted graphs.
109    pub fn build(
110        initial: &SparseDistanceMatrix,
111        updates: &[SparseDistanceMatrix],
112        params: &RipsParams,
113        certificate_limits: CertificateLimits,
114    ) -> std::result::Result<Self, TrajectoryError> {
115        let (initial_atlas, mut atlas) =
116            AtlasArtifact::compile(initial, params, certificate_limits)
117                .map_err(|error| TrajectoryError::new(error.to_string()))?;
118        let mut steps = Vec::with_capacity(updates.len());
119        for input in updates {
120            let events = atlas.events(input);
121            if events.is_empty() {
122                atlas
123                    .evaluate(input)
124                    .map_err(|error| TrajectoryError::new(error.to_string()))?;
125                steps.push(TrajectoryStep {
126                    input: input.clone(),
127                    mode: UpdateMode::Reused,
128                    events,
129                    checkpoint: None,
130                });
131            } else {
132                let (checkpoint, next) = AtlasArtifact::compile(input, params, certificate_limits)
133                    .map_err(|error| TrajectoryError::new(error.to_string()))?;
134                atlas = next;
135                steps.push(TrajectoryStep {
136                    input: input.clone(),
137                    mode: UpdateMode::Recomputed,
138                    events,
139                    checkpoint: Some(checkpoint),
140                });
141            }
142        }
143        Ok(Self {
144            initial_input: initial.clone(),
145            initial_atlas,
146            steps,
147        })
148    }
149
150    /// Initial graph.
151    pub fn initial_input(&self) -> &SparseDistanceMatrix {
152        &self.initial_input
153    }
154
155    /// Initial atlas.
156    pub fn initial_atlas(&self) -> &AtlasArtifact {
157        &self.initial_atlas
158    }
159
160    /// Ordered trajectory steps.
161    pub fn steps(&self) -> &[TrajectoryStep] {
162        &self.steps
163    }
164
165    /// Encode the canonical `HOLOSTRC` version 1 envelope.
166    pub fn encode(&self) -> std::result::Result<Vec<u8>, TrajectoryError> {
167        self.check_shape()?;
168        let initial_atlas = encode::encode_atlas(&self.initial_atlas)?;
169        let checkpoints = encode::encode_checkpoints(&self.steps)?;
170        let mut out = Vec::new();
171        encode::encode_trajectory_header(&mut out, self.steps.len(), initial_atlas.len())?;
172        encode::encode_graph(&mut out, &self.initial_input)?;
173        out.extend_from_slice(&initial_atlas);
174        for (step, checkpoint) in self.steps.iter().zip(checkpoints) {
175            encode::encode_trajectory_step(&mut out, step, checkpoint.as_deref())?;
176        }
177        Ok(out)
178    }
179
180    /// Decode and structurally validate a bounded trajectory envelope.
181    pub fn decode(
182        bytes: &[u8],
183        limits: TrajectoryDecodeLimits,
184        atlas_limits: AtlasDecodeLimits,
185        certificate_limits: CertificateLimits,
186    ) -> std::result::Result<Self, TrajectoryError> {
187        decode::check_envelope_size(bytes, limits.max_bytes)?;
188        let mut reader = super::primitives::Reader::new(bytes);
189        let header = decode::decode_trajectory_header(&mut reader, limits)?;
190        let mut total_edges = 0usize;
191        let initial_input = decode::decode_graph(&mut reader, limits, &mut total_edges)?;
192        let initial_atlas = decode::decode_nested_atlas(
193            &mut reader,
194            header.initial_atlas_bytes,
195            limits,
196            atlas_limits,
197            certificate_limits,
198        )?;
199        let mut total_events = 0usize;
200        let mut context = decode::TrajectoryDecodeContext {
201            limits,
202            atlas_limits,
203            certificate_limits,
204            total_edges: &mut total_edges,
205            total_events: &mut total_events,
206        };
207        let steps = decode::decode_trajectory_steps(&mut reader, header.step_count, &mut context)?;
208        decode::check_no_trailing_bytes(&reader)?;
209        let artifact = Self {
210            initial_input,
211            initial_atlas,
212            steps,
213        };
214        artifact.check_shape()?;
215        Ok(artifact)
216    }
217
218    /// Verify all proofs and region transitions.
219    pub fn verify(
220        &self,
221        certificate_limits: CertificateLimits,
222    ) -> std::result::Result<VerifiedTrajectory, TrajectoryError> {
223        self.check_shape()?;
224        let mut atlas = verification::verify_atlas(
225            &self.initial_atlas,
226            &self.initial_input,
227            certificate_limits,
228        )?;
229        let initial = atlas
230            .evaluate(&self.initial_input)
231            .map_err(|error| TrajectoryError::new(error.to_string()))?;
232        let mut verified_steps = Vec::with_capacity(self.steps.len());
233        for (index, step) in self.steps.iter().enumerate() {
234            verified_steps.push(verification::verify_trajectory_step(
235                &mut atlas,
236                step,
237                index,
238                certificate_limits,
239            )?);
240        }
241        Ok(VerifiedTrajectory {
242            initial,
243            steps: verified_steps,
244        })
245    }
246
247    fn check_shape(&self) -> std::result::Result<(), TrajectoryError> {
248        for (index, step) in self.steps.iter().enumerate() {
249            let checkpoint_matches = matches!(
250                (step.mode, step.checkpoint.is_some()),
251                (UpdateMode::Reused, false) | (UpdateMode::Recomputed, true)
252            );
253            if !checkpoint_matches {
254                return Err(TrajectoryError::new(format!(
255                    "step {index} checkpoint does not match its mode"
256                )));
257            }
258            if (step.mode == UpdateMode::Reused) != step.events.is_empty() {
259                return Err(TrajectoryError::new(format!(
260                    "step {index} event count does not match its mode"
261                )));
262            }
263        }
264        Ok(())
265    }
266}
267
268/// Evaluation at one trajectory step.
269#[derive(Debug, Clone)]
270pub struct VerifiedTrajectoryStep {
271    /// Transition mode.
272    pub mode: UpdateMode,
273    /// Events derived from the preceding atlas.
274    pub events: Vec<TopologyEvent>,
275    /// Exact H0 and H1 result.
276    pub evaluation: AtlasEvaluation,
277}
278
279/// Results reconstructed from a trajectory artifact.
280#[derive(Debug, Clone)]
281pub struct VerifiedTrajectory {
282    /// Exact result for the initial graph.
283    pub initial: AtlasEvaluation,
284    /// Update results.
285    pub steps: Vec<VerifiedTrajectoryStep>,
286}