use crate::{
CorrespondenceMode, Error, IndexDeltaProof, IndexProofError, IndexSnapshotProof,
IndexTransition, PersistenceIndex, Result, SparseDistanceMatrix, TopologyPatch,
};
#[derive(Debug, Clone)]
pub enum IndexStreamProof {
Delta(IndexDeltaProof),
Snapshot(IndexSnapshotProof),
}
impl IndexStreamProof {
pub fn encode(&self) -> std::result::Result<Vec<u8>, IndexProofError> {
match self {
Self::Delta(proof) => proof.encode(),
Self::Snapshot(proof) => proof.encode(),
}
}
pub fn is_snapshot(&self) -> bool {
matches!(self, Self::Snapshot(_))
}
}
#[derive(Debug, Clone)]
pub struct IndexStreamStep {
pub sequence: u64,
pub transition: IndexTransition,
pub proof: IndexStreamProof,
}
#[derive(Debug, Clone)]
pub struct IndexStream {
index: PersistenceIndex,
sequence: u64,
}
impl IndexStream {
pub fn new(index: PersistenceIndex) -> Self {
Self { index, sequence: 0 }
}
pub fn current(&self) -> &PersistenceIndex {
&self.index
}
pub fn sequence(&self) -> u64 {
self.sequence
}
pub fn checkpoint(&self) -> std::result::Result<IndexSnapshotProof, IndexProofError> {
IndexSnapshotProof::from_index(&self.index)
}
pub fn apply_patch(
&mut self,
patch: &TopologyPatch,
correspondence_mode: CorrespondenceMode,
) -> Result<IndexStreamStep> {
let old = self.index.clone();
let transition = old.transition_patch_with(patch, correspondence_mode)?;
self.commit(old, transition)
}
pub fn apply_graph(
&mut self,
graph: &SparseDistanceMatrix,
correspondence_mode: CorrespondenceMode,
) -> Result<IndexStreamStep> {
let old = self.index.clone();
let transition = old.transition_with(graph, correspondence_mode)?;
self.commit(old, transition)
}
pub fn apply_patches(
&mut self,
patches: &[TopologyPatch],
correspondence_mode: CorrespondenceMode,
) -> Result<Vec<IndexStreamStep>> {
let mut candidate = self.clone();
let mut steps = Vec::with_capacity(patches.len());
for patch in patches {
steps.push(candidate.apply_patch(patch, correspondence_mode)?);
}
*self = candidate;
Ok(steps)
}
fn commit(
&mut self,
old: PersistenceIndex,
transition: IndexTransition,
) -> Result<IndexStreamStep> {
let proof = if old.topology() == transition.index.topology()
&& old.graph().len() == transition.index.graph().len()
{
IndexStreamProof::Delta(
IndexDeltaProof::between(&old, &transition.index).map_err(proof_error)?,
)
} else {
IndexStreamProof::Snapshot(
IndexSnapshotProof::from_index(&transition.index).map_err(proof_error)?,
)
};
let sequence = self
.sequence
.checked_add(1)
.ok_or_else(|| Error::InvalidInput("index stream sequence overflow".into()))?;
self.index = transition.index.clone();
self.sequence = sequence;
Ok(IndexStreamStep {
sequence,
transition,
proof,
})
}
}
fn proof_error(error: IndexProofError) -> Error {
Error::InvalidInput(error.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{CertificateLimits, IndexEdit, IndexParams, RipsParams};
fn graph(weight: f64) -> SparseDistanceMatrix {
SparseDistanceMatrix::from_triplets(
5,
&[
(0, 1, weight),
(0, 2, 1.0),
(1, 2, 1.5),
(0, 3, 1.1),
(1, 3, 1.6),
(0, 4, 1.2),
(1, 4, 1.7),
],
)
.unwrap()
}
#[test]
fn stream_emits_warm_and_cold_records() {
let mut params = RipsParams::new(1).with_modulus(3);
params.threshold = Some(2.0);
let index = PersistenceIndex::compile(
&graph(0.0),
¶ms,
IndexParams::default(),
CertificateLimits::default(),
)
.unwrap();
let mut stream = IndexStream::new(index);
let patch = TopologyPatch::new(vec![IndexEdit::deactivate(0, 1)]);
let warm = stream
.apply_patch(&patch, CorrespondenceMode::Omit)
.unwrap();
assert_eq!(warm.sequence, 1);
assert!(!warm.proof.is_snapshot());
let changed_envelope = SparseDistanceMatrix::from_triplets(5, &[(0, 1, 0.5)]).unwrap();
let cold = stream
.apply_graph(&changed_envelope, CorrespondenceMode::Omit)
.unwrap();
assert_eq!(cold.sequence, 2);
assert!(cold.proof.is_snapshot());
}
#[test]
fn patch_batch_is_atomic() {
let mut params = RipsParams::new(1);
params.threshold = Some(2.0);
let index = PersistenceIndex::compile(
&graph(0.0),
¶ms,
IndexParams::default(),
CertificateLimits::default(),
)
.unwrap();
let mut stream = IndexStream::new(index);
let old_root = stream.current().version();
let patches = [
TopologyPatch::new(vec![IndexEdit::set_weight(0, 2, 1.1)]),
TopologyPatch::new(vec![IndexEdit::activate(0, 1, 3.0)]),
];
assert!(
stream
.apply_patches(&patches, CorrespondenceMode::Omit)
.is_err()
);
assert_eq!(stream.sequence(), 0);
assert_eq!(stream.current().version(), old_root);
}
}