use std::collections::{HashSet, VecDeque};
use rand::{Rng, SeedableRng};
#[derive(Clone, Debug)]
pub struct CsrGraphFixture {
pub node_count: u32,
pub edge_offsets: Vec<u32>,
pub edge_targets: Vec<u32>,
pub edge_kind_mask: Vec<u32>,
pub expected_reachability: Vec<HashSet<u32>>,
}
pub struct CsrFactory;
impl CsrFactory {
pub fn random_valid(node_count: u32, edge_probability: f64, seed: u64) -> CsrGraphFixture {
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let probability = edge_probability.clamp(0.0, 1.0);
let mut adjacency = vec![Vec::new(); node_count as usize];
for src in 0..node_count {
for dst in 0..node_count {
if rng.random::<f64>() < probability {
adjacency[src as usize].push(dst);
}
}
adjacency[src as usize].sort_unstable();
adjacency[src as usize].dedup();
}
fixture_from_adjacency(adjacency)
}
pub fn adversarial_empty_rows() -> CsrGraphFixture {
fixture_from_adjacency(vec![vec![1], Vec::new(), vec![0]])
}
pub fn adversarial_non_monotonic_offsets() -> CsrGraphFixture {
CsrGraphFixture {
node_count: 3,
edge_offsets: vec![0, 2, 1, 3],
edge_targets: vec![1, 2, 0],
edge_kind_mask: vec![u32::MAX; 3],
expected_reachability: Vec::new(),
}
}
pub fn adversarial_oob_targets() -> CsrGraphFixture {
CsrGraphFixture {
node_count: 3,
edge_offsets: vec![0, 1, 2, 3],
edge_targets: vec![1, 99, 0],
edge_kind_mask: vec![u32::MAX; 3],
expected_reachability: Vec::new(),
}
}
pub fn adversarial_terminal_mismatch() -> CsrGraphFixture {
CsrGraphFixture {
node_count: 3,
edge_offsets: vec![0, 1, 2, 4],
edge_targets: vec![1, 2, 0],
edge_kind_mask: vec![u32::MAX; 3],
expected_reachability: Vec::new(),
}
}
}
fn fixture_from_adjacency(adjacency: Vec<Vec<u32>>) -> CsrGraphFixture {
let node_count = adjacency.len() as u32;
let mut edge_offsets = Vec::with_capacity(adjacency.len() + 1);
let mut edge_targets = Vec::new();
edge_offsets.push(0);
for targets in &adjacency {
edge_targets.extend(targets.iter().copied());
edge_offsets.push(edge_targets.len() as u32);
}
let edge_kind_mask = vec![u32::MAX; edge_targets.len()];
let expected_reachability = reachability_sets(&adjacency);
CsrGraphFixture {
node_count,
edge_offsets,
edge_targets,
edge_kind_mask,
expected_reachability,
}
}
fn reachability_sets(adjacency: &[Vec<u32>]) -> Vec<HashSet<u32>> {
let mut out = Vec::with_capacity(adjacency.len());
for start in 0..adjacency.len() as u32 {
let mut seen = HashSet::new();
let mut queue = VecDeque::new();
seen.insert(start);
queue.push_back(start);
while let Some(node) = queue.pop_front() {
let Some(targets) = adjacency.get(node as usize) else {
continue;
};
for &target in targets {
if (target as usize) < adjacency.len() && seen.insert(target) {
queue.push_back(target);
}
}
}
out.push(seen);
}
out
}