use super::*;
#[test]
fn direct_ifds_prepare_bytes_record_only_cache_misses() {
let mut batch = super::DirectResidentIfdsBatch::<usize>::with_max_entries(2);
let dispatch = DirectSolveDispatch::new();
let graph_key =
DirectResidentIfdsGraphKey::from_edges(1, 4, 1, &[(0u32, 0u32, 1u32)], &[], &[], &[]);
let seed_a = [(0u32, 0u32, 0u32)];
let seed_b = [(0u32, 1u32, 0u32)];
let mut results = Vec::new();
batch
.solve_many_with_graph_key_into(
&dispatch,
graph_key,
1,
4,
1,
&[(0u32, 0u32, 1u32)],
&[],
&[],
&[],
&[&seed_a[..], &seed_b[..]],
4,
&mut results,
)
.expect("first direct resident IFDS solve must prepare resident CSR");
assert_eq!(results, vec![vec![0], vec![1024]]);
let first_stats = batch.stats();
assert_eq!(first_stats.misses, 1);
assert_eq!(first_stats.hits, 0);
assert_eq!(first_stats.direct_prepares, 1);
assert_eq!(first_stats.resident_solve_batches, 1);
assert_eq!(first_stats.resident_solve_queries, 2);
assert_eq!(first_stats.resident_solve_non_empty_queries, 2);
assert_eq!(first_stats.resident_dispatches, 1);
assert_eq!(
first_stats.resident_dispatches,
first_stats.resident_solver.resident_dispatches,
"direct IFDS facade stats must expose underlying resident dispatch batches without making callers dig into nested solver stats"
);
assert_eq!(
first_stats.seed_upload_bytes,
first_stats.resident_solver.seed_upload_bytes
);
assert_eq!(
first_stats.result_readback_bytes,
first_stats.resident_solver.result_readback_bytes
);
assert!(
first_stats.seed_upload_bytes > 0 && first_stats.result_readback_bytes > 0,
"direct IFDS batch stats must make seed upload and result readback bytes visible for benchmark evidence"
);
assert_eq!(
first_stats.direct_prepare_bytes,
first_stats.retained_bytes as u64
);
let cached_key =
DirectResidentIfdsGraphKey::from_edges(1, 4, 1, &[(0u32, 0u32, 1u32)], &[], &[], &[]);
batch
.solve_cached_with_graph_key_into(&dispatch, cached_key, &[&seed_b[..]], 4, &mut results)
.expect("cached direct resident IFDS solve must reuse prepared resident CSR");
assert_eq!(results, vec![vec![1024]]);
let second_stats = batch.stats();
assert_eq!(second_stats.hits, 1);
assert_eq!(second_stats.misses, 1);
assert_eq!(second_stats.direct_prepares, 1);
assert_eq!(second_stats.resident_solve_batches, 2);
assert_eq!(second_stats.resident_solve_queries, 3);
assert_eq!(second_stats.resident_solve_non_empty_queries, 3);
assert_eq!(second_stats.resident_dispatches, 2);
assert!(
second_stats.seed_upload_bytes > first_stats.seed_upload_bytes
&& second_stats.result_readback_bytes > first_stats.result_readback_bytes,
"cached direct IFDS solves must still report their per-query upload/readback movement"
);
assert_eq!(
second_stats.direct_prepare_bytes, first_stats.direct_prepare_bytes,
"cached-key direct IFDS hot path must not count another resident CSR prepare"
);
assert_eq!(
second_stats.resident_graph_reuse_telemetry(),
vyre::ResidentGraphReuseTelemetry::from_counters(
1,
1,
first_stats.direct_prepare_bytes,
first_stats.direct_prepare_bytes
),
"direct IFDS repeated graph solves must use shared resident graph reuse telemetry"
);
assert_eq!(
dispatch.dispatches.get(),
8,
"resident parallel IFDS fake dispatch should launch the bounded iteration window for each solve"
);
}
#[test]
fn direct_ifds_graph_view_reuses_hash_for_prepare_or_hit_path() {
let mut batch = super::DirectResidentIfdsBatch::<usize>::with_max_entries(2);
let dispatch = DirectSolveDispatch::new();
let intra = [(0u32, 0u32, 1u32)];
let graph = DirectResidentIfdsGraph::from_edges(1, 4, 1, &intra, &[], &[], &[]);
let seed_a = [(0u32, 0u32, 0u32)];
let seed_b = [(0u32, 1u32, 0u32)];
let mut results = Vec::new();
batch
.solve_many_with_graph_view_into(&dispatch, graph, &[&seed_a[..]], 4, &mut results)
.expect("graph-view direct resident IFDS solve must prepare resident CSR");
assert_eq!(results, vec![vec![0]]);
batch
.solve_many_with_graph_view_into(&dispatch, graph, &[&seed_b[..]], 4, &mut results)
.expect("graph-view direct resident IFDS solve must reuse resident CSR");
assert_eq!(results, vec![vec![1024]]);
let stats = batch.stats();
assert_eq!(stats.misses, 1);
assert_eq!(stats.hits, 1);
assert_eq!(stats.direct_prepares, 1);
assert_eq!(
stats.direct_prepare_bytes,
stats.retained_bytes as u64,
"graph-view repeated solves must account retained direct CSR bytes only for the single cache miss"
);
assert_eq!(
batch.resident_graph_reuse_telemetry(),
vyre::ResidentGraphReuseTelemetry::from_counters(
1,
1,
stats.direct_prepare_bytes,
stats.direct_prepare_bytes
),
"graph-view repeated direct IFDS solves must expose shared graph reuse telemetry"
);
}
#[test]
fn direct_ifds_graph_view_single_solve_routes_without_rehashing_edges() {
let mut batch = super::DirectResidentIfdsBatch::<usize>::with_max_entries(2);
let dispatch = DirectSolveDispatch::new();
let intra = [(0u32, 0u32, 1u32)];
let graph = DirectResidentIfdsGraph::from_edges(1, 4, 1, &intra, &[], &[], &[]);
let seed_a = [(0u32, 0u32, 0u32)];
let seed_b = [(0u32, 1u32, 0u32)];
let mut result = Vec::new();
batch
.solve_with_graph_view_into(&dispatch, graph, &seed_a, 4, &mut result)
.expect("graph-view single direct IFDS solve must prepare resident CSR");
assert_eq!(result, vec![0]);
batch
.solve_with_graph_view_into(&dispatch, graph, &seed_b, 4, &mut result)
.expect("graph-view single direct IFDS solve must reuse resident CSR");
assert_eq!(result, vec![1024]);
let stats = batch.stats();
assert_eq!(stats.misses, 1);
assert_eq!(stats.hits, 1);
assert_eq!(stats.direct_prepares, 1);
assert_eq!(
stats.direct_prepare_bytes, stats.retained_bytes as u64,
"graph-view single hot path must retain the direct CSR after one prepare"
);
}
#[test]
fn direct_ifds_cached_single_key_requires_no_edge_slices_after_warmup() {
let mut batch = super::DirectResidentIfdsBatch::<usize>::with_max_entries(2);
let dispatch = DirectSolveDispatch::new();
let intra = [(0u32, 0u32, 1u32)];
let graph = DirectResidentIfdsGraph::from_edges(1, 4, 1, &intra, &[], &[], &[]);
let seed_a = [(0u32, 0u32, 0u32)];
let seed_b = [(0u32, 1u32, 0u32)];
let mut result = Vec::new();
batch
.solve_with_graph_view_into(&dispatch, graph, &seed_a, 4, &mut result)
.expect("graph-view warmup must prepare the resident graph");
assert_eq!(result, vec![0]);
batch
.solve_cached_single_with_graph_key_into(&dispatch, graph.key(), &seed_b, 4, &mut result)
.expect("cached-key single solve must reuse the warmed resident graph");
assert_eq!(result, vec![1024]);
let stats = batch.stats();
assert_eq!(stats.misses, 1);
assert_eq!(stats.hits, 1);
assert_eq!(stats.direct_prepares, 1);
assert_eq!(
stats.direct_prepare_bytes, stats.retained_bytes as u64,
"cached single-key hot path must not prepare or account another direct CSR"
);
}