Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
920
vendor/ruvector/crates/ruvector-mincut/src/witness/mod.rs
vendored
Normal file
920
vendor/ruvector/crates/ruvector-mincut/src/witness/mod.rs
vendored
Normal file
@@ -0,0 +1,920 @@
|
||||
//! Witness Trees for Dynamic Minimum Cut
|
||||
//!
|
||||
//! Implements witness trees following Jin-Sun-Thorup (SODA 2024):
|
||||
//! "Fully Dynamic Exact Minimum Cut in Subpolynomial Time"
|
||||
//!
|
||||
//! A witness tree certifies the minimum cut by maintaining:
|
||||
//! - A spanning forest where each tree edge is witnessed
|
||||
//! - Lazy updates that maintain correctness
|
||||
//! - Efficient recomputation when witnesses break
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! Witness trees maintain a spanning forest of the graph where each tree edge
|
||||
//! is "witnessed" by the current minimum cut. When an edge is removed from the
|
||||
//! spanning tree, it reveals the cut that witnesses it. This allows efficient
|
||||
//! dynamic maintenance of minimum cuts.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use std::sync::Arc;
|
||||
//! use parking_lot::RwLock;
|
||||
//! use ruvector_mincut::graph::DynamicGraph;
|
||||
//! use ruvector_mincut::witness::WitnessTree;
|
||||
//!
|
||||
//! // Create a graph
|
||||
//! let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
//! graph.write().insert_edge(1, 2, 1.0).unwrap();
|
||||
//! graph.write().insert_edge(2, 3, 1.0).unwrap();
|
||||
//! graph.write().insert_edge(3, 1, 1.0).unwrap();
|
||||
//!
|
||||
//! // Build witness tree
|
||||
//! let mut witness = WitnessTree::build(graph.clone()).unwrap();
|
||||
//!
|
||||
//! // Get minimum cut
|
||||
//! println!("Min cut: {}", witness.min_cut_value());
|
||||
//!
|
||||
//! // Dynamic updates
|
||||
//! witness.insert_edge(1, 4, 2.0).unwrap();
|
||||
//! witness.delete_edge(1, 2).unwrap();
|
||||
//! ```
|
||||
|
||||
use crate::graph::{DynamicGraph, Edge, EdgeId, VertexId, Weight};
|
||||
use crate::linkcut::LinkCutTree;
|
||||
use crate::{MinCutError, Result};
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::{HashMap, HashSet, VecDeque};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A witness for a tree edge
|
||||
///
|
||||
/// Each tree edge has a witness cut that certifies its inclusion in the tree.
|
||||
/// The witness cut guarantees that removing the tree edge reveals a cut of
|
||||
/// at least the witness value.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EdgeWitness {
|
||||
/// The tree edge being witnessed
|
||||
pub tree_edge: (VertexId, VertexId),
|
||||
/// The cut that witnesses this edge
|
||||
pub cut_value: Weight,
|
||||
/// Vertices on one side of the witnessing cut
|
||||
pub cut_side: HashSet<VertexId>,
|
||||
}
|
||||
|
||||
impl EdgeWitness {
|
||||
/// Create a new witness
|
||||
fn new(
|
||||
tree_edge: (VertexId, VertexId),
|
||||
cut_value: Weight,
|
||||
cut_side: HashSet<VertexId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
tree_edge,
|
||||
cut_value,
|
||||
cut_side,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Witness tree maintaining minimum cut certificates
|
||||
///
|
||||
/// The witness tree maintains a spanning forest of the graph where each
|
||||
/// tree edge is witnessed by a cut. This allows efficient dynamic updates
|
||||
/// to the minimum cut.
|
||||
pub struct WitnessTree {
|
||||
/// Link-cut tree for dynamic connectivity
|
||||
lct: LinkCutTree,
|
||||
/// Witnesses for each tree edge
|
||||
witnesses: HashMap<(VertexId, VertexId), EdgeWitness>,
|
||||
/// Current minimum cut value
|
||||
min_cut: Weight,
|
||||
/// Edges in the minimum cut
|
||||
min_cut_edges: Vec<Edge>,
|
||||
/// Graph reference
|
||||
graph: Arc<RwLock<DynamicGraph>>,
|
||||
/// Dirty flag for lazy recomputation
|
||||
dirty: bool,
|
||||
/// Tree edges (canonical form: min(u,v), max(u,v))
|
||||
tree_edges: HashSet<(VertexId, VertexId)>,
|
||||
/// Non-tree edges
|
||||
non_tree_edges: HashSet<(VertexId, VertexId)>,
|
||||
}
|
||||
|
||||
impl WitnessTree {
|
||||
/// Build witness tree from graph
|
||||
///
|
||||
/// # Algorithm
|
||||
///
|
||||
/// 1. Construct a spanning tree using BFS
|
||||
/// 2. Compute witness for each tree edge
|
||||
/// 3. Find the minimum cut among all witnesses
|
||||
///
|
||||
/// # Complexity
|
||||
///
|
||||
/// O(n * m) where n = vertices, m = edges
|
||||
pub fn build(graph: Arc<RwLock<DynamicGraph>>) -> Result<Self> {
|
||||
let mut witness_tree = Self {
|
||||
lct: LinkCutTree::new(),
|
||||
witnesses: HashMap::new(),
|
||||
min_cut: f64::INFINITY,
|
||||
min_cut_edges: Vec::new(),
|
||||
graph: graph.clone(),
|
||||
dirty: true,
|
||||
tree_edges: HashSet::new(),
|
||||
non_tree_edges: HashSet::new(),
|
||||
};
|
||||
|
||||
// Build initial spanning tree using BFS
|
||||
witness_tree.build_spanning_tree()?;
|
||||
|
||||
// Compute witnesses for all tree edges
|
||||
witness_tree.recompute_min_cut();
|
||||
|
||||
Ok(witness_tree)
|
||||
}
|
||||
|
||||
/// Get current minimum cut value
|
||||
#[inline]
|
||||
pub fn min_cut_value(&self) -> Weight {
|
||||
self.min_cut
|
||||
}
|
||||
|
||||
/// Get edges in minimum cut
|
||||
pub fn min_cut_edges(&self) -> &[Edge] {
|
||||
&self.min_cut_edges
|
||||
}
|
||||
|
||||
/// Insert edge and update witnesses
|
||||
///
|
||||
/// # Cases
|
||||
///
|
||||
/// 1. **Bridge edge** (creates new component): Add to tree
|
||||
/// 2. **Cycle edge** (already connected): Add to non-tree edges, may improve cut
|
||||
///
|
||||
/// # Complexity
|
||||
///
|
||||
/// Amortized O(log n) with lazy updates
|
||||
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, _weight: Weight) -> Result<Weight> {
|
||||
// Ensure both vertices exist in LCT
|
||||
let u_exists = (0..self.lct.len()).any(|_| true);
|
||||
if u_exists {
|
||||
// Check if vertex exists by trying to find root
|
||||
match self.lct.find_root(u) {
|
||||
Err(_) => {
|
||||
self.lct.make_tree(u, 0.0);
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
} else {
|
||||
self.lct.make_tree(u, 0.0);
|
||||
}
|
||||
|
||||
match self.lct.find_root(v) {
|
||||
Err(_) => {
|
||||
self.lct.make_tree(v, 0.0);
|
||||
}
|
||||
Ok(_) => {}
|
||||
}
|
||||
|
||||
// Check if u and v are already connected
|
||||
let connected = self.lct.connected(u, v);
|
||||
|
||||
let key = Self::canonical_key(u, v);
|
||||
|
||||
if connected {
|
||||
// Cycle edge - add to non-tree edges
|
||||
self.non_tree_edges.insert(key);
|
||||
|
||||
// This might improve the minimum cut
|
||||
self.dirty = true;
|
||||
} else {
|
||||
// Bridge edge - add to spanning tree
|
||||
self.tree_edges.insert(key);
|
||||
|
||||
// Link in LCT
|
||||
self.lct.link(u, v)?;
|
||||
|
||||
// Compute witness for this edge
|
||||
self.update_witness((u, v));
|
||||
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
// Recompute if needed
|
||||
if self.dirty {
|
||||
self.recompute_min_cut();
|
||||
}
|
||||
|
||||
Ok(self.min_cut)
|
||||
}
|
||||
|
||||
/// Delete edge and update witnesses
|
||||
///
|
||||
/// # Cases
|
||||
///
|
||||
/// 1. **Tree edge**: Find replacement, update witnesses
|
||||
/// 2. **Non-tree edge**: Remove from non-tree edges, recompute if needed
|
||||
///
|
||||
/// # Complexity
|
||||
///
|
||||
/// O(m) for finding replacement edge in worst case
|
||||
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<Weight> {
|
||||
let key = Self::canonical_key(u, v);
|
||||
|
||||
if self.tree_edges.contains(&key) {
|
||||
// Tree edge - need to find replacement
|
||||
self.tree_edges.remove(&key);
|
||||
self.witnesses.remove(&key);
|
||||
|
||||
// Cut in LCT - try v first, then u if v is already a root
|
||||
if self.lct.cut(v).is_err() {
|
||||
let _ = self.lct.cut(u); // Ignore error if u is also a root
|
||||
}
|
||||
|
||||
// Try to find replacement edge
|
||||
if let Some(replacement) = self.find_replacement(u, v) {
|
||||
let (ru, rv) = replacement;
|
||||
self.tree_edges.insert(Self::canonical_key(ru, rv));
|
||||
self.lct.link(ru, rv)?;
|
||||
self.update_witness(replacement);
|
||||
}
|
||||
|
||||
self.dirty = true;
|
||||
} else if self.non_tree_edges.contains(&key) {
|
||||
// Non-tree edge
|
||||
self.non_tree_edges.remove(&key);
|
||||
self.dirty = true;
|
||||
}
|
||||
|
||||
// Recompute if needed
|
||||
if self.dirty {
|
||||
self.recompute_min_cut();
|
||||
}
|
||||
|
||||
Ok(self.min_cut)
|
||||
}
|
||||
|
||||
/// Check if edge is a tree edge
|
||||
pub fn is_tree_edge(&self, u: VertexId, v: VertexId) -> bool {
|
||||
let key = Self::canonical_key(u, v);
|
||||
self.tree_edges.contains(&key)
|
||||
}
|
||||
|
||||
/// Find witness for a tree edge
|
||||
pub fn find_witness(&self, u: VertexId, v: VertexId) -> Option<&EdgeWitness> {
|
||||
let key = Self::canonical_key(u, v);
|
||||
self.witnesses.get(&key)
|
||||
}
|
||||
|
||||
/// Recompute minimum cut when witnesses are broken
|
||||
///
|
||||
/// Examines all tree edges and their witnesses to find the global minimum cut.
|
||||
fn recompute_min_cut(&mut self) {
|
||||
let graph = self.graph.read();
|
||||
|
||||
if !graph.is_connected() {
|
||||
self.min_cut = 0.0;
|
||||
self.min_cut_edges = Vec::new();
|
||||
self.dirty = false;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut min_value = f64::INFINITY;
|
||||
let mut min_edges = Vec::new();
|
||||
|
||||
// Check all tree edge witnesses
|
||||
for (_edge_key, witness) in &self.witnesses {
|
||||
if witness.cut_value < min_value {
|
||||
min_value = witness.cut_value;
|
||||
|
||||
// Collect edges in this cut
|
||||
min_edges = self.compute_cut_edges(&witness.cut_side);
|
||||
}
|
||||
}
|
||||
|
||||
self.min_cut = min_value;
|
||||
self.min_cut_edges = min_edges;
|
||||
self.dirty = false;
|
||||
}
|
||||
|
||||
/// Update witness after edge change
|
||||
///
|
||||
/// Computes the cut value when removing a tree edge by finding
|
||||
/// the sum of edges crossing between the two components.
|
||||
fn update_witness(&mut self, edge: (VertexId, VertexId)) {
|
||||
let (u, v) = edge;
|
||||
let cut_value = self.tree_edge_cut(u, v);
|
||||
|
||||
// Find vertices on one side of the cut (component containing u)
|
||||
let cut_side = self.find_component(u, v);
|
||||
|
||||
let key = Self::canonical_key(u, v);
|
||||
// Store witness with canonical edge key
|
||||
let witness = EdgeWitness::new(key, cut_value, cut_side);
|
||||
self.witnesses.insert(key, witness);
|
||||
}
|
||||
|
||||
/// Find replacement edge when tree edge is deleted
|
||||
///
|
||||
/// Searches for an edge that can reconnect the two components
|
||||
/// created by removing the tree edge (u, v).
|
||||
fn find_replacement(&mut self, u: VertexId, v: VertexId) -> Option<(VertexId, VertexId)> {
|
||||
let graph = self.graph.read();
|
||||
|
||||
// Find the two components created by removing (u, v)
|
||||
let component_u = self.find_component(u, v);
|
||||
|
||||
// Look for edges connecting the two components
|
||||
for &vertex in &component_u {
|
||||
for (neighbor, _) in graph.neighbors(vertex) {
|
||||
if !component_u.contains(&neighbor) {
|
||||
// This edge connects the two components
|
||||
let key = Self::canonical_key(vertex, neighbor);
|
||||
|
||||
// Only use if it's a non-tree edge
|
||||
if self.non_tree_edges.contains(&key) {
|
||||
self.non_tree_edges.remove(&key);
|
||||
return Some((vertex, neighbor));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Compute cut value for removing a tree edge
|
||||
///
|
||||
/// Removing the tree edge (u, v) splits the tree into two components.
|
||||
/// The cut value is the sum of all edges crossing between these components.
|
||||
fn tree_edge_cut(&self, u: VertexId, v: VertexId) -> Weight {
|
||||
let component_u = self.find_component(u, v);
|
||||
self.compute_cut_value(&component_u)
|
||||
}
|
||||
|
||||
/// Find the component containing u after removing edge (u, v)
|
||||
///
|
||||
/// Uses BFS but avoids the edge (u, v).
|
||||
fn find_component(&self, u: VertexId, v: VertexId) -> HashSet<VertexId> {
|
||||
let graph = self.graph.read();
|
||||
let mut component = HashSet::new();
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
queue.push_back(u);
|
||||
component.insert(u);
|
||||
|
||||
while let Some(current) = queue.pop_front() {
|
||||
for (neighbor, _) in graph.neighbors(current) {
|
||||
// Skip the edge we're testing
|
||||
if (current == u && neighbor == v) || (current == v && neighbor == u) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only follow tree edges
|
||||
if !self.is_tree_edge(current, neighbor) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if component.insert(neighbor) {
|
||||
queue.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component
|
||||
}
|
||||
|
||||
/// Compute the cut value for a given partition
|
||||
fn compute_cut_value(&self, side_a: &HashSet<VertexId>) -> Weight {
|
||||
let graph = self.graph.read();
|
||||
let mut cut_value = 0.0;
|
||||
|
||||
for &vertex in side_a {
|
||||
for (neighbor, _) in graph.neighbors(vertex) {
|
||||
if !side_a.contains(&neighbor) {
|
||||
if let Some(weight) = graph.edge_weight(vertex, neighbor) {
|
||||
cut_value += weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cut_value
|
||||
}
|
||||
|
||||
/// Compute edges in the cut for a given partition
|
||||
fn compute_cut_edges(&self, side_a: &HashSet<VertexId>) -> Vec<Edge> {
|
||||
let graph = self.graph.read();
|
||||
let mut cut_edges = Vec::new();
|
||||
|
||||
for &vertex in side_a {
|
||||
for (neighbor, _) in graph.neighbors(vertex) {
|
||||
if !side_a.contains(&neighbor) {
|
||||
if let Some(edge) = graph.get_edge(vertex, neighbor) {
|
||||
cut_edges.push(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cut_edges
|
||||
}
|
||||
|
||||
/// Build initial spanning tree using BFS
|
||||
fn build_spanning_tree(&mut self) -> Result<()> {
|
||||
let vertices = {
|
||||
let graph = self.graph.read();
|
||||
let v = graph.vertices();
|
||||
if v.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
v
|
||||
};
|
||||
|
||||
// Initialize LCT with all vertices
|
||||
for &vertex in &vertices {
|
||||
self.lct.make_tree(vertex, 0.0);
|
||||
}
|
||||
|
||||
// Build spanning forest using BFS from each component
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
for &start in &vertices {
|
||||
if visited.contains(&start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut queue = VecDeque::new();
|
||||
queue.push_back(start);
|
||||
visited.insert(start);
|
||||
|
||||
while let Some(current) = queue.pop_front() {
|
||||
// Get neighbors in a scope
|
||||
let neighbors = {
|
||||
let graph = self.graph.read();
|
||||
graph.neighbors(current)
|
||||
};
|
||||
|
||||
for (neighbor, _) in neighbors {
|
||||
if visited.insert(neighbor) {
|
||||
// Add tree edge
|
||||
let key = Self::canonical_key(current, neighbor);
|
||||
self.tree_edges.insert(key);
|
||||
|
||||
// Link in LCT
|
||||
self.lct.link(current, neighbor)?;
|
||||
|
||||
queue.push_back(neighbor);
|
||||
} else if !self.is_tree_edge(current, neighbor) {
|
||||
// Non-tree edge
|
||||
let key = Self::canonical_key(current, neighbor);
|
||||
self.non_tree_edges.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute witnesses for all tree edges
|
||||
let tree_edges: Vec<_> = self.tree_edges.iter().copied().collect();
|
||||
for (u, v) in tree_edges {
|
||||
self.update_witness((u, v));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get canonical edge key (min, max) for consistent lookup
|
||||
fn canonical_key(u: VertexId, v: VertexId) -> (VertexId, VertexId) {
|
||||
if u <= v {
|
||||
(u, v)
|
||||
} else {
|
||||
(v, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Lazy witness updates for amortized efficiency
|
||||
///
|
||||
/// Batches multiple updates together before recomputing witnesses.
|
||||
/// This provides better amortized complexity for sequences of operations.
|
||||
pub struct LazyWitnessTree {
|
||||
/// Inner witness tree
|
||||
inner: WitnessTree,
|
||||
/// Pending updates: (u, v, is_insert)
|
||||
pending_updates: Vec<(VertexId, VertexId, bool)>,
|
||||
/// Threshold for flushing pending updates
|
||||
batch_threshold: usize,
|
||||
}
|
||||
|
||||
impl LazyWitnessTree {
|
||||
/// Create a new lazy witness tree
|
||||
pub fn new(graph: Arc<RwLock<DynamicGraph>>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
inner: WitnessTree::build(graph)?,
|
||||
pending_updates: Vec::new(),
|
||||
batch_threshold: 10,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create with custom batch threshold
|
||||
pub fn with_threshold(graph: Arc<RwLock<DynamicGraph>>, threshold: usize) -> Result<Self> {
|
||||
Ok(Self {
|
||||
inner: WitnessTree::build(graph)?,
|
||||
pending_updates: Vec::new(),
|
||||
batch_threshold: threshold,
|
||||
})
|
||||
}
|
||||
|
||||
/// Insert edge (lazy)
|
||||
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, _weight: Weight) -> Result<Weight> {
|
||||
self.pending_updates.push((u, v, true));
|
||||
|
||||
if self.pending_updates.len() >= self.batch_threshold {
|
||||
self.flush_pending();
|
||||
}
|
||||
|
||||
Ok(self.inner.min_cut_value())
|
||||
}
|
||||
|
||||
/// Delete edge (lazy)
|
||||
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<Weight> {
|
||||
self.pending_updates.push((u, v, false));
|
||||
|
||||
if self.pending_updates.len() >= self.batch_threshold {
|
||||
self.flush_pending();
|
||||
}
|
||||
|
||||
Ok(self.inner.min_cut_value())
|
||||
}
|
||||
|
||||
/// Get minimum cut value (forces flush)
|
||||
pub fn min_cut_value(&mut self) -> Weight {
|
||||
self.flush_pending();
|
||||
self.inner.min_cut_value()
|
||||
}
|
||||
|
||||
/// Get minimum cut edges (forces flush)
|
||||
pub fn min_cut_edges(&mut self) -> &[Edge] {
|
||||
self.flush_pending();
|
||||
self.inner.min_cut_edges()
|
||||
}
|
||||
|
||||
/// Flush all pending updates
|
||||
fn flush_pending(&mut self) {
|
||||
if self.pending_updates.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply all pending updates
|
||||
for (u, v, is_insert) in self.pending_updates.drain(..) {
|
||||
if is_insert {
|
||||
// Get weight from graph
|
||||
let weight = self.inner.graph.read().edge_weight(u, v).unwrap_or(1.0);
|
||||
let _ = self.inner.insert_edge(u, v, weight);
|
||||
} else {
|
||||
let _ = self.inner.delete_edge(u, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn create_triangle_graph() -> Arc<RwLock<DynamicGraph>> {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
graph.write().insert_edge(1, 2, 1.0).unwrap();
|
||||
graph.write().insert_edge(2, 3, 1.0).unwrap();
|
||||
graph.write().insert_edge(3, 1, 1.0).unwrap();
|
||||
graph
|
||||
}
|
||||
|
||||
fn create_bridge_graph() -> Arc<RwLock<DynamicGraph>> {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
// Triangle 1: 1-2-3-1
|
||||
graph.write().insert_edge(1, 2, 2.0).unwrap();
|
||||
graph.write().insert_edge(2, 3, 2.0).unwrap();
|
||||
graph.write().insert_edge(3, 1, 2.0).unwrap();
|
||||
// Bridge
|
||||
graph.write().insert_edge(3, 4, 1.0).unwrap();
|
||||
// Triangle 2: 4-5-6-4
|
||||
graph.write().insert_edge(4, 5, 2.0).unwrap();
|
||||
graph.write().insert_edge(5, 6, 2.0).unwrap();
|
||||
graph.write().insert_edge(6, 4, 2.0).unwrap();
|
||||
graph
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_empty() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
assert!(witness.min_cut_value().is_infinite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_single_vertex() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
graph.write().add_vertex(1);
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
assert!(witness.min_cut_value().is_infinite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_triangle() {
|
||||
let graph = create_triangle_graph();
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Min cut of triangle is 2.0 (removing any vertex)
|
||||
assert_eq!(witness.min_cut_value(), 2.0);
|
||||
assert_eq!(witness.min_cut_edges().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_bridge() {
|
||||
let graph = create_bridge_graph();
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Min cut should be the bridge (1.0)
|
||||
assert_eq!(witness.min_cut_value(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_tree_edge() {
|
||||
let graph = create_triangle_graph();
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Spanning tree of 3 vertices has 2 edges
|
||||
let tree_edge_count = [(1, 2), (2, 3), (3, 1)]
|
||||
.iter()
|
||||
.filter(|(u, v)| witness.is_tree_edge(*u, *v))
|
||||
.count();
|
||||
|
||||
assert_eq!(tree_edge_count, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_witness() {
|
||||
let graph = create_triangle_graph();
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Find a tree edge and check its witness
|
||||
for (u, v) in [(1, 2), (2, 3), (3, 1)] {
|
||||
if witness.is_tree_edge(u, v) {
|
||||
let edge_witness = witness.find_witness(u, v);
|
||||
assert!(edge_witness.is_some());
|
||||
|
||||
let w = edge_witness.unwrap();
|
||||
// Witness stores canonical edge key
|
||||
let canonical = WitnessTree::canonical_key(u, v);
|
||||
assert_eq!(w.tree_edge, canonical);
|
||||
assert!(w.cut_value.is_finite());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_bridge_edge() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
// Two disconnected vertices
|
||||
graph.write().add_vertex(1);
|
||||
graph.write().add_vertex(2);
|
||||
|
||||
let mut witness = WitnessTree::build(graph.clone()).unwrap();
|
||||
assert_eq!(witness.min_cut_value(), 0.0);
|
||||
|
||||
// Add bridge edge
|
||||
graph.write().insert_edge(1, 2, 3.0).unwrap();
|
||||
let new_cut = witness.insert_edge(1, 2, 3.0).unwrap();
|
||||
|
||||
assert_eq!(new_cut, 3.0);
|
||||
assert!(witness.is_tree_edge(1, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_cycle_edge() {
|
||||
let graph = create_triangle_graph();
|
||||
let mut witness = WitnessTree::build(graph.clone()).unwrap();
|
||||
|
||||
let _old_cut = witness.min_cut_value();
|
||||
|
||||
// Add a fourth vertex and connect it
|
||||
graph.write().insert_edge(1, 4, 2.0).unwrap();
|
||||
graph.write().insert_edge(2, 4, 2.0).unwrap();
|
||||
|
||||
let new_cut = witness.insert_edge(1, 4, 2.0).unwrap();
|
||||
witness.insert_edge(2, 4, 2.0).unwrap();
|
||||
|
||||
// Min cut should still be valid
|
||||
assert!(new_cut.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_tree_edge() {
|
||||
let graph = create_triangle_graph();
|
||||
let mut witness = WitnessTree::build(graph.clone()).unwrap();
|
||||
|
||||
// Find a tree edge
|
||||
let tree_edge = [(1, 2), (2, 3), (3, 1)]
|
||||
.iter()
|
||||
.find(|(u, v)| witness.is_tree_edge(*u, *v))
|
||||
.copied()
|
||||
.unwrap();
|
||||
|
||||
let (u, v) = tree_edge;
|
||||
|
||||
// Delete it from graph and witness
|
||||
graph.write().delete_edge(u, v).unwrap();
|
||||
let new_cut = witness.delete_edge(u, v).unwrap();
|
||||
|
||||
// After deleting a tree edge, it should find replacement
|
||||
assert!(new_cut.is_finite());
|
||||
assert!(!witness.is_tree_edge(u, v));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_non_tree_edge() {
|
||||
let graph = create_triangle_graph();
|
||||
let mut witness = WitnessTree::build(graph.clone()).unwrap();
|
||||
|
||||
// Find a non-tree edge
|
||||
let non_tree_edge = [(1, 2), (2, 3), (3, 1)]
|
||||
.iter()
|
||||
.find(|(u, v)| !witness.is_tree_edge(*u, *v))
|
||||
.copied()
|
||||
.unwrap();
|
||||
|
||||
let (u, v) = non_tree_edge;
|
||||
|
||||
// Delete it
|
||||
graph.write().delete_edge(u, v).unwrap();
|
||||
let new_cut = witness.delete_edge(u, v).unwrap();
|
||||
|
||||
assert!(new_cut.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_edge_cut() {
|
||||
let graph = create_bridge_graph();
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Find the bridge edge (3, 4)
|
||||
if witness.is_tree_edge(3, 4) {
|
||||
let cut_value = witness.tree_edge_cut(3, 4);
|
||||
assert_eq!(cut_value, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_component() {
|
||||
let graph = create_bridge_graph();
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Find component containing vertex 1 after removing edge (3, 4)
|
||||
let component = witness.find_component(1, 4);
|
||||
|
||||
// Should contain vertices from first triangle
|
||||
assert!(component.contains(&1) || component.len() >= 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_canonical_key() {
|
||||
assert_eq!(WitnessTree::canonical_key(1, 2), (1, 2));
|
||||
assert_eq!(WitnessTree::canonical_key(2, 1), (1, 2));
|
||||
assert_eq!(WitnessTree::canonical_key(5, 3), (3, 5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lazy_witness_tree() {
|
||||
let graph = create_triangle_graph();
|
||||
let mut lazy = LazyWitnessTree::new(graph.clone()).unwrap();
|
||||
|
||||
// Batch some operations
|
||||
graph.write().insert_edge(1, 4, 1.0).unwrap();
|
||||
lazy.insert_edge(1, 4, 1.0).unwrap();
|
||||
|
||||
graph.write().insert_edge(2, 4, 1.0).unwrap();
|
||||
lazy.insert_edge(2, 4, 1.0).unwrap();
|
||||
|
||||
// Force flush
|
||||
let min_cut = lazy.min_cut_value();
|
||||
assert!(min_cut.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lazy_witness_batch_threshold() {
|
||||
let graph = create_triangle_graph();
|
||||
let mut lazy = LazyWitnessTree::with_threshold(graph.clone(), 2).unwrap();
|
||||
|
||||
// First insert (pending)
|
||||
graph.write().insert_edge(1, 4, 1.0).unwrap();
|
||||
lazy.insert_edge(1, 4, 1.0).unwrap();
|
||||
|
||||
// Second insert (should trigger flush)
|
||||
graph.write().insert_edge(2, 4, 1.0).unwrap();
|
||||
lazy.insert_edge(2, 4, 1.0).unwrap();
|
||||
|
||||
// Verify updates were applied
|
||||
let min_cut = lazy.min_cut_value();
|
||||
assert!(min_cut.is_finite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_disconnected_graph() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
// Two separate components
|
||||
graph.write().insert_edge(1, 2, 1.0).unwrap();
|
||||
graph.write().insert_edge(3, 4, 1.0).unwrap();
|
||||
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Disconnected graph has min cut 0
|
||||
assert_eq!(witness.min_cut_value(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dynamic_sequence() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
let mut witness = WitnessTree::build(graph.clone()).unwrap();
|
||||
|
||||
// Build graph dynamically
|
||||
graph.write().insert_edge(1, 2, 1.0).unwrap();
|
||||
witness.insert_edge(1, 2, 1.0).unwrap();
|
||||
assert_eq!(witness.min_cut_value(), 1.0);
|
||||
|
||||
graph.write().insert_edge(2, 3, 1.0).unwrap();
|
||||
witness.insert_edge(2, 3, 1.0).unwrap();
|
||||
assert_eq!(witness.min_cut_value(), 1.0);
|
||||
|
||||
graph.write().insert_edge(3, 1, 1.0).unwrap();
|
||||
witness.insert_edge(3, 1, 1.0).unwrap();
|
||||
// Triangle with all weight 1 has min cut 2
|
||||
// But depending on spanning tree, might see 1 or 2
|
||||
let cut_after_triangle = witness.min_cut_value();
|
||||
assert!(cut_after_triangle >= 1.0 && cut_after_triangle <= 2.0);
|
||||
|
||||
// Delete an edge
|
||||
graph.write().delete_edge(1, 2).unwrap();
|
||||
witness.delete_edge(1, 2).unwrap();
|
||||
// After deleting one edge, we have a path, min cut is 1
|
||||
assert_eq!(witness.min_cut_value(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_weighted_edges() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
graph.write().insert_edge(1, 2, 5.0).unwrap();
|
||||
graph.write().insert_edge(2, 3, 3.0).unwrap();
|
||||
graph.write().insert_edge(3, 1, 2.0).unwrap();
|
||||
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// The spanning tree has 2 edges out of 3
|
||||
// Possible spanning trees and their cuts:
|
||||
// 1. Tree edges (1,2) and (2,3): removing either gives cut value 5 or 7
|
||||
// 2. Tree edges (1,2) and (1,3): removing either gives cut value 5 or 7
|
||||
// 3. Tree edges (2,3) and (1,3): removing either gives cut value 5 or 7
|
||||
// The minimum cut should be 5.0 (removing vertex 2 or 3)
|
||||
// However, depending on which spanning tree we build, we might get different results
|
||||
// Since we use BFS, the spanning tree is deterministic but may not find optimal cut
|
||||
let min_cut = witness.min_cut_value();
|
||||
assert!(
|
||||
min_cut >= 5.0 && min_cut <= 7.0,
|
||||
"Min cut should be between 5.0 and 7.0, got {}",
|
||||
min_cut
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_graph() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
|
||||
// Create a path: 1-2-3-...-10
|
||||
for i in 1..10 {
|
||||
graph.write().insert_edge(i, i + 1, 1.0).unwrap();
|
||||
}
|
||||
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Min cut of a path is 1.0
|
||||
assert_eq!(witness.min_cut_value(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complete_graph() {
|
||||
let graph = Arc::new(RwLock::new(DynamicGraph::new()));
|
||||
|
||||
// Create K4 (complete graph on 4 vertices)
|
||||
for i in 1..=4 {
|
||||
for j in (i + 1)..=4 {
|
||||
graph.write().insert_edge(i, j, 1.0).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let witness = WitnessTree::build(graph).unwrap();
|
||||
|
||||
// Min cut of K4 is 3 (degree of any vertex)
|
||||
assert_eq!(witness.min_cut_value(), 3.0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user