git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
328 lines
9.8 KiB
Rust
328 lines
9.8 KiB
Rust
//! LocalKCut Algorithm Demonstration
|
|
//!
|
|
//! This example demonstrates the deterministic LocalKCut algorithm from the
|
|
//! December 2024 paper. It shows how to:
|
|
//!
|
|
//! 1. Find local minimum cuts near specific vertices
|
|
//! 2. Use deterministic edge colorings for reproducibility
|
|
//! 3. Apply forest packing for witness guarantees
|
|
//! 4. Compare with global minimum cut algorithms
|
|
|
|
use ruvector_mincut::prelude::*;
|
|
use std::sync::Arc;
|
|
|
|
fn main() {
|
|
println!("=== LocalKCut Algorithm Demonstration ===\n");
|
|
|
|
// Example 1: Simple graph with bridge
|
|
println!("Example 1: Bridge Detection");
|
|
demo_bridge_detection();
|
|
println!();
|
|
|
|
// Example 2: Deterministic behavior
|
|
println!("Example 2: Deterministic Coloring");
|
|
demo_deterministic_coloring();
|
|
println!();
|
|
|
|
// Example 3: Forest packing
|
|
println!("Example 3: Forest Packing Witnesses");
|
|
demo_forest_packing();
|
|
println!();
|
|
|
|
// Example 4: Comparison with global mincut
|
|
println!("Example 4: Local vs Global Minimum Cut");
|
|
demo_local_vs_global();
|
|
println!();
|
|
|
|
// Example 5: Complex graph
|
|
println!("Example 5: Complex Graph Analysis");
|
|
demo_complex_graph();
|
|
}
|
|
|
|
/// Demonstrates finding a bridge using LocalKCut
|
|
fn demo_bridge_detection() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create two components connected by a bridge
|
|
// Component 1: triangle {1, 2, 3}
|
|
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: 3 -> 4 (the minimum cut!)
|
|
graph.insert_edge(3, 4, 1.0).unwrap();
|
|
|
|
// Component 2: triangle {4, 5, 6}
|
|
graph.insert_edge(4, 5, 1.0).unwrap();
|
|
graph.insert_edge(5, 6, 1.0).unwrap();
|
|
graph.insert_edge(6, 4, 1.0).unwrap();
|
|
|
|
println!("Graph: Two triangles connected by a bridge");
|
|
println!(
|
|
"Vertices: {}, Edges: {}",
|
|
graph.num_vertices(),
|
|
graph.num_edges()
|
|
);
|
|
|
|
// Find local cut from vertex 1
|
|
let local_kcut = LocalKCut::new(graph.clone(), 5);
|
|
|
|
println!("\nSearching for local cut from vertex 1 with k=5...");
|
|
if let Some(result) = local_kcut.find_cut(1) {
|
|
println!("✓ Found local cut!");
|
|
println!(" Cut value: {}", result.cut_value);
|
|
println!(" Cut set size: {}", result.cut_set.len());
|
|
println!(" Cut edges: {:?}", result.cut_edges);
|
|
println!(" Iterations: {}", result.iterations);
|
|
|
|
// The bridge should be found
|
|
if result.cut_value == 1.0 {
|
|
println!(" → Successfully detected the bridge!");
|
|
}
|
|
} else {
|
|
println!("✗ No cut found within bound k=5");
|
|
}
|
|
}
|
|
|
|
/// Demonstrates deterministic coloring behavior
|
|
fn demo_deterministic_coloring() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create a simple path graph: 1-2-3-4-5
|
|
for i in 1..=4 {
|
|
graph.insert_edge(i, i + 1, 1.0).unwrap();
|
|
}
|
|
|
|
println!("Graph: Path of 5 vertices");
|
|
|
|
// Create two LocalKCut instances
|
|
let lk1 = LocalKCut::new(graph.clone(), 3);
|
|
let lk2 = LocalKCut::new(graph.clone(), 3);
|
|
|
|
println!("\nEdge colorings (deterministic):");
|
|
for edge in graph.edges() {
|
|
let color1 = lk1.edge_color(edge.id).unwrap();
|
|
let color2 = lk2.edge_color(edge.id).unwrap();
|
|
|
|
println!(" Edge ({}, {}): {:?}", edge.source, edge.target, color1);
|
|
|
|
// Verify determinism
|
|
assert_eq!(color1, color2, "Colors should be deterministic!");
|
|
}
|
|
|
|
println!("\n✓ All edge colorings are deterministic!");
|
|
|
|
// Find cuts from different vertices
|
|
println!("\nFinding cuts from different starting vertices:");
|
|
for start_vertex in 1..=5 {
|
|
if let Some(result) = lk1.find_cut(start_vertex) {
|
|
println!(
|
|
" Vertex {}: cut value = {}, set size = {}",
|
|
start_vertex,
|
|
result.cut_value,
|
|
result.cut_set.len()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Demonstrates forest packing and witness properties
|
|
fn demo_forest_packing() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create a more complex graph
|
|
// 1 - 2
|
|
// | |
|
|
// 3 - 4 - 5
|
|
// | |
|
|
// 6 - 7
|
|
|
|
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();
|
|
graph.insert_edge(4, 5, 1.0).unwrap();
|
|
graph.insert_edge(4, 6, 1.0).unwrap();
|
|
graph.insert_edge(5, 7, 1.0).unwrap();
|
|
graph.insert_edge(6, 7, 1.0).unwrap();
|
|
|
|
println!("Graph: Complex grid-like structure");
|
|
println!(
|
|
"Vertices: {}, Edges: {}",
|
|
graph.num_vertices(),
|
|
graph.num_edges()
|
|
);
|
|
|
|
// Create forest packing
|
|
let lambda_max = 3; // Upper bound on min cut
|
|
let epsilon = 0.1; // Approximation parameter
|
|
|
|
println!(
|
|
"\nCreating forest packing with λ_max={}, ε={}...",
|
|
lambda_max, epsilon
|
|
);
|
|
let packing = ForestPacking::greedy_packing(&*graph, lambda_max, epsilon);
|
|
|
|
println!("✓ Created {} forests", packing.num_forests());
|
|
|
|
// Show forest structures
|
|
for i in 0..packing.num_forests().min(3) {
|
|
if let Some(forest) = packing.forest(i) {
|
|
println!(" Forest {}: {} edges", i, forest.len());
|
|
}
|
|
}
|
|
|
|
// Find a cut and check witness property
|
|
let local_kcut = LocalKCut::new(graph.clone(), 5);
|
|
if let Some(result) = local_kcut.find_cut(1) {
|
|
println!("\nFound cut with value {}", result.cut_value);
|
|
|
|
let is_witnessed = packing.witnesses_cut(&result.cut_edges);
|
|
println!(" Witnessed by all forests: {}", is_witnessed);
|
|
|
|
if is_witnessed {
|
|
println!(" ✓ Cut satisfies witness property!");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Compare local and global minimum cuts
|
|
fn demo_local_vs_global() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create a graph where local and global cuts differ
|
|
// 1 - 2 - 3
|
|
// | | |
|
|
// 4 - 5 - 6
|
|
// | | |
|
|
// 7 - 8 - 9
|
|
|
|
// Top row
|
|
graph.insert_edge(1, 2, 2.0).unwrap();
|
|
graph.insert_edge(2, 3, 2.0).unwrap();
|
|
|
|
// Middle row
|
|
graph.insert_edge(4, 5, 2.0).unwrap();
|
|
graph.insert_edge(5, 6, 2.0).unwrap();
|
|
|
|
// Bottom row
|
|
graph.insert_edge(7, 8, 2.0).unwrap();
|
|
graph.insert_edge(8, 9, 2.0).unwrap();
|
|
|
|
// Vertical connections (weaker)
|
|
graph.insert_edge(1, 4, 1.0).unwrap();
|
|
graph.insert_edge(2, 5, 1.0).unwrap();
|
|
graph.insert_edge(3, 6, 1.0).unwrap();
|
|
graph.insert_edge(4, 7, 1.0).unwrap();
|
|
graph.insert_edge(5, 8, 1.0).unwrap();
|
|
graph.insert_edge(6, 9, 1.0).unwrap();
|
|
|
|
println!("Graph: 3x3 grid with different edge weights");
|
|
println!(
|
|
"Vertices: {}, Edges: {}",
|
|
graph.num_vertices(),
|
|
graph.num_edges()
|
|
);
|
|
|
|
// Find local cuts from different vertices
|
|
let local_kcut = LocalKCut::new(graph.clone(), 10);
|
|
|
|
println!("\nLocal cuts from different vertices:");
|
|
for vertex in &[1, 5, 9] {
|
|
if let Some(result) = local_kcut.find_cut(*vertex) {
|
|
println!(
|
|
" Vertex {}: cut value = {}, iterations = {}",
|
|
vertex, result.cut_value, result.iterations
|
|
);
|
|
}
|
|
}
|
|
|
|
// Build global minimum cut (using the algorithm)
|
|
let mut mincut = MinCutBuilder::new().exact().build().unwrap();
|
|
|
|
// Add edges to global mincut
|
|
for edge in graph.edges() {
|
|
let _ = mincut.insert_edge(edge.source, edge.target, edge.weight);
|
|
}
|
|
|
|
let global_value = mincut.min_cut_value();
|
|
println!("\nGlobal minimum cut value: {}", global_value);
|
|
|
|
println!("\n✓ Local cuts provide fast approximations to global minimum cut");
|
|
}
|
|
|
|
/// Analyze a complex graph with multiple cut candidates
|
|
fn demo_complex_graph() {
|
|
let graph = Arc::new(DynamicGraph::new());
|
|
|
|
// Create a graph with multiple communities
|
|
// Community 1: clique {1,2,3,4}
|
|
for i in 1..=4 {
|
|
for j in i + 1..=4 {
|
|
graph.insert_edge(i, j, 2.0).unwrap();
|
|
}
|
|
}
|
|
|
|
// Community 2: clique {5,6,7,8}
|
|
for i in 5..=8 {
|
|
for j in i + 1..=8 {
|
|
graph.insert_edge(i, j, 2.0).unwrap();
|
|
}
|
|
}
|
|
|
|
// Weak connections between communities
|
|
graph.insert_edge(4, 5, 0.5).unwrap();
|
|
graph.insert_edge(3, 6, 0.5).unwrap();
|
|
|
|
println!("Graph: Two dense communities with weak connections");
|
|
println!(
|
|
"Vertices: {}, Edges: {}",
|
|
graph.num_vertices(),
|
|
graph.num_edges()
|
|
);
|
|
|
|
let stats = graph.stats();
|
|
println!("Average degree: {:.2}", stats.avg_degree);
|
|
println!("Total weight: {:.2}", stats.total_weight);
|
|
|
|
// Find local cuts
|
|
let local_kcut = LocalKCut::new(graph.clone(), 5);
|
|
|
|
println!("\nSearching for cuts with k=5...");
|
|
|
|
// Try from community 1
|
|
if let Some(result) = local_kcut.find_cut(1) {
|
|
println!(" From community 1:");
|
|
println!(" Cut value: {}", result.cut_value);
|
|
println!(
|
|
" Separates {} vertices from {}",
|
|
result.cut_set.len(),
|
|
graph.num_vertices() - result.cut_set.len()
|
|
);
|
|
}
|
|
|
|
// Try from community 2
|
|
if let Some(result) = local_kcut.find_cut(5) {
|
|
println!(" From community 2:");
|
|
println!(" Cut value: {}", result.cut_value);
|
|
println!(
|
|
" Separates {} vertices from {}",
|
|
result.cut_set.len(),
|
|
graph.num_vertices() - result.cut_set.len()
|
|
);
|
|
}
|
|
|
|
// Enumerate paths to understand graph structure
|
|
println!("\nPath enumeration from vertex 1:");
|
|
let paths = local_kcut.enumerate_paths(1, 2);
|
|
println!(" Found {} distinct reachable sets at depth 2", paths.len());
|
|
|
|
// Show diversity of reachable sets
|
|
let mut sizes: Vec<_> = paths.iter().map(|p| p.len()).collect();
|
|
sizes.sort_unstable();
|
|
sizes.dedup();
|
|
println!(" Reachable set sizes: {:?}", sizes);
|
|
|
|
println!("\n✓ LocalKCut successfully analyzes community structure");
|
|
}
|