Files
wifi-densepose/crates/ruvector-mincut/src/subpolynomial/mod.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

1385 lines
42 KiB
Rust

//! Subpolynomial Dynamic Minimum Cut Algorithm
//!
//! Implementation of the December 2024 breakthrough achieving n^{o(1)} update time:
//! "Deterministic and Exact Fully-dynamic Minimum Cut of Superpolylogarithmic Size
//! in Subpolynomial Time" (arXiv:2512.13105)
//!
//! # Key Components
//!
//! 1. **Multi-Level Hierarchy**: O(log^{1/4} n) levels of expander decomposition
//! 2. **Deterministic LocalKCut**: Tree packing with color-coded enumeration
//! 3. **Fragmenting Algorithm**: Boundary-sparse cut detection
//! 4. **Witness Trees**: Certificate-based cut verification
//!
//! # Complexity Guarantees
//!
//! - **Update Time**: O(n^{o(1)}) = 2^{O(log^{1-c} n)} amortized
//! - **Query Time**: O(1)
//! - **Space**: O(m log n)
//! - **Cut Size**: Up to 2^{Θ(log^{3/4-c} n)}
//!
//! # Example
//!
//! ```rust,no_run
//! use ruvector_mincut::subpolynomial::{SubpolynomialMinCut, SubpolyConfig};
//!
//! let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
//!
//! // Build initial graph
//! mincut.insert_edge(1, 2, 1.0);
//! mincut.insert_edge(2, 3, 1.0);
//! mincut.insert_edge(3, 1, 1.0);
//!
//! // Query minimum cut
//! let cut_value = mincut.min_cut_value();
//! println!("Min cut: {}", cut_value);
//!
//! // Updates are subpolynomial!
//! mincut.insert_edge(3, 4, 1.0);
//! println!("New min cut: {}", mincut.min_cut_value());
//! ```
use std::collections::{HashMap, HashSet, VecDeque};
use std::time::Instant;
use crate::cluster::hierarchy::{
Expander, HierarchyCluster, HierarchyConfig, Precluster, ThreeLevelHierarchy,
};
use crate::error::{MinCutError, Result};
use crate::expander::{ExpanderComponent, ExpanderDecomposition};
use crate::fragmentation::{Fragmentation, FragmentationConfig, TrimResult};
use crate::graph::{DynamicGraph, EdgeId, VertexId, Weight};
use crate::localkcut::deterministic::{DeterministicLocalKCut, LocalCut as DetLocalCut};
use crate::witness::{LazyWitnessTree, WitnessTree};
/// Configuration for the subpolynomial algorithm
#[derive(Debug, Clone)]
pub struct SubpolyConfig {
/// Expansion parameter φ = 2^{-Θ(log^{3/4} n)}
/// For n < 10^6, we use a practical approximation
pub phi: f64,
/// Maximum cut size to support exactly
/// λ_max = 2^{Θ(log^{3/4-c} n)}
pub lambda_max: u64,
/// Approximation parameter ε for (1+ε)-approximate internal operations
pub epsilon: f64,
/// Target number of hierarchy levels: O(log^{1/4} n)
pub target_levels: usize,
/// Enable recourse tracking for complexity verification
pub track_recourse: bool,
/// Enable mirror cut certification
pub certify_cuts: bool,
/// Enable parallel processing
pub parallel: bool,
}
impl Default for SubpolyConfig {
fn default() -> Self {
Self {
phi: 0.01,
lambda_max: 1000,
epsilon: 0.1,
target_levels: 4, // O(log^{1/4} n) for n ~= 10^6
track_recourse: true,
certify_cuts: true,
parallel: true,
}
}
}
impl SubpolyConfig {
/// Create config optimized for graph of size n
pub fn for_size(n: usize) -> Self {
let log_n = (n.max(2) as f64).ln();
// φ = 2^{-Θ(log^{3/4} n)}
let phi = 2.0_f64.powf(-log_n.powf(0.75) / 4.0);
// λ_max = 2^{Θ(log^{3/4-c} n)} with c = 0.1
let lambda_max = 2.0_f64.powf(log_n.powf(0.65)).min(1e9) as u64;
// Target levels = O(log^{1/4} n)
let target_levels = (log_n.powf(0.25).ceil() as usize).max(2).min(10);
Self {
phi,
lambda_max,
epsilon: 0.1,
target_levels,
track_recourse: true,
certify_cuts: true,
parallel: true,
}
}
}
/// Statistics for recourse tracking
#[derive(Debug, Clone, Default)]
pub struct RecourseStats {
/// Total recourse across all updates
pub total_recourse: u64,
/// Number of updates processed
pub num_updates: u64,
/// Maximum recourse in a single update
pub max_single_recourse: u64,
/// Recourse per level
pub recourse_per_level: Vec<u64>,
/// Average update time in microseconds
pub avg_update_time_us: f64,
/// Theoretical subpolynomial bound (computed)
pub theoretical_bound: f64,
}
impl RecourseStats {
/// Check if recourse is within subpolynomial bounds
pub fn is_subpolynomial(&self, n: usize) -> bool {
if n < 2 || self.num_updates == 0 {
return true;
}
let log_n = (n as f64).ln();
// Subpolynomial: 2^{O(log^{1-c} n)} with c = 0.1
let bound = 2.0_f64.powf(log_n.powf(0.9));
(self.total_recourse as f64 / self.num_updates as f64) <= bound
}
/// Get amortized recourse per update
pub fn amortized_recourse(&self) -> f64 {
if self.num_updates == 0 {
return 0.0;
}
self.total_recourse as f64 / self.num_updates as f64
}
}
/// A level in the multi-level hierarchy
#[derive(Debug)]
pub struct HierarchyLevel {
/// Level index (0 = base, higher = coarser)
pub level: usize,
/// Expander decomposition at this level
pub expanders: HashMap<u64, LevelExpander>,
/// Vertex to expander mapping
pub vertex_expander: HashMap<VertexId, u64>,
/// Next expander ID
next_id: u64,
/// Recourse at this level
pub recourse: u64,
/// Configuration
phi: f64,
}
/// An expander within a hierarchy level
#[derive(Debug, Clone)]
pub struct LevelExpander {
/// Unique ID
pub id: u64,
/// Vertices in this expander
pub vertices: HashSet<VertexId>,
/// Boundary edges
pub boundary_size: usize,
/// Volume (sum of degrees)
pub volume: usize,
/// Certified minimum cut within expander
pub internal_min_cut: f64,
/// Is this a valid φ-expander?
pub is_valid_expander: bool,
/// Parent expander ID at next level (if any)
pub parent_id: Option<u64>,
/// Child expander IDs at previous level
pub children_ids: Vec<u64>,
}
/// The main subpolynomial dynamic minimum cut structure
#[derive(Debug)]
pub struct SubpolynomialMinCut {
/// Configuration
config: SubpolyConfig,
/// Graph adjacency
adjacency: HashMap<VertexId, HashMap<VertexId, Weight>>,
/// All edges
edges: HashSet<(VertexId, VertexId)>,
/// Multi-level hierarchy
levels: Vec<HierarchyLevel>,
/// Deterministic LocalKCut for cut discovery
local_kcut: Option<DeterministicLocalKCut>,
/// Current minimum cut value
current_min_cut: f64,
/// Recourse statistics
recourse_stats: RecourseStats,
/// Number of vertices
num_vertices: usize,
/// Number of edges
num_edges: usize,
/// Next vertex/edge ID tracking
next_id: u64,
/// Is hierarchy built?
hierarchy_built: bool,
}
impl SubpolynomialMinCut {
/// Create new subpolynomial min-cut structure
pub fn new(config: SubpolyConfig) -> Self {
let num_levels = config.target_levels;
let levels = (0..num_levels)
.map(|i| HierarchyLevel {
level: i,
expanders: HashMap::new(),
vertex_expander: HashMap::new(),
next_id: 1,
recourse: 0,
phi: config.phi * (1.0 + i as f64 * 0.1), // Slightly increasing φ per level
})
.collect();
Self {
config,
adjacency: HashMap::new(),
edges: HashSet::new(),
levels,
local_kcut: None,
current_min_cut: f64::INFINITY,
recourse_stats: RecourseStats::default(),
num_vertices: 0,
num_edges: 0,
next_id: 1,
hierarchy_built: false,
}
}
/// Create with config optimized for expected graph size
pub fn for_size(expected_n: usize) -> Self {
Self::new(SubpolyConfig::for_size(expected_n))
}
/// Insert an edge
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> Result<f64> {
let start = Instant::now();
let key = Self::edge_key(u, v);
if self.edges.contains(&key) {
return Err(MinCutError::EdgeExists(u, v));
}
// Add to graph
self.edges.insert(key);
let is_new_u = !self.adjacency.contains_key(&u);
let is_new_v = !self.adjacency.contains_key(&v);
self.adjacency.entry(u).or_default().insert(v, weight);
self.adjacency.entry(v).or_default().insert(u, weight);
if is_new_u {
self.num_vertices += 1;
}
if is_new_v && u != v {
self.num_vertices += 1;
}
self.num_edges += 1;
// Update hierarchy incrementally if built
if self.hierarchy_built {
let recourse = self.handle_edge_insert(u, v, weight);
self.update_recourse_stats(recourse, start.elapsed().as_micros() as f64);
}
// Update LocalKCut
if let Some(ref mut lkc) = self.local_kcut {
lkc.insert_edge(u, v, weight);
}
Ok(self.current_min_cut)
}
/// Delete an edge
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<f64> {
let start = Instant::now();
let key = Self::edge_key(u, v);
if !self.edges.remove(&key) {
return Err(MinCutError::EdgeNotFound(u, v));
}
// Remove from graph
if let Some(neighbors) = self.adjacency.get_mut(&u) {
neighbors.remove(&v);
}
if let Some(neighbors) = self.adjacency.get_mut(&v) {
neighbors.remove(&u);
}
self.num_edges -= 1;
// Update hierarchy incrementally if built
if self.hierarchy_built {
let recourse = self.handle_edge_delete(u, v);
self.update_recourse_stats(recourse, start.elapsed().as_micros() as f64);
}
// Update LocalKCut
if let Some(ref mut lkc) = self.local_kcut {
lkc.delete_edge(u, v);
}
Ok(self.current_min_cut)
}
/// Build the multi-level hierarchy
///
/// This creates O(log^{1/4} n) levels of expander decomposition,
/// enabling subpolynomial update time.
pub fn build(&mut self) {
if self.adjacency.is_empty() {
return;
}
// Adjust number of levels based on actual graph size
let n = self.num_vertices;
let log_n = (n.max(2) as f64).ln();
let optimal_levels = (log_n.powf(0.25).ceil() as usize).max(2).min(10);
// Resize levels if needed
while self.levels.len() < optimal_levels {
let i = self.levels.len();
self.levels.push(HierarchyLevel {
level: i,
expanders: HashMap::new(),
vertex_expander: HashMap::new(),
next_id: 1,
recourse: 0,
phi: self.config.phi * (1.0 + i as f64 * 0.1),
});
}
// Build base level (level 0) expanders
self.build_base_level();
// Build subsequent levels by coarsening
for level in 1..self.levels.len() {
self.build_level(level);
}
// Initialize LocalKCut
self.local_kcut = Some(DeterministicLocalKCut::new(
self.config.lambda_max,
self.num_vertices * 10,
2,
));
// Collect edge data first
let edge_data: Vec<(VertexId, VertexId, Weight)> = self
.edges
.iter()
.map(|&(u, v)| (u, v, self.get_weight(u, v).unwrap_or(1.0)))
.collect();
// Add edges to LocalKCut
if let Some(ref mut lkc) = self.local_kcut {
for (u, v, weight) in edge_data {
lkc.insert_edge(u, v, weight);
}
}
// Compute initial minimum cut
self.recompute_min_cut();
self.hierarchy_built = true;
// Update theoretical bound
self.recourse_stats.theoretical_bound = 2.0_f64.powf(log_n.powf(0.9));
}
/// Build the base level (level 0) expanders
fn build_base_level(&mut self) {
let vertices: HashSet<_> = self.adjacency.keys().copied().collect();
if vertices.is_empty() {
return;
}
// Get phi from level before mutating
let phi = self.levels[0].phi;
// First pass: grow all expanders (uses immutable self)
let mut remaining = vertices.clone();
let mut expander_sets: Vec<HashSet<VertexId>> = Vec::new();
while !remaining.is_empty() {
let start = *remaining.iter().next().unwrap();
let expander_vertices = self.grow_expander(&remaining, start, phi);
if expander_vertices.is_empty() {
remaining.remove(&start);
continue;
}
for &v in &expander_vertices {
remaining.remove(&v);
}
expander_sets.push(expander_vertices);
}
// Second pass: compute properties (uses immutable self)
let mut expanders_to_create: Vec<LevelExpander> = Vec::new();
let mut vertex_mappings: Vec<(VertexId, u64)> = Vec::new();
let mut next_id = self.levels[0].next_id;
for expander_vertices in &expander_sets {
let id = next_id;
next_id += 1;
let volume = expander_vertices.iter().map(|&v| self.degree(v)).sum();
let boundary_size = self.count_boundary(expander_vertices);
let expander = LevelExpander {
id,
vertices: expander_vertices.clone(),
boundary_size,
volume,
internal_min_cut: f64::INFINITY,
is_valid_expander: true,
parent_id: None,
children_ids: Vec::new(),
};
for &v in expander_vertices {
vertex_mappings.push((v, id));
}
expanders_to_create.push(expander);
}
// Third pass: apply all changes (uses mutable self)
let level = &mut self.levels[0];
level.expanders.clear();
level.vertex_expander.clear();
level.next_id = next_id;
for expander in expanders_to_create {
level.expanders.insert(expander.id, expander);
}
for (v, id) in vertex_mappings {
level.vertex_expander.insert(v, id);
}
}
/// Build a level by coarsening the previous level
fn build_level(&mut self, level_idx: usize) {
if level_idx == 0 || level_idx >= self.levels.len() {
return;
}
// Collect expander IDs from previous level
let prev_expander_ids: Vec<u64> = self.levels[level_idx - 1]
.expanders
.keys()
.copied()
.collect();
// Group adjacent expanders
let mut groups: Vec<Vec<u64>> = Vec::new();
let mut assigned: HashSet<u64> = HashSet::new();
for &exp_id in &prev_expander_ids {
if assigned.contains(&exp_id) {
continue;
}
let mut group = vec![exp_id];
assigned.insert(exp_id);
// Find adjacent expanders
for &other_id in &prev_expander_ids {
if assigned.contains(&other_id) {
continue;
}
if self.expanders_adjacent_at_level(level_idx - 1, exp_id, other_id) {
group.push(other_id);
assigned.insert(other_id);
// Limit group size
if group.len() >= 4 {
break;
}
}
}
groups.push(group);
}
// First, collect all vertices from child expanders
let mut group_vertices: Vec<(Vec<u64>, HashSet<VertexId>)> = Vec::new();
for group in &groups {
let mut vertices = HashSet::new();
for &child_id in group {
if let Some(child) = self.levels[level_idx - 1].expanders.get(&child_id) {
vertices.extend(&child.vertices);
}
}
group_vertices.push((group.clone(), vertices));
}
// Now compute properties using immutable self
let mut expanders_to_create: Vec<(u64, LevelExpander, HashMap<VertexId, u64>)> = Vec::new();
let mut next_id = self.levels[level_idx].next_id;
for (group, vertices) in &group_vertices {
let id = next_id;
next_id += 1;
let volume = vertices.iter().map(|&v| self.degree(v)).sum();
let boundary_size = self.count_boundary(vertices);
let expander = LevelExpander {
id,
vertices: vertices.clone(),
boundary_size,
volume,
internal_min_cut: f64::INFINITY,
is_valid_expander: true,
parent_id: None,
children_ids: group.clone(),
};
let mut vertex_map = HashMap::new();
for &v in vertices {
vertex_map.insert(v, id);
}
expanders_to_create.push((id, expander, vertex_map));
}
// Now apply all changes
let level = &mut self.levels[level_idx];
level.expanders.clear();
level.vertex_expander.clear();
level.next_id = next_id;
for (id, expander, vertex_map) in expanders_to_create {
level.expanders.insert(id, expander);
level.vertex_expander.extend(vertex_map);
}
// Update parent pointers in children (separate borrow)
for (group, _) in &group_vertices {
// Find the parent ID for this group
let parent_id = self.levels[level_idx]
.expanders
.values()
.find(|e| &e.children_ids == group)
.map(|e| e.id);
if let Some(pid) = parent_id {
for &child_id in group {
if let Some(child) = self.levels[level_idx - 1].expanders.get_mut(&child_id) {
child.parent_id = Some(pid);
}
}
}
}
}
/// Grow an expander from a starting vertex
fn grow_expander(
&self,
available: &HashSet<VertexId>,
start: VertexId,
phi: f64,
) -> HashSet<VertexId> {
let mut expander = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(start);
expander.insert(start);
let max_size = (self.num_vertices / 4).max(10);
while let Some(v) = queue.pop_front() {
if expander.len() >= max_size {
break;
}
for (neighbor, _) in self.neighbors(v) {
if !available.contains(&neighbor) || expander.contains(&neighbor) {
continue;
}
// Check expansion property
let mut test_set = expander.clone();
test_set.insert(neighbor);
let volume: usize = test_set.iter().map(|&u| self.degree(u)).sum();
let boundary = self.count_boundary(&test_set);
let expansion = if volume > 0 {
boundary as f64 / volume as f64
} else {
0.0
};
// Add if it maintains reasonable expansion
if expansion >= phi * 0.5 || expander.len() < 3 {
expander.insert(neighbor);
queue.push_back(neighbor);
}
}
}
expander
}
/// Handle edge insertion with subpolynomial update
fn handle_edge_insert(&mut self, u: VertexId, v: VertexId, weight: Weight) -> u64 {
let mut total_recourse = 0u64;
// Find affected expanders at each level
for level_idx in 0..self.levels.len() {
let recourse = self.update_level_for_insert(level_idx, u, v, weight);
total_recourse += recourse;
if level_idx < self.levels.len() {
self.levels[level_idx].recourse += recourse;
}
}
// Update minimum cut
self.update_min_cut_incremental(u, v, true);
total_recourse
}
/// Handle edge deletion with subpolynomial update
fn handle_edge_delete(&mut self, u: VertexId, v: VertexId) -> u64 {
let mut total_recourse = 0u64;
// Find affected expanders at each level
for level_idx in 0..self.levels.len() {
let recourse = self.update_level_for_delete(level_idx, u, v);
total_recourse += recourse;
if level_idx < self.levels.len() {
self.levels[level_idx].recourse += recourse;
}
}
// Update minimum cut
self.update_min_cut_incremental(u, v, false);
total_recourse
}
/// Update a level for edge insertion
fn update_level_for_insert(
&mut self,
level_idx: usize,
u: VertexId,
v: VertexId,
_weight: Weight,
) -> u64 {
if level_idx >= self.levels.len() {
return 0;
}
let mut recourse = 0u64;
// Get expanders containing u and v
let exp_u = self.levels[level_idx].vertex_expander.get(&u).copied();
let exp_v = self.levels[level_idx].vertex_expander.get(&v).copied();
match (exp_u, exp_v) {
(Some(eu), Some(ev)) if eu == ev => {
// Same expander - just update internal properties
recourse += 1;
self.update_expander_properties(level_idx, eu);
}
(Some(eu), Some(ev)) => {
// Different expanders - update both
recourse += 2;
self.update_expander_properties(level_idx, eu);
self.update_expander_properties(level_idx, ev);
}
(Some(eu), None) => {
// Add v to expander containing u
recourse += self.try_add_to_expander(level_idx, v, eu);
}
(None, Some(ev)) => {
// Add u to expander containing v
recourse += self.try_add_to_expander(level_idx, u, ev);
}
(None, None) => {
// Create new expander for both vertices
recourse += self.create_new_expander(level_idx, &[u, v]);
}
}
recourse
}
/// Update a level for edge deletion
fn update_level_for_delete(&mut self, level_idx: usize, u: VertexId, v: VertexId) -> u64 {
if level_idx >= self.levels.len() {
return 0;
}
let mut recourse = 0u64;
// Get expanders containing u and v
let exp_u = self.levels[level_idx].vertex_expander.get(&u).copied();
let exp_v = self.levels[level_idx].vertex_expander.get(&v).copied();
if let (Some(eu), Some(ev)) = (exp_u, exp_v) {
if eu == ev {
// Same expander - check if it needs to split
recourse += self.check_and_split_expander(level_idx, eu);
} else {
// Different expanders - update boundary
recourse += 2;
self.update_expander_properties(level_idx, eu);
self.update_expander_properties(level_idx, ev);
}
}
recourse
}
/// Update properties of an expander
fn update_expander_properties(&mut self, level_idx: usize, exp_id: u64) {
if level_idx >= self.levels.len() {
return;
}
// Get vertices and phi first
let (vertices, phi) = match self.levels[level_idx].expanders.get(&exp_id) {
Some(e) => (e.vertices.clone(), self.levels[level_idx].phi),
None => return,
};
let volume: usize = vertices.iter().map(|&v| self.degree(v)).sum();
let boundary_size = self.count_boundary(&vertices);
// Check if still valid expander
let expansion = if volume > 0 {
boundary_size as f64 / volume as f64
} else {
0.0
};
let is_valid = expansion >= phi * 0.3;
if let Some(expander) = self.levels[level_idx].expanders.get_mut(&exp_id) {
expander.volume = volume;
expander.boundary_size = boundary_size;
expander.is_valid_expander = is_valid;
}
}
/// Try to add a vertex to an expander
fn try_add_to_expander(&mut self, level_idx: usize, v: VertexId, exp_id: u64) -> u64 {
if level_idx >= self.levels.len() {
return 0;
}
// Check if adding would violate expansion
let (can_add, volume, boundary) = {
let level = &self.levels[level_idx];
if let Some(expander) = level.expanders.get(&exp_id) {
let mut test_vertices = expander.vertices.clone();
test_vertices.insert(v);
let volume: usize = test_vertices.iter().map(|&u| self.degree(u)).sum();
let boundary = self.count_boundary(&test_vertices);
let expansion = if volume > 0 {
boundary as f64 / volume as f64
} else {
0.0
};
(expansion >= level.phi * 0.3, volume, boundary)
} else {
(false, 0, 0)
}
};
if can_add {
let level = &mut self.levels[level_idx];
if let Some(expander) = level.expanders.get_mut(&exp_id) {
expander.vertices.insert(v);
expander.volume = volume;
expander.boundary_size = boundary;
}
level.vertex_expander.insert(v, exp_id);
1
} else {
self.create_new_expander(level_idx, &[v])
}
}
/// Create a new expander for vertices
fn create_new_expander(&mut self, level_idx: usize, vertices: &[VertexId]) -> u64 {
if level_idx >= self.levels.len() {
return 0;
}
let vertex_set: HashSet<_> = vertices.iter().copied().collect();
let volume: usize = vertex_set.iter().map(|&v| self.degree(v)).sum();
let boundary_size = self.count_boundary(&vertex_set);
let level = &mut self.levels[level_idx];
let id = level.next_id;
level.next_id += 1;
let expander = LevelExpander {
id,
vertices: vertex_set.clone(),
boundary_size,
volume,
internal_min_cut: f64::INFINITY,
is_valid_expander: true,
parent_id: None,
children_ids: Vec::new(),
};
for &v in &vertex_set {
level.vertex_expander.insert(v, id);
}
level.expanders.insert(id, expander);
vertices.len() as u64
}
/// Check if an expander needs to split after edge deletion
fn check_and_split_expander(&mut self, level_idx: usize, exp_id: u64) -> u64 {
if level_idx >= self.levels.len() {
return 0;
}
// Check expansion property
let needs_split = {
let level = &self.levels[level_idx];
if let Some(expander) = level.expanders.get(&exp_id) {
let expansion = if expander.volume > 0 {
expander.boundary_size as f64 / expander.volume as f64
} else {
0.0
};
expansion < level.phi * 0.2
} else {
false
}
};
if needs_split {
// For now, just mark as invalid and update properties
// A full split would require more complex logic
self.update_expander_properties(level_idx, exp_id);
2
} else {
self.update_expander_properties(level_idx, exp_id);
1
}
}
/// Update minimum cut incrementally
fn update_min_cut_incremental(&mut self, u: VertexId, v: VertexId, is_insert: bool) {
// Use LocalKCut for local cut discovery
if let Some(ref lkc) = self.local_kcut {
let cuts_u = lkc.query(u);
let cuts_v = lkc.query(v);
let mut min_local = f64::INFINITY;
for cut in cuts_u.iter().chain(cuts_v.iter()) {
if cut.cut_value < min_local {
min_local = cut.cut_value;
}
}
if is_insert {
// Edge insertion can only increase cuts
// But might enable new paths that reduce other cuts
self.current_min_cut = self.current_min_cut.min(min_local);
} else {
// Edge deletion might decrease the min cut
if min_local < self.current_min_cut * 1.5 {
// Need to verify more carefully
self.recompute_min_cut();
}
}
} else {
// Fallback to full recomputation
self.recompute_min_cut();
}
}
/// Recompute the minimum cut from scratch
fn recompute_min_cut(&mut self) {
if self.edges.is_empty() {
self.current_min_cut = f64::INFINITY;
return;
}
let mut min_cut = f64::INFINITY;
// Check all level boundaries
for level in &self.levels {
for expander in level.expanders.values() {
// Boundary cut value
let boundary_cut = expander.boundary_size as f64;
min_cut = min_cut.min(boundary_cut);
// Internal cut (from cached value)
min_cut = min_cut.min(expander.internal_min_cut);
}
}
// Also query LocalKCut for local cuts
if let Some(ref lkc) = self.local_kcut {
for v in self.adjacency.keys().take(10) {
let cuts = lkc.query(*v);
for cut in cuts {
min_cut = min_cut.min(cut.cut_value);
}
}
}
self.current_min_cut = min_cut;
}
/// Update recourse statistics
fn update_recourse_stats(&mut self, recourse: u64, time_us: f64) {
self.recourse_stats.total_recourse += recourse;
self.recourse_stats.num_updates += 1;
self.recourse_stats.max_single_recourse =
self.recourse_stats.max_single_recourse.max(recourse);
// Update average time
let n = self.recourse_stats.num_updates as f64;
self.recourse_stats.avg_update_time_us =
(self.recourse_stats.avg_update_time_us * (n - 1.0) + time_us) / n;
// Update per-level recourse
self.recourse_stats.recourse_per_level = self.levels.iter().map(|l| l.recourse).collect();
}
// === Helper methods ===
fn edge_key(u: VertexId, v: VertexId) -> (VertexId, VertexId) {
if u < v {
(u, v)
} else {
(v, u)
}
}
fn get_weight(&self, u: VertexId, v: VertexId) -> Option<Weight> {
self.adjacency.get(&u).and_then(|n| n.get(&v).copied())
}
fn degree(&self, v: VertexId) -> usize {
self.adjacency.get(&v).map_or(0, |n| n.len())
}
fn neighbors(&self, v: VertexId) -> Vec<(VertexId, Weight)> {
self.adjacency
.get(&v)
.map(|n| n.iter().map(|(&v, &w)| (v, w)).collect())
.unwrap_or_default()
}
fn count_boundary(&self, vertices: &HashSet<VertexId>) -> usize {
let mut boundary = 0;
for &v in vertices {
for (neighbor, _) in self.neighbors(v) {
if !vertices.contains(&neighbor) {
boundary += 1;
}
}
}
boundary
}
fn expanders_adjacent_at_level(&self, level_idx: usize, exp1: u64, exp2: u64) -> bool {
if level_idx >= self.levels.len() {
return false;
}
let level = &self.levels[level_idx];
let e1 = match level.expanders.get(&exp1) {
Some(e) => e,
None => return false,
};
let e2 = match level.expanders.get(&exp2) {
Some(e) => e,
None => return false,
};
// Check if any vertex in e1 has a neighbor in e2
for &v in &e1.vertices {
for (neighbor, _) in self.neighbors(v) {
if e2.vertices.contains(&neighbor) {
return true;
}
}
}
false
}
// === Public API ===
/// Get the current minimum cut value
pub fn min_cut_value(&self) -> f64 {
self.current_min_cut
}
/// Get detailed minimum cut result
pub fn min_cut(&self) -> MinCutQueryResult {
MinCutQueryResult {
value: self.current_min_cut,
cut_edges: None, // Would need more work to track
partition: None,
is_exact: true,
complexity_verified: self.recourse_stats.is_subpolynomial(self.num_vertices),
}
}
/// Get configuration
pub fn config(&self) -> &SubpolyConfig {
&self.config
}
/// Get number of vertices
pub fn num_vertices(&self) -> usize {
self.num_vertices
}
/// Get number of edges
pub fn num_edges(&self) -> usize {
self.num_edges
}
/// Get number of hierarchy levels
pub fn num_levels(&self) -> usize {
self.levels.len()
}
/// Get recourse statistics
pub fn recourse_stats(&self) -> &RecourseStats {
&self.recourse_stats
}
/// Get hierarchy statistics
pub fn hierarchy_stats(&self) -> HierarchyStatistics {
HierarchyStatistics {
num_levels: self.levels.len(),
expanders_per_level: self.levels.iter().map(|l| l.expanders.len()).collect(),
total_expanders: self.levels.iter().map(|l| l.expanders.len()).sum(),
avg_expander_size: if self.levels[0].expanders.is_empty() {
0.0
} else {
self.levels[0]
.expanders
.values()
.map(|e| e.vertices.len())
.sum::<usize>() as f64
/ self.levels[0].expanders.len() as f64
},
}
}
/// Check if updates are subpolynomial
pub fn is_subpolynomial(&self) -> bool {
self.recourse_stats.is_subpolynomial(self.num_vertices)
}
/// Certify cuts using LocalKCut verification
pub fn certify_cuts(&mut self) {
// First collect all expander info (exp_id, vertices)
let mut expander_data: Vec<(usize, u64, HashSet<VertexId>)> = Vec::new();
for level_idx in 0..self.levels.len() {
for (&exp_id, expander) in &self.levels[level_idx].expanders {
expander_data.push((level_idx, exp_id, expander.vertices.clone()));
}
}
// Now process each expander with immutable self
let mut updates: Vec<(usize, u64, f64)> = Vec::new();
if let Some(ref lkc) = self.local_kcut {
for (level_idx, exp_id, vertices) in &expander_data {
// Sample boundary vertices
let boundary_verts: Vec<_> = vertices
.iter()
.filter(|&&v| self.neighbors(v).iter().any(|(n, _)| !vertices.contains(n)))
.take(5)
.copied()
.collect();
let mut min_internal_cut = f64::INFINITY;
for v in boundary_verts {
let cuts = lkc.query(v);
for cut in cuts {
// Check if cut is internal to expander
let is_internal = cut.vertices.iter().all(|u| vertices.contains(u));
if is_internal {
min_internal_cut = min_internal_cut.min(cut.cut_value);
}
}
}
updates.push((*level_idx, *exp_id, min_internal_cut));
}
}
// Now apply all updates
for (level_idx, exp_id, min_cut) in updates {
if let Some(expander) = self.levels[level_idx].expanders.get_mut(&exp_id) {
expander.internal_min_cut = min_cut;
}
}
}
}
/// Result of a minimum cut query
#[derive(Debug, Clone)]
pub struct MinCutQueryResult {
/// The minimum cut value
pub value: f64,
/// Edges in the cut (if computed)
pub cut_edges: Option<Vec<(VertexId, VertexId)>>,
/// Partition (S, T) (if computed)
pub partition: Option<(Vec<VertexId>, Vec<VertexId>)>,
/// Whether this is an exact result
pub is_exact: bool,
/// Whether subpolynomial complexity is verified
pub complexity_verified: bool,
}
/// Statistics about the hierarchy
#[derive(Debug, Clone)]
pub struct HierarchyStatistics {
/// Number of levels
pub num_levels: usize,
/// Expanders at each level
pub expanders_per_level: Vec<usize>,
/// Total expanders across all levels
pub total_expanders: usize,
/// Average expander size at base level
pub avg_expander_size: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subpoly_config_default() {
let config = SubpolyConfig::default();
assert!(config.phi > 0.0);
assert!(config.lambda_max > 0);
assert!(config.target_levels > 0);
}
#[test]
fn test_subpoly_config_for_size() {
let config = SubpolyConfig::for_size(1_000_000);
assert!(config.phi < 0.1);
assert!(config.lambda_max > 100);
assert!(config.target_levels >= 2);
}
#[test]
fn test_create_empty() {
let mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
assert_eq!(mincut.num_vertices(), 0);
assert_eq!(mincut.num_edges(), 0);
assert_eq!(mincut.min_cut_value(), f64::INFINITY);
}
#[test]
fn test_insert_edges() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
mincut.insert_edge(1, 2, 1.0).unwrap();
mincut.insert_edge(2, 3, 1.0).unwrap();
mincut.insert_edge(3, 1, 1.0).unwrap();
assert_eq!(mincut.num_vertices(), 3);
assert_eq!(mincut.num_edges(), 3);
}
#[test]
fn test_build_hierarchy() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
// Build a path graph
for i in 0..10 {
mincut.insert_edge(i, i + 1, 1.0).unwrap();
}
mincut.build();
assert!(mincut.num_levels() >= 2);
let stats = mincut.hierarchy_stats();
assert!(stats.total_expanders > 0);
}
#[test]
fn test_min_cut_triangle() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
mincut.insert_edge(1, 2, 1.0).unwrap();
mincut.insert_edge(2, 3, 1.0).unwrap();
mincut.insert_edge(3, 1, 1.0).unwrap();
mincut.build();
assert!(mincut.min_cut_value() <= 2.0);
}
#[test]
fn test_min_cut_bridge() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
// Two triangles connected by a bridge
mincut.insert_edge(1, 2, 1.0).unwrap();
mincut.insert_edge(2, 3, 1.0).unwrap();
mincut.insert_edge(3, 1, 1.0).unwrap();
mincut.insert_edge(3, 4, 1.0).unwrap(); // Bridge
mincut.insert_edge(4, 5, 1.0).unwrap();
mincut.insert_edge(5, 6, 1.0).unwrap();
mincut.insert_edge(6, 4, 1.0).unwrap();
mincut.build();
// Min cut should be 1 (the bridge)
assert!(mincut.min_cut_value() <= 2.0);
}
#[test]
fn test_incremental_updates() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
// Build initial graph
mincut.insert_edge(1, 2, 1.0).unwrap();
mincut.insert_edge(2, 3, 1.0).unwrap();
mincut.insert_edge(3, 1, 1.0).unwrap();
mincut.build();
let initial_cut = mincut.min_cut_value();
// Add more edges
mincut.insert_edge(3, 4, 1.0).unwrap();
mincut.insert_edge(4, 5, 1.0).unwrap();
// Cut might have changed
assert!(mincut.min_cut_value() <= initial_cut * 2.0);
// Check recourse tracking
let stats = mincut.recourse_stats();
assert!(stats.num_updates > 0);
}
#[test]
fn test_delete_edge() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
mincut.insert_edge(1, 2, 1.0).unwrap();
mincut.insert_edge(2, 3, 1.0).unwrap();
mincut.insert_edge(3, 1, 1.0).unwrap();
mincut.build();
mincut.delete_edge(1, 2).unwrap();
assert_eq!(mincut.num_edges(), 2);
}
#[test]
fn test_recourse_stats() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
// Build graph
for i in 0..20 {
mincut.insert_edge(i, i + 1, 1.0).unwrap();
}
mincut.build();
// Do updates
mincut.insert_edge(0, 10, 1.0).unwrap();
mincut.insert_edge(5, 15, 1.0).unwrap();
let stats = mincut.recourse_stats();
assert!(stats.num_updates >= 2);
assert!(stats.amortized_recourse() >= 0.0);
}
#[test]
fn test_is_subpolynomial() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
// Build small graph
for i in 0..10 {
mincut.insert_edge(i, i + 1, 1.0).unwrap();
}
mincut.build();
// Do some updates
mincut.insert_edge(0, 5, 1.0).unwrap();
// Should be subpolynomial for small graph
assert!(mincut.is_subpolynomial());
}
#[test]
fn test_certify_cuts() {
let mut mincut = SubpolynomialMinCut::new(SubpolyConfig::default());
// Build graph
mincut.insert_edge(1, 2, 1.0).unwrap();
mincut.insert_edge(2, 3, 1.0).unwrap();
mincut.insert_edge(3, 1, 1.0).unwrap();
mincut.build();
mincut.certify_cuts();
// Should complete without panic
}
#[test]
fn test_large_graph() {
let mut mincut = SubpolynomialMinCut::for_size(1000);
// Build a larger graph
for i in 0..100 {
mincut.insert_edge(i, i + 1, 1.0).unwrap();
}
// Add some cross edges
for i in 0..10 {
mincut.insert_edge(i * 10, i * 10 + 50, 1.0).unwrap();
}
mincut.build();
let stats = mincut.hierarchy_stats();
assert!(stats.num_levels >= 2);
// Test updates
mincut.insert_edge(25, 75, 1.0).unwrap();
assert!(mincut.recourse_stats().num_updates > 0);
}
}