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 std::collections::{HashMap, HashSet, VecDeque};
25use crate::graph::VertexId;
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] =
345                            self.level_sizes[old_level].saturating_sub(1);
346                        self.level_sizes[level] += 1;
347
348                        // Update forests
349                        for ll in old_level..=level {
350                            self.levels[ll].non_tree_edges.remove(&rep_edge);
351                            self.levels[ll].tree_edges.insert(rep_edge);
352                        }
353                    }
354                } else {
355                    // No replacement - component split
356                    self.component_count += 1;
357                }
358            }
359        }
360
361        // Rebuild affected levels
362        self.rebuild_level(level);
363    }
364
365    /// Check if two vertices are connected
366    ///
367    /// Time complexity: O(log n) worst-case
368    pub fn connected(&mut self, u: VertexId, v: VertexId) -> bool {
369        self.stats.queries += 1;
370
371        // Check at level 0 (contains all edges)
372        self.levels[0].connected(u, v)
373    }
374
375    /// Check if the entire graph is connected
376    pub fn is_connected(&self) -> bool {
377        self.component_count <= 1
378    }
379
380    /// Get number of connected components
381    pub fn component_count(&self) -> usize {
382        self.component_count
383    }
384
385    /// Get number of vertices
386    pub fn vertex_count(&self) -> usize {
387        self.vertex_count
388    }
389
390    /// Get number of edges
391    pub fn edge_count(&self) -> usize {
392        self.edges.len()
393    }
394
395    /// Get statistics
396    pub fn stats(&self) -> &PolylogStats {
397        &self.stats
398    }
399
400    /// Find a replacement edge for deleted tree edge
401    /// Optimized: Uses adjacency list and smaller component first
402    fn find_replacement(&mut self, u: VertexId, v: VertexId, level: usize) -> Option<(VertexId, VertexId)> {
403        // Choose smaller component for BFS (optimization)
404        let size_u = self.levels[level].get_component_size(u);
405        let size_v = self.levels[level].get_component_size(v);
406        let (start, _target) = if size_u <= size_v { (u, v) } else { (v, u) };
407
408        // Use FxHashSet for faster hashing if available, fallback to HashSet
409        let mut visited = HashSet::with_capacity(size_u.min(size_v).min(1000));
410        let mut queue = VecDeque::with_capacity(64);
411
412        // Start BFS from smaller component
413        queue.push_back(start);
414        visited.insert(start);
415
416        // Early termination limit
417        let max_search = (self.vertex_count / 2).max(100);
418
419        while let Some(current) = queue.pop_front() {
420            // Check non-tree edges first (more likely to find replacement)
421            let non_tree_edges: Vec<_> = self.levels[level]
422                .non_tree_edges
423                .iter()
424                .filter(|&&(a, b)| a == current || b == current)
425                .copied()
426                .collect();
427
428            for (a, b) in non_tree_edges {
429                let other = if a == current { b } else { a };
430
431                // If other is not in visited set, it's in the other component
432                if !visited.contains(&other) {
433                    return Some((a, b));
434                }
435            }
436
437            // Use adjacency list for faster neighbor iteration
438            let neighbors: Vec<_> = self.levels[level].neighbors(current).to_vec();
439            for neighbor in neighbors {
440                if !visited.contains(&neighbor) {
441                    visited.insert(neighbor);
442                    queue.push_back(neighbor);
443                }
444            }
445
446            // Limit search to avoid worst-case
447            if visited.len() >= max_search {
448                break;
449            }
450        }
451
452        None
453    }
454
455    /// Check if level needs rebuild
456    fn check_rebuild(&mut self, level: usize) {
457        if self.initial_sizes[level] == 0 {
458            self.initial_sizes[level] = self.level_sizes[level].max(1);
459            return;
460        }
461
462        let threshold = (self.initial_sizes[level] as f64 * REBUILD_FACTOR) as usize;
463        if self.level_sizes[level] > threshold {
464            self.rebuild_level(level);
465        }
466    }
467
468    /// Rebuild a level of the hierarchy
469    fn rebuild_level(&mut self, level: usize) {
470        self.stats.rebuilds += 1;
471        self.stats.max_level = self.stats.max_level.max(level);
472
473        // Collect all edges at this level and below
474        let edges_to_rebuild: Vec<_> = self
475            .edges
476            .iter()
477            .filter(|(_, &l)| l >= level)
478            .map(|(&e, &l)| (e, l))
479            .collect();
480
481        // Reset level
482        self.levels[level] = LevelForest::new();
483        self.level_sizes[level] = 0;
484
485        // Re-insert edges
486        for ((u, v), _) in edges_to_rebuild {
487            self.levels[level].insert_edge(u, v);
488            self.level_sizes[level] += 1;
489        }
490
491        // Update initial size
492        self.initial_sizes[level] = self.level_sizes[level].max(1);
493    }
494}
495
496impl Default for PolylogConnectivity {
497    fn default() -> Self {
498        Self::new()
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use super::*;
505
506    #[test]
507    fn test_basic_connectivity() {
508        let mut conn = PolylogConnectivity::new();
509
510        conn.insert_edge(0, 1);
511        conn.insert_edge(1, 2);
512
513        assert!(conn.connected(0, 1));
514        assert!(conn.connected(0, 2));
515        assert!(conn.connected(1, 2));
516        assert!(!conn.connected(0, 3));
517    }
518
519    #[test]
520    fn test_delete_edge() {
521        let mut conn = PolylogConnectivity::new();
522
523        conn.insert_edge(0, 1);
524        conn.insert_edge(1, 2);
525        conn.insert_edge(2, 3);
526
527        assert!(conn.connected(0, 3));
528
529        conn.delete_edge(1, 2);
530
531        assert!(conn.connected(0, 1));
532        assert!(conn.connected(2, 3));
533        assert!(!conn.connected(0, 2));
534    }
535
536    #[test]
537    fn test_component_count() {
538        let mut conn = PolylogConnectivity::new();
539
540        conn.insert_edge(0, 1);
541        assert_eq!(conn.component_count(), 1);
542
543        conn.insert_edge(2, 3);
544        assert_eq!(conn.component_count(), 2);
545
546        conn.insert_edge(1, 2);
547        assert_eq!(conn.component_count(), 1);
548
549        conn.delete_edge(1, 2);
550        assert_eq!(conn.component_count(), 2);
551    }
552
553    #[test]
554    fn test_replacement_edge() {
555        let mut conn = PolylogConnectivity::new();
556
557        // Create a cycle: 0-1-2-3-0
558        conn.insert_edge(0, 1);
559        conn.insert_edge(1, 2);
560        conn.insert_edge(2, 3);
561        conn.insert_edge(3, 0);
562
563        assert_eq!(conn.component_count(), 1);
564
565        // Delete one edge - should find replacement
566        conn.delete_edge(1, 2);
567
568        // Still connected via 0-3-2
569        assert!(conn.connected(0, 2));
570        assert_eq!(conn.component_count(), 1);
571    }
572
573    #[test]
574    fn test_stats() {
575        let mut conn = PolylogConnectivity::new();
576
577        conn.insert_edge(0, 1);
578        conn.insert_edge(1, 2);
579        conn.delete_edge(0, 1);
580        conn.connected(1, 2);
581
582        let stats = conn.stats();
583        assert_eq!(stats.insertions, 2);
584        assert_eq!(stats.deletions, 1);
585        assert_eq!(stats.queries, 1);
586    }
587}