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}