///|
/// Exact right-continuous Betti counts at filtration event scales.
pub struct BettiEvent {
scale : Double
h0 : Int
h1 : Int
} derive(@debug.Debug)
///|
pub extend BettiEvent with @debug.Debug::{to_repr}
///|
/// Aggregate births and deaths at the same scale before recording counts.
/// Includes the first cell scale and cutoff, even when no count changes there.
pub fn betti_events(analysis : Analysis) -> Array[BettiEvent] {
let cutoff = analysis.filtration.cutoff
let start = if analysis.filtration.cells.is_empty() {
cutoff
} else {
analysis.filtration.cells[0].value
}
let deltas : Array[(Double, Int, Int)] = [(start, 0, 0), (cutoff, 0, 0)]
for interval in analysis.intervals {
if interval.dimension > 1 || interval.dimension < 0 {
continue
}
let h0 = if interval.dimension == 0 { 1 } else { 0 }
let h1 = if interval.dimension == 1 { 1 } else { 0 }
deltas.push((interval.birth, h0, h1))
if interval.death is Some(death) {
deltas.push((death, -h0, -h1))
}
}
deltas.sort_by(fn(a, b) {
if a.0 < b.0 {
-1
} else if a.0 > b.0 {
1
} else {
0
}
})
let result : Array[BettiEvent] = []
let mut h0 = 0
let mut h1 = 0
let mut i = 0
while i < deltas.length() {
let scale = deltas[i].0
while i < deltas.length() && deltas[i].0 == scale {
h0 += deltas[i].1
h1 += deltas[i].2
i += 1
}
result.push({ scale, h0, h1, })
}
result
}
///|
pub fn betti_events_json(analysis : Analysis) -> Json {
Json::array(
betti_events(analysis).map(fn(event) {
{ "scale": event.scale, "h0": event.h0, "h1": event.h1 }
}),
)
}
///|
/// Event rows, unlike uniform samples, retain arbitrarily short intervals.
pub fn betti_events_csv(analysis : Analysis) -> String {
let mut text = "scale,H0,H1\n"
for event in betti_events(analysis) {
text += "\{event.scale},\{event.h0},\{event.h1}\n"
}
text
}