Files
wifi-densepose/crates/ruvector-mincut/tests/localkcut_paper_integration.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

313 lines
8.5 KiB
Rust

//! Integration tests for paper-compliant LocalKCut implementation
//!
//! Tests the integration between the paper implementation and the
//! rest of the minimum cut system.
use ruvector_mincut::{
DeterministicFamilyGenerator, DeterministicLocalKCut, DynamicGraph, LocalKCutOracle,
LocalKCutQuery, PaperLocalKCutResult as LocalKCutResult,
};
use std::sync::Arc;
#[test]
fn test_paper_api_basic_usage() {
// Create a simple graph
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
// Create oracle
let oracle = DeterministicLocalKCut::new(5);
// Create query
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 2,
radius: 3,
};
// Execute search
let result = oracle.search(&graph, query);
// Should find some cut or report none
match result {
LocalKCutResult::Found { cut_value, witness } => {
assert!(cut_value <= 2);
assert!(witness.boundary_size() <= 2);
println!("Found cut with value: {}", cut_value);
}
LocalKCutResult::NoneInLocality => {
println!("No cut found in locality");
}
}
}
#[test]
fn test_paper_api_with_family_generator() {
let graph = Arc::new(DynamicGraph::new());
// Create a more complex graph
for i in 1..=10 {
graph.insert_edge(i, i + 1, 1.0).unwrap();
}
// Add some cross edges
graph.insert_edge(1, 5, 1.0).unwrap();
graph.insert_edge(3, 7, 1.0).unwrap();
// Create oracle with custom family generator
let generator = DeterministicFamilyGenerator::new(5);
let oracle = DeterministicLocalKCut::with_family_generator(8, generator);
let query = LocalKCutQuery {
seed_vertices: vec![1, 2, 3],
budget_k: 3,
radius: 5,
};
let result = oracle.search(&graph, query);
// Verify result type
match result {
LocalKCutResult::Found { cut_value, witness } => {
assert!(cut_value <= 3);
assert_eq!(witness.boundary_size(), cut_value);
}
LocalKCutResult::NoneInLocality => {
// Acceptable
}
}
}
#[test]
fn test_witness_integration() {
let graph = Arc::new(DynamicGraph::new());
// Create two components connected by a single edge
// Component 1
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 1, 1.0).unwrap();
// Bridge
graph.insert_edge(3, 4, 1.0).unwrap();
// Component 2
graph.insert_edge(4, 5, 1.0).unwrap();
graph.insert_edge(5, 6, 1.0).unwrap();
graph.insert_edge(6, 4, 1.0).unwrap();
let oracle = DeterministicLocalKCut::new(10);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 2,
radius: 10,
};
let result = oracle.search(&graph, query);
match result {
LocalKCutResult::Found { cut_value, witness } => {
// Should find the bridge (cut value = 1)
assert_eq!(cut_value, 1);
// Witness should be consistent
assert_eq!(witness.boundary_size(), 1);
// Seed should be in witness
assert!(witness.contains(witness.seed()));
// Can materialize partition
let (u, _v_minus_u) = witness.materialize_partition();
assert!(!u.is_empty());
println!("Found bridge cut with {} vertices", witness.cardinality());
}
LocalKCutResult::NoneInLocality => {
panic!("Should find the bridge");
}
}
}
#[test]
fn test_determinism_across_calls() {
let graph = Arc::new(DynamicGraph::new());
// Create deterministic graph structure
for i in 1..=8 {
graph.insert_edge(i, i + 1, 1.0).unwrap();
}
graph.insert_edge(4, 6, 1.0).unwrap();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![2, 3],
budget_k: 3,
radius: 4,
};
// Run multiple times
let mut results = Vec::new();
for _ in 0..5 {
results.push(oracle.search(&graph, query.clone()));
}
// All results should be identical
for i in 1..results.len() {
match (&results[0], &results[i]) {
(
LocalKCutResult::Found {
cut_value: v1,
witness: w1,
},
LocalKCutResult::Found {
cut_value: v2,
witness: w2,
},
) => {
assert_eq!(v1, v2, "Cut values should be deterministic");
assert_eq!(w1.seed(), w2.seed(), "Seeds should match");
assert_eq!(
w1.boundary_size(),
w2.boundary_size(),
"Boundary sizes should match"
);
assert_eq!(
w1.cardinality(),
w2.cardinality(),
"Cardinalities should match"
);
}
(LocalKCutResult::NoneInLocality, LocalKCutResult::NoneInLocality) => {
// Both none - consistent
}
_ => {
panic!("Results are not deterministic!");
}
}
}
}
#[test]
fn test_budget_boundary() {
let graph = Arc::new(DynamicGraph::new());
// Create a graph with known minimum cut = 2
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(1, 3, 1.0).unwrap();
graph.insert_edge(2, 4, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
let oracle = DeterministicLocalKCut::new(5);
// Try with budget = 1 (should fail or find nothing)
let query_low = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 1,
radius: 5,
};
let result_low = oracle.search(&graph, query_low);
if let LocalKCutResult::Found { cut_value, .. } = result_low {
assert!(cut_value <= 1, "Must respect budget constraint");
}
// Try with budget = 3 (should succeed)
let query_high = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 3,
radius: 5,
};
let result_high = oracle.search(&graph, query_high);
match result_high {
LocalKCutResult::Found { cut_value, .. } => {
assert!(cut_value <= 3, "Must respect budget constraint");
}
LocalKCutResult::NoneInLocality => {
// Acceptable based on radius
}
}
}
#[test]
fn test_radius_limiting() {
let graph = Arc::new(DynamicGraph::new());
// Create a long path
for i in 1..=20 {
graph.insert_edge(i, i + 1, 1.0).unwrap();
}
let oracle = DeterministicLocalKCut::new(3); // max_radius = 3
// Request large radius (should be capped at max_radius)
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 2,
radius: 100,
};
// Should not panic, should cap at max_radius = 3
let result = oracle.search(&graph, query);
// Result should be based on radius=3, not radius=100
match result {
LocalKCutResult::Found { witness, .. } => {
// With radius=3, should find at most 4 vertices (seed + 3 layers)
assert!(witness.cardinality() <= 4);
}
LocalKCutResult::NoneInLocality => {
// Acceptable
}
}
}
#[test]
fn test_empty_graph() {
let graph = Arc::new(DynamicGraph::new());
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 10,
radius: 5,
};
let result = oracle.search(&graph, query);
// Should return NoneInLocality for empty graph
assert!(matches!(result, LocalKCutResult::NoneInLocality));
}
#[test]
fn test_single_vertex() {
let graph = Arc::new(DynamicGraph::new());
// Add a single vertex (via an edge to itself would be invalid, so just query)
graph.insert_edge(1, 2, 1.0).unwrap();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 10,
radius: 0, // Don't expand
};
let result = oracle.search(&graph, query);
// With radius=0, should find single vertex or none
match result {
LocalKCutResult::Found { witness, .. } => {
assert_eq!(witness.cardinality(), 1);
}
LocalKCutResult::NoneInLocality => {
// Also acceptable
}
}
}