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}