Skip to main content

holos_tda/index/
model.rs

1use std::sync::Arc;
2
3use crate::{
4    Bar, CertificateLimits, ClassCorrespondence, Diagram, EdgeKey, GradedReductionCertificate,
5    RelativeInterfaceCertificate, RipsParams, SparseDistanceMatrix,
6};
7
8/// Bounds and decomposition choices for a [`PersistenceIndex`].
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub struct IndexParams {
12    /// Largest vertex separator considered by the deterministic search.
13    pub max_separator_width: usize,
14    /// Largest total candidate count across one tree compilation.
15    pub separator_search_limit: usize,
16    /// A scope at or below this vertex count remains a leaf.
17    pub leaf_vertices: usize,
18    /// How parent interfaces compose or retain reductions.
19    pub interface_policy: InterfacePolicy,
20}
21
22impl Default for IndexParams {
23    fn default() -> Self {
24        Self {
25            max_separator_width: 4,
26            separator_search_limit: 100_000,
27            leaf_vertices: 4,
28            interface_policy: InterfacePolicy::Relative,
29        }
30    }
31}
32
33/// Policy for parent interfaces.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum InterfacePolicy {
36    /// Compose relative cores through arbitrary protected separators.
37    Relative,
38    /// Compose at a certified separator and materialize other parents.
39    Compose,
40    /// Retain a reduction over every parent scope.
41    Materialize,
42}
43
44/// Structural and algebraic size of one compiled separator tree.
45#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
46pub struct IndexSummary {
47    /// Highest homology dimension maintained by this index.
48    pub max_dim: usize,
49    /// Interface nodes in the tree, including the root.
50    pub nodes: usize,
51    /// Nodes without children.
52    pub leaves: usize,
53    /// Nodes split by a nonempty separator.
54    pub separators: usize,
55    /// Nodes split into disconnected components.
56    pub component_splits: usize,
57    /// Largest separator used by the tree.
58    pub widest_separator: usize,
59    /// Largest vertex scope retained by one materialized interface.
60    pub largest_interface_vertices: usize,
61    /// Largest listed-edge scope retained by one materialized interface.
62    pub largest_interface_edges: usize,
63    /// Interfaces composed without a reduction over their full scope.
64    pub composed_interfaces: usize,
65    /// Interfaces that retain a checked reduction over their full scope.
66    pub materialized_interfaces: usize,
67    /// Interfaces that retain an exact relative filtered core.
68    pub relative_interfaces: usize,
69    /// Cells supplied to all relative interfaces before cancellation.
70    pub relative_input_cells: usize,
71    /// Cells retained by all relative interfaces after cancellation.
72    pub relative_core_cells: usize,
73    /// Largest cell count retained by one relative interface.
74    pub largest_relative_core_cells: usize,
75    /// Equal-filtration pairs removed across all relative interfaces.
76    pub relative_cancellations: usize,
77    /// Whether the root omits a reduction over its full scope.
78    pub root_composed: bool,
79    /// Candidate vertex sets checked during decomposition.
80    pub separator_candidates_checked: usize,
81    /// Whether the bounded search visited every candidate in scope.
82    pub separator_search_complete: bool,
83}
84
85/// Exact work charged to one index transition.
86#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
87pub struct IndexWork {
88    /// Envelope edges compared when a full graph was supplied.
89    pub edges_checked: usize,
90    /// Tree nodes whose scopes contained at least one changed edge.
91    pub nodes_touched: usize,
92    /// Tree nodes shared with the preceding version.
93    pub nodes_shared: usize,
94    /// Touched reductions that retained at least one dependency column.
95    pub nodes_repaired: usize,
96    /// Touched reductions rebuilt without a retained dependency column.
97    pub nodes_rebuilt: usize,
98    /// Touched interfaces composed from their direct children.
99    pub nodes_composed: usize,
100    /// Touched relative leaf cores rebuilt from their local graph.
101    pub relative_nodes_rebuilt: usize,
102    /// Touched relative parent cores recomposed from child cores.
103    pub relative_nodes_composed: usize,
104    /// Cells supplied to touched relative interfaces before cancellation.
105    pub relative_input_cells: usize,
106    /// Cells retained by touched relative interfaces after cancellation.
107    pub relative_core_cells: usize,
108    /// Equal-filtration pairs removed in touched relative interfaces.
109    pub relative_cancellations: usize,
110    /// Reduction columns retained without another reduction pass.
111    pub reduction_columns_reused: usize,
112    /// Reduction columns processed by repair or rebuild.
113    pub reduction_columns_reduced: usize,
114    /// Sparse column additions performed during repair.
115    pub reduction_column_additions: usize,
116}
117
118/// How an index produced a new version.
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum IndexUpdateMode {
121    /// The supplied graph was bit-for-bit equal to the current graph.
122    Unchanged,
123    /// Every changed node retained at least one reduction column.
124    Repaired,
125    /// Changed routes required no reduction over a parent scope.
126    Composed,
127    /// Changed routes were rebuilt from exact local relative cores.
128    Relative,
129    /// At least one changed node rebuilt its complete reduction.
130    Rebuilt,
131    /// The listed-edge envelope changed and a new tree was compiled.
132    Recompiled,
133}
134
135/// Why an index transition changed execution state.
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137#[non_exhaustive]
138pub enum IndexEventKind {
139    /// An edge crossed the fixed filtration threshold.
140    ThresholdCrossing,
141    /// A reduction retained a valid dependency prefix.
142    ReductionRepaired,
143    /// A reduction retained no dependency prefix.
144    ReductionRebuilt,
145    /// A parent diagram was composed from checked child interfaces.
146    InterfaceComposed,
147    /// A relative leaf core was rebuilt from its local graph.
148    RelativeCoreRebuilt,
149    /// A relative parent core was recomposed from direct child cores.
150    RelativeCoreComposed,
151    /// The fixed listed-edge envelope changed.
152    EnvelopeRecompiled,
153}
154
155/// One event reported by an index transition.
156#[derive(Debug, Clone, PartialEq, Eq)]
157pub struct IndexEvent {
158    /// Event kind.
159    pub kind: IndexEventKind,
160    /// Content identifier of the affected old node, when one exists.
161    pub node: Option<[u8; 32]>,
162    /// First changed edge in the node, when one exists.
163    pub edge: Option<EdgeKey>,
164}
165
166/// Exact multiset difference between two persistence diagrams.
167#[derive(Debug, Clone, Default)]
168pub struct DiagramDelta {
169    /// Intervals removed from the preceding version.
170    pub removed: Vec<Bar>,
171    /// Intervals added in the new version.
172    pub added: Vec<Bar>,
173}
174
175/// One change to an edge already present in an index envelope.
176#[derive(Debug, Clone, Copy, PartialEq)]
177#[non_exhaustive]
178pub enum IndexEdit {
179    /// Set the edge to the supplied non-negative finite weight.
180    SetWeight {
181        /// Edge in the fixed listed-edge envelope.
182        edge: EdgeKey,
183        /// New edge weight.
184        value: f64,
185    },
186    /// Move an envelope edge into the finite filtration.
187    Activate {
188        /// Edge in the fixed listed-edge envelope.
189        edge: EdgeKey,
190        /// New edge weight at or below the finite threshold.
191        value: f64,
192    },
193    /// Keep the edge listed but place it above the finite threshold.
194    Deactivate {
195        /// Edge in the fixed listed-edge envelope.
196        edge: EdgeKey,
197    },
198}
199
200impl IndexEdit {
201    /// Construct a weight change in canonical endpoint order.
202    pub fn set_weight(u: usize, v: usize, value: f64) -> Self {
203        Self::SetWeight {
204            edge: EdgeKey::new(u, v),
205            value,
206        }
207    }
208
209    /// Construct an edge deactivation in canonical endpoint order.
210    pub fn deactivate(u: usize, v: usize) -> Self {
211        Self::Deactivate {
212            edge: EdgeKey::new(u, v),
213        }
214    }
215
216    /// Construct an edge activation in canonical endpoint order.
217    pub fn activate(u: usize, v: usize, value: f64) -> Self {
218        Self::Activate {
219            edge: EdgeKey::new(u, v),
220            value,
221        }
222    }
223
224    pub(crate) fn edge(self) -> EdgeKey {
225        match self {
226            Self::SetWeight { edge, .. }
227            | Self::Activate { edge, .. }
228            | Self::Deactivate { edge } => edge,
229        }
230    }
231}
232
233/// One atomic active-topology patch inside a fixed edge envelope.
234#[derive(Debug, Clone, Default, PartialEq)]
235pub struct TopologyPatch {
236    edits: Vec<IndexEdit>,
237}
238
239impl TopologyPatch {
240    /// Construct a patch from edge edits applied as one transaction.
241    pub fn new(edits: Vec<IndexEdit>) -> Self {
242        Self { edits }
243    }
244
245    /// Edits in this transaction.
246    pub fn edits(&self) -> &[IndexEdit] {
247        &self.edits
248    }
249
250    /// Consume this patch and return its edits.
251    pub fn into_edits(self) -> Vec<IndexEdit> {
252        self.edits
253    }
254}
255
256/// Difference between two index versions.
257#[derive(Debug, Clone)]
258pub struct IndexDiff {
259    /// Whether both versions use the same listed-edge envelope.
260    pub same_envelope: bool,
261    /// Tree nodes physically shared by both versions.
262    pub shared_nodes: usize,
263    /// Exact persistence-diagram difference.
264    pub diagram: DiagramDelta,
265}
266
267/// One ordered alternative advanced from a shared index version.
268#[derive(Debug, Clone)]
269pub struct IndexBranch {
270    /// Position of the alternative in the input batch.
271    pub index: usize,
272    /// Transition from the shared branch point.
273    pub transition: IndexTransition,
274}
275
276/// Summary of one filtered interface.
277#[derive(Debug, Clone, PartialEq, Eq)]
278pub struct InterfaceSummary {
279    /// Content identifier of this node.
280    pub digest: [u8; 32],
281    /// Depth from the root.
282    pub depth: usize,
283    /// Original labeled vertices in the node scope.
284    pub vertices: Vec<usize>,
285    /// Original labeled separator vertices shared by its children.
286    pub separator: Vec<usize>,
287    /// Original labeled vertices fixed for every ancestor composition.
288    pub protected_vertices: Vec<usize>,
289    /// Listed edges in the node scope.
290    pub edges: usize,
291    /// Direct child count.
292    pub children: usize,
293    /// Algebra used by this interface.
294    pub mode: InterfaceMode,
295    /// Boundary columns in the checked reduction.
296    pub reduction_columns: usize,
297    /// Boundary-column counts for simplex dimensions one through `max_dim + 1`.
298    pub columns_by_dimension: Vec<usize>,
299    /// Cells supplied before relative cancellation.
300    pub relative_input_cells: usize,
301    /// Cells retained by the relative interface.
302    pub relative_core_cells: usize,
303    /// Equal-filtration pairs removed by relative cancellation.
304    pub relative_cancellations: usize,
305}
306
307/// Algebra retained by one separator interface.
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum InterfaceMode {
310    /// The interface stores a filtered core relative to its parent boundary.
311    Relative,
312    /// The interface stores a reduction over its full vertex scope.
313    Materialized,
314    /// The scope is a disjoint union of its child scopes.
315    Disjoint,
316    /// Child scopes meet in one zero-filtration simplex.
317    ZeroSimplex,
318    /// Child scopes meet in a zero-filtration flag cone.
319    ZeroCone,
320}
321
322/// Result of constructing a new index version.
323#[derive(Debug, Clone)]
324pub struct IndexTransition {
325    /// New exact index version.
326    pub index: PersistenceIndex,
327    /// How the version was produced.
328    pub mode: IndexUpdateMode,
329    /// Exact diagram difference.
330    pub delta: DiagramDelta,
331    /// Transition events.
332    pub events: Vec<IndexEvent>,
333    /// Optional exact class relations on the common filtered subcomplex.
334    pub correspondence: Vec<ClassCorrespondence>,
335    /// Exact work charged to this transition.
336    pub work: IndexWork,
337}
338
339#[derive(Debug, Clone)]
340pub(crate) struct InterfaceNode {
341    pub(crate) digest: [u8; 32],
342    pub(crate) vertices: Vec<usize>,
343    pub(crate) edge_positions: Vec<usize>,
344    pub(crate) separator: Vec<usize>,
345    pub(crate) protected_vertices: Vec<usize>,
346    pub(crate) children: Vec<Arc<InterfaceNode>>,
347    pub(crate) state: InterfaceState,
348}
349
350#[derive(Debug, Clone)]
351pub(crate) enum InterfaceState {
352    Relative(RelativeInterfaceCertificate),
353    Materialized(GradedReductionCertificate),
354    Composed {
355        mode: InterfaceMode,
356        diagram: Diagram,
357    },
358}
359
360impl InterfaceNode {
361    pub(crate) fn diagram(&self) -> &Diagram {
362        match &self.state {
363            InterfaceState::Relative(relative) => relative.diagram(),
364            InterfaceState::Materialized(reduction) => reduction.diagram(),
365            InterfaceState::Composed { diagram, .. } => diagram,
366        }
367    }
368
369    pub(crate) fn mode(&self) -> InterfaceMode {
370        match &self.state {
371            InterfaceState::Relative(_) => InterfaceMode::Relative,
372            InterfaceState::Materialized(_) => InterfaceMode::Materialized,
373            InterfaceState::Composed { mode, .. } => *mode,
374        }
375    }
376
377    pub(crate) fn reduction(&self) -> Option<&GradedReductionCertificate> {
378        match &self.state {
379            InterfaceState::Materialized(reduction) => Some(reduction),
380            InterfaceState::Relative(_) | InterfaceState::Composed { .. } => None,
381        }
382    }
383
384    pub(crate) fn relative(&self) -> Option<&RelativeInterfaceCertificate> {
385        match &self.state {
386            InterfaceState::Relative(relative) => Some(relative),
387            InterfaceState::Materialized(_) | InterfaceState::Composed { .. } => None,
388        }
389    }
390}
391
392/// An immutable, versioned persistence index.
393///
394/// The listed vertices and edges form an envelope. Weight changes and
395/// threshold crossings inside that envelope preserve the separator tree.
396/// A different listed-edge set compiles a new tree and reports that event.
397#[derive(Debug, Clone)]
398pub struct PersistenceIndex {
399    pub(crate) params: RipsParams,
400    pub(crate) index_params: IndexParams,
401    pub(crate) limits: CertificateLimits,
402    pub(crate) graph: Arc<SparseDistanceMatrix>,
403    pub(crate) topology: Arc<Vec<EdgeKey>>,
404    pub(crate) root: Arc<InterfaceNode>,
405    pub(crate) summary: IndexSummary,
406}