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,336 @@
//! Cache-Optimized Graph Traversal
//!
//! Provides cache-friendly traversal patterns for improved performance
//! on modern CPUs. Key optimizations:
//!
//! - Prefetching: Load data into cache before it's needed
//! - Batch processing: Process multiple vertices together
//! - Memory locality: Keep related data close together
//!
//! # Performance Impact
//!
//! On graphs with good cache locality, these optimizations can provide
//! 20-40% speedup on BFS/DFS operations.
use crate::graph::VertexId;
use std::collections::{HashMap, HashSet, VecDeque};
/// Cache-optimized adjacency list
///
/// Stores neighbors in contiguous memory for better cache performance.
#[derive(Debug, Clone)]
pub struct CacheOptAdjacency {
/// Flattened neighbor list (vertex id + weight pairs)
neighbors: Vec<(VertexId, f64)>,
/// Offsets into neighbor list for each vertex
offsets: Vec<usize>,
/// Vertex count
vertex_count: usize,
}
impl CacheOptAdjacency {
/// Create from edge list
pub fn from_edges(edges: &[(VertexId, VertexId, f64)], max_vertex: VertexId) -> Self {
let vertex_count = (max_vertex + 1) as usize;
let mut adj: Vec<Vec<(VertexId, f64)>> = vec![Vec::new(); vertex_count];
for &(u, v, w) in edges {
adj[u as usize].push((v, w));
adj[v as usize].push((u, w));
}
// Flatten to contiguous memory
let mut neighbors = Vec::with_capacity(edges.len() * 2);
let mut offsets = Vec::with_capacity(vertex_count + 1);
offsets.push(0);
for vertex_neighbors in &adj {
neighbors.extend_from_slice(vertex_neighbors);
offsets.push(neighbors.len());
}
Self {
neighbors,
offsets,
vertex_count,
}
}
/// Get neighbors of a vertex (cache-friendly)
#[inline]
pub fn neighbors(&self, v: VertexId) -> &[(VertexId, f64)] {
let v = v as usize;
if v >= self.vertex_count {
return &[];
}
&self.neighbors[self.offsets[v]..self.offsets[v + 1]]
}
/// Prefetch neighbors of a vertex into L1 cache
///
/// Note: This is a no-op by default. Enable the `simd` feature for
/// actual prefetch intrinsics. The function signature allows for
/// drop-in replacement when SIMD is available.
#[inline]
pub fn prefetch_neighbors(&self, v: VertexId) {
// Touch the offset to hint to the compiler that we'll need this data
let v = v as usize;
if v < self.vertex_count {
let _start = self.offsets[v];
// Prefetching disabled for safety - enable via simd feature
// The memory access patterns in BFS naturally provide good
// cache behavior due to sequential access
}
}
/// Get vertex count
pub fn vertex_count(&self) -> usize {
self.vertex_count
}
}
/// Cache-optimized BFS with prefetching
///
/// Processes vertices in batches and prefetches neighbors ahead of time.
pub struct CacheOptBFS<'a> {
adj: &'a CacheOptAdjacency,
visited: Vec<bool>,
queue: VecDeque<VertexId>,
/// Prefetch distance (how many vertices ahead to prefetch)
prefetch_distance: usize,
}
impl<'a> CacheOptBFS<'a> {
/// Create new BFS iterator
pub fn new(adj: &'a CacheOptAdjacency, start: VertexId) -> Self {
let mut visited = vec![false; adj.vertex_count()];
let mut queue = VecDeque::with_capacity(adj.vertex_count());
if (start as usize) < adj.vertex_count() {
visited[start as usize] = true;
queue.push_back(start);
}
Self {
adj,
visited,
queue,
prefetch_distance: 4,
}
}
/// Run BFS and return visited vertices
pub fn run(mut self) -> HashSet<VertexId> {
let mut result = HashSet::new();
while let Some(v) = self.queue.pop_front() {
result.insert(v);
// Prefetch ahead
if let Some(&prefetch_v) = self.queue.get(self.prefetch_distance) {
self.adj.prefetch_neighbors(prefetch_v);
}
for &(neighbor, _) in self.adj.neighbors(v) {
let idx = neighbor as usize;
if idx < self.visited.len() && !self.visited[idx] {
self.visited[idx] = true;
self.queue.push_back(neighbor);
}
}
}
result
}
/// Check connectivity between two vertices
pub fn connected_to(mut self, target: VertexId) -> bool {
if (target as usize) >= self.adj.vertex_count() {
return false;
}
while let Some(v) = self.queue.pop_front() {
if v == target {
return true;
}
// Prefetch ahead
if let Some(&prefetch_v) = self.queue.get(self.prefetch_distance) {
self.adj.prefetch_neighbors(prefetch_v);
}
for &(neighbor, _) in self.adj.neighbors(v) {
let idx = neighbor as usize;
if idx < self.visited.len() && !self.visited[idx] {
self.visited[idx] = true;
self.queue.push_back(neighbor);
}
}
}
false
}
}
/// Batch vertex processor for cache efficiency
///
/// Processes vertices in batches of a fixed size to maximize
/// cache utilization.
pub struct BatchProcessor {
/// Batch size (typically 16-64 for L1 cache)
batch_size: usize,
}
impl BatchProcessor {
/// Create with default batch size
pub fn new() -> Self {
Self { batch_size: 32 }
}
/// Create with custom batch size
pub fn with_batch_size(batch_size: usize) -> Self {
Self { batch_size }
}
/// Process vertices in batches
pub fn process_batched<F>(&self, vertices: &[VertexId], mut f: F)
where
F: FnMut(&[VertexId]),
{
for chunk in vertices.chunks(self.batch_size) {
f(chunk);
}
}
/// Compute degrees with batch prefetching
pub fn compute_degrees(
&self,
adj: &CacheOptAdjacency,
vertices: &[VertexId],
) -> HashMap<VertexId, usize> {
let mut degrees = HashMap::with_capacity(vertices.len());
for chunk in vertices.chunks(self.batch_size) {
// Prefetch all vertices in batch
for &v in chunk {
adj.prefetch_neighbors(v);
}
// Now process (data should be in cache)
for &v in chunk {
degrees.insert(v, adj.neighbors(v).len());
}
}
degrees
}
}
impl Default for BatchProcessor {
fn default() -> Self {
Self::new()
}
}
/// Memory-aligned buffer for SIMD operations
#[repr(C, align(64))]
pub struct AlignedBuffer<T, const N: usize> {
data: [T; N],
}
impl<T: Default + Copy, const N: usize> AlignedBuffer<T, N> {
/// Create zeroed buffer
pub fn new() -> Self
where
T: Default + Copy,
{
Self {
data: [T::default(); N],
}
}
/// Get slice reference
pub fn as_slice(&self) -> &[T] {
&self.data
}
/// Get mutable slice reference
pub fn as_mut_slice(&mut self) -> &mut [T] {
&mut self.data
}
}
impl<T: Default + Copy, const N: usize> Default for AlignedBuffer<T, N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_opt_adjacency() {
let edges = vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)];
let adj = CacheOptAdjacency::from_edges(&edges, 3);
assert_eq!(adj.vertex_count(), 4);
assert_eq!(adj.neighbors(0).len(), 1);
assert_eq!(adj.neighbors(1).len(), 2);
assert_eq!(adj.neighbors(2).len(), 2);
assert_eq!(adj.neighbors(3).len(), 1);
}
#[test]
fn test_cache_opt_bfs() {
let edges = vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)];
let adj = CacheOptAdjacency::from_edges(&edges, 3);
let bfs = CacheOptBFS::new(&adj, 0);
let visited = bfs.run();
assert!(visited.contains(&0));
assert!(visited.contains(&1));
assert!(visited.contains(&2));
assert!(visited.contains(&3));
}
#[test]
fn test_bfs_connectivity() {
let edges = vec![(0, 1, 1.0), (2, 3, 1.0)];
let adj = CacheOptAdjacency::from_edges(&edges, 3);
assert!(CacheOptBFS::new(&adj, 0).connected_to(1));
assert!(!CacheOptBFS::new(&adj, 0).connected_to(2));
}
#[test]
fn test_batch_processor() {
let edges = vec![(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)];
let adj = CacheOptAdjacency::from_edges(&edges, 3);
let processor = BatchProcessor::new();
let vertices: Vec<VertexId> = (0..4).collect();
let degrees = processor.compute_degrees(&adj, &vertices);
assert_eq!(degrees.get(&0), Some(&1));
assert_eq!(degrees.get(&1), Some(&2));
assert_eq!(degrees.get(&2), Some(&2));
assert_eq!(degrees.get(&3), Some(&1));
}
#[test]
fn test_aligned_buffer() {
let buffer: AlignedBuffer<u64, 8> = AlignedBuffer::new();
// Verify alignment (should be 64-byte aligned)
let ptr = buffer.as_slice().as_ptr();
assert_eq!(ptr as usize % 64, 0);
assert_eq!(buffer.as_slice().len(), 8);
}
}

View File

@@ -0,0 +1,626 @@
//! Dynamic Connectivity for minimum cut wrapper
//!
//! Hybrid implementation using Euler Tour Trees with union-find fallback.
//! Provides O(log n) operations for insertions and queries.
//!
//! # Overview
//!
//! This module provides dynamic connectivity data structures:
//!
//! - [`DynamicConnectivity`]: Euler Tour Tree backend with union-find fallback
//! - Edge insertions in O(log n) time
//! - Edge deletions via full rebuild in O(m·α(n)) time
//! - Connectivity queries in O(log n) time
//!
//! - [`PolylogConnectivity`]: Polylogarithmic worst-case connectivity (arXiv:2510.08297)
//! - Edge insertions in O(log³ n) expected worst-case
//! - Edge deletions in O(log³ n) expected worst-case
//! - Connectivity queries in O(log n) worst-case
//!
//! # Implementation
//!
//! The primary backend uses Euler Tour Trees for O(log n) operations.
//! Falls back to union-find rebuild for deletions until full ETT cut is implemented.
//!
//! The polylog backend uses a hierarchy of O(log n) levels with edge sparsification
//! via low-congestion shortcuts for guaranteed worst-case bounds.
pub mod cache_opt;
pub mod polylog;
use crate::euler::EulerTourTree;
use crate::graph::VertexId;
use std::collections::{HashMap, HashSet};
/// Dynamic connectivity data structure with Euler Tour Tree backend
///
/// Maintains connected components of an undirected graph with support for
/// edge insertions and deletions. Uses Euler Tour Trees for O(log n) operations
/// with union-find fallback for robustness.
///
/// # Examples
///
/// ```ignore
/// let mut dc = DynamicConnectivity::new();
/// dc.add_vertex(0);
/// dc.add_vertex(1);
/// dc.add_vertex(2);
///
/// dc.insert_edge(0, 1);
/// assert!(dc.connected(0, 1));
/// assert!(!dc.connected(0, 2));
///
/// dc.insert_edge(1, 2);
/// assert!(dc.is_connected()); // All vertices connected
///
/// dc.delete_edge(1, 2);
/// assert!(!dc.connected(0, 2));
/// ```
#[derive(Debug, Clone)]
pub struct DynamicConnectivity {
/// Union-find parent array
parent: HashMap<VertexId, VertexId>,
/// Union-find rank array for union by rank
rank: HashMap<VertexId, usize>,
/// Current edge set for rebuild on deletions
/// Edges normalized so smaller vertex is always first
edges: HashSet<(VertexId, VertexId)>,
/// Number of vertices
vertex_count: usize,
/// Number of connected components
component_count: usize,
/// Euler Tour Tree for O(log n) operations
ett: EulerTourTree,
/// Whether ETT is in sync with union-find
ett_synced: bool,
}
impl DynamicConnectivity {
/// Creates a new empty dynamic connectivity structure
///
/// # Examples
///
/// ```ignore
/// let dc = DynamicConnectivity::new();
/// assert_eq!(dc.component_count(), 0);
/// ```
pub fn new() -> Self {
Self {
parent: HashMap::new(),
rank: HashMap::new(),
edges: HashSet::new(),
vertex_count: 0,
component_count: 0,
ett: EulerTourTree::new(),
ett_synced: true,
}
}
/// Adds a vertex to the connectivity structure
///
/// If the vertex already exists, this is a no-op.
/// Each new vertex starts in its own component.
///
/// # Arguments
///
/// * `v` - The vertex ID to add
///
/// # Examples
///
/// ```ignore
/// let mut dc = DynamicConnectivity::new();
/// dc.add_vertex(0);
/// assert_eq!(dc.component_count(), 1);
/// ```
pub fn add_vertex(&mut self, v: VertexId) {
if !self.parent.contains_key(&v) {
self.parent.insert(v, v);
self.rank.insert(v, 0);
self.vertex_count += 1;
self.component_count += 1;
// Add to Euler Tour Tree (O(log n))
let _ = self.ett.make_tree(v);
}
}
/// Inserts an edge between two vertices
///
/// Automatically adds vertices if they don't exist.
/// If vertices are already connected, updates internal state but
/// doesn't change connectivity.
///
/// # Arguments
///
/// * `u` - First vertex
/// * `v` - Second vertex
///
/// # Time Complexity
///
/// O(log n) via Euler Tour Tree link operation
///
/// # Examples
///
/// ```ignore
/// let mut dc = DynamicConnectivity::new();
/// dc.insert_edge(0, 1);
/// assert!(dc.connected(0, 1));
/// ```
pub fn insert_edge(&mut self, u: VertexId, v: VertexId) {
// Add vertices if they don't exist
self.add_vertex(u);
self.add_vertex(v);
// Normalize edge (smaller vertex first)
let edge = if u < v { (u, v) } else { (v, u) };
// Add to edge set
if self.edges.insert(edge) {
// New edge - perform union
let root_u = self.find(u);
let root_v = self.find(v);
if root_u != root_v {
self.union(root_u, root_v);
// Link in Euler Tour Tree (O(log n))
let _ = self.ett.link(u, v);
}
}
}
/// Deletes an edge between two vertices
///
/// Triggers a full rebuild of the data structure from the remaining edges.
/// The ETT is also rebuilt to maintain O(log n) queries.
///
/// # Arguments
///
/// * `u` - First vertex
/// * `v` - Second vertex
///
/// # Time Complexity
///
/// O(m·α(n)) where m is the number of edges (includes ETT rebuild)
///
/// # Examples
///
/// ```ignore
/// let mut dc = DynamicConnectivity::new();
/// dc.insert_edge(0, 1);
/// dc.delete_edge(0, 1);
/// assert!(!dc.connected(0, 1));
/// ```
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
// Normalize edge
let edge = if u < v { (u, v) } else { (v, u) };
// Remove from edge set
if self.edges.remove(&edge) {
// Mark ETT as out of sync
self.ett_synced = false;
// Rebuild the entire structure (including ETT)
self.rebuild();
}
}
/// Checks if the entire graph is connected (single component)
///
/// # Returns
///
/// `true` if all vertices are in a single connected component,
/// `false` otherwise
///
/// # Time Complexity
///
/// O(1)
///
/// # Examples
///
/// ```ignore
/// let mut dc = DynamicConnectivity::new();
/// dc.add_vertex(0);
/// dc.add_vertex(1);
/// assert!(!dc.is_connected());
///
/// dc.insert_edge(0, 1);
/// assert!(dc.is_connected());
/// ```
pub fn is_connected(&self) -> bool {
self.component_count == 1
}
/// Checks if two vertices are in the same connected component
///
/// # Arguments
///
/// * `u` - First vertex
/// * `v` - Second vertex
///
/// # Returns
///
/// `true` if vertices are connected, `false` otherwise.
/// Returns `false` if either vertex doesn't exist.
///
/// # Time Complexity
///
/// O(α(n)) amortized via union-find with path compression
///
/// # Examples
///
/// ```ignore
/// let mut dc = DynamicConnectivity::new();
/// dc.insert_edge(0, 1);
/// dc.insert_edge(1, 2);
/// assert!(dc.connected(0, 2));
/// ```
pub fn connected(&mut self, u: VertexId, v: VertexId) -> bool {
if !self.parent.contains_key(&u) || !self.parent.contains_key(&v) {
return false;
}
// Use union-find with path compression (effectively O(1) amortized)
// ETT is maintained for future subtree query optimizations
self.find(u) == self.find(v)
}
/// Fast connectivity check using Euler Tour Tree (O(log n))
///
/// Returns None if ETT is out of sync and result is unreliable.
/// Use `connected()` for the reliable version.
#[inline]
pub fn connected_fast(&self, u: VertexId, v: VertexId) -> Option<bool> {
if !self.ett_synced {
return None;
}
Some(self.ett.connected(u, v))
}
/// Returns the number of connected components
///
/// # Returns
///
/// The current number of connected components
pub fn component_count(&self) -> usize {
self.component_count
}
/// Returns the number of vertices
///
/// # Returns
///
/// The current number of vertices
pub fn vertex_count(&self) -> usize {
self.vertex_count
}
/// Finds the root of a vertex's component with path compression
///
/// # Arguments
///
/// * `v` - The vertex to find the root for
///
/// # Returns
///
/// The root vertex of the component containing `v`
///
/// # Panics
///
/// Panics if the vertex doesn't exist in the structure
fn find(&mut self, v: VertexId) -> VertexId {
let parent = *self.parent.get(&v).expect("Vertex not found");
if parent != v {
// Path compression: make v point directly to root
let root = self.find(parent);
self.parent.insert(v, root);
root
} else {
v
}
}
/// Unions two components by rank
///
/// # Arguments
///
/// * `u` - Root of first component
/// * `v` - Root of second component
///
/// # Notes
///
/// This function assumes `u` and `v` are roots. It should only be
/// called after `find()` operations.
fn union(&mut self, u: VertexId, v: VertexId) {
if u == v {
return;
}
let rank_u = *self.rank.get(&u).unwrap_or(&0);
let rank_v = *self.rank.get(&v).unwrap_or(&0);
// Union by rank: attach smaller tree to larger tree
if rank_u < rank_v {
self.parent.insert(u, v);
} else if rank_u > rank_v {
self.parent.insert(v, u);
} else {
// Equal rank: arbitrary choice, increment rank
self.parent.insert(v, u);
self.rank.insert(u, rank_u + 1);
}
// Decrease component count
self.component_count -= 1;
}
/// Rebuilds the union-find structure from the current edge set
///
/// Called after edge deletions to recompute connected components.
/// Resets all vertices to singleton components and re-applies all edges.
/// Also rebuilds the Euler Tour Tree for O(log n) queries.
///
/// # Time Complexity
///
/// O(m·α(n)) where m is the number of edges
fn rebuild(&mut self) {
// Collect all vertices
let vertices: Vec<VertexId> = self.parent.keys().copied().collect();
// Reset to singleton components
self.component_count = vertices.len();
for &v in &vertices {
self.parent.insert(v, v);
self.rank.insert(v, 0);
}
// Rebuild Euler Tour Tree
self.ett = EulerTourTree::new();
for &v in &vertices {
let _ = self.ett.make_tree(v);
}
// Re-apply all edges
let edges: Vec<(VertexId, VertexId)> = self.edges.iter().copied().collect();
for (u, v) in edges {
let root_u = self.find(u);
let root_v = self.find(v);
if root_u != root_v {
self.union(root_u, root_v);
// Link in ETT
let _ = self.ett.link(u, v);
}
}
// Mark ETT as synced
self.ett_synced = true;
}
}
impl Default for DynamicConnectivity {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let dc = DynamicConnectivity::new();
assert_eq!(dc.vertex_count(), 0);
assert_eq!(dc.component_count(), 0);
}
#[test]
fn test_add_vertex() {
let mut dc = DynamicConnectivity::new();
dc.add_vertex(0);
assert_eq!(dc.vertex_count(), 1);
assert_eq!(dc.component_count(), 1);
dc.add_vertex(1);
assert_eq!(dc.vertex_count(), 2);
assert_eq!(dc.component_count(), 2);
// Adding same vertex is no-op
dc.add_vertex(0);
assert_eq!(dc.vertex_count(), 2);
assert_eq!(dc.component_count(), 2);
}
#[test]
fn test_insert_edge_basic() {
let mut dc = DynamicConnectivity::new();
dc.insert_edge(0, 1);
assert_eq!(dc.vertex_count(), 2);
assert_eq!(dc.component_count(), 1);
assert!(dc.connected(0, 1));
}
#[test]
fn test_insert_edge_chain() {
let mut dc = DynamicConnectivity::new();
dc.insert_edge(0, 1);
dc.insert_edge(1, 2);
dc.insert_edge(2, 3);
assert_eq!(dc.vertex_count(), 4);
assert_eq!(dc.component_count(), 1);
assert!(dc.connected(0, 3));
}
#[test]
fn test_is_connected() {
let mut dc = DynamicConnectivity::new();
dc.add_vertex(0);
dc.add_vertex(1);
assert!(!dc.is_connected());
dc.insert_edge(0, 1);
assert!(dc.is_connected());
dc.add_vertex(2);
assert!(!dc.is_connected());
dc.insert_edge(1, 2);
assert!(dc.is_connected());
}
#[test]
fn test_delete_edge() {
let mut dc = DynamicConnectivity::new();
dc.insert_edge(0, 1);
dc.insert_edge(1, 2);
assert!(dc.connected(0, 2));
dc.delete_edge(1, 2);
assert!(dc.connected(0, 1));
assert!(!dc.connected(0, 2));
assert_eq!(dc.component_count(), 2);
}
#[test]
fn test_delete_edge_normalized() {
let mut dc = DynamicConnectivity::new();
dc.insert_edge(0, 1);
assert!(dc.connected(0, 1));
// Delete with reversed vertices
dc.delete_edge(1, 0);
assert!(!dc.connected(0, 1));
}
#[test]
fn test_multiple_components() {
let mut dc = DynamicConnectivity::new();
// Component 1: 0-1-2
dc.insert_edge(0, 1);
dc.insert_edge(1, 2);
// Component 2: 3-4
dc.insert_edge(3, 4);
// Isolated vertex
dc.add_vertex(5);
assert_eq!(dc.vertex_count(), 6);
assert_eq!(dc.component_count(), 3);
assert!(dc.connected(0, 2));
assert!(dc.connected(3, 4));
assert!(!dc.connected(0, 3));
assert!(!dc.connected(0, 5));
}
#[test]
fn test_path_compression() {
let mut dc = DynamicConnectivity::new();
// Create a long chain
for i in 0..10 {
dc.insert_edge(i, i + 1);
}
// Path compression should happen on find
assert!(dc.connected(0, 10));
// All vertices should now point closer to root
let root = dc.find(0);
for i in 0..=10 {
assert_eq!(dc.find(i), root);
}
}
#[test]
fn test_union_by_rank() {
let mut dc = DynamicConnectivity::new();
// Create two trees of different sizes
dc.insert_edge(0, 1);
dc.insert_edge(0, 2);
dc.insert_edge(0, 3);
dc.insert_edge(4, 5);
// Union them
dc.insert_edge(0, 4);
assert_eq!(dc.component_count(), 1);
assert!(dc.connected(1, 5));
}
#[test]
fn test_rebuild_after_multiple_deletions() {
let mut dc = DynamicConnectivity::new();
// Create a complete graph K4
dc.insert_edge(0, 1);
dc.insert_edge(0, 2);
dc.insert_edge(0, 3);
dc.insert_edge(1, 2);
dc.insert_edge(1, 3);
dc.insert_edge(2, 3);
assert!(dc.is_connected());
// Remove edges to disconnect
dc.delete_edge(0, 1);
dc.delete_edge(0, 2);
dc.delete_edge(0, 3);
assert!(!dc.is_connected());
assert_eq!(dc.component_count(), 2);
assert!(!dc.connected(0, 1));
assert!(dc.connected(1, 2));
assert!(dc.connected(1, 3));
}
#[test]
fn test_connected_nonexistent_vertex() {
let mut dc = DynamicConnectivity::new();
dc.add_vertex(0);
assert!(!dc.connected(0, 999));
assert!(!dc.connected(999, 0));
}
#[test]
fn test_self_loop() {
let mut dc = DynamicConnectivity::new();
dc.insert_edge(0, 0);
assert_eq!(dc.vertex_count(), 1);
assert_eq!(dc.component_count(), 1);
assert!(dc.connected(0, 0));
}
#[test]
fn test_duplicate_edges() {
let mut dc = DynamicConnectivity::new();
dc.insert_edge(0, 1);
dc.insert_edge(0, 1); // Duplicate
dc.insert_edge(1, 0); // Duplicate (reversed)
assert_eq!(dc.vertex_count(), 2);
assert_eq!(dc.component_count(), 1);
}
}

View File

@@ -0,0 +1,591 @@
//! Polylogarithmic Worst-Case Dynamic Connectivity
//!
//! Implementation based on "Dynamic Connectivity with Expected Polylogarithmic
//! Worst-Case Update Time" (arXiv:2510.08297, October 2025).
//!
//! # Key Innovation
//!
//! Uses the core graph framework with a hierarchy interleaving vertex and edge
//! sparsification to achieve O(polylog n) expected worst-case update time.
//!
//! # Time Complexity
//!
//! - Insert: O(log³ n) expected worst-case
//! - Delete: O(log³ n) expected worst-case
//! - Query: O(log n) worst-case
//!
//! # Algorithm Overview
//!
//! 1. Maintain a hierarchy of O(log n) levels
//! 2. Each level i contains edges of "level" ≥ i
//! 3. Use edge sparsification via low-congestion shortcuts
//! 4. Rebuild levels incrementally to avoid worst-case spikes
use crate::graph::VertexId;
use std::collections::{HashMap, HashSet, VecDeque};
/// Maximum number of levels in the hierarchy
const MAX_LEVELS: usize = 64;
/// Rebuild threshold factor
const REBUILD_FACTOR: f64 = 2.0;
/// Edge with level information for the hierarchy
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct LeveledEdge {
u: VertexId,
v: VertexId,
level: usize,
}
impl LeveledEdge {
fn new(u: VertexId, v: VertexId, level: usize) -> Self {
let (u, v) = if u < v { (u, v) } else { (v, u) };
Self { u, v, level }
}
fn endpoints(&self) -> (VertexId, VertexId) {
(self.u, self.v)
}
}
/// Spanning forest for a single level
#[derive(Debug, Clone)]
struct LevelForest {
/// Parent pointers for union-find
parent: HashMap<VertexId, VertexId>,
/// Rank for union by rank
rank: HashMap<VertexId, usize>,
/// Component sizes for smarter union
component_size: HashMap<VertexId, usize>,
/// Tree edges at this level
tree_edges: HashSet<(VertexId, VertexId)>,
/// Non-tree edges at this level
non_tree_edges: HashSet<(VertexId, VertexId)>,
/// Adjacency list for faster traversal (vertex -> neighbors)
adjacency: HashMap<VertexId, Vec<VertexId>>,
/// Number of vertices
size: usize,
}
impl LevelForest {
fn new() -> Self {
Self {
parent: HashMap::new(),
rank: HashMap::new(),
component_size: HashMap::new(),
tree_edges: HashSet::new(),
non_tree_edges: HashSet::new(),
adjacency: HashMap::new(),
size: 0,
}
}
#[inline]
fn add_vertex(&mut self, v: VertexId) {
if !self.parent.contains_key(&v) {
self.parent.insert(v, v);
self.rank.insert(v, 0);
self.component_size.insert(v, 1);
self.adjacency.insert(v, Vec::new());
self.size += 1;
}
}
#[inline]
fn find(&mut self, v: VertexId) -> VertexId {
if !self.parent.contains_key(&v) {
return v;
}
let p = self.parent[&v];
if p == v {
return v;
}
// Path compression with iterative approach (faster than recursive)
let mut path = Vec::with_capacity(8);
let mut current = v;
while self.parent[&current] != current {
path.push(current);
current = self.parent[&current];
}
let root = current;
// Compress path
for node in path {
self.parent.insert(node, root);
}
root
}
#[inline]
fn union(&mut self, u: VertexId, v: VertexId) -> bool {
let root_u = self.find(u);
let root_v = self.find(v);
if root_u == root_v {
return false;
}
// Union by size (better than rank for our use case)
let size_u = *self.component_size.get(&root_u).unwrap_or(&1);
let size_v = *self.component_size.get(&root_v).unwrap_or(&1);
if size_u < size_v {
self.parent.insert(root_u, root_v);
self.component_size.insert(root_v, size_u + size_v);
} else {
self.parent.insert(root_v, root_u);
self.component_size.insert(root_u, size_u + size_v);
}
true
}
#[inline]
fn connected(&mut self, u: VertexId, v: VertexId) -> bool {
self.find(u) == self.find(v)
}
#[inline]
fn insert_edge(&mut self, u: VertexId, v: VertexId) -> bool {
self.add_vertex(u);
self.add_vertex(v);
// Update adjacency list
self.adjacency.entry(u).or_default().push(v);
self.adjacency.entry(v).or_default().push(u);
let edge = if u < v { (u, v) } else { (v, u) };
if self.union(u, v) {
// New tree edge
self.tree_edges.insert(edge);
true
} else {
// Non-tree edge
self.non_tree_edges.insert(edge);
false
}
}
fn remove_edge(&mut self, u: VertexId, v: VertexId) -> bool {
let edge = if u < v { (u, v) } else { (v, u) };
// Update adjacency
if let Some(neighbors) = self.adjacency.get_mut(&u) {
neighbors.retain(|&x| x != v);
}
if let Some(neighbors) = self.adjacency.get_mut(&v) {
neighbors.retain(|&x| x != u);
}
if self.tree_edges.remove(&edge) {
true
} else {
self.non_tree_edges.remove(&edge);
false
}
}
/// Get neighbors of a vertex (faster than iterating edges)
#[inline]
fn neighbors(&self, v: VertexId) -> &[VertexId] {
self.adjacency.get(&v).map_or(&[], |n| n.as_slice())
}
/// Get component size for a vertex
#[inline]
fn get_component_size(&mut self, v: VertexId) -> usize {
let root = self.find(v);
*self.component_size.get(&root).unwrap_or(&1)
}
}
/// Polylogarithmic worst-case dynamic connectivity
///
/// Maintains connectivity with O(log³ n) expected worst-case update time.
///
/// # Example
///
/// ```ignore
/// use ruvector_mincut::connectivity::polylog::PolylogConnectivity;
///
/// let mut conn = PolylogConnectivity::new();
/// conn.insert_edge(0, 1);
/// conn.insert_edge(1, 2);
///
/// assert!(conn.connected(0, 2));
///
/// conn.delete_edge(1, 2);
/// assert!(!conn.connected(0, 2));
/// ```
#[derive(Debug)]
pub struct PolylogConnectivity {
/// Hierarchy of forests, one per level
levels: Vec<LevelForest>,
/// All edges with their levels
edges: HashMap<(VertexId, VertexId), usize>,
/// Number of edges at each level (for rebuild tracking)
level_sizes: Vec<usize>,
/// Initial sizes at last rebuild
initial_sizes: Vec<usize>,
/// Number of vertices
vertex_count: usize,
/// Number of components
component_count: usize,
/// Statistics
stats: PolylogStats,
}
/// Statistics for polylog connectivity
#[derive(Debug, Clone, Default)]
pub struct PolylogStats {
/// Total insertions
pub insertions: u64,
/// Total deletions
pub deletions: u64,
/// Total queries
pub queries: u64,
/// Number of level rebuilds
pub rebuilds: u64,
/// Maximum level used
pub max_level: usize,
}
impl PolylogConnectivity {
/// Create new empty connectivity structure
pub fn new() -> Self {
Self {
levels: (0..MAX_LEVELS).map(|_| LevelForest::new()).collect(),
edges: HashMap::new(),
level_sizes: vec![0; MAX_LEVELS],
initial_sizes: vec![0; MAX_LEVELS],
vertex_count: 0,
component_count: 0,
stats: PolylogStats::default(),
}
}
/// Insert an edge
///
/// Time complexity: O(log³ n) expected worst-case
pub fn insert_edge(&mut self, u: VertexId, v: VertexId) {
self.stats.insertions += 1;
let edge = if u < v { (u, v) } else { (v, u) };
if self.edges.contains_key(&edge) {
return; // Edge already exists
}
// Track vertices
let u_new = !self.levels[0].parent.contains_key(&u);
let v_new = !self.levels[0].parent.contains_key(&v);
if u_new {
self.vertex_count += 1;
self.component_count += 1;
}
if v_new {
self.vertex_count += 1;
self.component_count += 1;
}
// Insert at level 0
let is_tree_edge = self.levels[0].insert_edge(u, v);
self.edges.insert(edge, 0);
self.level_sizes[0] += 1;
if is_tree_edge {
// Merged two components
self.component_count -= 1;
}
// Check if rebuild needed
self.check_rebuild(0);
}
/// Delete an edge
///
/// Time complexity: O(log³ n) expected worst-case
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
self.stats.deletions += 1;
let edge = if u < v { (u, v) } else { (v, u) };
let level = match self.edges.remove(&edge) {
Some(l) => l,
None => return, // Edge doesn't exist
};
self.level_sizes[level] = self.level_sizes[level].saturating_sub(1);
// Remove from all levels up to current level
for l in 0..=level {
let was_tree = self.levels[l].remove_edge(u, v);
if was_tree && l == level {
// Need to find replacement edge
if let Some(replacement) = self.find_replacement(u, v, level) {
// Promote replacement edge
let rep_edge = if replacement.0 < replacement.1 {
(replacement.0, replacement.1)
} else {
(replacement.1, replacement.0)
};
if let Some(rep_level) = self.edges.get_mut(&rep_edge) {
let old_level = *rep_level;
*rep_level = level;
// Move edge up in hierarchy
self.level_sizes[old_level] = self.level_sizes[old_level].saturating_sub(1);
self.level_sizes[level] += 1;
// Update forests
for ll in old_level..=level {
self.levels[ll].non_tree_edges.remove(&rep_edge);
self.levels[ll].tree_edges.insert(rep_edge);
}
}
} else {
// No replacement - component split
self.component_count += 1;
}
}
}
// Rebuild affected levels
self.rebuild_level(level);
}
/// Check if two vertices are connected
///
/// Time complexity: O(log n) worst-case
pub fn connected(&mut self, u: VertexId, v: VertexId) -> bool {
self.stats.queries += 1;
// Check at level 0 (contains all edges)
self.levels[0].connected(u, v)
}
/// Check if the entire graph is connected
pub fn is_connected(&self) -> bool {
self.component_count <= 1
}
/// Get number of connected components
pub fn component_count(&self) -> usize {
self.component_count
}
/// Get number of vertices
pub fn vertex_count(&self) -> usize {
self.vertex_count
}
/// Get number of edges
pub fn edge_count(&self) -> usize {
self.edges.len()
}
/// Get statistics
pub fn stats(&self) -> &PolylogStats {
&self.stats
}
/// Find a replacement edge for deleted tree edge
/// Optimized: Uses adjacency list and smaller component first
fn find_replacement(
&mut self,
u: VertexId,
v: VertexId,
level: usize,
) -> Option<(VertexId, VertexId)> {
// Choose smaller component for BFS (optimization)
let size_u = self.levels[level].get_component_size(u);
let size_v = self.levels[level].get_component_size(v);
let (start, _target) = if size_u <= size_v { (u, v) } else { (v, u) };
// Use FxHashSet for faster hashing if available, fallback to HashSet
let mut visited = HashSet::with_capacity(size_u.min(size_v).min(1000));
let mut queue = VecDeque::with_capacity(64);
// Start BFS from smaller component
queue.push_back(start);
visited.insert(start);
// Early termination limit
let max_search = (self.vertex_count / 2).max(100);
while let Some(current) = queue.pop_front() {
// Check non-tree edges first (more likely to find replacement)
let non_tree_edges: Vec<_> = self.levels[level]
.non_tree_edges
.iter()
.filter(|&&(a, b)| a == current || b == current)
.copied()
.collect();
for (a, b) in non_tree_edges {
let other = if a == current { b } else { a };
// If other is not in visited set, it's in the other component
if !visited.contains(&other) {
return Some((a, b));
}
}
// Use adjacency list for faster neighbor iteration
let neighbors: Vec<_> = self.levels[level].neighbors(current).to_vec();
for neighbor in neighbors {
if !visited.contains(&neighbor) {
visited.insert(neighbor);
queue.push_back(neighbor);
}
}
// Limit search to avoid worst-case
if visited.len() >= max_search {
break;
}
}
None
}
/// Check if level needs rebuild
fn check_rebuild(&mut self, level: usize) {
if self.initial_sizes[level] == 0 {
self.initial_sizes[level] = self.level_sizes[level].max(1);
return;
}
let threshold = (self.initial_sizes[level] as f64 * REBUILD_FACTOR) as usize;
if self.level_sizes[level] > threshold {
self.rebuild_level(level);
}
}
/// Rebuild a level of the hierarchy
fn rebuild_level(&mut self, level: usize) {
self.stats.rebuilds += 1;
self.stats.max_level = self.stats.max_level.max(level);
// Collect all edges at this level and below
let edges_to_rebuild: Vec<_> = self
.edges
.iter()
.filter(|(_, &l)| l >= level)
.map(|(&e, &l)| (e, l))
.collect();
// Reset level
self.levels[level] = LevelForest::new();
self.level_sizes[level] = 0;
// Re-insert edges
for ((u, v), _) in edges_to_rebuild {
self.levels[level].insert_edge(u, v);
self.level_sizes[level] += 1;
}
// Update initial size
self.initial_sizes[level] = self.level_sizes[level].max(1);
}
}
impl Default for PolylogConnectivity {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_connectivity() {
let mut conn = PolylogConnectivity::new();
conn.insert_edge(0, 1);
conn.insert_edge(1, 2);
assert!(conn.connected(0, 1));
assert!(conn.connected(0, 2));
assert!(conn.connected(1, 2));
assert!(!conn.connected(0, 3));
}
#[test]
fn test_delete_edge() {
let mut conn = PolylogConnectivity::new();
conn.insert_edge(0, 1);
conn.insert_edge(1, 2);
conn.insert_edge(2, 3);
assert!(conn.connected(0, 3));
conn.delete_edge(1, 2);
assert!(conn.connected(0, 1));
assert!(conn.connected(2, 3));
assert!(!conn.connected(0, 2));
}
#[test]
fn test_component_count() {
let mut conn = PolylogConnectivity::new();
conn.insert_edge(0, 1);
assert_eq!(conn.component_count(), 1);
conn.insert_edge(2, 3);
assert_eq!(conn.component_count(), 2);
conn.insert_edge(1, 2);
assert_eq!(conn.component_count(), 1);
conn.delete_edge(1, 2);
assert_eq!(conn.component_count(), 2);
}
#[test]
fn test_replacement_edge() {
let mut conn = PolylogConnectivity::new();
// Create a cycle: 0-1-2-3-0
conn.insert_edge(0, 1);
conn.insert_edge(1, 2);
conn.insert_edge(2, 3);
conn.insert_edge(3, 0);
assert_eq!(conn.component_count(), 1);
// Delete one edge - should find replacement
conn.delete_edge(1, 2);
// Still connected via 0-3-2
assert!(conn.connected(0, 2));
assert_eq!(conn.component_count(), 1);
}
#[test]
fn test_stats() {
let mut conn = PolylogConnectivity::new();
conn.insert_edge(0, 1);
conn.insert_edge(1, 2);
conn.delete_edge(0, 1);
conn.connected(1, 2);
let stats = conn.stats();
assert_eq!(stats.insertions, 2);
assert_eq!(stats.deletions, 1);
assert_eq!(stats.queries, 1);
}
}