ruvector_mincut/localkcut/
deterministic.rs

1//! Deterministic LocalKCut Algorithm
2//!
3//! Implementation of the deterministic local minimum cut algorithm from:
4//! "Deterministic and Exact Fully-dynamic Minimum Cut of Superpolylogarithmic
5//! Size in Subpolynomial Time" (arXiv:2512.13105)
6//!
7//! Key components:
8//! - Color coding families (red-blue, green-yellow)
9//! - Forest packing with greedy edge assignment
10//! - Color-coded DFS for cut enumeration
11
12use std::collections::{HashMap, HashSet, VecDeque};
13use crate::graph::{VertexId, Weight};
14
15/// Color for edge partitioning in deterministic LocalKCut.
16/// Uses 4-color scheme for forest/non-forest edge classification.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub enum EdgeColor {
19    /// Red color - used for forest edges in one color class
20    Red,
21    /// Blue color - used for forest edges in other color class
22    Blue,
23    /// Green color - used for non-forest edges in one color class
24    Green,
25    /// Yellow color - used for non-forest edges in other color class
26    Yellow,
27}
28
29/// A coloring assignment for edges based on the (a,b)-coloring family.
30/// Per the paper, coloring families ensure witness coverage.
31#[derive(Debug, Clone)]
32pub struct EdgeColoring {
33    /// Map from edge (canonical key) to color
34    colors: HashMap<(VertexId, VertexId), EdgeColor>,
35    /// Parameter 'a' for the coloring family (related to cut size)
36    pub a: usize,
37    /// Parameter 'b' for the coloring family (related to volume)
38    pub b: usize,
39}
40
41impl EdgeColoring {
42    /// Create new empty coloring
43    pub fn new(a: usize, b: usize) -> Self {
44        Self {
45            colors: HashMap::new(),
46            a,
47            b,
48        }
49    }
50
51    /// Get color for edge
52    pub fn get(&self, u: VertexId, v: VertexId) -> Option<EdgeColor> {
53        let key = if u < v { (u, v) } else { (v, u) };
54        self.colors.get(&key).copied()
55    }
56
57    /// Set color for edge
58    pub fn set(&mut self, u: VertexId, v: VertexId, color: EdgeColor) {
59        let key = if u < v { (u, v) } else { (v, u) };
60        self.colors.insert(key, color);
61    }
62
63    /// Check if edge has specific color
64    pub fn has_color(&self, u: VertexId, v: VertexId, color: EdgeColor) -> bool {
65        self.get(u, v) == Some(color)
66    }
67}
68
69/// Generate color coding family per Lemma 3.3
70/// Family size: 2^{O(min(a,b) · log(a+b))} · log n
71pub fn generate_coloring_family(
72    a: usize,
73    b: usize,
74    num_edges: usize,
75) -> Vec<EdgeColoring> {
76    // Simplified implementation using hashing-based derandomization
77    // Full implementation would use perfect hash families
78
79    let log_n = (num_edges.max(2) as f64).log2().ceil() as usize;
80    let family_size = (1 << (a.min(b) * (a + b).max(1).ilog2() as usize + 1)) * log_n;
81    let family_size = family_size.min(100); // Cap for practicality
82
83    let mut family = Vec::with_capacity(family_size);
84
85    for seed in 0..family_size {
86        let coloring = EdgeColoring::new(a, b);
87        // Each coloring in the family uses different hash function
88        // to partition edges
89        family.push(coloring);
90    }
91
92    family
93}
94
95/// Greedy forest packing structure
96#[derive(Debug, Clone)]
97pub struct GreedyForestPacking {
98    /// Number of forests
99    pub num_forests: usize,
100    /// Forest assignment for each edge: edge -> forest_id
101    edge_forest: HashMap<(VertexId, VertexId), usize>,
102    /// Edges in each forest
103    forests: Vec<HashSet<(VertexId, VertexId)>>,
104    /// Union-find for each forest to track connectivity
105    forest_parents: Vec<HashMap<VertexId, VertexId>>,
106}
107
108impl GreedyForestPacking {
109    /// Create new forest packing with k forests
110    /// Per paper: k = 6λ_max · log m / ε²
111    pub fn new(num_forests: usize) -> Self {
112        Self {
113            num_forests,
114            edge_forest: HashMap::new(),
115            forests: vec![HashSet::new(); num_forests],
116            forest_parents: vec![HashMap::new(); num_forests],
117        }
118    }
119
120    /// Find root in forest using path compression
121    fn find_root(&mut self, forest_id: usize, v: VertexId) -> VertexId {
122        if !self.forest_parents[forest_id].contains_key(&v) {
123            self.forest_parents[forest_id].insert(v, v);
124            return v;
125        }
126
127        let parent = self.forest_parents[forest_id][&v];
128        if parent == v {
129            return v;
130        }
131
132        let root = self.find_root(forest_id, parent);
133        self.forest_parents[forest_id].insert(v, root);
134        root
135    }
136
137    /// Union two vertices in a forest
138    fn union(&mut self, forest_id: usize, u: VertexId, v: VertexId) {
139        let root_u = self.find_root(forest_id, u);
140        let root_v = self.find_root(forest_id, v);
141        if root_u != root_v {
142            self.forest_parents[forest_id].insert(root_u, root_v);
143        }
144    }
145
146    /// Check if edge would create cycle in forest
147    fn would_create_cycle(&mut self, forest_id: usize, u: VertexId, v: VertexId) -> bool {
148        self.find_root(forest_id, u) == self.find_root(forest_id, v)
149    }
150
151    /// Insert edge greedily into first available forest
152    pub fn insert_edge(&mut self, u: VertexId, v: VertexId) -> Option<usize> {
153        let key = if u < v { (u, v) } else { (v, u) };
154
155        // Already assigned
156        if self.edge_forest.contains_key(&key) {
157            return self.edge_forest.get(&key).copied();
158        }
159
160        // Find first forest where this edge doesn't create cycle
161        for forest_id in 0..self.num_forests {
162            if !self.would_create_cycle(forest_id, u, v) {
163                self.forests[forest_id].insert(key);
164                self.edge_forest.insert(key, forest_id);
165                self.union(forest_id, u, v);
166                return Some(forest_id);
167            }
168        }
169
170        // Edge doesn't fit in any forest (it's a high-connectivity edge)
171        None
172    }
173
174    /// Delete edge from its forest
175    pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Option<usize> {
176        let key = if u < v { (u, v) } else { (v, u) };
177
178        if let Some(forest_id) = self.edge_forest.remove(&key) {
179            self.forests[forest_id].remove(&key);
180            // Need to rebuild connectivity for this forest
181            self.rebuild_forest_connectivity(forest_id);
182            return Some(forest_id);
183        }
184        None
185    }
186
187    /// Rebuild union-find for a forest after edge deletion
188    fn rebuild_forest_connectivity(&mut self, forest_id: usize) {
189        self.forest_parents[forest_id].clear();
190        // Collect edges first to avoid borrow conflict
191        let edges: Vec<_> = self.forests[forest_id].iter().copied().collect();
192        for (u, v) in edges {
193            self.union(forest_id, u, v);
194        }
195    }
196
197    /// Check if edge is a tree edge in some forest
198    pub fn is_tree_edge(&self, u: VertexId, v: VertexId) -> bool {
199        let key = if u < v { (u, v) } else { (v, u) };
200        self.edge_forest.contains_key(&key)
201    }
202
203    /// Get forest ID for an edge
204    pub fn get_forest(&self, u: VertexId, v: VertexId) -> Option<usize> {
205        let key = if u < v { (u, v) } else { (v, u) };
206        self.edge_forest.get(&key).copied()
207    }
208
209    /// Get all edges in a specific forest
210    pub fn forest_edges(&self, forest_id: usize) -> &HashSet<(VertexId, VertexId)> {
211        &self.forests[forest_id]
212    }
213}
214
215/// A discovered cut from LocalKCut query
216#[derive(Debug, Clone)]
217pub struct LocalCut {
218    /// Vertices in the cut set S
219    pub vertices: HashSet<VertexId>,
220    /// Boundary edges (crossing the cut)
221    pub boundary: Vec<(VertexId, VertexId)>,
222    /// Cut value (sum of boundary edge weights)
223    pub cut_value: f64,
224    /// Volume of the cut (sum of degrees)
225    pub volume: usize,
226}
227
228/// Deterministic LocalKCut data structure
229/// Per Theorem 4.1 of the paper
230#[derive(Debug)]
231pub struct DeterministicLocalKCut {
232    /// Maximum cut size to consider
233    lambda_max: u64,
234    /// Maximum volume to explore
235    nu: usize,
236    /// Approximation factor
237    beta: usize,
238    /// Forest packing
239    forests: GreedyForestPacking,
240    /// Red-blue coloring family (for forest edges)
241    red_blue_colorings: Vec<EdgeColoring>,
242    /// Green-yellow coloring family (for non-forest edges)
243    green_yellow_colorings: Vec<EdgeColoring>,
244    /// Graph adjacency
245    adjacency: HashMap<VertexId, HashMap<VertexId, Weight>>,
246    /// All edges
247    edges: HashSet<(VertexId, VertexId)>,
248}
249
250impl DeterministicLocalKCut {
251    /// Create new LocalKCut structure
252    pub fn new(lambda_max: u64, nu: usize, beta: usize) -> Self {
253        // Number of forests: 6λ_max · log m / ε² (simplified)
254        let num_forests = ((6 * lambda_max) as usize).max(10);
255
256        // Color coding parameters
257        let a_rb = 2 * beta;
258        let b_rb = nu;
259        let a_gy = 2 * beta - 1;
260        let b_gy = lambda_max as usize;
261
262        Self {
263            lambda_max,
264            nu,
265            beta,
266            forests: GreedyForestPacking::new(num_forests),
267            red_blue_colorings: generate_coloring_family(a_rb, b_rb, 1000),
268            green_yellow_colorings: generate_coloring_family(a_gy, b_gy, 1000),
269            adjacency: HashMap::new(),
270            edges: HashSet::new(),
271        }
272    }
273
274    /// Insert an edge
275    pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) {
276        let key = if u < v { (u, v) } else { (v, u) };
277
278        if self.edges.contains(&key) {
279            return;
280        }
281
282        self.edges.insert(key);
283        self.adjacency.entry(u).or_default().insert(v, weight);
284        self.adjacency.entry(v).or_default().insert(u, weight);
285
286        // Add to forest packing
287        if let Some(forest_id) = self.forests.insert_edge(u, v) {
288            // Assign color in red-blue family based on forest
289            for coloring in &mut self.red_blue_colorings {
290                let color = if (u + v + forest_id as u64) % 2 == 0 {
291                    EdgeColor::Blue
292                } else {
293                    EdgeColor::Red
294                };
295                coloring.set(u, v, color);
296            }
297        } else {
298            // Non-tree edge: assign color in green-yellow family
299            for coloring in &mut self.green_yellow_colorings {
300                let color = if (u * v) % 2 == 0 {
301                    EdgeColor::Green
302                } else {
303                    EdgeColor::Yellow
304                };
305                coloring.set(u, v, color);
306            }
307        }
308    }
309
310    /// Delete an edge
311    pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
312        let key = if u < v { (u, v) } else { (v, u) };
313
314        if !self.edges.remove(&key) {
315            return;
316        }
317
318        if let Some(neighbors) = self.adjacency.get_mut(&u) {
319            neighbors.remove(&v);
320        }
321        if let Some(neighbors) = self.adjacency.get_mut(&v) {
322            neighbors.remove(&u);
323        }
324
325        self.forests.delete_edge(u, v);
326    }
327
328    /// Query: Find all cuts containing vertex v with volume ≤ ν and cut-size ≤ λ_max
329    /// This is Algorithm 4.1 from the paper
330    pub fn query(&self, v: VertexId) -> Vec<LocalCut> {
331        let mut results = Vec::new();
332        let mut seen_cuts: HashSet<Vec<VertexId>> = HashSet::new();
333
334        // For each (forest, red-blue coloring, green-yellow coloring) triple
335        for forest_id in 0..self.forests.num_forests {
336            for rb_coloring in &self.red_blue_colorings {
337                for gy_coloring in &self.green_yellow_colorings {
338                    // Execute color-coded DFS
339                    if let Some(cut) = self.color_coded_dfs(
340                        v,
341                        forest_id,
342                        rb_coloring,
343                        gy_coloring,
344                    ) {
345                        // Deduplicate cuts
346                        let mut sorted_vertices: Vec<_> = cut.vertices.iter().copied().collect();
347                        sorted_vertices.sort();
348
349                        if !seen_cuts.contains(&sorted_vertices) && cut.cut_value <= self.lambda_max as f64 {
350                            seen_cuts.insert(sorted_vertices);
351                            results.push(cut);
352                        }
353                    }
354                }
355            }
356        }
357
358        results
359    }
360
361    /// Color-coded DFS from vertex v
362    /// Explores: blue edges in forest + green non-forest edges
363    /// Caps at volume ν
364    fn color_coded_dfs(
365        &self,
366        start: VertexId,
367        _forest_id: usize,
368        rb_coloring: &EdgeColoring,
369        gy_coloring: &EdgeColoring,
370    ) -> Option<LocalCut> {
371        let mut visited = HashSet::new();
372        let mut stack = vec![start];
373        let mut volume = 0usize;
374        let mut boundary = Vec::new();
375
376        while let Some(u) = stack.pop() {
377            if visited.contains(&u) {
378                continue;
379            }
380            visited.insert(u);
381
382            // Update volume
383            if let Some(neighbors) = self.adjacency.get(&u) {
384                volume += neighbors.len();
385
386                if volume > self.nu {
387                    // Volume exceeded - this cut is too large
388                    return None;
389                }
390
391                for (&v, &_weight) in neighbors {
392                    let is_tree_edge = self.forests.is_tree_edge(u, v);
393
394                    if is_tree_edge {
395                        // Tree edge: only follow if blue
396                        if rb_coloring.has_color(u, v, EdgeColor::Blue) {
397                            if !visited.contains(&v) {
398                                stack.push(v);
399                            }
400                        } else {
401                            // Red tree edge crosses the boundary
402                            boundary.push((u, v));
403                        }
404                    } else {
405                        // Non-tree edge: only follow if green
406                        if gy_coloring.has_color(u, v, EdgeColor::Green) {
407                            if !visited.contains(&v) {
408                                stack.push(v);
409                            }
410                        } else {
411                            // Yellow non-tree edge crosses the boundary
412                            if !visited.contains(&v) {
413                                boundary.push((u, v));
414                            }
415                        }
416                    }
417                }
418            }
419        }
420
421        // Calculate cut value
422        let cut_value: f64 = boundary.iter()
423            .map(|&(u, v)| {
424                self.adjacency.get(&u)
425                    .and_then(|n| n.get(&v))
426                    .copied()
427                    .unwrap_or(1.0)
428            })
429            .sum();
430
431        Some(LocalCut {
432            vertices: visited,
433            boundary,
434            cut_value,
435            volume,
436        })
437    }
438
439    /// Get all vertices
440    pub fn vertices(&self) -> Vec<VertexId> {
441        self.adjacency.keys().copied().collect()
442    }
443
444    /// Get neighbors of a vertex
445    pub fn neighbors(&self, v: VertexId) -> Vec<(VertexId, Weight)> {
446        self.adjacency.get(&v)
447            .map(|n| n.iter().map(|(&v, &w)| (v, w)).collect())
448            .unwrap_or_default()
449    }
450}
451
452#[cfg(test)]
453mod tests {
454    use super::*;
455
456    #[test]
457    fn test_forest_packing_basic() {
458        let mut packing = GreedyForestPacking::new(3);
459
460        // Path: 1-2-3-4
461        assert!(packing.insert_edge(1, 2).is_some());
462        assert!(packing.insert_edge(2, 3).is_some());
463        assert!(packing.insert_edge(3, 4).is_some());
464
465        assert!(packing.is_tree_edge(1, 2));
466        assert!(packing.is_tree_edge(2, 3));
467    }
468
469    #[test]
470    fn test_forest_packing_cycle() {
471        let mut packing = GreedyForestPacking::new(3);
472
473        // Triangle: 1-2-3-1
474        packing.insert_edge(1, 2);
475        packing.insert_edge(2, 3);
476        // This edge closes the cycle - goes to different forest
477        let forest = packing.insert_edge(1, 3);
478
479        // Should still fit in some forest
480        assert!(forest.is_some());
481    }
482
483    #[test]
484    fn test_localkcut_query() {
485        let mut lkc = DeterministicLocalKCut::new(10, 100, 2);
486
487        // Simple path graph
488        lkc.insert_edge(1, 2, 1.0);
489        lkc.insert_edge(2, 3, 1.0);
490        lkc.insert_edge(3, 4, 1.0);
491
492        let cuts = lkc.query(1);
493
494        // Should find at least one cut containing vertex 1
495        assert!(!cuts.is_empty());
496        assert!(cuts.iter().any(|c| c.vertices.contains(&1)));
497    }
498
499    #[test]
500    fn test_coloring_family() {
501        let family = generate_coloring_family(2, 5, 100);
502        assert!(!family.is_empty());
503    }
504
505    #[test]
506    fn test_edge_deletion() {
507        let mut lkc = DeterministicLocalKCut::new(10, 100, 2);
508
509        lkc.insert_edge(1, 2, 1.0);
510        lkc.insert_edge(2, 3, 1.0);
511
512        lkc.delete_edge(1, 2);
513
514        // Edge should be gone
515        assert!(!lkc.forests.is_tree_edge(1, 2));
516    }
517}