ruvector_mincut/algorithm/
mod.rs

1//! Core Dynamic Minimum Cut Algorithm
2//!
3//! Provides the main algorithm with:
4//! - O(n^{o(1)}) amortized update time
5//! - Support for edge insertions and deletions
6//! - Both exact and approximate modes
7//!
8//! ## Modules
9//!
10//! - [`replacement`]: Replacement edge index for tree edge deletions
11//! - [`approximate`]: (1+ε)-approximate min-cut for all cut sizes (SODA 2025)
12
13pub mod approximate;
14pub mod replacement;
15
16pub use replacement::{ReplacementEdgeIndex, ReplacementIndexStats};
17
18use crate::error::{MinCutError, Result};
19use crate::euler::EulerTourTree;
20use crate::graph::{DynamicGraph, Edge, EdgeId, VertexId, Weight};
21use crate::linkcut::LinkCutTree;
22use crate::tree::HierarchicalDecomposition;
23use parking_lot::RwLock;
24use std::sync::Arc;
25use std::time::Instant;
26
27/// Configuration for the minimum cut algorithm
28#[derive(Debug, Clone)]
29pub struct MinCutConfig {
30    /// Maximum cut size supported for exact algorithm
31    pub max_exact_cut_size: usize,
32    /// Epsilon for approximate algorithm (0 < ε ≤ 1)
33    pub epsilon: f64,
34    /// Whether to use approximate mode
35    pub approximate: bool,
36    /// Enable parallel computation
37    pub parallel: bool,
38    /// Cache size for intermediate results
39    pub cache_size: usize,
40}
41
42impl Default for MinCutConfig {
43    fn default() -> Self {
44        Self {
45            max_exact_cut_size: 1000,
46            epsilon: 0.1,
47            approximate: false,
48            parallel: true,
49            cache_size: 10000,
50        }
51    }
52}
53
54/// Result of a minimum cut query
55#[derive(Debug, Clone)]
56pub struct MinCutResult {
57    /// The minimum cut value
58    pub value: f64,
59    /// Edges in the cut (if requested)
60    pub cut_edges: Option<Vec<Edge>>,
61    /// Partition (if requested): (S, T) where S and T are vertex sets
62    pub partition: Option<(Vec<VertexId>, Vec<VertexId>)>,
63    /// Whether this is an exact or approximate result
64    pub is_exact: bool,
65    /// Approximation ratio (1.0 for exact)
66    pub approximation_ratio: f64,
67}
68
69/// Statistics about algorithm performance
70#[derive(Debug, Clone, Default)]
71pub struct AlgorithmStats {
72    /// Total number of insertions
73    pub insertions: u64,
74    /// Total number of deletions
75    pub deletions: u64,
76    /// Total number of queries
77    pub queries: u64,
78    /// Average update time in microseconds
79    pub avg_update_time_us: f64,
80    /// Average query time in microseconds
81    pub avg_query_time_us: f64,
82    /// Number of tree restructures
83    pub restructures: u64,
84}
85
86/// The main dynamic minimum cut structure
87pub struct DynamicMinCut {
88    /// The underlying graph
89    graph: Arc<RwLock<DynamicGraph>>,
90    /// Hierarchical decomposition
91    decomposition: HierarchicalDecomposition,
92    /// Link-cut tree for connectivity
93    link_cut_tree: LinkCutTree,
94    /// Spanning forest for the graph (using Euler Tour Tree)
95    spanning_forest: EulerTourTree,
96    /// Current minimum cut value
97    current_min_cut: f64,
98    /// Configuration
99    config: MinCutConfig,
100    /// Statistics
101    stats: Arc<RwLock<AlgorithmStats>>,
102    /// Tracks which edges are in the spanning forest (tree edges)
103    tree_edges: Arc<RwLock<std::collections::HashSet<(VertexId, VertexId)>>>,
104}
105
106impl DynamicMinCut {
107    /// Create a new dynamic minimum cut structure
108    pub fn new(config: MinCutConfig) -> Self {
109        let empty_graph = Arc::new(DynamicGraph::new());
110        Self {
111            graph: Arc::new(RwLock::new(DynamicGraph::new())),
112            decomposition: HierarchicalDecomposition::build(empty_graph).unwrap(),
113            link_cut_tree: LinkCutTree::new(),
114            spanning_forest: EulerTourTree::new(),
115            current_min_cut: f64::INFINITY,
116            config,
117            stats: Arc::new(RwLock::new(AlgorithmStats::default())),
118            tree_edges: Arc::new(RwLock::new(std::collections::HashSet::new())),
119        }
120    }
121
122    /// Build from an existing graph
123    pub fn from_graph(graph: DynamicGraph, config: MinCutConfig) -> Result<Self> {
124        // Create shared graph instance
125        let graph_shared = Arc::new(RwLock::new(graph.clone()));
126
127        // Initialize link-cut tree and Euler tour tree with all vertices
128        let mut link_cut_tree = LinkCutTree::new();
129        let mut spanning_forest = EulerTourTree::new();
130
131        let vertices = graph.vertices();
132        for &v in &vertices {
133            link_cut_tree.make_tree(v, 0.0);
134            spanning_forest.make_tree(v)?;
135        }
136
137        // Build spanning forest using DFS
138        let mut visited = std::collections::HashSet::new();
139        let mut tree_edges = std::collections::HashSet::new();
140
141        for &start_vertex in &vertices {
142            if visited.contains(&start_vertex) {
143                continue;
144            }
145
146            // DFS to build spanning tree for this component
147            let mut stack = vec![start_vertex];
148            visited.insert(start_vertex);
149
150            while let Some(u) = stack.pop() {
151                let neighbors = graph.neighbors(u);
152                for (v, _) in neighbors {
153                    if !visited.contains(&v) {
154                        visited.insert(v);
155                        stack.push(v);
156
157                        // Add edge to spanning forest
158                        link_cut_tree.link(u, v)?;
159                        spanning_forest.link(u, v)?;
160
161                        // Track as tree edge
162                        let key = if u < v { (u, v) } else { (v, u) };
163                        tree_edges.insert(key);
164                    }
165                }
166            }
167        }
168
169        // Initialize hierarchical decomposition with the same shared graph
170        let graph_for_decomp = Arc::new(graph);
171        let decomposition = HierarchicalDecomposition::build(graph_for_decomp)?;
172
173        // Create the structure first
174        let mut mincut = Self {
175            graph: graph_shared,
176            decomposition,
177            link_cut_tree,
178            spanning_forest,
179            current_min_cut: f64::INFINITY,
180            config,
181            stats: Arc::new(RwLock::new(AlgorithmStats::default())),
182            tree_edges: Arc::new(RwLock::new(tree_edges)),
183        };
184
185        // Now compute the initial minimum cut using the tree-edge-based method
186        mincut.recompute_min_cut();
187
188        Ok(mincut)
189    }
190
191    /// Insert an edge
192    pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) -> Result<f64> {
193        let start_time = Instant::now();
194
195        // Add edge to graph (use write lock)
196        {
197            let graph = self.graph.write();
198            graph.insert_edge(u, v, weight)?;
199        }
200
201        // Ensure vertices exist in data structures
202        // Create vertices in link-cut tree and Euler tour tree if they don't exist
203        let u_exists = self.link_cut_tree.len() > 0 && self.link_cut_tree.find_root(u).is_ok();
204        let v_exists = self.link_cut_tree.len() > 0 && self.link_cut_tree.find_root(v).is_ok();
205
206        if !u_exists {
207            self.link_cut_tree.make_tree(u, 0.0);
208            self.spanning_forest.make_tree(u)?;
209        }
210        if !v_exists {
211            self.link_cut_tree.make_tree(v, 0.0);
212            self.spanning_forest.make_tree(v)?;
213        }
214
215        // Check if they're already in different components
216        let connected = self.link_cut_tree.connected(u, v);
217
218        if !connected {
219            // Vertices are in different components - this edge connects them
220            self.handle_bridge_edge(u, v, weight)?;
221        } else {
222            // Edge creates a cycle - this is a non-tree edge
223            self.handle_cycle_edge(u, v, weight)?;
224        }
225
226        // Rebuild decomposition with updated graph
227        self.rebuild_decomposition();
228
229        // Recompute minimum cut
230        self.recompute_min_cut();
231
232        // Update statistics
233        let elapsed = start_time.elapsed().as_micros() as f64;
234        let mut stats = self.stats.write();
235        stats.insertions += 1;
236        let n = stats.insertions as f64;
237        stats.avg_update_time_us = (stats.avg_update_time_us * (n - 1.0) + elapsed) / n;
238        drop(stats);
239
240        Ok(self.current_min_cut)
241    }
242
243    /// Delete an edge
244    pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<f64> {
245        let start_time = Instant::now();
246
247        // Remove from graph first (use write lock)
248        {
249            let graph = self.graph.write();
250            graph.delete_edge(u, v)?;
251        }
252
253        // Check if edge was a tree edge
254        let key = if u < v { (u, v) } else { (v, u) };
255        let is_tree_edge = self.tree_edges.read().contains(&key);
256
257        if is_tree_edge {
258            self.handle_tree_edge_deletion(u, v)?;
259        } else {
260            self.handle_non_tree_edge_deletion(u, v)?;
261        }
262
263        // Rebuild decomposition with updated graph
264        self.rebuild_decomposition();
265
266        // Recompute minimum cut
267        self.recompute_min_cut();
268
269        // Update statistics
270        let elapsed = start_time.elapsed().as_micros() as f64;
271        let mut stats = self.stats.write();
272        stats.deletions += 1;
273        let n = stats.deletions as f64;
274        stats.avg_update_time_us = (stats.avg_update_time_us * (n - 1.0) + elapsed) / n;
275        drop(stats);
276
277        Ok(self.current_min_cut)
278    }
279
280    /// Get the current minimum cut value (O(1))
281    pub fn min_cut_value(&self) -> f64 {
282        let start_time = Instant::now();
283
284        let value = self.current_min_cut;
285
286        // Update query statistics
287        let elapsed = start_time.elapsed().as_micros() as f64;
288        let mut stats = self.stats.write();
289        stats.queries += 1;
290        let n = stats.queries as f64;
291        stats.avg_query_time_us = (stats.avg_query_time_us * (n - 1.0) + elapsed) / n;
292
293        value
294    }
295
296    /// Get detailed minimum cut result
297    pub fn min_cut(&self) -> MinCutResult {
298        let value = self.min_cut_value();
299        let (partition_s, partition_t) = self.partition();
300        let edges = self.cut_edges();
301
302        MinCutResult {
303            value,
304            cut_edges: Some(edges),
305            partition: Some((partition_s, partition_t)),
306            is_exact: !self.config.approximate,
307            approximation_ratio: if self.config.approximate {
308                1.0 + self.config.epsilon
309            } else {
310                1.0
311            },
312        }
313    }
314
315    /// Get the cut partition
316    pub fn partition(&self) -> (Vec<VertexId>, Vec<VertexId>) {
317        // Use the decomposition's partition
318        let (set_a, set_b) = self.decomposition.min_cut_partition();
319        (set_a.into_iter().collect(), set_b.into_iter().collect())
320    }
321
322    /// Get edges in the minimum cut
323    pub fn cut_edges(&self) -> Vec<Edge> {
324        let (partition_s, partition_t) = self.partition();
325        let partition_t_set: std::collections::HashSet<_> = partition_t.iter().copied().collect();
326
327        let graph = self.graph.read();
328        let mut cut_edges = Vec::new();
329
330        for &u in &partition_s {
331            let neighbors = graph.neighbors(u);
332            for (v, _) in neighbors {
333                if partition_t_set.contains(&v) {
334                    if let Some(edge) = graph.get_edge(u, v) {
335                        cut_edges.push(edge);
336                    }
337                }
338            }
339        }
340
341        cut_edges
342    }
343
344    /// Check if graph is connected
345    pub fn is_connected(&self) -> bool {
346        let graph = self.graph.read();
347        graph.is_connected()
348    }
349
350    /// Get algorithm statistics
351    pub fn stats(&self) -> AlgorithmStats {
352        self.stats.read().clone()
353    }
354
355    /// Reset statistics
356    pub fn reset_stats(&mut self) {
357        *self.stats.write() = AlgorithmStats::default();
358    }
359
360    /// Get configuration
361    pub fn config(&self) -> &MinCutConfig {
362        &self.config
363    }
364
365    /// Get reference to underlying graph
366    pub fn graph(&self) -> Arc<RwLock<DynamicGraph>> {
367        Arc::clone(&self.graph)
368    }
369
370    /// Number of vertices
371    pub fn num_vertices(&self) -> usize {
372        self.graph.read().num_vertices()
373    }
374
375    /// Number of edges
376    pub fn num_edges(&self) -> usize {
377        self.graph.read().num_edges()
378    }
379
380    // ===== Internal methods =====
381
382    /// Handle insertion when edge creates a cycle (non-tree edge)
383    fn handle_cycle_edge(&mut self, _u: VertexId, _v: VertexId, _weight: Weight) -> Result<()> {
384        // Non-tree edges don't change connectivity but may affect cut value
385        // The decomposition handles cut value updates
386        Ok(())
387    }
388
389    /// Handle insertion when edge connects components (bridge edge)
390    fn handle_bridge_edge(&mut self, u: VertexId, v: VertexId, _weight: Weight) -> Result<()> {
391        // Add to spanning forest
392        self.link_cut_tree.link(u, v)?;
393        self.spanning_forest.link(u, v)?;
394
395        // Track as tree edge
396        let key = if u < v { (u, v) } else { (v, u) };
397        self.tree_edges.write().insert(key);
398
399        Ok(())
400    }
401
402    /// Handle deletion of a tree edge (find replacement)
403    fn handle_tree_edge_deletion(&mut self, u: VertexId, v: VertexId) -> Result<()> {
404        // Remove from tree edges
405        let key = if u < v { (u, v) } else { (v, u) };
406        self.tree_edges.write().remove(&key);
407
408        // Cut in link-cut tree if they're still connected
409        // (They might already be disconnected from previous deletions)
410        if self.link_cut_tree.connected(u, v) {
411            // Cut from u's perspective (or v's if u is already a root)
412            // Try cutting u first, if it fails try v
413            if self.link_cut_tree.cut(u).is_err() {
414                let _ = self.link_cut_tree.cut(v); // Ignore error if both fail
415            }
416        }
417
418        // Try to find a replacement edge
419        if let Some((x, y)) = self.find_replacement_edge(u, v) {
420            // Check if they're already connected before linking
421            let already_connected = self.link_cut_tree.connected(x, y);
422
423            if !already_connected {
424                // Add replacement edge to spanning forest
425                self.link_cut_tree.link(x, y)?;
426
427                // Only link in spanning forest if not already connected
428                if !self.spanning_forest.connected(x, y) {
429                    let _ = self.spanning_forest.link(x, y); // Ignore errors here
430                }
431
432                // Track as tree edge
433                let key = if x < y { (x, y) } else { (y, x) };
434                self.tree_edges.write().insert(key);
435            }
436        } else {
437            // Graph is now disconnected
438            // Minimum cut becomes 0
439            self.stats.write().restructures += 1;
440        }
441
442        Ok(())
443    }
444
445    /// Handle deletion of a non-tree edge
446    fn handle_non_tree_edge_deletion(&mut self, _u: VertexId, _v: VertexId) -> Result<()> {
447        // Non-tree edge deletion doesn't affect connectivity
448        // But may affect minimum cut value (handled by decomposition)
449        Ok(())
450    }
451
452    /// Find a replacement edge for a deleted tree edge
453    fn find_replacement_edge(&mut self, u: VertexId, v: VertexId) -> Option<(VertexId, VertexId)> {
454        // Get the two components after cutting
455        // Use BFS to find vertices reachable from u (without using edge u-v)
456        let graph = self.graph.read();
457
458        let mut comp_u = std::collections::HashSet::new();
459        let mut queue = std::collections::VecDeque::new();
460
461        queue.push_back(u);
462        comp_u.insert(u);
463
464        while let Some(x) = queue.pop_front() {
465            let neighbors = graph.neighbors(x);
466            for (y, _) in neighbors {
467                // Skip the deleted edge
468                if (x == u && y == v) || (x == v && y == u) {
469                    continue;
470                }
471
472                // Only traverse tree edges for connectivity
473                let key = if x < y { (x, y) } else { (y, x) };
474                if !self.tree_edges.read().contains(&key) {
475                    continue;
476                }
477
478                if comp_u.insert(y) {
479                    queue.push_back(y);
480                }
481            }
482        }
483
484        // Now search for non-tree edges crossing the cut
485        for &x in &comp_u {
486            let neighbors = graph.neighbors(x);
487            for (y, _) in neighbors {
488                if !comp_u.contains(&y) {
489                    // Found a replacement edge
490                    return Some((x, y));
491                }
492            }
493        }
494
495        None
496    }
497
498    /// Rebuild the hierarchical decomposition with current graph state
499    fn rebuild_decomposition(&mut self) {
500        let graph = self.graph.read();
501        let graph_clone = graph.clone();
502        drop(graph);
503
504        let graph_for_decomp = Arc::new(graph_clone);
505        self.decomposition =
506            HierarchicalDecomposition::build(graph_for_decomp).unwrap_or_else(|_| {
507                // If build fails, create an empty one
508                let empty = Arc::new(DynamicGraph::new());
509                HierarchicalDecomposition::build(empty).unwrap()
510            });
511    }
512
513    /// Recompute minimum cut after structural change
514    fn recompute_min_cut(&mut self) {
515        let graph = self.graph.read();
516
517        // If graph is disconnected, minimum cut is 0
518        if !graph.is_connected() {
519            self.current_min_cut = 0.0;
520            drop(graph);
521            return;
522        }
523
524        // Compute minimum cut by checking all tree edge cuts
525        let mut min_cut = self.decomposition.min_cut_value();
526
527        // For each tree edge, compute the cut value when removing it
528        // This gives us all possible 2-partitions induced by the spanning tree
529        let tree_edges: Vec<_> = self.tree_edges.read().iter().copied().collect();
530
531        for (u, v) in tree_edges {
532            let cut_value = self.compute_tree_edge_cut(&graph, u, v);
533            if cut_value < min_cut {
534                min_cut = cut_value;
535            }
536        }
537
538        drop(graph);
539        self.current_min_cut = min_cut;
540    }
541
542    /// Compute the cut value induced by removing a tree edge
543    fn compute_tree_edge_cut(&self, graph: &DynamicGraph, u: VertexId, v: VertexId) -> f64 {
544        // Find all vertices reachable from u without using edge (u,v)
545        let mut component_u = std::collections::HashSet::new();
546        let mut queue = std::collections::VecDeque::new();
547
548        queue.push_back(u);
549        component_u.insert(u);
550
551        while let Some(x) = queue.pop_front() {
552            for (y, _) in graph.neighbors(x) {
553                // Skip the tree edge we're "removing"
554                if (x == u && y == v) || (x == v && y == u) {
555                    continue;
556                }
557
558                // Only traverse tree edges for connectivity
559                let key = if x < y { (x, y) } else { (y, x) };
560                if !self.tree_edges.read().contains(&key) {
561                    continue;
562                }
563
564                if component_u.insert(y) {
565                    queue.push_back(y);
566                }
567            }
568        }
569
570        // Now compute cut: sum of all edge weights crossing the partition
571        let mut cut_weight = 0.0;
572        for &x in &component_u {
573            for (y, _) in graph.neighbors(x) {
574                if !component_u.contains(&y) {
575                    if let Some(weight) = graph.edge_weight(x, y) {
576                        cut_weight += weight;
577                    }
578                }
579            }
580        }
581
582        cut_weight
583    }
584}
585
586/// Builder for DynamicMinCut
587pub struct MinCutBuilder {
588    config: MinCutConfig,
589    initial_edges: Vec<(VertexId, VertexId, Weight)>,
590}
591
592impl MinCutBuilder {
593    /// Create a new builder
594    pub fn new() -> Self {
595        Self {
596            config: MinCutConfig::default(),
597            initial_edges: Vec::new(),
598        }
599    }
600
601    /// Use exact algorithm
602    pub fn exact(mut self) -> Self {
603        self.config.approximate = false;
604        self
605    }
606
607    /// Use approximate algorithm with given epsilon
608    pub fn approximate(mut self, epsilon: f64) -> Self {
609        assert!(epsilon > 0.0 && epsilon <= 1.0, "Epsilon must be in (0, 1]");
610        self.config.approximate = true;
611        self.config.epsilon = epsilon;
612        self
613    }
614
615    /// Set maximum cut size for exact algorithm
616    pub fn max_cut_size(mut self, size: usize) -> Self {
617        self.config.max_exact_cut_size = size;
618        self
619    }
620
621    /// Enable or disable parallel computation
622    pub fn parallel(mut self, enabled: bool) -> Self {
623        self.config.parallel = enabled;
624        self
625    }
626
627    /// Add initial edges
628    pub fn with_edges(mut self, edges: Vec<(VertexId, VertexId, Weight)>) -> Self {
629        self.initial_edges = edges;
630        self
631    }
632
633    /// Build the minimum cut structure
634    pub fn build(self) -> Result<DynamicMinCut> {
635        if self.initial_edges.is_empty() {
636            Ok(DynamicMinCut::new(self.config))
637        } else {
638            // Create graph with initial edges
639            let graph = DynamicGraph::new();
640            for (u, v, weight) in &self.initial_edges {
641                graph.insert_edge(*u, *v, *weight)?;
642            }
643
644            DynamicMinCut::from_graph(graph, self.config)
645        }
646    }
647}
648
649impl Default for MinCutBuilder {
650    fn default() -> Self {
651        Self::new()
652    }
653}
654
655#[cfg(test)]
656mod tests {
657    use super::*;
658
659    #[test]
660    fn test_empty_graph() {
661        let mincut = DynamicMinCut::new(MinCutConfig::default());
662        assert_eq!(mincut.min_cut_value(), f64::INFINITY);
663        assert_eq!(mincut.num_vertices(), 0);
664        assert_eq!(mincut.num_edges(), 0);
665    }
666
667    #[test]
668    fn test_single_edge() {
669        let mincut = MinCutBuilder::new()
670            .with_edges(vec![(1, 2, 1.0)])
671            .build()
672            .unwrap();
673
674        assert_eq!(mincut.num_vertices(), 2);
675        assert_eq!(mincut.num_edges(), 1);
676        assert_eq!(mincut.min_cut_value(), 1.0);
677        assert!(mincut.is_connected());
678    }
679
680    #[test]
681    fn test_triangle() {
682        let edges = vec![(1, 2, 1.0), (2, 3, 1.0), (3, 1, 1.0)];
683
684        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
685
686        assert_eq!(mincut.num_vertices(), 3);
687        assert_eq!(mincut.num_edges(), 3);
688        assert_eq!(mincut.min_cut_value(), 2.0); // Minimum cut is 2
689        assert!(mincut.is_connected());
690    }
691
692    #[test]
693    fn test_insert_edge() {
694        let mut mincut = MinCutBuilder::new()
695            .with_edges(vec![(1, 2, 1.0)])
696            .build()
697            .unwrap();
698
699        let cut_value = mincut.insert_edge(2, 3, 1.0).unwrap();
700        assert_eq!(mincut.num_edges(), 2);
701        assert_eq!(cut_value, 1.0);
702    }
703
704    #[test]
705    fn test_delete_edge() {
706        let mut mincut = MinCutBuilder::new()
707            .with_edges(vec![(1, 2, 1.0), (2, 3, 1.0)])
708            .build()
709            .unwrap();
710
711        assert!(mincut.is_connected());
712
713        let cut_value = mincut.delete_edge(1, 2).unwrap();
714        assert_eq!(mincut.num_edges(), 1);
715        // After deleting edge, graph becomes disconnected
716        assert_eq!(cut_value, 0.0);
717    }
718
719    #[test]
720    fn test_disconnected_graph() {
721        let mincut = MinCutBuilder::new()
722            .with_edges(vec![(1, 2, 1.0), (3, 4, 1.0)])
723            .build()
724            .unwrap();
725
726        assert!(!mincut.is_connected());
727        assert_eq!(mincut.min_cut_value(), 0.0);
728    }
729
730    #[test]
731    fn test_weighted_edges() {
732        let edges = vec![(1, 2, 2.0), (2, 3, 3.0), (3, 1, 1.0)];
733
734        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
735
736        // Minimum cut should be 2.0 (cutting {1} from {2,3} or similar)
737        assert_eq!(mincut.min_cut_value(), 3.0);
738    }
739
740    #[test]
741    fn test_partition() {
742        let edges = vec![(1, 2, 1.0), (2, 3, 1.0), (3, 4, 1.0)];
743
744        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
745
746        let (s, t) = mincut.partition();
747        assert!(!s.is_empty());
748        assert!(!t.is_empty());
749        assert_eq!(s.len() + t.len(), 4);
750    }
751
752    #[test]
753    fn test_cut_edges() {
754        let edges = vec![(1, 2, 1.0), (2, 3, 1.0)];
755
756        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
757
758        let cut = mincut.cut_edges();
759        assert!(!cut.is_empty());
760        assert!(cut.len() <= 2);
761    }
762
763    #[test]
764    fn test_min_cut_result() {
765        let edges = vec![(1, 2, 1.0), (2, 3, 1.0), (3, 1, 1.0)];
766
767        let mincut = MinCutBuilder::new()
768            .exact()
769            .with_edges(edges)
770            .build()
771            .unwrap();
772
773        let result = mincut.min_cut();
774        assert!(result.is_exact);
775        assert_eq!(result.approximation_ratio, 1.0);
776        assert!(result.cut_edges.is_some());
777        assert!(result.partition.is_some());
778    }
779
780    #[test]
781    fn test_approximate_mode() {
782        let mincut = MinCutBuilder::new().approximate(0.1).build().unwrap();
783
784        let result = mincut.min_cut();
785        assert!(!result.is_exact);
786        assert_eq!(result.approximation_ratio, 1.1);
787    }
788
789    #[test]
790    fn test_statistics() {
791        let mut mincut = MinCutBuilder::new()
792            .with_edges(vec![(1, 2, 1.0)])
793            .build()
794            .unwrap();
795
796        mincut.insert_edge(2, 3, 1.0).unwrap();
797        mincut.delete_edge(1, 2).unwrap();
798        let _ = mincut.min_cut_value();
799
800        let stats = mincut.stats();
801        assert_eq!(stats.insertions, 1);
802        assert_eq!(stats.deletions, 1);
803        assert_eq!(stats.queries, 1);
804        assert!(stats.avg_update_time_us > 0.0);
805    }
806
807    #[test]
808    fn test_reset_stats() {
809        let mut mincut = MinCutBuilder::new()
810            .with_edges(vec![(1, 2, 1.0)])
811            .build()
812            .unwrap();
813
814        mincut.insert_edge(2, 3, 1.0).unwrap();
815        assert_eq!(mincut.stats().insertions, 1);
816
817        mincut.reset_stats();
818        assert_eq!(mincut.stats().insertions, 0);
819    }
820
821    #[test]
822    fn test_builder_pattern() {
823        let mincut = MinCutBuilder::new()
824            .exact()
825            .max_cut_size(500)
826            .parallel(true)
827            .with_edges(vec![(1, 2, 1.0)])
828            .build()
829            .unwrap();
830
831        assert!(!mincut.config().approximate);
832        assert_eq!(mincut.config().max_exact_cut_size, 500);
833        assert!(mincut.config().parallel);
834    }
835
836    #[test]
837    fn test_large_graph() {
838        let mut edges = Vec::new();
839
840        // Create a chain: 0 - 1 - 2 - ... - 99
841        for i in 0..99 {
842            edges.push((i, i + 1, 1.0));
843        }
844
845        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
846
847        assert_eq!(mincut.num_vertices(), 100);
848        assert_eq!(mincut.num_edges(), 99);
849        assert_eq!(mincut.min_cut_value(), 1.0); // Minimum cut is 1
850        assert!(mincut.is_connected());
851    }
852
853    #[test]
854    fn test_tree_edge_deletion_with_replacement() {
855        let mut mincut = MinCutBuilder::new()
856            .with_edges(vec![
857                (1, 2, 1.0),
858                (2, 3, 1.0),
859                (1, 3, 1.0), // Creates a cycle
860            ])
861            .build()
862            .unwrap();
863
864        assert!(mincut.is_connected());
865
866        // Delete one edge - graph should remain connected due to replacement
867        mincut.delete_edge(1, 2).unwrap();
868
869        // Still has 2 edges
870        assert_eq!(mincut.num_edges(), 2);
871    }
872
873    #[test]
874    fn test_multiple_components() {
875        let edges = vec![(1, 2, 1.0), (3, 4, 1.0), (5, 6, 1.0)];
876
877        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
878
879        assert!(!mincut.is_connected());
880        assert_eq!(mincut.min_cut_value(), 0.0);
881    }
882
883    #[test]
884    fn test_dynamic_updates() {
885        let mut mincut = MinCutBuilder::new().build().unwrap();
886
887        // Start empty
888        assert_eq!(mincut.min_cut_value(), f64::INFINITY);
889
890        // Add first edge (creates two vertices)
891        mincut.insert_edge(1, 2, 2.0).unwrap();
892        assert_eq!(mincut.min_cut_value(), 2.0);
893
894        // Complete the triangle
895        mincut.insert_edge(2, 3, 3.0).unwrap();
896        mincut.insert_edge(3, 1, 1.0).unwrap();
897        assert_eq!(mincut.min_cut_value(), 3.0); // min cut
898
899        // Delete heaviest edge
900        mincut.delete_edge(2, 3).unwrap();
901        assert_eq!(mincut.min_cut_value(), 1.0); // Now path graph
902    }
903
904    #[test]
905    fn test_config_access() {
906        let mincut = MinCutBuilder::new()
907            .approximate(0.2)
908            .max_cut_size(2000)
909            .build()
910            .unwrap();
911
912        let config = mincut.config();
913        assert_eq!(config.epsilon, 0.2);
914        assert_eq!(config.max_exact_cut_size, 2000);
915        assert!(config.approximate);
916    }
917
918    #[test]
919    fn test_graph_access() {
920        let mincut = MinCutBuilder::new()
921            .with_edges(vec![(1, 2, 1.0)])
922            .build()
923            .unwrap();
924
925        let graph = mincut.graph();
926        let g = graph.read();
927        assert_eq!(g.num_vertices(), 2);
928        assert_eq!(g.num_edges(), 1);
929    }
930
931    #[test]
932    fn test_bridge_graph() {
933        // Two triangles connected by a bridge
934        let edges = vec![
935            // Triangle 1: 1-2-3-1
936            (1, 2, 2.0),
937            (2, 3, 2.0),
938            (3, 1, 2.0),
939            // Bridge
940            (3, 4, 1.0),
941            // Triangle 2: 4-5-6-4
942            (4, 5, 2.0),
943            (5, 6, 2.0),
944            (6, 4, 2.0),
945        ];
946
947        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
948
949        // Minimum cut should be the bridge with weight 1.0
950        assert_eq!(mincut.min_cut_value(), 1.0);
951        assert!(mincut.is_connected());
952    }
953
954    #[test]
955    fn test_complete_graph_k4() {
956        // Complete graph on 4 vertices
957        let mut edges = Vec::new();
958        for i in 1..=4 {
959            for j in (i + 1)..=4 {
960                edges.push((i, j, 1.0));
961            }
962        }
963
964        let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
965
966        assert_eq!(mincut.num_vertices(), 4);
967        assert_eq!(mincut.num_edges(), 6);
968        // Minimum cut of K4 is 3 (degree of any vertex)
969        assert_eq!(mincut.min_cut_value(), 3.0);
970    }
971
972    #[test]
973    fn test_sequential_insertions() {
974        let mut mincut = MinCutBuilder::new().build().unwrap();
975
976        // Build graph incrementally
977        mincut.insert_edge(1, 2, 1.0).unwrap();
978        assert_eq!(mincut.min_cut_value(), 1.0);
979
980        mincut.insert_edge(2, 3, 1.0).unwrap();
981        assert_eq!(mincut.min_cut_value(), 1.0);
982
983        mincut.insert_edge(3, 4, 1.0).unwrap();
984        assert_eq!(mincut.min_cut_value(), 1.0);
985
986        // Add cycle closure
987        mincut.insert_edge(4, 1, 1.0).unwrap();
988        assert_eq!(mincut.min_cut_value(), 2.0);
989    }
990
991    #[test]
992    fn test_sequential_deletions() {
993        let mut mincut = MinCutBuilder::new()
994            .with_edges(vec![(1, 2, 1.0), (2, 3, 1.0), (3, 1, 1.0)])
995            .build()
996            .unwrap();
997
998        assert_eq!(mincut.min_cut_value(), 2.0);
999
1000        // Delete one edge
1001        mincut.delete_edge(1, 2).unwrap();
1002        assert_eq!(mincut.min_cut_value(), 1.0);
1003
1004        // Delete another edge - disconnects graph
1005        mincut.delete_edge(2, 3).unwrap();
1006        assert_eq!(mincut.min_cut_value(), 0.0);
1007    }
1008}