git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
445 lines
12 KiB
Rust
445 lines
12 KiB
Rust
//! Integration tests for bounded-range dynamic minimum cut
|
|
//!
|
|
//! Tests the full system: wrapper + instances + LocalKCut
|
|
|
|
use ruvector_mincut::instance::StubInstance;
|
|
use ruvector_mincut::prelude::*;
|
|
use ruvector_mincut::wrapper::{MinCutResult, MinCutWrapper};
|
|
use std::sync::Arc;
|
|
|
|
/// Test path graph P_n has min cut 1
|
|
#[test]
|
|
fn test_path_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Build path graph: 0-1-2-3-4-5-6-7-8-9
|
|
for i in 0..9 {
|
|
graph.insert_edge(i, i + 1, 1.0).unwrap();
|
|
}
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Notify wrapper of edges
|
|
for i in 0..9 {
|
|
wrapper.insert_edge(i as u64, i, i + 1);
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected(), "Path graph should be connected");
|
|
assert_eq!(result.value(), 1, "Path graph has min cut 1");
|
|
}
|
|
|
|
/// Test cycle graph C_n has min cut 2
|
|
#[test]
|
|
fn test_cycle_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Build cycle graph: 0-1-2-3-4-0
|
|
let n = 5;
|
|
for i in 0..n {
|
|
let j = (i + 1) % n;
|
|
graph.insert_edge(i, j, 1.0).unwrap();
|
|
}
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Notify wrapper of edges
|
|
for i in 0..n {
|
|
let j = (i + 1) % n;
|
|
wrapper.insert_edge(i as u64, i, j);
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected(), "Cycle graph should be connected");
|
|
assert_eq!(result.value(), 2, "Cycle graph has min cut 2");
|
|
}
|
|
|
|
/// Test complete graph K_n has min cut n-1
|
|
#[test]
|
|
fn test_complete_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Build K_5 (complete graph with 5 vertices)
|
|
let n = 5;
|
|
let mut edge_id = 0;
|
|
for i in 0..n {
|
|
for j in (i + 1)..n {
|
|
graph.insert_edge(i, j, 1.0).unwrap();
|
|
}
|
|
}
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Notify wrapper of all edges
|
|
for i in 0..n {
|
|
for j in (i + 1)..n {
|
|
wrapper.insert_edge(edge_id, i, j);
|
|
edge_id += 1;
|
|
}
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected(), "Complete graph should be connected");
|
|
assert_eq!(result.value(), (n - 1) as u64, "K_5 has min cut 4");
|
|
}
|
|
|
|
/// Test dynamic updates maintain correctness
|
|
#[test]
|
|
fn test_dynamic_updates_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Start with path: 0-1-2-3
|
|
for i in 0..3 {
|
|
graph.insert_edge(i, i + 1, 1.0).unwrap();
|
|
wrapper.insert_edge(i as u64, i, i + 1);
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert_eq!(result.value(), 1, "Path has min cut 1");
|
|
|
|
// Add edge to form cycle: 0-1-2-3-0
|
|
graph.insert_edge(3, 0, 1.0).unwrap();
|
|
wrapper.insert_edge(3, 3, 0);
|
|
|
|
let result = wrapper.query();
|
|
assert_eq!(result.value(), 2, "Cycle has min cut 2");
|
|
|
|
// Delete an edge to go back to path
|
|
graph.delete_edge(1, 2).unwrap();
|
|
wrapper.delete_edge(1, 1, 2);
|
|
|
|
let result = wrapper.query();
|
|
assert_eq!(result.value(), 1, "After deletion, min cut should be 1");
|
|
}
|
|
|
|
/// Test disconnected graph returns 0
|
|
#[test]
|
|
fn test_disconnected_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create two components: {0, 1} and {2, 3}
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
graph.insert_edge(2, 3, 1.0).unwrap();
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
wrapper.insert_edge(0, 0, 1);
|
|
wrapper.insert_edge(1, 2, 3);
|
|
|
|
let result = wrapper.query();
|
|
assert!(!result.is_connected(), "Graph should be disconnected");
|
|
assert_eq!(result.value(), 0, "Disconnected graph has min cut 0");
|
|
}
|
|
|
|
/// Test star graph (min cut = 1, deleting center vertex)
|
|
#[test]
|
|
fn test_star_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create star with center 0 and leaves 1,2,3,4
|
|
let n = 5;
|
|
for i in 1..n {
|
|
graph.insert_edge(0, i, 1.0).unwrap();
|
|
}
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
for i in 1..n {
|
|
wrapper.insert_edge((i - 1) as u64, 0, i);
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected());
|
|
assert_eq!(result.value(), 1, "Star graph has min cut 1");
|
|
}
|
|
|
|
/// Test weighted graph (min cut respects weights)
|
|
#[test]
|
|
fn test_weighted_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Triangle with different weights
|
|
// Edge (0,1) weight 5
|
|
// Edge (1,2) weight 3
|
|
// Edge (2,0) weight 4
|
|
// Min cut should be 3 (cutting edge 1-2)
|
|
graph.insert_edge(0, 1, 5.0).unwrap();
|
|
graph.insert_edge(1, 2, 3.0).unwrap();
|
|
graph.insert_edge(2, 0, 4.0).unwrap();
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
wrapper.insert_edge(0, 0, 1);
|
|
wrapper.insert_edge(1, 1, 2);
|
|
wrapper.insert_edge(2, 2, 0);
|
|
|
|
// Note: The wrapper works with integer weights internally
|
|
// For this test, we're checking it reports a proper cut
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected());
|
|
assert!(
|
|
result.value() > 0,
|
|
"Weighted graph should have positive min cut"
|
|
);
|
|
}
|
|
|
|
/// Stress test with many updates
|
|
#[test]
|
|
fn test_stress_many_updates() {
|
|
use rand::rngs::StdRng;
|
|
use rand::{Rng, SeedableRng};
|
|
|
|
let mut rng = StdRng::seed_from_u64(12345);
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// 1000 random insertions
|
|
let mut successful_inserts = 0;
|
|
for i in 0..1000 {
|
|
let u = rng.gen_range(0..100);
|
|
let v = rng.gen_range(0..100);
|
|
if u != v {
|
|
if graph.insert_edge(u, v, 1.0).is_ok() {
|
|
wrapper.insert_edge(i, u, v);
|
|
successful_inserts += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("Successfully inserted {} edges", successful_inserts);
|
|
|
|
// Query should not panic
|
|
let result = wrapper.query();
|
|
|
|
// Result should be valid (either disconnected or connected with positive cut)
|
|
if result.is_connected() {
|
|
assert!(
|
|
result.value() >= 1,
|
|
"Connected graph should have min cut >= 1"
|
|
);
|
|
} else {
|
|
assert_eq!(
|
|
result.value(),
|
|
0,
|
|
"Disconnected graph should have min cut 0"
|
|
);
|
|
}
|
|
|
|
// Should have buffered updates initially
|
|
assert_eq!(
|
|
wrapper.pending_updates(),
|
|
0,
|
|
"After query, updates should be processed"
|
|
);
|
|
}
|
|
|
|
/// Test determinism: same sequence produces same result
|
|
#[test]
|
|
fn test_determinism() {
|
|
// First run
|
|
let graph1 = Arc::new(DynamicGraph::new());
|
|
let mut wrapper1 = MinCutWrapper::new(Arc::clone(&graph1));
|
|
|
|
let edges = vec![(0, 1), (1, 2), (2, 3), (3, 0), (0, 2)];
|
|
for (i, (u, v)) in edges.iter().enumerate() {
|
|
graph1.insert_edge(*u, *v, 1.0).unwrap();
|
|
wrapper1.insert_edge(i as u64, *u, *v);
|
|
}
|
|
|
|
let result1 = wrapper1.query();
|
|
|
|
// Second run with identical sequence
|
|
let graph2 = Arc::new(DynamicGraph::new());
|
|
let mut wrapper2 = MinCutWrapper::new(Arc::clone(&graph2));
|
|
|
|
for (i, (u, v)) in edges.iter().enumerate() {
|
|
graph2.insert_edge(*u, *v, 1.0).unwrap();
|
|
wrapper2.insert_edge(i as u64, *u, *v);
|
|
}
|
|
|
|
let result2 = wrapper2.query();
|
|
|
|
// Both should produce identical results
|
|
assert_eq!(
|
|
result1.value(),
|
|
result2.value(),
|
|
"Determinism: same input should produce same output"
|
|
);
|
|
assert_eq!(result1.is_connected(), result2.is_connected());
|
|
}
|
|
|
|
/// Test buffered updates are processed correctly
|
|
#[test]
|
|
fn test_buffered_updates() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Add edges without querying
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
graph.insert_edge(1, 2, 1.0).unwrap();
|
|
graph.insert_edge(2, 3, 1.0).unwrap();
|
|
|
|
wrapper.insert_edge(0, 0, 1);
|
|
wrapper.insert_edge(1, 1, 2);
|
|
wrapper.insert_edge(2, 2, 3);
|
|
|
|
// Should have pending updates
|
|
assert_eq!(wrapper.pending_updates(), 3);
|
|
|
|
// Query processes them
|
|
let result = wrapper.query();
|
|
assert_eq!(wrapper.pending_updates(), 0);
|
|
assert_eq!(result.value(), 1);
|
|
}
|
|
|
|
/// Test lazy instantiation of instances
|
|
#[test]
|
|
fn test_lazy_instantiation() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// No instances should exist initially
|
|
assert_eq!(wrapper.num_instances(), 0);
|
|
|
|
// Add some edges
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
graph.insert_edge(1, 2, 1.0).unwrap();
|
|
wrapper.insert_edge(0, 0, 1);
|
|
wrapper.insert_edge(1, 1, 2);
|
|
|
|
// Still no instances until query
|
|
assert_eq!(wrapper.num_instances(), 0);
|
|
|
|
// Query triggers instantiation
|
|
let _ = wrapper.query();
|
|
|
|
// Now instances should be created
|
|
assert!(
|
|
wrapper.num_instances() > 0,
|
|
"Query should instantiate instances"
|
|
);
|
|
}
|
|
|
|
/// Test multiple queries are consistent
|
|
#[test]
|
|
fn test_multiple_queries_consistent() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// Build triangle
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
graph.insert_edge(1, 2, 1.0).unwrap();
|
|
graph.insert_edge(2, 0, 1.0).unwrap();
|
|
|
|
wrapper.insert_edge(0, 0, 1);
|
|
wrapper.insert_edge(1, 1, 2);
|
|
wrapper.insert_edge(2, 2, 0);
|
|
|
|
// Multiple queries should give same result
|
|
let result1 = wrapper.query();
|
|
let result2 = wrapper.query();
|
|
let result3 = wrapper.query();
|
|
|
|
assert_eq!(result1.value(), result2.value());
|
|
assert_eq!(result2.value(), result3.value());
|
|
assert_eq!(result1.value(), 2, "Triangle has min cut 2");
|
|
}
|
|
|
|
/// Test empty graph
|
|
#[test]
|
|
fn test_empty_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
let result = wrapper.query();
|
|
// Empty graph is considered disconnected
|
|
assert_eq!(result.value(), 0);
|
|
}
|
|
|
|
/// Test single edge
|
|
#[test]
|
|
fn test_single_edge_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
wrapper.insert_edge(0, 0, 1);
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected());
|
|
assert_eq!(result.value(), 1, "Single edge has min cut 1");
|
|
}
|
|
|
|
/// Test grid graph (known structure with min cut = width for vertical cut)
|
|
#[test]
|
|
fn test_grid_graph_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
|
|
// 3x3 grid
|
|
let width = 3;
|
|
let height = 3;
|
|
let mut edge_id = 0;
|
|
|
|
for i in 0..height {
|
|
for j in 0..width {
|
|
let v = i * width + j;
|
|
|
|
// Horizontal edge
|
|
if j + 1 < width {
|
|
let u = v;
|
|
let w = v + 1;
|
|
graph.insert_edge(u as u64, w as u64, 1.0).unwrap();
|
|
wrapper.insert_edge(edge_id, u as u64, w as u64);
|
|
edge_id += 1;
|
|
}
|
|
|
|
// Vertical edge
|
|
if i + 1 < height {
|
|
let u = v;
|
|
let w = v + width;
|
|
graph.insert_edge(u as u64, w as u64, 1.0).unwrap();
|
|
wrapper.insert_edge(edge_id, u as u64, w as u64);
|
|
edge_id += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected());
|
|
// 3x3 grid has min cut of 2 (cutting off a corner vertex)
|
|
// Corner vertices have degree 2, so min cut is 2
|
|
assert_eq!(result.value(), 2, "3x3 grid has min cut 2");
|
|
}
|
|
|
|
/// Test bridge edge (edge whose removal disconnects graph)
|
|
#[test]
|
|
fn test_bridge_edge_integration() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create dumbbell: triangle-bridge-triangle
|
|
// Left triangle: 0-1-2-0
|
|
graph.insert_edge(0, 1, 1.0).unwrap();
|
|
graph.insert_edge(1, 2, 1.0).unwrap();
|
|
graph.insert_edge(2, 0, 1.0).unwrap();
|
|
|
|
// Bridge: 2-3
|
|
graph.insert_edge(2, 3, 1.0).unwrap();
|
|
|
|
// Right triangle: 3-4-5-3
|
|
graph.insert_edge(3, 4, 1.0).unwrap();
|
|
graph.insert_edge(4, 5, 1.0).unwrap();
|
|
graph.insert_edge(5, 3, 1.0).unwrap();
|
|
|
|
let mut wrapper = MinCutWrapper::new(Arc::clone(&graph));
|
|
wrapper.insert_edge(0, 0, 1);
|
|
wrapper.insert_edge(1, 1, 2);
|
|
wrapper.insert_edge(2, 2, 0);
|
|
wrapper.insert_edge(3, 2, 3); // Bridge
|
|
wrapper.insert_edge(4, 3, 4);
|
|
wrapper.insert_edge(5, 4, 5);
|
|
wrapper.insert_edge(6, 5, 3);
|
|
|
|
let result = wrapper.query();
|
|
assert!(result.is_connected());
|
|
assert_eq!(result.value(), 1, "Bridge edge gives min cut 1");
|
|
}
|