Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,327 @@
//! 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");
}

View File

@@ -0,0 +1,97 @@
//! Demonstration of graph sparsification for approximate minimum cuts
use ruvector_mincut::graph::DynamicGraph;
use ruvector_mincut::sparsify::{karger_sparsify, NagamochiIbaraki, SparseGraph, SparsifyConfig};
use std::sync::Arc;
fn main() {
println!("=== Graph Sparsification Demo ===\n");
// Create a sample graph (complete graph on 10 vertices)
println!("Creating complete graph with 10 vertices...");
let graph = create_complete_graph(10);
println!(
"Original graph: {} vertices, {} edges\n",
graph.num_vertices(),
graph.num_edges()
);
// Demo 1: Benczúr-Karger sparsification
println!("--- Benczúr-Karger Sparsification ---");
demo_benczur_karger(&graph);
// Demo 2: Karger sparsification (convenience function)
println!("\n--- Karger Sparsification (convenience) ---");
demo_karger(&graph);
// Demo 3: Nagamochi-Ibaraki deterministic sparsification
println!("\n--- Nagamochi-Ibaraki Deterministic Sparsification ---");
demo_nagamochi_ibaraki(&graph);
println!("\n=== Demo Complete ===");
}
fn create_complete_graph(n: usize) -> DynamicGraph {
let g = DynamicGraph::new();
for i in 0..n {
for j in (i + 1)..n {
g.insert_edge(i as u64, j as u64, 1.0).unwrap();
}
}
g
}
fn demo_benczur_karger(graph: &DynamicGraph) {
let epsilons = vec![0.1, 0.2, 0.5];
for epsilon in epsilons {
let config = SparsifyConfig::new(epsilon).unwrap().with_seed(42);
let sparse = SparseGraph::from_graph(graph, config).unwrap();
println!(
" ε = {:.2}: {} edges ({:.1}% of original)",
epsilon,
sparse.num_edges(),
sparse.sparsification_ratio() * 100.0
);
let approx_cut = sparse.approximate_min_cut();
println!(" Approximate min cut: {:.2}", approx_cut);
}
}
fn demo_karger(graph: &DynamicGraph) {
let epsilon = 0.15;
let sparse = karger_sparsify(graph, epsilon, Some(123)).unwrap();
println!(
" ε = {:.2}: {} edges ({:.1}% of original)",
epsilon,
sparse.num_edges(),
sparse.sparsification_ratio() * 100.0
);
}
fn demo_nagamochi_ibaraki(graph: &DynamicGraph) {
let ni = NagamochiIbaraki::new(Arc::new(graph.clone()));
let k_values = vec![2, 3, 5];
for k in k_values {
match ni.sparse_k_certificate(k) {
Ok(sparse) => {
let ratio = sparse.num_edges() as f64 / graph.num_edges() as f64;
println!(
" k = {}: {} edges ({:.1}% of original)",
k,
sparse.num_edges(),
ratio * 100.0
);
}
Err(e) => {
println!(" k = {}: Error - {}", k, e);
}
}
}
}

View File

@@ -0,0 +1,156 @@
//! Benchmark for SubpolynomialMinCut
//!
//! Demonstrates subpolynomial update performance.
use ruvector_mincut::subpolynomial::{SubpolyConfig, SubpolynomialMinCut};
use std::time::Instant;
fn main() {
println!("=== SubpolynomialMinCut Benchmark ===\n");
// Test different graph sizes
for &n in &[100, 500, 1000, 5000] {
benchmark_size(n);
}
println!("\n=== Complexity Verification ===\n");
verify_subpolynomial_complexity();
}
fn benchmark_size(n: usize) {
println!("Graph size: {} vertices", n);
let mut mincut = SubpolynomialMinCut::for_size(n);
// Build a random-ish graph
let build_start = Instant::now();
// Path + cross edges for connectivity
for i in 0..(n as u64 - 1) {
mincut.insert_edge(i, i + 1, 1.0).unwrap();
}
// Add cross edges
for i in (0..n as u64).step_by(10) {
let j = (i + n as u64 / 2) % n as u64;
if i != j {
let _ = mincut.insert_edge(i, j, 1.0);
}
}
println!(" Build graph: {:?}", build_start.elapsed());
// Build hierarchy
let hier_start = Instant::now();
mincut.build();
println!(" Build hierarchy: {:?}", hier_start.elapsed());
let stats = mincut.hierarchy_stats();
println!(
" Levels: {}, Expanders: {}",
stats.num_levels, stats.total_expanders
);
// Benchmark updates
let num_updates = 100;
let update_start = Instant::now();
for i in 0..num_updates {
let u = (i * 7) as u64 % n as u64;
let v = (i * 13 + 5) as u64 % n as u64;
if u != v {
let _ = mincut.insert_edge(u, v, 0.5);
}
}
let update_time = update_start.elapsed();
let avg_update_us = update_time.as_micros() as f64 / num_updates as f64;
println!(
" {} updates: {:?} ({:.2} μs/update)",
num_updates, update_time, avg_update_us
);
println!(" Min cut: {:.1}", mincut.min_cut_value());
let recourse = mincut.recourse_stats();
println!(
" Avg recourse: {:.2}, Is subpolynomial: {}",
recourse.amortized_recourse(),
recourse.is_subpolynomial(n)
);
println!();
}
fn verify_subpolynomial_complexity() {
// Compare update time scaling
let sizes = [100, 200, 400, 800, 1600];
let mut results = Vec::new();
for &n in &sizes {
let mut mincut = SubpolynomialMinCut::for_size(n);
// Build graph
for i in 0..(n as u64 - 1) {
mincut.insert_edge(i, i + 1, 1.0).unwrap();
}
for i in (0..n as u64).step_by(5) {
let j = (i + n as u64 / 3) % n as u64;
if i != j {
let _ = mincut.insert_edge(i, j, 1.0);
}
}
mincut.build();
// Measure updates
let num_updates = 50;
let start = Instant::now();
for i in 0..num_updates {
let u = (i * 11) as u64 % n as u64;
let v = (i * 17 + 3) as u64 % n as u64;
if u != v {
let _ = mincut.insert_edge(u, v, 0.5);
}
}
let avg_us = start.elapsed().as_micros() as f64 / num_updates as f64;
results.push((n, avg_us));
}
println!("Size\tAvg Update (μs)\tScaling");
println!("----\t---------------\t-------");
for i in 0..results.len() {
let (n, time) = results[i];
let scaling = if i > 0 {
let (prev_n, prev_time) = results[i - 1];
let n_ratio = n as f64 / prev_n as f64;
let time_ratio = time / prev_time;
let exponent = time_ratio.log2() / n_ratio.log2();
format!("n^{:.2}", exponent)
} else {
"-".to_string()
};
println!("{}\t{:.2}\t\t{}", n, time, scaling);
}
// For subpolynomial: exponent should approach 0 as n grows
let last_ratio = results.last().unwrap().1 / results.first().unwrap().1;
let size_ratio = sizes.last().unwrap() / sizes.first().unwrap();
let overall_exponent = last_ratio.log2() / (size_ratio as f64).log2();
println!("\nOverall scaling: n^{:.2}", overall_exponent);
println!("For subpolynomial, expect exponent → 0 as n → ∞");
println!(
"Current exponent ({:.2}) is {} polynomial",
overall_exponent,
if overall_exponent < 0.5 {
"sub"
} else {
"super"
}
);
}