use std::fmt;
use crate::{
AtlasArtifact, AtlasDecodeLimits, AtlasEvaluation, CertificateLimits, RipsParams,
SparseDistanceMatrix, TopologyEvent, UpdateMode,
};
use super::{decode, encode, verification};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TrajectoryError {
message: String,
}
impl TrajectoryError {
pub(super) fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
impl fmt::Display for TrajectoryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "trajectory artifact: {}", self.message)
}
}
impl std::error::Error for TrajectoryError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct TrajectoryDecodeLimits {
pub max_bytes: usize,
pub max_steps: usize,
pub max_vertices: usize,
pub max_total_edges: usize,
pub max_total_events: usize,
pub max_atlas_bytes: usize,
}
impl Default for TrajectoryDecodeLimits {
fn default() -> Self {
Self {
max_bytes: 1 << 30,
max_steps: 10_000_000,
max_vertices: 1_000_000,
max_total_edges: 200_000_000,
max_total_events: 100_000_000,
max_atlas_bytes: 1 << 30,
}
}
}
#[derive(Debug, Clone)]
pub struct TrajectoryStep {
pub(super) input: SparseDistanceMatrix,
pub(super) mode: UpdateMode,
pub(super) events: Vec<TopologyEvent>,
pub(super) checkpoint: Option<AtlasArtifact>,
}
impl TrajectoryStep {
pub fn input(&self) -> &SparseDistanceMatrix {
&self.input
}
pub fn mode(&self) -> UpdateMode {
self.mode
}
pub fn events(&self) -> &[TopologyEvent] {
&self.events
}
pub fn checkpoint(&self) -> Option<&AtlasArtifact> {
self.checkpoint.as_ref()
}
}
#[derive(Debug, Clone)]
pub struct TrajectoryArtifact {
pub(super) initial_input: SparseDistanceMatrix,
pub(super) initial_atlas: AtlasArtifact,
pub(super) steps: Vec<TrajectoryStep>,
}
impl TrajectoryArtifact {
pub fn build(
initial: &SparseDistanceMatrix,
updates: &[SparseDistanceMatrix],
params: &RipsParams,
certificate_limits: CertificateLimits,
) -> std::result::Result<Self, TrajectoryError> {
let (initial_atlas, mut atlas) =
AtlasArtifact::compile(initial, params, certificate_limits)
.map_err(|error| TrajectoryError::new(error.to_string()))?;
let mut steps = Vec::with_capacity(updates.len());
for input in updates {
let events = atlas.events(input);
if events.is_empty() {
atlas
.evaluate(input)
.map_err(|error| TrajectoryError::new(error.to_string()))?;
steps.push(TrajectoryStep {
input: input.clone(),
mode: UpdateMode::Reused,
events,
checkpoint: None,
});
} else {
let (checkpoint, next) = AtlasArtifact::compile(input, params, certificate_limits)
.map_err(|error| TrajectoryError::new(error.to_string()))?;
atlas = next;
steps.push(TrajectoryStep {
input: input.clone(),
mode: UpdateMode::Recomputed,
events,
checkpoint: Some(checkpoint),
});
}
}
Ok(Self {
initial_input: initial.clone(),
initial_atlas,
steps,
})
}
pub fn initial_input(&self) -> &SparseDistanceMatrix {
&self.initial_input
}
pub fn initial_atlas(&self) -> &AtlasArtifact {
&self.initial_atlas
}
pub fn steps(&self) -> &[TrajectoryStep] {
&self.steps
}
pub fn encode(&self) -> std::result::Result<Vec<u8>, TrajectoryError> {
self.check_shape()?;
let initial_atlas = encode::encode_atlas(&self.initial_atlas)?;
let checkpoints = encode::encode_checkpoints(&self.steps)?;
let mut out = Vec::new();
encode::encode_trajectory_header(&mut out, self.steps.len(), initial_atlas.len())?;
encode::encode_graph(&mut out, &self.initial_input)?;
out.extend_from_slice(&initial_atlas);
for (step, checkpoint) in self.steps.iter().zip(checkpoints) {
encode::encode_trajectory_step(&mut out, step, checkpoint.as_deref())?;
}
Ok(out)
}
pub fn decode(
bytes: &[u8],
limits: TrajectoryDecodeLimits,
atlas_limits: AtlasDecodeLimits,
certificate_limits: CertificateLimits,
) -> std::result::Result<Self, TrajectoryError> {
decode::check_envelope_size(bytes, limits.max_bytes)?;
let mut reader = super::primitives::Reader::new(bytes);
let header = decode::decode_trajectory_header(&mut reader, limits)?;
let mut total_edges = 0usize;
let initial_input = decode::decode_graph(&mut reader, limits, &mut total_edges)?;
let initial_atlas = decode::decode_nested_atlas(
&mut reader,
header.initial_atlas_bytes,
limits,
atlas_limits,
certificate_limits,
)?;
let mut total_events = 0usize;
let mut context = decode::TrajectoryDecodeContext {
limits,
atlas_limits,
certificate_limits,
total_edges: &mut total_edges,
total_events: &mut total_events,
};
let steps = decode::decode_trajectory_steps(&mut reader, header.step_count, &mut context)?;
decode::check_no_trailing_bytes(&reader)?;
let artifact = Self {
initial_input,
initial_atlas,
steps,
};
artifact.check_shape()?;
Ok(artifact)
}
pub fn verify(
&self,
certificate_limits: CertificateLimits,
) -> std::result::Result<VerifiedTrajectory, TrajectoryError> {
self.check_shape()?;
let mut atlas = verification::verify_atlas(
&self.initial_atlas,
&self.initial_input,
certificate_limits,
)?;
let initial = atlas
.evaluate(&self.initial_input)
.map_err(|error| TrajectoryError::new(error.to_string()))?;
let mut verified_steps = Vec::with_capacity(self.steps.len());
for (index, step) in self.steps.iter().enumerate() {
verified_steps.push(verification::verify_trajectory_step(
&mut atlas,
step,
index,
certificate_limits,
)?);
}
Ok(VerifiedTrajectory {
initial,
steps: verified_steps,
})
}
fn check_shape(&self) -> std::result::Result<(), TrajectoryError> {
for (index, step) in self.steps.iter().enumerate() {
let checkpoint_matches = matches!(
(step.mode, step.checkpoint.is_some()),
(UpdateMode::Reused, false) | (UpdateMode::Recomputed, true)
);
if !checkpoint_matches {
return Err(TrajectoryError::new(format!(
"step {index} checkpoint does not match its mode"
)));
}
if (step.mode == UpdateMode::Reused) != step.events.is_empty() {
return Err(TrajectoryError::new(format!(
"step {index} event count does not match its mode"
)));
}
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct VerifiedTrajectoryStep {
pub mode: UpdateMode,
pub events: Vec<TopologyEvent>,
pub evaluation: AtlasEvaluation,
}
#[derive(Debug, Clone)]
pub struct VerifiedTrajectory {
pub initial: AtlasEvaluation,
pub steps: Vec<VerifiedTrajectoryStep>,
}