use crate::*;
use super::execution::collapse_and_solve;
use super::routing::*;
use super::*;
use crate::collapse::verify::verify_dense;
use crate::distances;
fn grid(side: usize) -> DistanceMatrix {
let mut points = Vec::new();
for i in 0..side {
for j in 0..side {
points.push(vec![i as f64, j as f64]);
}
}
DistanceMatrix::from_points(&points).unwrap()
}
fn bits(d: &Diagram) -> Vec<(usize, u64, u64)> {
d.bars
.iter()
.map(|b| (b.dim, b.birth.to_bits(), b.death.to_bits()))
.collect()
}
fn counting<T>(f: impl FnOnce() -> T) -> (usize, T) {
distances::SQUARE_BUILDS.with(|c| c.set(0));
let value = f();
(distances::SQUARE_BUILDS.with(|c| c.get()), value)
}
#[test]
fn the_routing_rule_holds_its_constants() {
assert!(!may_route(N_MIN - 1, 1.0), "under N_MIN nothing routes");
assert!(may_route(N_MIN, 1.0));
assert!(
may_route(N_MIN, f64::INFINITY),
"an infinite threshold routes"
);
for bad in [f64::NAN, -1.0] {
assert!(!may_route(N_MIN, bad), "threshold {bad} must not route");
}
for (n, last_routed) in [
(N_MIN, 396usize),
(33, 422),
(100, 3960),
(1001, 400_400),
(4000, 6_398_400),
] {
let pairs = n * (n - 1) / 2;
assert_eq!(last_routed, 4 * pairs / 5, "{n}: the cutoff moved");
assert!(density_routes(n, 0), "{n}: an empty graph routes");
assert!(density_routes(n, last_routed), "{n}: the cutoff routes");
assert!(!density_routes(n, last_routed + 1), "{n}: one edge over");
assert!(!density_routes(n, pairs), "{n}: a complete graph");
}
}
#[test]
fn the_conversion_budget_gates_at_the_byte() {
let small = 1000;
assert!(pair_count(small) * 8 < MIN_CONVERSION_BYTES);
let last_fit = ((MIN_CONVERSION_BYTES - 24 * small as u128 - 8) / 24) as usize;
assert_eq!(last_fit, 1_397_101);
assert!(memory_routes(small, last_fit));
assert!(!memory_routes(small, last_fit + 1));
let large = 20_000;
let budget = pair_count(large) * 8;
assert!(budget > MIN_CONVERSION_BYTES);
let last_fit = ((budget - 24 * large as u128 - 8) / 24) as usize;
assert_eq!(last_fit, 66_643_333);
assert!(memory_routes(large, last_fit));
assert!(!memory_routes(large, last_fit + 1));
}
#[test]
fn a_large_near_clique_stays_dense() {
let n = 2000;
let data: Vec<f64> = (0..n * (n - 1) / 2)
.map(|k| if k % 4 == 0 { 3.0 } else { 1.0 })
.collect();
let dist = DistanceMatrix::from_condensed(data).unwrap();
let threshold = 1.0;
let edges = dist.count_edges_at(threshold);
assert!(edges > 1_000_000, "{edges} edges");
assert!(may_route(n, threshold));
assert!(density_routes(n, edges), "the density cutoff accepts it");
assert!(!memory_routes(n, edges), "the budget must refuse it");
assert!(!graph_routes(n, edges));
}
#[test]
fn the_storage_rule_holds_its_constants() {
assert!(!square_size_fits(1024), "1024 points stay compact");
assert!(
square_size_fits(1025),
"1025 points may take both triangles"
);
assert!(square_size_fits(8191), "8191 points fit the byte budget");
assert!(!square_size_fits(8192), "8192 points exceed it");
assert_eq!(square_extra_bytes(8191), 268_402_688);
for n in [1025usize, 2400, 8191] {
let last_refused = (SQUARE_READS_PER_CELL as usize - 1) * n - 1;
assert!(!square_work_pays(n, last_refused, 1), "{n}: one edge under");
assert!(
square_work_pays(n, last_refused + 1, 1),
"{n}: at the cutoff"
);
assert!(
square_work_pays(n, last_refused / 2 + 1, 2),
"{n}: max_dim 2"
);
}
assert!(!square_work_pays(2400, 863, 1), "a low threshold refuses");
assert!(
square_work_pays(2400, 14_273, 1),
"a sparse block graph pays"
);
}
#[test]
fn a_routed_run_never_builds_the_full_form() {
let n = 1030;
let dist = band(n, 4);
let threshold = resolved_threshold(&dist, &RipsParams::new(1));
let edges = dist.count_edges_at(threshold);
assert!(
may_route(n, threshold) && graph_routes(n, edges),
"the fixture must route: {edges} edges"
);
assert!(
square_size_fits(n) && square_work_pays(n, edges, 1),
"the storage rule must want the full form here"
);
let params = RipsParams::new(1);
let mut reference = None;
for storage in [
DenseStorage::Auto,
DenseStorage::Compact,
DenseStorage::Square,
] {
let p = params.clone().with_dense_storage(storage);
let (built, diagram) = counting(|| rips_persistence(&dist, &p).unwrap());
assert_eq!(built, 0, "{storage:?}: a routed run stays compact");
let bits = bits(&diagram);
assert_eq!(*reference.get_or_insert(bits.clone()), bits, "{storage:?}");
}
for (storage, want) in [
(DenseStorage::Auto, 1),
(DenseStorage::Compact, 0),
(DenseStorage::Square, 1),
] {
let p = params
.clone()
.with_engine(Engine::Dense)
.with_dense_storage(storage);
let (built, diagram) = counting(|| rips_persistence(&dist, &p).unwrap());
assert_eq!(built, want, "{storage:?}: conversions");
assert_eq!(bits(&diagram), *reference.as_ref().unwrap(), "{storage:?}");
}
}
#[test]
fn every_storage_form_gives_one_diagram() {
let dist = grid(6);
assert!(
!square_size_fits(dist.len()),
"the fixture is under the bound"
);
let params = RipsParams::new(2);
let mut reference = None;
for engine in [Engine::Auto, Engine::Dense, Engine::Sparse] {
for storage in [
DenseStorage::Auto,
DenseStorage::Compact,
DenseStorage::Square,
] {
let p = params
.clone()
.with_engine(engine)
.with_dense_storage(storage);
let (built, diagram) = counting(|| rips_persistence(&dist, &p).unwrap());
let dense_run = engine == Engine::Dense
|| (engine == Engine::Auto
&& !graph_routes(
dist.len(),
dist.count_edges_at(resolved_threshold(&dist, &p)),
));
let want = usize::from(dense_run && storage == DenseStorage::Square);
assert_eq!(built, want, "{engine:?}, {storage:?}: conversions");
let bits = bits(&diagram);
assert_eq!(
*reference.get_or_insert(bits.clone()),
bits,
"{engine:?}, {storage:?}"
);
}
}
}
fn band(n: usize, width: usize) -> DistanceMatrix {
let mut data = Vec::with_capacity(n * (n - 1) / 2);
for i in 1..n {
for j in 0..i {
data.push(if i - j <= width {
(i - j) as f64
} else {
f64::INFINITY
});
}
}
DistanceMatrix::from_condensed(data).unwrap()
}
#[test]
fn an_infinite_threshold_routes_on_density() {
let n = 40;
let dist = band(n, 3);
assert_eq!(dist.enclosing_radius(), f64::INFINITY);
let edges = dist.count_edges_at(f64::INFINITY);
assert_eq!(edges, 3 * n - 6, "the band holds its own edges");
assert!(may_route(n, f64::INFINITY) && graph_routes(n, edges));
let complete = grid(7);
let n = complete.len();
let all = complete.count_edges_at(f64::INFINITY);
assert_eq!(all, n * (n - 1) / 2, "every pair of a grid is finite");
assert!(!graph_routes(n, all), "a complete matrix stays dense");
let params = RipsParams::new(2);
let dense = rips_persistence(&dist, ¶ms.clone().with_engine(Engine::Dense)).unwrap();
for threshold in [None, Some(f64::INFINITY)] {
let mut p = params.clone();
p.threshold = threshold;
for engine in [Engine::Auto, Engine::Sparse] {
let got = rips_persistence(&dist, &p.clone().with_engine(engine)).unwrap();
assert_eq!(bits(&got), bits(&dense), "{threshold:?}, {engine:?}");
}
}
}
#[test]
fn a_routed_input_gives_the_dense_diagram() {
let side = (N_MIN as f64).sqrt().ceil() as usize + 1;
let dist = grid(side);
let threshold = 1.5;
let edges = dist.count_edges_at(threshold);
assert!(
may_route(dist.len(), threshold) && graph_routes(dist.len(), edges),
"the fixture must route: {} points, {edges} edges",
dist.len()
);
let params = RipsParams::new(1).with_threshold(threshold);
let dense = rips_persistence(&dist, ¶ms.clone().with_engine(Engine::Dense)).unwrap();
for engine in [Engine::Auto, Engine::Sparse] {
let got = rips_persistence(&dist, ¶ms.clone().with_engine(engine)).unwrap();
assert_eq!(bits(&got), bits(&dense), "{engine:?}");
}
}
#[test]
fn routing_carries_the_dense_default_threshold() {
let side = (N_MIN as f64).sqrt().ceil() as usize + 1;
let dist = grid(side);
let radius = dist.enclosing_radius();
let params = RipsParams::new(1);
let auto = rips_persistence(&dist, ¶ms).unwrap();
let explicit = rips_persistence(
&dist,
¶ms
.clone()
.with_engine(Engine::Sparse)
.with_threshold(radius),
)
.unwrap();
assert_eq!(bits(&auto), bits(&explicit));
assert!(dist.count_edges_at(radius) < dist.len() * (dist.len() - 1) / 2);
}
#[test]
fn pipeline_runs_the_selected_schedule() {
let dist = grid(5);
let plain = rips_persistence(&dist, &RipsParams::new(2)).unwrap();
let mut seen = Vec::new();
for schedule in [
CollapseSchedule::Serial,
CollapseSchedule::Ordered,
CollapseSchedule::Rounds,
CollapseSchedule::Adaptive,
] {
let params = RipsParams::new(2)
.with_threads(4)
.with_collapse_schedule(schedule);
let mut captured = None;
let diagram = collapse_and_solve(&dist, ¶ms, |c| {
captured = Some(c.clone());
Ok(())
})
.unwrap();
let captured = captured.expect("report must see the collapse");
verify_dense(&dist, None, &captured).unwrap();
let expected_version = match schedule {
CollapseSchedule::Serial | CollapseSchedule::Ordered => 1,
CollapseSchedule::Rounds => 2,
CollapseSchedule::Adaptive => 3,
};
assert_eq!(captured.certificate.algorithm_version(), expected_version);
let mut a = diagram.clone();
let mut b = plain.clone();
a.canonicalize();
b.canonicalize();
assert_eq!(a.bars, b.bars, "{schedule:?}");
seen.push((schedule, captured.stats));
}
let serial = seen[0].1;
let ordered = seen[1].1;
assert_eq!(ordered.logical_tests, serial.edge_tests);
assert!(
ordered.edge_tests > serial.edge_tests,
"the ordered schedule did not speculate: {} vs {}",
ordered.edge_tests,
serial.edge_tests
);
assert_eq!(serial.window_batches, 0);
assert!(ordered.window_batches > 0);
}