Skip to main content

holos_tda/classes/
model.rs

1use std::fmt;
2
3use crate::{Bar, Diagram};
4
5/// Identifier of a persistent interval group and its class space.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub struct IntervalGroupId([u8; 32]);
8
9impl IntervalGroupId {
10    /// Identifier bytes.
11    pub fn as_bytes(&self) -> &[u8; 32] {
12        &self.0
13    }
14
15    /// Construct an identifier from its serialized bytes.
16    pub fn from_bytes(bytes: [u8; 32]) -> Self {
17        Self(bytes)
18    }
19}
20
21impl fmt::Display for IntervalGroupId {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        for byte in self.0 {
24            write!(f, "{byte:02x}")?;
25        }
26        Ok(())
27    }
28}
29
30/// Identifier of one vector in a declared canonical class-space basis.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub struct BasisClassId([u8; 32]);
33
34impl BasisClassId {
35    /// Identifier bytes.
36    pub fn as_bytes(&self) -> &[u8; 32] {
37        &self.0
38    }
39
40    /// Construct an identifier from its serialized bytes.
41    pub fn from_bytes(bytes: [u8; 32]) -> Self {
42        Self(bytes)
43    }
44}
45
46impl fmt::Display for BasisClassId {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        for byte in self.0 {
49            write!(f, "{byte:02x}")?;
50        }
51        Ok(())
52    }
53}
54
55/// One nonzero coefficient on an oriented edge.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
57pub struct CocycleTerm {
58    /// Lower endpoint. The edge is oriented from `u` to `v`.
59    pub u: usize,
60    /// Higher endpoint.
61    pub v: usize,
62    /// Coefficient in `1..modulus`.
63    pub coefficient: u32,
64}
65
66/// Canonical H1 cocycle at one filtration scale.
67#[derive(Debug, Clone, PartialEq)]
68pub struct Cocycle {
69    /// Prime coefficient modulus.
70    pub modulus: u32,
71    /// Scale at which the terms represent the class.
72    pub scale: f64,
73    /// Nonzero terms in ascending endpoint order. The first coefficient is
74    /// one.
75    pub terms: Vec<CocycleTerm>,
76}
77
78/// One simplex that creates or destroys a persistence interval.
79#[derive(Debug, Clone, PartialEq)]
80pub struct CriticalSimplex {
81    /// Vertices in ascending order.
82    pub vertices: Vec<usize>,
83    /// Filtration value of the simplex.
84    pub value: f64,
85}
86
87/// Creator and optional destroyer of one generated interval.
88#[derive(Debug, Clone, PartialEq)]
89pub struct CriticalPair {
90    /// Edge that creates the H1 interval.
91    pub birth: CriticalSimplex,
92    /// Triangle that destroys a finite H1 interval.
93    pub death: Option<CriticalSimplex>,
94}
95
96/// Source binding for one interval-bound H1 class.
97///
98/// The source digest covers the active labeled graph at the representative
99/// scale. The class digest is the canonical [`BasisClassId`] bytes. Derived
100/// class spaces can omit this binding when their source graph is not the
101/// scalar Rips input.
102#[derive(Debug, Clone, Copy, PartialEq)]
103pub struct PersistentClassProvenance {
104    pub(crate) source_graph_digest: [u8; 32],
105    pub(crate) class_digest: [u8; 32],
106    pub(crate) interval: Bar,
107    pub(crate) modulus: u32,
108    pub(crate) scale: f64,
109}
110
111impl PersistentClassProvenance {
112    /// Raw digest of the active labeled graph at the representative scale.
113    pub fn source_graph_digest(&self) -> &[u8; 32] {
114        &self.source_graph_digest
115    }
116
117    /// Deterministic digest of the canonical class identity.
118    pub fn class_digest(&self) -> &[u8; 32] {
119        &self.class_digest
120    }
121
122    /// Persistence interval bound to the source graph.
123    pub fn interval(&self) -> Bar {
124        self.interval
125    }
126
127    /// Prime field bound to the representative.
128    pub fn modulus(&self) -> u32 {
129        self.modulus
130    }
131
132    /// Representative scale bound to the interval.
133    pub fn scale(&self) -> f64 {
134        self.scale
135    }
136}
137
138/// One vector in the declared basis of a persistent H1 class space.
139#[derive(Debug, Clone, PartialEq)]
140pub struct PersistentClass {
141    /// Identifier derived from the class space, basis position, and cocycle.
142    pub id: BasisClassId,
143    /// Class space that contains this basis vector.
144    pub group_id: IntervalGroupId,
145    /// Position in the canonical basis of the class space.
146    pub basis_index: usize,
147    /// H1 persistence interval.
148    pub interval: Bar,
149    /// Representative on the caller's graph.
150    pub cocycle: Cocycle,
151    /// Source binding for scalar Rips classes, when available.
152    pub provenance: Option<PersistentClassProvenance>,
153}
154
155/// All H1 classes with the same interval, represented as one class space.
156///
157/// Equal intervals do not have intrinsic individual identities. `basis` is
158/// a deterministic row-reduced basis tied to the labeled input graph.
159#[derive(Debug, Clone, PartialEq)]
160pub struct PersistentClassSpace {
161    /// Identifier derived from the interval and canonical basis.
162    pub id: IntervalGroupId,
163    /// Shared H1 persistence interval.
164    pub interval: Bar,
165    /// Canonical basis of this class space.
166    pub basis: Vec<PersistentClass>,
167    /// Creator and destroyer pairs from the fixed reduction before basis
168    /// canonicalization.
169    pub critical_pairs: Vec<CriticalPair>,
170}
171
172/// Diagram plus canonical spaces for every positive H1 interval.
173#[derive(Debug, Clone)]
174pub struct ExplainedDiagram {
175    /// Full persistence diagram through the requested dimension.
176    pub diagram: Diagram,
177    /// H1 class spaces in interval order, then identifier order.
178    pub spaces: Vec<PersistentClassSpace>,
179}
180
181impl ExplainedDiagram {
182    /// Number of positive H1 intervals, including multiplicity.
183    pub fn class_count(&self) -> usize {
184        self.spaces.iter().map(|space| space.basis.len()).sum()
185    }
186
187    /// Basis vectors from every class space in canonical order.
188    pub fn classes(&self) -> impl Iterator<Item = &PersistentClass> {
189        self.spaces.iter().flat_map(|space| &space.basis)
190    }
191}