use std::fs::File;
use std::io::BufRead;
use std::io::BufReader;
use std::path::PathBuf;
use idtree::IDTree;
use nohash_hasher::{IntMap, IntSet};
use rand::RngExt;
use rand::SeedableRng;
use rand::rngs::StdRng;
const USE_DSU: bool = false;
struct MtxData {
all_edges: Vec<(usize, usize)>,
empty_map_i32: IntMap<i32, IntSet<i32>>,
empty_map_usize: IntMap<usize, IntSet<usize>>,
}
impl MtxData {
fn new(filename: &str) -> Self {
let adj_list = MtxData::load_graph(filename);
let mut adj_dict: IntMap<i32, IntSet<i32>> = IntMap::default();
for &u in adj_list.keys() {
adj_dict.entry(u).or_default();
for &v in adj_list.get(&u).unwrap() {
adj_dict.entry(v).or_default();
adj_dict.get_mut(&u).unwrap().insert(v);
adj_dict.get_mut(&v).unwrap().insert(u);
}
}
let mut empty_map_i32: IntMap<i32, IntSet<i32>> = IntMap::default();
for &u in adj_list.keys() {
empty_map_i32.entry(u).or_default();
for &v in adj_list.get(&u).unwrap() {
empty_map_i32.entry(v).or_default();
}
}
let mut empty_map_usize: IntMap<usize, IntSet<usize>> = IntMap::default();
for &u in adj_list.keys() {
empty_map_usize.entry(u as usize).or_default();
for &v in adj_list.get(&u).unwrap() {
empty_map_usize.entry(v as usize).or_default();
}
}
let all_edges: Vec<(usize, usize)> = adj_list
.iter()
.flat_map(|(&u, neighbors)| neighbors.iter().map(move |&v| (u as usize, v as usize)))
.collect();
MtxData {
all_edges,
empty_map_i32,
empty_map_usize,
}
}
fn load_graph(filename: &str) -> IntMap<i32, Vec<i32>> {
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let mtx_path = PathBuf::from(manifest_dir)
.join("benches")
.join("data")
.join(filename);
let reader = BufReader::new(File::open(mtx_path).expect("MTX file missing"));
let mut adj_list: IntMap<i32, Vec<i32>> = IntMap::default();
let mut data_started = false;
for line in reader.lines().map_while(Result::ok) {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('%') {
continue;
}
if !data_started {
data_started = true;
continue;
}
let parts: Vec<&str> = trimmed.split_whitespace().collect();
if parts.len() >= 2 {
let mut u: i32 = parts[0].parse().unwrap();
let mut v: i32 = parts[1].parse().unwrap();
if u > v {
std::mem::swap(&mut u, &mut v);
}
adj_list.entry(u - 1).or_default().push(v - 1);
}
}
adj_list
}
}
fn make_adj_i32(n: usize, edges: &[(usize, usize)]) -> IntMap<usize, IntSet<usize>> {
let mut adj: IntMap<usize, IntSet<usize>> = IntMap::default();
for i in 0..n {
adj.insert(i, IntSet::default());
}
for &(u, v) in edges {
adj.get_mut(&(u)).unwrap().insert(v);
adj.get_mut(&(v)).unwrap().insert(u);
}
adj
}
fn make_adj(n: usize) -> IntMap<usize, IntSet<usize>> {
let mut adj: IntMap<usize, IntSet<usize>> = IntMap::default();
for i in 0..n {
adj.insert(i, IntSet::default());
}
adj
}
fn make_edges_for_nodes(node_count: usize, edge_count: usize) -> Vec<(usize, usize)> {
let mut edges = Vec::with_capacity(edge_count);
for i in 0..edge_count {
let u = i % node_count;
let v = (i * 7 + 13) % node_count;
if u != v {
edges.push((u, v));
}
}
edges
}
fn make_caterpillar_graph(
n: usize,
spine_length_ratio: f64, extra_edges_ratio: f64, ) -> IntMap<usize, IntSet<usize>> {
let mut adj: IntMap<usize, IntSet<usize>> = IntMap::default();
for i in 0..n {
adj.insert(i, IntSet::default());
}
let mut rng = StdRng::seed_from_u64(42);
let spine_len = (n as f64 * spine_length_ratio).max(10.0).min(n as f64) as usize;
let mut spine = Vec::with_capacity(spine_len);
for i in 0..spine_len {
spine.push(i);
if i > 0 {
let prev = spine[i - 1];
adj.get_mut(&prev).unwrap().insert(i);
adj.get_mut(&(i)).unwrap().insert(prev);
}
}
let mut next_node = spine_len;
while next_node < n {
let attach_to = spine[rng.random_range(0..spine.len())];
let chain_len = rng.random_range(1..=4);
let mut prev = attach_to;
for _ in 0..chain_len {
if next_node >= n {
break;
}
adj.get_mut(&prev).unwrap().insert(next_node);
adj.get_mut(&next_node).unwrap().insert(prev);
prev = next_node;
next_node += 1;
}
}
let extra_count = (n as f64 * extra_edges_ratio) as usize;
for _ in 0..extra_count {
let u = rng.random_range(0..n);
let v = rng.random_range(0..n);
if u != v && !adj.get(&u).unwrap().contains(&v) {
adj.get_mut(&u).unwrap().insert(v);
adj.get_mut(&v).unwrap().insert(u);
}
}
adj
}
mod no_dsu_no_compress {
use super::*;
#[test]
fn test_basic_insert_delete_query() {
let edges = vec![(0, 1), (1, 2), (2, 3)];
let adj = make_adj_i32(4, &edges);
let mut t = IDTree::from_adj(&adj);
assert!(t.query(0, 3), "query 1");
t.delete_edge(1, 2);
assert!(!t.query(0, 3), "query 2");
t.insert_edge(1, 2);
assert!(t.query(0, 3), "query 3");
}
#[test]
fn test_unlink_splits_correctly() {
let edges = vec![(0, 1), (1, 2), (2, 3)];
let adj = make_adj_i32(4, &edges);
let mut t = IDTree::from_adj(&adj);
t.delete_edge(1, 2);
assert!(t.query(0, 1));
assert!(!t.query(0, 3));
assert!(t.query(2, 3));
}
#[test]
fn test_replacement_edge_found() {
let edges = vec![(0, 1), (1, 2), (2, 3), (0, 3)];
let adj = make_adj_i32(4, &edges);
let mut t = IDTree::from_adj(&adj);
let r = t.delete_edge(1, 2);
assert_eq!(r, 1);
assert!(t.query(1, 2));
assert!(t.query(0, 3));
}
#[test]
fn test_replacement_edge_not_found() {
let edges = vec![(0, 1), (1, 2), (2, 3)];
let adj = make_adj_i32(4, &edges);
let mut t = IDTree::from_adj(&adj);
let r = t.delete_edge(1, 2);
assert_eq!(r, 2);
assert!(!t.query(0, 3));
}
#[test]
fn test_mixed_ops_query_heavy() {
use rand::SeedableRng;
use rand::prelude::SliceRandom;
use rand::rngs::StdRng;
let n = 1_000;
let query_factor = 10;
let mut edges = make_edges_for_nodes(n, n * 2);
let mut rng = StdRng::seed_from_u64(12345);
edges.shuffle(&mut rng);
let (present_edges, absent_edges) = edges.split_at(n);
let mut adj = make_adj(n);
for &(u, v) in present_edges.iter() {
adj.get_mut(&(u)).unwrap().insert(v);
adj.get_mut(&(v)).unwrap().insert(u);
}
let mut tree = IDTree::from_adj(&adj);
let mut present: Vec<usize> = (0..n).collect();
let mut absent: Vec<usize> = (0..n).collect();
for i in 0..n {
let pi = present[i];
let (du, dv) = present_edges[pi];
tree.delete_edge(du, dv);
for q in 0..query_factor {
let qi = present[(i + q) % n];
let (qu, qv) = present_edges[qi];
let _ = tree.query(qu, qv);
}
let ai = absent[i];
let (iu, iv) = absent_edges[ai];
tree.insert_edge(iu, iv);
present[i] = ai;
absent[i] = pi;
}
}
#[test]
fn mixed_ops_query_heavy_catgraph() {
const QUERY_FACTOR: f64 = 0.05;
use rand::SeedableRng;
use rand::prelude::SliceRandom;
use rand::rngs::StdRng;
let n = 20_000;
let mut edges = make_edges_for_nodes(n, n * 2);
let mut rng = StdRng::seed_from_u64(12345);
edges.shuffle(&mut rng);
let (present_edges, absent_edges) = edges.split_at(n);
let adj = make_caterpillar_graph(n, 0.3, 0.05);
let num_query_pairs = (QUERY_FACTOR * n as f64) as usize;
let mut query_pairs = Vec::with_capacity(num_query_pairs);
for _ in 0..num_query_pairs {
let qu = rng.random_range(0..n);
let qv = rng.random_range(0..n);
query_pairs.push((qu, qv));
}
let mut tree = IDTree::from_adj(&adj);
let mut present: Vec<usize> = (0..n).collect();
let mut absent: Vec<usize> = (0..n).collect();
for i in 0..n {
let pi = present[i];
let (du, dv) = present_edges[pi];
tree.delete_edge(du, dv);
for &(qu, qv) in &query_pairs {
let _ = tree.query(qu, qv);
}
let ai = absent[i];
let (iu, iv) = absent_edges[ai];
tree.insert_edge(iu, iv);
present[i] = ai;
absent[i] = pi;
}
}
}
fn make_adj_usize(n: usize, edges: &[(usize, usize)]) -> IntMap<usize, IntSet<usize>> {
let mut adj: IntMap<usize, IntSet<usize>> = IntMap::default();
for i in 0..n {
adj.insert(i, IntSet::default());
}
for &(u, v) in edges {
adj.get_mut(&u).unwrap().insert(v);
adj.get_mut(&v).unwrap().insert(u);
}
adj
}
#[cfg(feature = "cpp")]
mod cpp_tests {
use super::*;
use cxx::UniquePtr;
use idtree::bridge::ffi;
use log::debug;
use rand::SeedableRng;
use rand::rngs::StdRng;
fn setup_cpp_tree(
n: usize,
edges: &[(i32, i32)],
use_dsu: bool,
) -> cxx::UniquePtr<ffi::CPPDNDTree> {
let mut adj = vec![vec![]; n];
for &(u, v) in edges {
if u >= 0 && u < n as i32 && v >= 0 && v < n as i32 {
adj[u as usize].push(v);
adj[v as usize].push(u);
}
}
let mut degrees = Vec::with_capacity(n);
let mut flat_neighbors = Vec::new();
for neighbors in &adj {
degrees.push(neighbors.len() as i32);
for &v in neighbors {
flat_neighbors.push(v);
}
}
ffi::new_cpp_dndtree_from_flat_adj(n as i32, °rees, &flat_neighbors, use_dsu)
}
#[test]
fn test_basic_insert_delete_query() {
let edges = vec![(0, 1), (1, 2), (2, 3)];
let t = setup_cpp_tree(4, &edges, USE_DSU);
assert!(t.query(0, 3), "query 1");
t.delete_edge(1, 2);
assert!(!t.query(0, 3), "query 2");
t.insert_edge(1, 2);
assert!(t.query(0, 3), "query 3");
}
#[test]
fn test_unlink_splits_correctly() {
let edges = vec![(0, 1), (1, 2), (2, 3)];
let t = setup_cpp_tree(4, &edges, USE_DSU);
t.delete_edge(1, 2);
assert!(t.query(0, 1));
assert!(!t.query(0, 3));
assert!(t.query(2, 3));
}
#[test]
fn test_replacement_edge_found() {
let edges = vec![(0, 1), (1, 2), (2, 3), (0, 3)];
let t = setup_cpp_tree(4, &edges, USE_DSU);
let r = t.delete_edge(1, 2);
assert_eq!(r, 1);
assert!(t.query(1, 2));
assert!(t.query(0, 3));
}
#[test]
fn test_replacement_edge_not_found() {
let edges = vec![(0, 1), (1, 2), (2, 3)];
let t = setup_cpp_tree(4, &edges, USE_DSU);
let r = t.delete_edge(1, 2);
assert_eq!(r, 2);
assert!(!t.query(0, 3));
}
#[test]
fn test_dndtree_matches_idtree() {
let mut rng = StdRng::seed_from_u64(99999);
let n = 50;
let mut edges = vec![];
for _ in 0..100 {
let u = rng.random_range(0..n);
let v = rng.random_range(0..n);
if u != v {
edges.push((u, v));
}
}
let adj_id = make_adj_usize(n, &edges);
let cpp_edges: Vec<(i32, i32)> = edges.iter().map(|&(u, v)| (u as i32, v as i32)).collect();
let cpp = setup_cpp_tree(n, &cpp_edges, USE_DSU);
let mut idt = IDTree::from_adj(&adj_id);
for _ in 0..200 {
let u = rng.random_range(0..n);
let v = rng.random_range(0..n);
let op = rng.random_range(0..3);
match op {
0 => {
cpp.insert_edge(u as i32, v as i32);
idt.insert_edge(u, v);
}
1 => {
cpp.delete_edge(u as i32, v as i32);
idt.delete_edge(u, v);
}
_ => {}
}
for _ in 0..20 {
let a = rng.random_range(0..n);
let b = rng.random_range(0..n);
assert_eq!(cpp.query(a as i32, b as i32), idt.query(a, b));
}
}
}
#[test]
fn test_mixed_ops_query_heavy() {
let n = 1_000;
let query_factor = 10;
let edges = make_edges_for_nodes(n, n * 2);
let (present_edges, absent_edges) = edges.split_at(n);
let cpp_init: Vec<(i32, i32)> = present_edges
.iter()
.map(|&(u, v)| (u as i32, v as i32))
.collect();
let mut max_node_id = 0;
for &(u, v) in present_edges.iter() {
max_node_id = max_node_id.max(u);
max_node_id = max_node_id.max(v);
}
for &(u, v) in absent_edges.iter() {
max_node_id = max_node_id.max(u);
max_node_id = max_node_id.max(v);
}
assert!(max_node_id <= n);
debug!("max_node_id = {}, n = {}", max_node_id, n);
let tree = setup_cpp_tree(max_node_id, &cpp_init, USE_DSU);
let mut present: Vec<usize> = (0..n).collect();
let mut absent: Vec<usize> = (0..n).collect();
debug!("begin");
for i in 0..n {
let pi = present[i];
let (du, dv) = present_edges[pi];
debug!(" delete edge ({}, {})", du, dv);
tree.delete_edge(du as i32, dv as i32);
for q in 0..query_factor {
let qi = present[(i + q) % n];
let (qu, qv) = present_edges[qi];
debug!(" query ({}, {})", qu, qv);
let _ = tree.query(qu as i32, qv as i32);
}
let ai = absent[i];
let (iu, iv) = absent_edges[ai];
debug!(" insert edge ({}, {})", iu, iv);
tree.insert_edge(iu as i32, iv as i32);
present[i] = ai;
absent[i] = pi;
}
}
#[test]
fn mixed_ops_query_heavy_catgraph() {
const QUERY_FACTOR: f64 = 0.05;
let n = 20_000;
let mut rng = StdRng::seed_from_u64(12345);
let edges = make_edges_for_nodes(n, n * 2);
let (present_edges, absent_edges) = edges.split_at(n);
let adj = make_caterpillar_graph(n, 0.3, 0.05);
let mut init_edges = vec![];
for (u, neighbors) in adj.iter() {
for &v in neighbors {
if (*u as i32) < v as i32 {
init_edges.push((*u as i32, v as i32));
}
}
}
let tree = setup_cpp_tree(n, &init_edges, USE_DSU);
let num_query_pairs = (QUERY_FACTOR * n as f64) as usize;
let mut query_pairs = Vec::with_capacity(num_query_pairs);
for _ in 0..num_query_pairs {
query_pairs.push((rng.random_range(0..n), rng.random_range(0..n)));
}
let mut present: Vec<usize> = (0..n).collect();
let mut absent: Vec<usize> = (0..n).collect();
for i in 0..n {
let pi = present[i];
let (du, dv) = present_edges[pi];
tree.delete_edge(du as i32, dv as i32);
for &(qu, qv) in &query_pairs {
let _ = tree.query(qu as i32, qv as i32);
}
let ai = absent[i];
let (iu, iv) = absent_edges[ai];
tree.insert_edge(iu as i32, iv as i32);
present[i] = ai;
absent[i] = pi;
}
}
fn verify_idtree_topology_sync(cpp: &UniquePtr<ffi::CPPDNDTree>, idt: &IDTree, n: usize) {
for i in 0..n {
let cpp_p = cpp.get_tree_parent(i as i32);
let idt_p = idt.get_parent(i);
if cpp_p != idt_p as i32 {
panic!(
"TOPOLOGY DRIFT at Node {}: CPP parent = {}, IDT parent = {}",
i, cpp_p, idt_p
);
}
}
}
#[test]
fn test_dndtree_matches_idtree_mtx() {
use std::io::Write;
let mtx_data = MtxData::new("bdo_exploration_graph.mtx");
let all_nodes = mtx_data.empty_map_i32.keys().collect::<Vec<_>>();
let node_count = all_nodes.len();
let cpp = setup_cpp_tree(all_nodes.len(), &[], USE_DSU);
let mut idt = IDTree::from_adj(&mtx_data.empty_map_usize);
for &(u, v) in mtx_data.all_edges.iter() {
debug!(
"----------------------------------------------\n inserting edge u = {u}, v = {v}\n----------------------------------------------"
);
let _ = std::io::stdout().flush();
debug!("-----CPPDNDTree");
let _ = std::io::stdout().flush();
let cpp_res = cpp.insert_edge(u as i32, v as i32);
debug!("\n-----IDTree");
let _ = std::io::stdout().flush();
let idt_res = idt.insert_edge(u as usize, v as usize);
verify_idtree_topology_sync(&cpp, &idt, node_count);
if cpp_res != idt_res as i32 {
panic!(
"Insert results don't match u = {u}, v = {v}, cpp_res = {cpp_res}, idt_res = {idt_res}"
);
}
}
for &(u, v) in mtx_data.all_edges.iter() {
debug!(
"----------------------------------------------\n deleting edge u = {u}, v = {v}\n----------------------------------------------"
);
debug!("-----CPPDNDTree");
let _ = std::io::stdout().flush();
let cpp_res = cpp.delete_edge(u as i32, v as i32);
debug!("\n-----IDTree");
let _ = std::io::stdout().flush();
let idt_res = idt.delete_edge(u as usize, v as usize);
verify_idtree_topology_sync(&cpp, &idt, node_count);
let _ = std::io::stdout().flush();
if cpp_res != idt_res as i32 {
debug!(
"Delete results don't match u = {u}, v = {v}, cpp_res = {cpp_res}, idt_res = {idt_res}"
);
let _ = std::io::stdout().flush();
panic!("CPPDNDTree and IDTree diverged!");
}
let cpp_conn = cpp.query(u as i32, v as i32);
let idt_conn = idt.query(u as usize, v as usize);
if cpp_conn != idt_conn {
debug!(
"Query results don't match after delete u = {u}, v = {v}, cpp_res = {cpp_conn}, idt_res = {idt_conn}"
);
let _ = std::io::stdout().flush();
panic!("CPPDNDTree and IDTree diverged!");
}
}
for &(u, v) in mtx_data.all_edges.iter() {
assert_eq!(
cpp.query(u as i32, v as i32),
idt.query(u as usize, v as usize),
"Final connectivity mismatch for edge ({}, {})",
u,
v
);
}
}
}