ruvector_mincut/connectivity/
polylog.rs

1//! Polylogarithmic Worst-Case Dynamic Connectivity
2//!
3//! Implementation based on "Dynamic Connectivity with Expected Polylogarithmic
4//! Worst-Case Update Time" (arXiv:2510.08297, October 2025).
5//!
6//! # Key Innovation
7//!
8//! Uses the core graph framework with a hierarchy interleaving vertex and edge
9//! sparsification to achieve O(polylog n) expected worst-case update time.
10//!
11//! # Time Complexity
12//!
13//! - Insert: O(log³ n) expected worst-case
14//! - Delete: O(log³ n) expected worst-case
15//! - Query: O(log n) worst-case
16//!
17//! # Algorithm Overview
18//!
19//! 1. Maintain a hierarchy of O(log n) levels
20//! 2. Each level i contains edges of "level" ≥ i
21//! 3. Use edge sparsification via low-congestion shortcuts
22//! 4. Rebuild levels incrementally to avoid worst-case spikes
23
24use crate::graph::VertexId;
25use std::collections::{HashMap, HashSet, VecDeque};
26
27/// Maximum number of levels in the hierarchy
28const MAX_LEVELS: usize = 64;
29
30/// Rebuild threshold factor
31const REBUILD_FACTOR: f64 = 2.0;
32
33/// Edge with level information for the hierarchy
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
35struct LeveledEdge {
36    u: VertexId,
37    v: VertexId,
38    level: usize,
39}
40
41impl LeveledEdge {
42    fn new(u: VertexId, v: VertexId, level: usize) -> Self {
43        let (u, v) = if u < v { (u, v) } else { (v, u) };
44        Self { u, v, level }
45    }
46
47    fn endpoints(&self) -> (VertexId, VertexId) {
48        (self.u, self.v)
49    }
50}
51
52/// Spanning forest for a single level
53#[derive(Debug, Clone)]
54struct LevelForest {
55    /// Parent pointers for union-find
56    parent: HashMap<VertexId, VertexId>,
57    /// Rank for union by rank
58    rank: HashMap<VertexId, usize>,
59    /// Component sizes for smarter union
60    component_size: HashMap<VertexId, usize>,
61    /// Tree edges at this level
62    tree_edges: HashSet<(VertexId, VertexId)>,
63    /// Non-tree edges at this level
64    non_tree_edges: HashSet<(VertexId, VertexId)>,
65    /// Adjacency list for faster traversal (vertex -> neighbors)
66    adjacency: HashMap<VertexId, Vec<VertexId>>,
67    /// Number of vertices
68    size: usize,
69}
70
71impl LevelForest {
72    fn new() -> Self {
73        Self {
74            parent: HashMap::new(),
75            rank: HashMap::new(),
76            component_size: HashMap::new(),
77            tree_edges: HashSet::new(),
78            non_tree_edges: HashSet::new(),
79            adjacency: HashMap::new(),
80            size: 0,
81        }
82    }
83
84    #[inline]
85    fn add_vertex(&mut self, v: VertexId) {
86        if !self.parent.contains_key(&v) {
87            self.parent.insert(v, v);
88            self.rank.insert(v, 0);
89            self.component_size.insert(v, 1);
90            self.adjacency.insert(v, Vec::new());
91            self.size += 1;
92        }
93    }
94
95    #[inline]
96    fn find(&mut self, v: VertexId) -> VertexId {
97        if !self.parent.contains_key(&v) {
98            return v;
99        }
100
101        let p = self.parent[&v];
102        if p == v {
103            return v;
104        }
105
106        // Path compression with iterative approach (faster than recursive)
107        let mut path = Vec::with_capacity(8);
108        let mut current = v;
109        while self.parent[&current] != current {
110            path.push(current);
111            current = self.parent[&current];
112        }
113        let root = current;
114
115        // Compress path
116        for node in path {
117            self.parent.insert(node, root);
118        }
119        root
120    }
121
122    #[inline]
123    fn union(&mut self, u: VertexId, v: VertexId) -> bool {
124        let root_u = self.find(u);
125        let root_v = self.find(v);
126
127        if root_u == root_v {
128            return false;
129        }
130
131        // Union by size (better than rank for our use case)
132        let size_u = *self.component_size.get(&root_u).unwrap_or(&1);
133        let size_v = *self.component_size.get(&root_v).unwrap_or(&1);
134
135        if size_u < size_v {
136            self.parent.insert(root_u, root_v);
137            self.component_size.insert(root_v, size_u + size_v);
138        } else {
139            self.parent.insert(root_v, root_u);
140            self.component_size.insert(root_u, size_u + size_v);
141        }
142
143        true
144    }
145
146    #[inline]
147    fn connected(&mut self, u: VertexId, v: VertexId) -> bool {
148        self.find(u) == self.find(v)
149    }
150
151    #[inline]
152    fn insert_edge(&mut self, u: VertexId, v: VertexId) -> bool {
153        self.add_vertex(u);
154        self.add_vertex(v);
155
156        // Update adjacency list
157        self.adjacency.entry(u).or_default().push(v);
158        self.adjacency.entry(v).or_default().push(u);
159
160        let edge = if u < v { (u, v) } else { (v, u) };
161
162        if self.union(u, v) {
163            // New tree edge
164            self.tree_edges.insert(edge);
165            true
166        } else {
167            // Non-tree edge
168            self.non_tree_edges.insert(edge);
169            false
170        }
171    }
172
173    fn remove_edge(&mut self, u: VertexId, v: VertexId) -> bool {
174        let edge = if u < v { (u, v) } else { (v, u) };
175
176        // Update adjacency
177        if let Some(neighbors) = self.adjacency.get_mut(&u) {
178            neighbors.retain(|&x| x != v);
179        }
180        if let Some(neighbors) = self.adjacency.get_mut(&v) {
181            neighbors.retain(|&x| x != u);
182        }
183
184        if self.tree_edges.remove(&edge) {
185            true
186        } else {
187            self.non_tree_edges.remove(&edge);
188            false
189        }
190    }
191
192    /// Get neighbors of a vertex (faster than iterating edges)
193    #[inline]
194    fn neighbors(&self, v: VertexId) -> &[VertexId] {
195        self.adjacency.get(&v).map_or(&[], |n| n.as_slice())
196    }
197
198    /// Get component size for a vertex
199    #[inline]
200    fn get_component_size(&mut self, v: VertexId) -> usize {
201        let root = self.find(v);
202        *self.component_size.get(&root).unwrap_or(&1)
203    }
204}
205
206/// Polylogarithmic worst-case dynamic connectivity
207///
208/// Maintains connectivity with O(log³ n) expected worst-case update time.
209///
210/// # Example
211///
212/// ```ignore
213/// use ruvector_mincut::connectivity::polylog::PolylogConnectivity;
214///
215/// let mut conn = PolylogConnectivity::new();
216/// conn.insert_edge(0, 1);
217/// conn.insert_edge(1, 2);
218///
219/// assert!(conn.connected(0, 2));
220///
221/// conn.delete_edge(1, 2);
222/// assert!(!conn.connected(0, 2));
223/// ```
224#[derive(Debug)]
225pub struct PolylogConnectivity {
226    /// Hierarchy of forests, one per level
227    levels: Vec<LevelForest>,
228    /// All edges with their levels
229    edges: HashMap<(VertexId, VertexId), usize>,
230    /// Number of edges at each level (for rebuild tracking)
231    level_sizes: Vec<usize>,
232    /// Initial sizes at last rebuild
233    initial_sizes: Vec<usize>,
234    /// Number of vertices
235    vertex_count: usize,
236    /// Number of components
237    component_count: usize,
238    /// Statistics
239    stats: PolylogStats,
240}
241
242/// Statistics for polylog connectivity
243#[derive(Debug, Clone, Default)]
244pub struct PolylogStats {
245    /// Total insertions
246    pub insertions: u64,
247    /// Total deletions
248    pub deletions: u64,
249    /// Total queries
250    pub queries: u64,
251    /// Number of level rebuilds
252    pub rebuilds: u64,
253    /// Maximum level used
254    pub max_level: usize,
255}
256
257impl PolylogConnectivity {
258    /// Create new empty connectivity structure
259    pub fn new() -> Self {
260        Self {
261            levels: (0..MAX_LEVELS).map(|_| LevelForest::new()).collect(),
262            edges: HashMap::new(),
263            level_sizes: vec![0; MAX_LEVELS],
264            initial_sizes: vec![0; MAX_LEVELS],
265            vertex_count: 0,
266            component_count: 0,
267            stats: PolylogStats::default(),
268        }
269    }
270
271    /// Insert an edge
272    ///
273    /// Time complexity: O(log³ n) expected worst-case
274    pub fn insert_edge(&mut self, u: VertexId, v: VertexId) {
275        self.stats.insertions += 1;
276
277        let edge = if u < v { (u, v) } else { (v, u) };
278
279        if self.edges.contains_key(&edge) {
280            return; // Edge already exists
281        }
282
283        // Track vertices
284        let u_new = !self.levels[0].parent.contains_key(&u);
285        let v_new = !self.levels[0].parent.contains_key(&v);
286
287        if u_new {
288            self.vertex_count += 1;
289            self.component_count += 1;
290        }
291        if v_new {
292            self.vertex_count += 1;
293            self.component_count += 1;
294        }
295
296        // Insert at level 0
297        let is_tree_edge = self.levels[0].insert_edge(u, v);
298        self.edges.insert(edge, 0);
299        self.level_sizes[0] += 1;
300
301        if is_tree_edge {
302            // Merged two components
303            self.component_count -= 1;
304        }
305
306        // Check if rebuild needed
307        self.check_rebuild(0);
308    }
309
310    /// Delete an edge
311    ///
312    /// Time complexity: O(log³ n) expected worst-case
313    pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
314        self.stats.deletions += 1;
315
316        let edge = if u < v { (u, v) } else { (v, u) };
317
318        let level = match self.edges.remove(&edge) {
319            Some(l) => l,
320            None => return, // Edge doesn't exist
321        };
322
323        self.level_sizes[level] = self.level_sizes[level].saturating_sub(1);
324
325        // Remove from all levels up to current level
326        for l in 0..=level {
327            let was_tree = self.levels[l].remove_edge(u, v);
328
329            if was_tree && l == level {
330                // Need to find replacement edge
331                if let Some(replacement) = self.find_replacement(u, v, level) {
332                    // Promote replacement edge
333                    let rep_edge = if replacement.0 < replacement.1 {
334                        (replacement.0, replacement.1)
335                    } else {
336                        (replacement.1, replacement.0)
337                    };
338
339                    if let Some(rep_level) = self.edges.get_mut(&rep_edge) {
340                        let old_level = *rep_level;
341                        *rep_level = level;
342
343                        // Move edge up in hierarchy
344                        self.level_sizes[old_level] = self.level_sizes[old_level].saturating_sub(1);
345                        self.level_sizes[level] += 1;
346
347                        // Update forests
348                        for ll in old_level..=level {
349                            self.levels[ll].non_tree_edges.remove(&rep_edge);
350                            self.levels[ll].tree_edges.insert(rep_edge);
351                        }
352                    }
353                } else {
354                    // No replacement - component split
355                    self.component_count += 1;
356                }
357            }
358        }
359
360        // Rebuild affected levels
361        self.rebuild_level(level);
362    }
363
364    /// Check if two vertices are connected
365    ///
366    /// Time complexity: O(log n) worst-case
367    pub fn connected(&mut self, u: VertexId, v: VertexId) -> bool {
368        self.stats.queries += 1;
369
370        // Check at level 0 (contains all edges)
371        self.levels[0].connected(u, v)
372    }
373
374    /// Check if the entire graph is connected
375    pub fn is_connected(&self) -> bool {
376        self.component_count <= 1
377    }
378
379    /// Get number of connected components
380    pub fn component_count(&self) -> usize {
381        self.component_count
382    }
383
384    /// Get number of vertices
385    pub fn vertex_count(&self) -> usize {
386        self.vertex_count
387    }
388
389    /// Get number of edges
390    pub fn edge_count(&self) -> usize {
391        self.edges.len()
392    }
393
394    /// Get statistics
395    pub fn stats(&self) -> &PolylogStats {
396        &self.stats
397    }
398
399    /// Find a replacement edge for deleted tree edge
400    /// Optimized: Uses adjacency list and smaller component first
401    fn find_replacement(
402        &mut self,
403        u: VertexId,
404        v: VertexId,
405        level: usize,
406    ) -> Option<(VertexId, VertexId)> {
407        // Choose smaller component for BFS (optimization)
408        let size_u = self.levels[level].get_component_size(u);
409        let size_v = self.levels[level].get_component_size(v);
410        let (start, _target) = if size_u <= size_v { (u, v) } else { (v, u) };
411
412        // Use FxHashSet for faster hashing if available, fallback to HashSet
413        let mut visited = HashSet::with_capacity(size_u.min(size_v).min(1000));
414        let mut queue = VecDeque::with_capacity(64);
415
416        // Start BFS from smaller component
417        queue.push_back(start);
418        visited.insert(start);
419
420        // Early termination limit
421        let max_search = (self.vertex_count / 2).max(100);
422
423        while let Some(current) = queue.pop_front() {
424            // Check non-tree edges first (more likely to find replacement)
425            let non_tree_edges: Vec<_> = self.levels[level]
426                .non_tree_edges
427                .iter()
428                .filter(|&&(a, b)| a == current || b == current)
429                .copied()
430                .collect();
431
432            for (a, b) in non_tree_edges {
433                let other = if a == current { b } else { a };
434
435                // If other is not in visited set, it's in the other component
436                if !visited.contains(&other) {
437                    return Some((a, b));
438                }
439            }
440
441            // Use adjacency list for faster neighbor iteration
442            let neighbors: Vec<_> = self.levels[level].neighbors(current).to_vec();
443            for neighbor in neighbors {
444                if !visited.contains(&neighbor) {
445                    visited.insert(neighbor);
446                    queue.push_back(neighbor);
447                }
448            }
449
450            // Limit search to avoid worst-case
451            if visited.len() >= max_search {
452                break;
453            }
454        }
455
456        None
457    }
458
459    /// Check if level needs rebuild
460    fn check_rebuild(&mut self, level: usize) {
461        if self.initial_sizes[level] == 0 {
462            self.initial_sizes[level] = self.level_sizes[level].max(1);
463            return;
464        }
465
466        let threshold = (self.initial_sizes[level] as f64 * REBUILD_FACTOR) as usize;
467        if self.level_sizes[level] > threshold {
468            self.rebuild_level(level);
469        }
470    }
471
472    /// Rebuild a level of the hierarchy
473    fn rebuild_level(&mut self, level: usize) {
474        self.stats.rebuilds += 1;
475        self.stats.max_level = self.stats.max_level.max(level);
476
477        // Collect all edges at this level and below
478        let edges_to_rebuild: Vec<_> = self
479            .edges
480            .iter()
481            .filter(|(_, &l)| l >= level)
482            .map(|(&e, &l)| (e, l))
483            .collect();
484
485        // Reset level
486        self.levels[level] = LevelForest::new();
487        self.level_sizes[level] = 0;
488
489        // Re-insert edges
490        for ((u, v), _) in edges_to_rebuild {
491            self.levels[level].insert_edge(u, v);
492            self.level_sizes[level] += 1;
493        }
494
495        // Update initial size
496        self.initial_sizes[level] = self.level_sizes[level].max(1);
497    }
498}
499
500impl Default for PolylogConnectivity {
501    fn default() -> Self {
502        Self::new()
503    }
504}
505
506#[cfg(test)]
507mod tests {
508    use super::*;
509
510    #[test]
511    fn test_basic_connectivity() {
512        let mut conn = PolylogConnectivity::new();
513
514        conn.insert_edge(0, 1);
515        conn.insert_edge(1, 2);
516
517        assert!(conn.connected(0, 1));
518        assert!(conn.connected(0, 2));
519        assert!(conn.connected(1, 2));
520        assert!(!conn.connected(0, 3));
521    }
522
523    #[test]
524    fn test_delete_edge() {
525        let mut conn = PolylogConnectivity::new();
526
527        conn.insert_edge(0, 1);
528        conn.insert_edge(1, 2);
529        conn.insert_edge(2, 3);
530
531        assert!(conn.connected(0, 3));
532
533        conn.delete_edge(1, 2);
534
535        assert!(conn.connected(0, 1));
536        assert!(conn.connected(2, 3));
537        assert!(!conn.connected(0, 2));
538    }
539
540    #[test]
541    fn test_component_count() {
542        let mut conn = PolylogConnectivity::new();
543
544        conn.insert_edge(0, 1);
545        assert_eq!(conn.component_count(), 1);
546
547        conn.insert_edge(2, 3);
548        assert_eq!(conn.component_count(), 2);
549
550        conn.insert_edge(1, 2);
551        assert_eq!(conn.component_count(), 1);
552
553        conn.delete_edge(1, 2);
554        assert_eq!(conn.component_count(), 2);
555    }
556
557    #[test]
558    fn test_replacement_edge() {
559        let mut conn = PolylogConnectivity::new();
560
561        // Create a cycle: 0-1-2-3-0
562        conn.insert_edge(0, 1);
563        conn.insert_edge(1, 2);
564        conn.insert_edge(2, 3);
565        conn.insert_edge(3, 0);
566
567        assert_eq!(conn.component_count(), 1);
568
569        // Delete one edge - should find replacement
570        conn.delete_edge(1, 2);
571
572        // Still connected via 0-3-2
573        assert!(conn.connected(0, 2));
574        assert_eq!(conn.component_count(), 1);
575    }
576
577    #[test]
578    fn test_stats() {
579        let mut conn = PolylogConnectivity::new();
580
581        conn.insert_edge(0, 1);
582        conn.insert_edge(1, 2);
583        conn.delete_edge(0, 1);
584        conn.connected(1, 2);
585
586        let stats = conn.stats();
587        assert_eq!(stats.insertions, 2);
588        assert_eq!(stats.deletions, 1);
589        assert_eq!(stats.queries, 1);
590    }
591}