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 replacement;
14pub mod approximate;
15
16pub use replacement::{ReplacementEdgeIndex, ReplacementIndexStats};
17
18use std::sync::Arc;
19use std::time::Instant;
20use parking_lot::RwLock;
21use crate::graph::{DynamicGraph, VertexId, EdgeId, Weight, Edge};
22use crate::tree::HierarchicalDecomposition;
23use crate::linkcut::LinkCutTree;
24use crate::euler::EulerTourTree;
25use crate::error::{MinCutError, Result};
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 &&
204            self.link_cut_tree.find_root(u).is_ok();
205        let v_exists = self.link_cut_tree.len() > 0 &&
206            self.link_cut_tree.find_root(v).is_ok();
207
208        if !u_exists {
209            self.link_cut_tree.make_tree(u, 0.0);
210            self.spanning_forest.make_tree(u)?;
211        }
212        if !v_exists {
213            self.link_cut_tree.make_tree(v, 0.0);
214            self.spanning_forest.make_tree(v)?;
215        }
216
217        // Check if they're already in different components
218        let connected = self.link_cut_tree.connected(u, v);
219
220        if !connected {
221            // Vertices are in different components - this edge connects them
222            self.handle_bridge_edge(u, v, weight)?;
223        } else {
224            // Edge creates a cycle - this is a non-tree edge
225            self.handle_cycle_edge(u, v, weight)?;
226        }
227
228        // Rebuild decomposition with updated graph
229        self.rebuild_decomposition();
230
231        // Recompute minimum cut
232        self.recompute_min_cut();
233
234        // Update statistics
235        let elapsed = start_time.elapsed().as_micros() as f64;
236        let mut stats = self.stats.write();
237        stats.insertions += 1;
238        let n = stats.insertions as f64;
239        stats.avg_update_time_us = (stats.avg_update_time_us * (n - 1.0) + elapsed) / n;
240        drop(stats);
241
242        Ok(self.current_min_cut)
243    }
244
245    /// Delete an edge
246    pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<f64> {
247        let start_time = Instant::now();
248
249        // Remove from graph first (use write lock)
250        {
251            let graph = self.graph.write();
252            graph.delete_edge(u, v)?;
253        }
254
255        // Check if edge was a tree edge
256        let key = if u < v { (u, v) } else { (v, u) };
257        let is_tree_edge = self.tree_edges.read().contains(&key);
258
259        if is_tree_edge {
260            self.handle_tree_edge_deletion(u, v)?;
261        } else {
262            self.handle_non_tree_edge_deletion(u, v)?;
263        }
264
265        // Rebuild decomposition with updated graph
266        self.rebuild_decomposition();
267
268        // Recompute minimum cut
269        self.recompute_min_cut();
270
271        // Update statistics
272        let elapsed = start_time.elapsed().as_micros() as f64;
273        let mut stats = self.stats.write();
274        stats.deletions += 1;
275        let n = stats.deletions as f64;
276        stats.avg_update_time_us = (stats.avg_update_time_us * (n - 1.0) + elapsed) / n;
277        drop(stats);
278
279        Ok(self.current_min_cut)
280    }
281
282    /// Get the current minimum cut value (O(1))
283    pub fn min_cut_value(&self) -> f64 {
284        let start_time = Instant::now();
285
286        let value = self.current_min_cut;
287
288        // Update query statistics
289        let elapsed = start_time.elapsed().as_micros() as f64;
290        let mut stats = self.stats.write();
291        stats.queries += 1;
292        let n = stats.queries as f64;
293        stats.avg_query_time_us = (stats.avg_query_time_us * (n - 1.0) + elapsed) / n;
294
295        value
296    }
297
298    /// Get detailed minimum cut result
299    pub fn min_cut(&self) -> MinCutResult {
300        let value = self.min_cut_value();
301        let (partition_s, partition_t) = self.partition();
302        let edges = self.cut_edges();
303
304        MinCutResult {
305            value,
306            cut_edges: Some(edges),
307            partition: Some((partition_s, partition_t)),
308            is_exact: !self.config.approximate,
309            approximation_ratio: if self.config.approximate {
310                1.0 + self.config.epsilon
311            } else {
312                1.0
313            },
314        }
315    }
316
317    /// Get the cut partition
318    pub fn partition(&self) -> (Vec<VertexId>, Vec<VertexId>) {
319        // Use the decomposition's partition
320        let (set_a, set_b) = self.decomposition.min_cut_partition();
321        (set_a.into_iter().collect(), set_b.into_iter().collect())
322    }
323
324    /// Get edges in the minimum cut
325    pub fn cut_edges(&self) -> Vec<Edge> {
326        let (partition_s, partition_t) = self.partition();
327        let partition_t_set: std::collections::HashSet<_> = partition_t.iter().copied().collect();
328
329        let graph = self.graph.read();
330        let mut cut_edges = Vec::new();
331
332        for &u in &partition_s {
333            let neighbors = graph.neighbors(u);
334            for (v, _) in neighbors {
335                if partition_t_set.contains(&v) {
336                    if let Some(edge) = graph.get_edge(u, v) {
337                        cut_edges.push(edge);
338                    }
339                }
340            }
341        }
342
343        cut_edges
344    }
345
346    /// Check if graph is connected
347    pub fn is_connected(&self) -> bool {
348        let graph = self.graph.read();
349        graph.is_connected()
350    }
351
352    /// Get algorithm statistics
353    pub fn stats(&self) -> AlgorithmStats {
354        self.stats.read().clone()
355    }
356
357    /// Reset statistics
358    pub fn reset_stats(&mut self) {
359        *self.stats.write() = AlgorithmStats::default();
360    }
361
362    /// Get configuration
363    pub fn config(&self) -> &MinCutConfig {
364        &self.config
365    }
366
367    /// Get reference to underlying graph
368    pub fn graph(&self) -> Arc<RwLock<DynamicGraph>> {
369        Arc::clone(&self.graph)
370    }
371
372    /// Number of vertices
373    pub fn num_vertices(&self) -> usize {
374        self.graph.read().num_vertices()
375    }
376
377    /// Number of edges
378    pub fn num_edges(&self) -> usize {
379        self.graph.read().num_edges()
380    }
381
382    // ===== Internal methods =====
383
384    /// Handle insertion when edge creates a cycle (non-tree edge)
385    fn handle_cycle_edge(&mut self, _u: VertexId, _v: VertexId, _weight: Weight) -> Result<()> {
386        // Non-tree edges don't change connectivity but may affect cut value
387        // The decomposition handles cut value updates
388        Ok(())
389    }
390
391    /// Handle insertion when edge connects components (bridge edge)
392    fn handle_bridge_edge(&mut self, u: VertexId, v: VertexId, _weight: Weight) -> Result<()> {
393        // Add to spanning forest
394        self.link_cut_tree.link(u, v)?;
395        self.spanning_forest.link(u, v)?;
396
397        // Track as tree edge
398        let key = if u < v { (u, v) } else { (v, u) };
399        self.tree_edges.write().insert(key);
400
401        Ok(())
402    }
403
404    /// Handle deletion of a tree edge (find replacement)
405    fn handle_tree_edge_deletion(&mut self, u: VertexId, v: VertexId) -> Result<()> {
406        // Remove from tree edges
407        let key = if u < v { (u, v) } else { (v, u) };
408        self.tree_edges.write().remove(&key);
409
410        // Cut in link-cut tree if they're still connected
411        // (They might already be disconnected from previous deletions)
412        if self.link_cut_tree.connected(u, v) {
413            // Cut from u's perspective (or v's if u is already a root)
414            // Try cutting u first, if it fails try v
415            if self.link_cut_tree.cut(u).is_err() {
416                let _ = self.link_cut_tree.cut(v); // Ignore error if both fail
417            }
418        }
419
420        // Try to find a replacement edge
421        if let Some((x, y)) = self.find_replacement_edge(u, v) {
422            // Check if they're already connected before linking
423            let already_connected = self.link_cut_tree.connected(x, y);
424
425            if !already_connected {
426                // Add replacement edge to spanning forest
427                self.link_cut_tree.link(x, y)?;
428
429                // Only link in spanning forest if not already connected
430                if !self.spanning_forest.connected(x, y) {
431                    let _ = self.spanning_forest.link(x, y); // Ignore errors here
432                }
433
434                // Track as tree edge
435                let key = if x < y { (x, y) } else { (y, x) };
436                self.tree_edges.write().insert(key);
437            }
438        } else {
439            // Graph is now disconnected
440            // Minimum cut becomes 0
441            self.stats.write().restructures += 1;
442        }
443
444        Ok(())
445    }
446
447    /// Handle deletion of a non-tree edge
448    fn handle_non_tree_edge_deletion(&mut self, _u: VertexId, _v: VertexId) -> Result<()> {
449        // Non-tree edge deletion doesn't affect connectivity
450        // But may affect minimum cut value (handled by decomposition)
451        Ok(())
452    }
453
454    /// Find a replacement edge for a deleted tree edge
455    fn find_replacement_edge(&mut self, u: VertexId, v: VertexId) -> Option<(VertexId, VertexId)> {
456        // Get the two components after cutting
457        // Use BFS to find vertices reachable from u (without using edge u-v)
458        let graph = self.graph.read();
459
460        let mut comp_u = std::collections::HashSet::new();
461        let mut queue = std::collections::VecDeque::new();
462
463        queue.push_back(u);
464        comp_u.insert(u);
465
466        while let Some(x) = queue.pop_front() {
467            let neighbors = graph.neighbors(x);
468            for (y, _) in neighbors {
469                // Skip the deleted edge
470                if (x == u && y == v) || (x == v && y == u) {
471                    continue;
472                }
473
474                // Only traverse tree edges for connectivity
475                let key = if x < y { (x, y) } else { (y, x) };
476                if !self.tree_edges.read().contains(&key) {
477                    continue;
478                }
479
480                if comp_u.insert(y) {
481                    queue.push_back(y);
482                }
483            }
484        }
485
486        // Now search for non-tree edges crossing the cut
487        for &x in &comp_u {
488            let neighbors = graph.neighbors(x);
489            for (y, _) in neighbors {
490                if !comp_u.contains(&y) {
491                    // Found a replacement edge
492                    return Some((x, y));
493                }
494            }
495        }
496
497        None
498    }
499
500    /// Rebuild the hierarchical decomposition with current graph state
501    fn rebuild_decomposition(&mut self) {
502        let graph = self.graph.read();
503        let graph_clone = graph.clone();
504        drop(graph);
505
506        let graph_for_decomp = Arc::new(graph_clone);
507        self.decomposition = HierarchicalDecomposition::build(graph_for_decomp)
508            .unwrap_or_else(|_| {
509                // If build fails, create an empty one
510                let empty = Arc::new(DynamicGraph::new());
511                HierarchicalDecomposition::build(empty).unwrap()
512            });
513    }
514
515    /// Recompute minimum cut after structural change
516    fn recompute_min_cut(&mut self) {
517        let graph = self.graph.read();
518
519        // If graph is disconnected, minimum cut is 0
520        if !graph.is_connected() {
521            self.current_min_cut = 0.0;
522            drop(graph);
523            return;
524        }
525
526        // Compute minimum cut by checking all tree edge cuts
527        let mut min_cut = self.decomposition.min_cut_value();
528
529        // For each tree edge, compute the cut value when removing it
530        // This gives us all possible 2-partitions induced by the spanning tree
531        let tree_edges: Vec<_> = self.tree_edges.read().iter().copied().collect();
532
533        for (u, v) in tree_edges {
534            let cut_value = self.compute_tree_edge_cut(&graph, u, v);
535            if cut_value < min_cut {
536                min_cut = cut_value;
537            }
538        }
539
540        drop(graph);
541        self.current_min_cut = min_cut;
542    }
543
544    /// Compute the cut value induced by removing a tree edge
545    fn compute_tree_edge_cut(&self, graph: &DynamicGraph, u: VertexId, v: VertexId) -> f64 {
546        // Find all vertices reachable from u without using edge (u,v)
547        let mut component_u = std::collections::HashSet::new();
548        let mut queue = std::collections::VecDeque::new();
549
550        queue.push_back(u);
551        component_u.insert(u);
552
553        while let Some(x) = queue.pop_front() {
554            for (y, _) in graph.neighbors(x) {
555                // Skip the tree edge we're "removing"
556                if (x == u && y == v) || (x == v && y == u) {
557                    continue;
558                }
559
560                // Only traverse tree edges for connectivity
561                let key = if x < y { (x, y) } else { (y, x) };
562                if !self.tree_edges.read().contains(&key) {
563                    continue;
564                }
565
566                if component_u.insert(y) {
567                    queue.push_back(y);
568                }
569            }
570        }
571
572        // Now compute cut: sum of all edge weights crossing the partition
573        let mut cut_weight = 0.0;
574        for &x in &component_u {
575            for (y, _) in graph.neighbors(x) {
576                if !component_u.contains(&y) {
577                    if let Some(weight) = graph.edge_weight(x, y) {
578                        cut_weight += weight;
579                    }
580                }
581            }
582        }
583
584        cut_weight
585    }
586}
587
588/// Builder for DynamicMinCut
589pub struct MinCutBuilder {
590    config: MinCutConfig,
591    initial_edges: Vec<(VertexId, VertexId, Weight)>,
592}
593
594impl MinCutBuilder {
595    /// Create a new builder
596    pub fn new() -> Self {
597        Self {
598            config: MinCutConfig::default(),
599            initial_edges: Vec::new(),
600        }
601    }
602
603    /// Use exact algorithm
604    pub fn exact(mut self) -> Self {
605        self.config.approximate = false;
606        self
607    }
608
609    /// Use approximate algorithm with given epsilon
610    pub fn approximate(mut self, epsilon: f64) -> Self {
611        assert!(epsilon > 0.0 && epsilon <= 1.0, "Epsilon must be in (0, 1]");
612        self.config.approximate = true;
613        self.config.epsilon = epsilon;
614        self
615    }
616
617    /// Set maximum cut size for exact algorithm
618    pub fn max_cut_size(mut self, size: usize) -> Self {
619        self.config.max_exact_cut_size = size;
620        self
621    }
622
623    /// Enable or disable parallel computation
624    pub fn parallel(mut self, enabled: bool) -> Self {
625        self.config.parallel = enabled;
626        self
627    }
628
629    /// Add initial edges
630    pub fn with_edges(mut self, edges: Vec<(VertexId, VertexId, Weight)>) -> Self {
631        self.initial_edges = edges;
632        self
633    }
634
635    /// Build the minimum cut structure
636    pub fn build(self) -> Result<DynamicMinCut> {
637        if self.initial_edges.is_empty() {
638            Ok(DynamicMinCut::new(self.config))
639        } else {
640            // Create graph with initial edges
641            let graph = DynamicGraph::new();
642            for (u, v, weight) in &self.initial_edges {
643                graph.insert_edge(*u, *v, *weight)?;
644            }
645
646            DynamicMinCut::from_graph(graph, self.config)
647        }
648    }
649}
650
651impl Default for MinCutBuilder {
652    fn default() -> Self {
653        Self::new()
654    }
655}
656
657#[cfg(test)]
658mod tests {
659    use super::*;
660
661    #[test]
662    fn test_empty_graph() {
663        let mincut = DynamicMinCut::new(MinCutConfig::default());
664        assert_eq!(mincut.min_cut_value(), f64::INFINITY);
665        assert_eq!(mincut.num_vertices(), 0);
666        assert_eq!(mincut.num_edges(), 0);
667    }
668
669    #[test]
670    fn test_single_edge() {
671        let mincut = MinCutBuilder::new()
672            .with_edges(vec![(1, 2, 1.0)])
673            .build()
674            .unwrap();
675
676        assert_eq!(mincut.num_vertices(), 2);
677        assert_eq!(mincut.num_edges(), 1);
678        assert_eq!(mincut.min_cut_value(), 1.0);
679        assert!(mincut.is_connected());
680    }
681
682    #[test]
683    fn test_triangle() {
684        let edges = vec![
685            (1, 2, 1.0),
686            (2, 3, 1.0),
687            (3, 1, 1.0),
688        ];
689
690        let mincut = MinCutBuilder::new()
691            .with_edges(edges)
692            .build()
693            .unwrap();
694
695        assert_eq!(mincut.num_vertices(), 3);
696        assert_eq!(mincut.num_edges(), 3);
697        assert_eq!(mincut.min_cut_value(), 2.0); // Minimum cut is 2
698        assert!(mincut.is_connected());
699    }
700
701    #[test]
702    fn test_insert_edge() {
703        let mut mincut = MinCutBuilder::new()
704            .with_edges(vec![(1, 2, 1.0)])
705            .build()
706            .unwrap();
707
708        let cut_value = mincut.insert_edge(2, 3, 1.0).unwrap();
709        assert_eq!(mincut.num_edges(), 2);
710        assert_eq!(cut_value, 1.0);
711    }
712
713    #[test]
714    fn test_delete_edge() {
715        let mut mincut = MinCutBuilder::new()
716            .with_edges(vec![
717                (1, 2, 1.0),
718                (2, 3, 1.0),
719            ])
720            .build()
721            .unwrap();
722
723        assert!(mincut.is_connected());
724
725        let cut_value = mincut.delete_edge(1, 2).unwrap();
726        assert_eq!(mincut.num_edges(), 1);
727        // After deleting edge, graph becomes disconnected
728        assert_eq!(cut_value, 0.0);
729    }
730
731    #[test]
732    fn test_disconnected_graph() {
733        let mincut = MinCutBuilder::new()
734            .with_edges(vec![
735                (1, 2, 1.0),
736                (3, 4, 1.0),
737            ])
738            .build()
739            .unwrap();
740
741        assert!(!mincut.is_connected());
742        assert_eq!(mincut.min_cut_value(), 0.0);
743    }
744
745    #[test]
746    fn test_weighted_edges() {
747        let edges = vec![
748            (1, 2, 2.0),
749            (2, 3, 3.0),
750            (3, 1, 1.0),
751        ];
752
753        let mincut = MinCutBuilder::new()
754            .with_edges(edges)
755            .build()
756            .unwrap();
757
758        // Minimum cut should be 2.0 (cutting {1} from {2,3} or similar)
759        assert_eq!(mincut.min_cut_value(), 3.0);
760    }
761
762    #[test]
763    fn test_partition() {
764        let edges = vec![
765            (1, 2, 1.0),
766            (2, 3, 1.0),
767            (3, 4, 1.0),
768        ];
769
770        let mincut = MinCutBuilder::new()
771            .with_edges(edges)
772            .build()
773            .unwrap();
774
775        let (s, t) = mincut.partition();
776        assert!(!s.is_empty());
777        assert!(!t.is_empty());
778        assert_eq!(s.len() + t.len(), 4);
779    }
780
781    #[test]
782    fn test_cut_edges() {
783        let edges = vec![
784            (1, 2, 1.0),
785            (2, 3, 1.0),
786        ];
787
788        let mincut = MinCutBuilder::new()
789            .with_edges(edges)
790            .build()
791            .unwrap();
792
793        let cut = mincut.cut_edges();
794        assert!(!cut.is_empty());
795        assert!(cut.len() <= 2);
796    }
797
798    #[test]
799    fn test_min_cut_result() {
800        let edges = vec![
801            (1, 2, 1.0),
802            (2, 3, 1.0),
803            (3, 1, 1.0),
804        ];
805
806        let mincut = MinCutBuilder::new()
807            .exact()
808            .with_edges(edges)
809            .build()
810            .unwrap();
811
812        let result = mincut.min_cut();
813        assert!(result.is_exact);
814        assert_eq!(result.approximation_ratio, 1.0);
815        assert!(result.cut_edges.is_some());
816        assert!(result.partition.is_some());
817    }
818
819    #[test]
820    fn test_approximate_mode() {
821        let mincut = MinCutBuilder::new()
822            .approximate(0.1)
823            .build()
824            .unwrap();
825
826        let result = mincut.min_cut();
827        assert!(!result.is_exact);
828        assert_eq!(result.approximation_ratio, 1.1);
829    }
830
831    #[test]
832    fn test_statistics() {
833        let mut mincut = MinCutBuilder::new()
834            .with_edges(vec![(1, 2, 1.0)])
835            .build()
836            .unwrap();
837
838        mincut.insert_edge(2, 3, 1.0).unwrap();
839        mincut.delete_edge(1, 2).unwrap();
840        let _ = mincut.min_cut_value();
841
842        let stats = mincut.stats();
843        assert_eq!(stats.insertions, 1);
844        assert_eq!(stats.deletions, 1);
845        assert_eq!(stats.queries, 1);
846        assert!(stats.avg_update_time_us > 0.0);
847    }
848
849    #[test]
850    fn test_reset_stats() {
851        let mut mincut = MinCutBuilder::new()
852            .with_edges(vec![(1, 2, 1.0)])
853            .build()
854            .unwrap();
855
856        mincut.insert_edge(2, 3, 1.0).unwrap();
857        assert_eq!(mincut.stats().insertions, 1);
858
859        mincut.reset_stats();
860        assert_eq!(mincut.stats().insertions, 0);
861    }
862
863    #[test]
864    fn test_builder_pattern() {
865        let mincut = MinCutBuilder::new()
866            .exact()
867            .max_cut_size(500)
868            .parallel(true)
869            .with_edges(vec![(1, 2, 1.0)])
870            .build()
871            .unwrap();
872
873        assert!(!mincut.config().approximate);
874        assert_eq!(mincut.config().max_exact_cut_size, 500);
875        assert!(mincut.config().parallel);
876    }
877
878    #[test]
879    fn test_large_graph() {
880        let mut edges = Vec::new();
881
882        // Create a chain: 0 - 1 - 2 - ... - 99
883        for i in 0..99 {
884            edges.push((i, i + 1, 1.0));
885        }
886
887        let mincut = MinCutBuilder::new()
888            .with_edges(edges)
889            .build()
890            .unwrap();
891
892        assert_eq!(mincut.num_vertices(), 100);
893        assert_eq!(mincut.num_edges(), 99);
894        assert_eq!(mincut.min_cut_value(), 1.0); // Minimum cut is 1
895        assert!(mincut.is_connected());
896    }
897
898    #[test]
899    fn test_tree_edge_deletion_with_replacement() {
900        let mut mincut = MinCutBuilder::new()
901            .with_edges(vec![
902                (1, 2, 1.0),
903                (2, 3, 1.0),
904                (1, 3, 1.0), // Creates a cycle
905            ])
906            .build()
907            .unwrap();
908
909        assert!(mincut.is_connected());
910
911        // Delete one edge - graph should remain connected due to replacement
912        mincut.delete_edge(1, 2).unwrap();
913
914        // Still has 2 edges
915        assert_eq!(mincut.num_edges(), 2);
916    }
917
918    #[test]
919    fn test_multiple_components() {
920        let edges = vec![
921            (1, 2, 1.0),
922            (3, 4, 1.0),
923            (5, 6, 1.0),
924        ];
925
926        let mincut = MinCutBuilder::new()
927            .with_edges(edges)
928            .build()
929            .unwrap();
930
931        assert!(!mincut.is_connected());
932        assert_eq!(mincut.min_cut_value(), 0.0);
933    }
934
935    #[test]
936    fn test_dynamic_updates() {
937        let mut mincut = MinCutBuilder::new().build().unwrap();
938
939        // Start empty
940        assert_eq!(mincut.min_cut_value(), f64::INFINITY);
941
942        // Add first edge (creates two vertices)
943        mincut.insert_edge(1, 2, 2.0).unwrap();
944        assert_eq!(mincut.min_cut_value(), 2.0);
945
946        // Complete the triangle
947        mincut.insert_edge(2, 3, 3.0).unwrap();
948        mincut.insert_edge(3, 1, 1.0).unwrap();
949        assert_eq!(mincut.min_cut_value(), 3.0); // min cut
950
951        // Delete heaviest edge
952        mincut.delete_edge(2, 3).unwrap();
953        assert_eq!(mincut.min_cut_value(), 1.0); // Now path graph
954    }
955
956    #[test]
957    fn test_config_access() {
958        let mincut = MinCutBuilder::new()
959            .approximate(0.2)
960            .max_cut_size(2000)
961            .build()
962            .unwrap();
963
964        let config = mincut.config();
965        assert_eq!(config.epsilon, 0.2);
966        assert_eq!(config.max_exact_cut_size, 2000);
967        assert!(config.approximate);
968    }
969
970    #[test]
971    fn test_graph_access() {
972        let mincut = MinCutBuilder::new()
973            .with_edges(vec![(1, 2, 1.0)])
974            .build()
975            .unwrap();
976
977        let graph = mincut.graph();
978        let g = graph.read();
979        assert_eq!(g.num_vertices(), 2);
980        assert_eq!(g.num_edges(), 1);
981    }
982
983    #[test]
984    fn test_bridge_graph() {
985        // Two triangles connected by a bridge
986        let edges = vec![
987            // Triangle 1: 1-2-3-1
988            (1, 2, 2.0),
989            (2, 3, 2.0),
990            (3, 1, 2.0),
991            // Bridge
992            (3, 4, 1.0),
993            // Triangle 2: 4-5-6-4
994            (4, 5, 2.0),
995            (5, 6, 2.0),
996            (6, 4, 2.0),
997        ];
998
999        let mincut = MinCutBuilder::new()
1000            .with_edges(edges)
1001            .build()
1002            .unwrap();
1003
1004        // Minimum cut should be the bridge with weight 1.0
1005        assert_eq!(mincut.min_cut_value(), 1.0);
1006        assert!(mincut.is_connected());
1007    }
1008
1009    #[test]
1010    fn test_complete_graph_k4() {
1011        // Complete graph on 4 vertices
1012        let mut edges = Vec::new();
1013        for i in 1..=4 {
1014            for j in (i + 1)..=4 {
1015                edges.push((i, j, 1.0));
1016            }
1017        }
1018
1019        let mincut = MinCutBuilder::new()
1020            .with_edges(edges)
1021            .build()
1022            .unwrap();
1023
1024        assert_eq!(mincut.num_vertices(), 4);
1025        assert_eq!(mincut.num_edges(), 6);
1026        // Minimum cut of K4 is 3 (degree of any vertex)
1027        assert_eq!(mincut.min_cut_value(), 3.0);
1028    }
1029
1030    #[test]
1031    fn test_sequential_insertions() {
1032        let mut mincut = MinCutBuilder::new().build().unwrap();
1033
1034        // Build graph incrementally
1035        mincut.insert_edge(1, 2, 1.0).unwrap();
1036        assert_eq!(mincut.min_cut_value(), 1.0);
1037
1038        mincut.insert_edge(2, 3, 1.0).unwrap();
1039        assert_eq!(mincut.min_cut_value(), 1.0);
1040
1041        mincut.insert_edge(3, 4, 1.0).unwrap();
1042        assert_eq!(mincut.min_cut_value(), 1.0);
1043
1044        // Add cycle closure
1045        mincut.insert_edge(4, 1, 1.0).unwrap();
1046        assert_eq!(mincut.min_cut_value(), 2.0);
1047    }
1048
1049    #[test]
1050    fn test_sequential_deletions() {
1051        let mut mincut = MinCutBuilder::new()
1052            .with_edges(vec![
1053                (1, 2, 1.0),
1054                (2, 3, 1.0),
1055                (3, 1, 1.0),
1056            ])
1057            .build()
1058            .unwrap();
1059
1060        assert_eq!(mincut.min_cut_value(), 2.0);
1061
1062        // Delete one edge
1063        mincut.delete_edge(1, 2).unwrap();
1064        assert_eq!(mincut.min_cut_value(), 1.0);
1065
1066        // Delete another edge - disconnects graph
1067        mincut.delete_edge(2, 3).unwrap();
1068        assert_eq!(mincut.min_cut_value(), 0.0);
1069    }
1070}