use crate::algorithms::four_d::{
strongly_connected_components_4d, GraphNode4D, SpatialRegion, TemporalWindow,
TraversalContext4D,
};
use glam::Vec3;
#[derive(Debug, Clone)]
pub struct TemporalPersistencePoint {
pub t: u64,
pub active: usize,
pub n_components: usize,
pub largest_size: usize,
pub fraction_largest: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct TemporalBarcode {
pub birth: u64,
pub death: Option<u64>,
pub peak_size: usize,
}
pub fn temporal_persistence_sweep(
nodes: &[GraphNode4D],
sphere_center: Vec3,
sphere_radius: f32,
time_slices: &[u64],
time_half_width: u64,
) -> Vec<TemporalPersistencePoint> {
time_slices
.iter()
.map(|&t| {
let ctx = TraversalContext4D {
spatial_region: Some(SpatialRegion::Sphere {
center: sphere_center,
radius: sphere_radius,
}),
time_window: Some(TemporalWindow {
start: t.saturating_sub(time_half_width),
end: t.saturating_add(time_half_width),
}),
..TraversalContext4D::default()
};
let sccs = strongly_connected_components_4d(nodes, &ctx);
let active: usize = sccs.iter().map(|c| c.len()).sum();
let n_components = sccs.len();
let largest_size = sccs.iter().map(|c| c.len()).max().unwrap_or(0);
let fraction_largest = if active == 0 {
0.0
} else {
largest_size as f32 / active as f32
};
TemporalPersistencePoint {
t,
active,
n_components,
largest_size,
fraction_largest,
}
})
.collect()
}
pub fn compute_temporal_barcode(points: &[TemporalPersistencePoint]) -> Vec<TemporalBarcode> {
let mut open: Vec<(u64, usize)> = Vec::new(); let mut bars: Vec<TemporalBarcode> = Vec::new();
let mut prev_count = 0usize;
for pt in points {
let curr = pt.n_components;
if curr > prev_count {
for _ in 0..(curr - prev_count) {
open.push((pt.t, pt.largest_size));
}
}
for ob in &mut open {
ob.1 = ob.1.max(pt.largest_size);
}
if curr < prev_count {
let died = prev_count - curr;
let close_from = open.len().saturating_sub(died);
for (birth, peak) in open.drain(close_from..) {
bars.push(TemporalBarcode {
birth,
death: Some(pt.t),
peak_size: peak,
});
}
}
prev_count = curr;
}
for (birth, peak) in open {
bars.push(TemporalBarcode {
birth,
death: None,
peak_size: peak,
});
}
bars.sort_by_key(|b| b.birth);
bars
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algorithms::four_d::{GraphProperties, TemporalEdge};
fn node(id: u64, x: f32, successors: Vec<TemporalEdge>) -> GraphNode4D {
GraphNode4D {
id,
x,
y: 0.0,
z: 0.0,
begin_ts: 0,
end_ts: 100,
properties: GraphProperties::new(),
successors,
}
}
fn edge(dst: u64) -> TemporalEdge {
TemporalEdge {
dst,
weight: 1.0,
begin_ts: 0,
end_ts: 100,
}
}
fn big_sphere() -> Vec3 {
Vec3::ZERO
}
#[test]
fn test_empty_nodes_zero_components() {
let pts = temporal_persistence_sweep(&[], big_sphere(), 1000.0, &[50], 10);
assert_eq!(pts.len(), 1);
assert_eq!(pts[0].active, 0);
assert_eq!(pts[0].n_components, 0);
}
#[test]
fn test_disconnected_nodes_n_components_equals_n_active() {
let nodes: Vec<GraphNode4D> = (0..4).map(|i| node(i, i as f32, vec![])).collect();
let pts = temporal_persistence_sweep(&nodes, big_sphere(), 1000.0, &[50], 10);
assert_eq!(pts[0].n_components, 4);
assert_eq!(pts[0].active, 4);
assert_eq!(pts[0].largest_size, 1);
}
#[test]
fn test_fully_connected_cycle_is_one_component() {
let nodes = vec![
node(0, 0.0, vec![edge(1)]),
node(1, 1.0, vec![edge(2)]),
node(2, 2.0, vec![edge(0)]),
];
let pts = temporal_persistence_sweep(&nodes, big_sphere(), 1000.0, &[50], 10);
assert_eq!(pts[0].n_components, 1);
assert_eq!(pts[0].largest_size, 3);
assert!((pts[0].fraction_largest - 1.0).abs() < 1e-6);
}
#[test]
fn test_time_filter_outside_node_validity_gives_zero() {
let nodes: Vec<GraphNode4D> = (0..3).map(|i| node(i, i as f32, vec![])).collect();
let pts = temporal_persistence_sweep(&nodes, big_sphere(), 1000.0, &[500], 10);
assert_eq!(pts[0].active, 0);
assert_eq!(pts[0].n_components, 0);
assert_eq!(pts[0].fraction_largest, 0.0);
}
#[test]
fn test_barcode_birth_and_death() {
let points = vec![
TemporalPersistencePoint {
t: 0,
active: 0,
n_components: 0,
largest_size: 0,
fraction_largest: 0.0,
},
TemporalPersistencePoint {
t: 10,
active: 3,
n_components: 1,
largest_size: 3,
fraction_largest: 1.0,
},
TemporalPersistencePoint {
t: 30,
active: 3,
n_components: 1,
largest_size: 3,
fraction_largest: 1.0,
},
TemporalPersistencePoint {
t: 50,
active: 0,
n_components: 0,
largest_size: 0,
fraction_largest: 0.0,
},
];
let bars = compute_temporal_barcode(&points);
assert_eq!(bars.len(), 1);
assert_eq!(bars[0].birth, 10);
assert_eq!(bars[0].death, Some(50));
assert_eq!(bars[0].peak_size, 3);
}
#[test]
fn test_barcode_surviving_component_has_no_death() {
let points = vec![
TemporalPersistencePoint {
t: 0,
active: 0,
n_components: 0,
largest_size: 0,
fraction_largest: 0.0,
},
TemporalPersistencePoint {
t: 10,
active: 2,
n_components: 1,
largest_size: 2,
fraction_largest: 1.0,
},
TemporalPersistencePoint {
t: 20,
active: 2,
n_components: 1,
largest_size: 2,
fraction_largest: 1.0,
},
];
let bars = compute_temporal_barcode(&points);
assert_eq!(bars.len(), 1);
assert_eq!(bars[0].birth, 10);
assert_eq!(bars[0].death, None);
}
}