1use std::collections::{HashMap, HashSet, VecDeque};
24use crate::graph::VertexId;
25
26#[derive(Debug, Clone)]
28pub struct ApproxMinCutConfig {
29 pub epsilon: f64,
31 pub num_samples: usize,
33 pub seed: u64,
35}
36
37impl Default for ApproxMinCutConfig {
38 fn default() -> Self {
39 Self {
40 epsilon: 0.1,
41 num_samples: 3,
42 seed: 42,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq)]
49struct WeightedEdge {
50 u: VertexId,
51 v: VertexId,
52 weight: f64,
53}
54
55impl WeightedEdge {
56 fn new(u: VertexId, v: VertexId, weight: f64) -> Self {
57 let (u, v) = if u < v { (u, v) } else { (v, u) };
58 Self { u, v, weight }
59 }
60
61 fn endpoints(&self) -> (VertexId, VertexId) {
62 (self.u, self.v)
63 }
64}
65
66#[derive(Debug)]
68struct SpectralSparsifier {
69 edges: Vec<WeightedEdge>,
71 vertices: HashSet<VertexId>,
73 adj: HashMap<VertexId, Vec<(VertexId, f64)>>,
75}
76
77impl SpectralSparsifier {
78 fn new() -> Self {
79 Self {
80 edges: Vec::new(),
81 vertices: HashSet::new(),
82 adj: HashMap::new(),
83 }
84 }
85
86 fn add_edge(&mut self, u: VertexId, v: VertexId, weight: f64) {
87 self.vertices.insert(u);
88 self.vertices.insert(v);
89 self.edges.push(WeightedEdge::new(u, v, weight));
90
91 self.adj.entry(u).or_default().push((v, weight));
92 self.adj.entry(v).or_default().push((u, weight));
93 }
94
95 fn vertex_count(&self) -> usize {
96 self.vertices.len()
97 }
98
99 fn edge_count(&self) -> usize {
100 self.edges.len()
101 }
102}
103
104#[derive(Debug)]
122pub struct ApproxMinCut {
123 edges: Vec<WeightedEdge>,
125 vertices: HashSet<VertexId>,
127 adj: HashMap<VertexId, Vec<(VertexId, f64)>>,
129 resistances: HashMap<(VertexId, VertexId), f64>,
131 config: ApproxMinCutConfig,
133 cached_min_cut: Option<f64>,
135 stats: ApproxMinCutStats,
137}
138
139#[derive(Debug, Clone, Default)]
141pub struct ApproxMinCutStats {
142 pub insertions: u64,
144 pub deletions: u64,
146 pub queries: u64,
148 pub rebuilds: u64,
150}
151
152#[derive(Debug, Clone)]
154pub struct ApproxMinCutResult {
155 pub value: f64,
157 pub lower_bound: f64,
159 pub upper_bound: f64,
161 pub partition: Option<(Vec<VertexId>, Vec<VertexId>)>,
163 pub epsilon: f64,
165}
166
167impl ApproxMinCut {
168 pub fn new(config: ApproxMinCutConfig) -> Self {
170 Self {
171 edges: Vec::new(),
172 vertices: HashSet::new(),
173 adj: HashMap::new(),
174 resistances: HashMap::new(),
175 config,
176 cached_min_cut: None,
177 stats: ApproxMinCutStats::default(),
178 }
179 }
180
181 pub fn with_epsilon(epsilon: f64) -> Self {
183 Self::new(ApproxMinCutConfig {
184 epsilon,
185 ..Default::default()
186 })
187 }
188
189 pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: f64) {
191 self.stats.insertions += 1;
192 self.cached_min_cut = None; let edge = WeightedEdge::new(u, v, weight);
195 self.edges.push(edge);
196 self.vertices.insert(u);
197 self.vertices.insert(v);
198
199 self.adj.entry(u).or_default().push((v, weight));
200 self.adj.entry(v).or_default().push((u, weight));
201
202 self.resistances.clear();
204 }
205
206 pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
208 self.stats.deletions += 1;
209 self.cached_min_cut = None;
210
211 let key = if u < v { (u, v) } else { (v, u) };
212 self.edges.retain(|e| e.endpoints() != key);
213
214 if let Some(neighbors) = self.adj.get_mut(&u) {
216 neighbors.retain(|(neighbor, _)| *neighbor != v);
217 }
218 if let Some(neighbors) = self.adj.get_mut(&v) {
219 neighbors.retain(|(neighbor, _)| *neighbor != u);
220 }
221
222 self.resistances.clear();
223 }
224
225 pub fn min_cut(&mut self) -> ApproxMinCutResult {
227 self.stats.queries += 1;
228
229 if self.edges.is_empty() {
230 return ApproxMinCutResult {
231 value: f64::INFINITY,
232 lower_bound: f64::INFINITY,
233 upper_bound: f64::INFINITY,
234 partition: None,
235 epsilon: self.config.epsilon,
236 };
237 }
238
239 if let Some(cached) = self.cached_min_cut {
241 let lower = cached / (1.0 + self.config.epsilon);
242 let upper = cached * (1.0 + self.config.epsilon);
243 return ApproxMinCutResult {
244 value: cached,
245 lower_bound: lower,
246 upper_bound: upper,
247 partition: None,
248 epsilon: self.config.epsilon,
249 };
250 }
251
252 let value = self.compute_min_cut_via_sparsifier();
254 self.cached_min_cut = Some(value);
255
256 let lower = value / (1.0 + self.config.epsilon);
257 let upper = value * (1.0 + self.config.epsilon);
258
259 ApproxMinCutResult {
260 value,
261 lower_bound: lower,
262 upper_bound: upper,
263 partition: self.compute_partition(value),
264 epsilon: self.config.epsilon,
265 }
266 }
267
268 pub fn min_cut_value(&mut self) -> f64 {
270 self.min_cut().value
271 }
272
273 pub fn is_connected(&self) -> bool {
275 if self.vertices.is_empty() {
276 return true;
277 }
278
279 let start = *self.vertices.iter().next().unwrap();
280 let mut visited = HashSet::new();
281 let mut queue = VecDeque::new();
282
283 queue.push_back(start);
284 visited.insert(start);
285
286 while let Some(current) = queue.pop_front() {
287 if let Some(neighbors) = self.adj.get(¤t) {
288 for &(neighbor, _) in neighbors {
289 if !visited.contains(&neighbor) {
290 visited.insert(neighbor);
291 queue.push_back(neighbor);
292 }
293 }
294 }
295 }
296
297 visited.len() == self.vertices.len()
298 }
299
300 pub fn vertex_count(&self) -> usize {
302 self.vertices.len()
303 }
304
305 pub fn edge_count(&self) -> usize {
307 self.edges.len()
308 }
309
310 pub fn stats(&self) -> &ApproxMinCutStats {
312 &self.stats
313 }
314
315 fn compute_min_cut_via_sparsifier(&mut self) -> f64 {
317 self.stats.rebuilds += 1;
318
319 if !self.is_connected() {
320 return 0.0;
321 }
322
323 if self.edges.len() <= 50 {
325 return self.compute_exact_min_cut();
326 }
327
328 self.compute_effective_resistances();
330
331 let mut estimates = Vec::new();
333 for i in 0..self.config.num_samples {
334 let sparsifier = self.build_sparsifier(self.config.seed + i as u64);
335 let cut = self.compute_sparsifier_min_cut(&sparsifier);
336 estimates.push(cut);
337 }
338
339 estimates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
340 estimates[estimates.len() / 2]
341 }
342
343 fn compute_effective_resistances(&mut self) {
345 if !self.resistances.is_empty() {
346 return;
347 }
348
349 for edge in &self.edges {
352 let (u, v) = edge.endpoints();
353
354 let deg_u = self.adj.get(&u).map_or(1, |n| n.len().max(1));
356 let deg_v = self.adj.get(&v).map_or(1, |n| n.len().max(1));
357 let approx_resistance = 2.0 / (deg_u + deg_v) as f64;
358
359 self.resistances.insert((u, v), approx_resistance);
360 }
361 }
362
363 fn build_sparsifier(&self, seed: u64) -> SpectralSparsifier {
365 let mut sparsifier = SpectralSparsifier::new();
366 let n = self.vertices.len();
367 let epsilon = self.config.epsilon;
368
369 let target_size = ((n as f64) * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
371 let target_size = target_size.min(self.edges.len()).max(n);
372
373 let hash = |i: usize| -> u64 {
375 let mut h = seed.wrapping_add(i as u64);
376 h = h.wrapping_mul(0x517cc1b727220a95);
377 h ^= h >> 32;
378 h
379 };
380
381 let total_weight: f64 = self.edges.iter().map(|e| e.weight).sum();
383
384 for (i, edge) in self.edges.iter().enumerate() {
385 let (u, v) = edge.endpoints();
386 let resistance = self.resistances.get(&(u, v)).copied().unwrap_or(1.0);
387
388 let prob = (edge.weight * resistance * target_size as f64 / total_weight).min(1.0);
390
391 let rand_val = (hash(i) as f64) / (u64::MAX as f64);
393
394 if rand_val < prob {
395 let new_weight = edge.weight / prob;
397 sparsifier.add_edge(u, v, new_weight);
398 }
399 }
400
401 self.ensure_sparsifier_connectivity(&mut sparsifier);
403
404 sparsifier
405 }
406
407 fn ensure_sparsifier_connectivity(&self, sparsifier: &mut SpectralSparsifier) {
409 if self.vertices.is_empty() {
411 return;
412 }
413
414 let start = *self.vertices.iter().next().unwrap();
415 let mut visited = HashSet::new();
416 let mut queue = VecDeque::new();
417
418 queue.push_back(start);
419 visited.insert(start);
420
421 while let Some(current) = queue.pop_front() {
422 if let Some(neighbors) = self.adj.get(¤t) {
423 for &(neighbor, weight) in neighbors {
424 if !visited.contains(&neighbor) {
425 visited.insert(neighbor);
426 queue.push_back(neighbor);
427
428 if !sparsifier.vertices.contains(&neighbor) {
430 sparsifier.add_edge(current, neighbor, weight);
431 }
432 }
433 }
434 }
435 }
436 }
437
438 fn compute_sparsifier_min_cut(&self, sparsifier: &SpectralSparsifier) -> f64 {
440 if sparsifier.vertex_count() <= 1 {
441 return f64::INFINITY;
442 }
443
444 self.stoer_wagner(&sparsifier.adj, &sparsifier.vertices)
446 }
447
448 fn stoer_wagner(
451 &self,
452 adj: &HashMap<VertexId, Vec<(VertexId, f64)>>,
453 vertices: &HashSet<VertexId>,
454 ) -> f64 {
455 if vertices.len() <= 1 {
456 return f64::INFINITY;
457 }
458
459 let min_degree: f64 = adj
461 .iter()
462 .filter(|(v, _)| vertices.contains(v))
463 .map(|(_, neighbors)| neighbors.iter().map(|(_, w)| *w).sum::<f64>())
464 .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
465 .unwrap_or(f64::INFINITY);
466
467 if vertices.len() <= 3 {
469 return min_degree;
470 }
471
472 let verts: Vec<VertexId> = vertices.iter().copied().collect();
474 let n = verts.len();
475 let vert_to_idx: HashMap<_, _> = verts.iter().enumerate().map(|(i, &v)| (v, i)).collect();
476
477 let mut weights = vec![vec![0.0; n]; n];
478 for (&v, neighbors) in adj {
479 if let Some(&i) = vert_to_idx.get(&v) {
480 for &(neighbor, weight) in neighbors {
481 if let Some(&j) = vert_to_idx.get(&neighbor) {
482 weights[i][j] += weight;
483 }
484 }
485 }
486 }
487
488 let mut min_cut = f64::INFINITY;
490 let mut active: Vec<bool> = vec![true; n];
491
492 for phase in 0..n - 1 {
493 if min_cut == 0.0 {
495 break;
496 }
497
498 let mut in_a = vec![false; n];
500 let mut cut_of_phase = vec![0.0; n];
501
502 let first = match (0..n).find(|&i| active[i]) {
504 Some(f) => f,
505 None => break,
506 };
507 in_a[first] = true;
508
509 let mut last = first;
510 let mut before_last = first;
511
512 let active_count = active.iter().filter(|&&a| a).count();
513 for _ in 1..active_count {
514 for j in 0..n {
516 if active[j] && !in_a[j] && weights[last][j] > 0.0 {
517 cut_of_phase[j] += weights[last][j];
518 }
519 }
520
521 before_last = last;
523 let mut max_val = f64::NEG_INFINITY;
524 for j in 0..n {
525 if active[j] && !in_a[j] && cut_of_phase[j] > max_val {
526 max_val = cut_of_phase[j];
527 last = j;
528 }
529 }
530
531 if max_val == f64::NEG_INFINITY {
532 break;
533 }
534 in_a[last] = true;
535 }
536
537 if cut_of_phase[last] > 0.0 || phase == 0 {
539 min_cut = min_cut.min(cut_of_phase[last]);
540 }
541
542 active[last] = false;
544 for j in 0..n {
545 weights[before_last][j] += weights[last][j];
546 weights[j][before_last] += weights[j][last];
547 }
548 }
549
550 min_cut
551 }
552
553 fn compute_exact_min_cut(&self) -> f64 {
555 self.stoer_wagner(&self.adj, &self.vertices)
556 }
557
558 fn compute_partition(&self, _cut_value: f64) -> Option<(Vec<VertexId>, Vec<VertexId>)> {
560 if self.vertices.len() <= 1 {
562 return None;
563 }
564
565 let start = *self.vertices.iter().next()?;
566 let mut visited = HashSet::new();
567 let mut queue = VecDeque::new();
568
569 queue.push_back(start);
570 visited.insert(start);
571
572 let target = self.vertices.len() / 2;
574 while visited.len() < target {
575 if let Some(current) = queue.pop_front() {
576 if let Some(neighbors) = self.adj.get(¤t) {
577 for &(neighbor, _) in neighbors {
578 if !visited.contains(&neighbor) {
579 visited.insert(neighbor);
580 queue.push_back(neighbor);
581 if visited.len() >= target {
582 break;
583 }
584 }
585 }
586 }
587 } else {
588 break;
589 }
590 }
591
592 let s: Vec<VertexId> = visited.into_iter().collect();
593 let t: Vec<VertexId> = self.vertices.iter()
594 .filter(|v| !s.contains(v))
595 .copied()
596 .collect();
597
598 Some((s, t))
599 }
600}
601
602impl Default for ApproxMinCut {
603 fn default() -> Self {
604 Self::new(ApproxMinCutConfig::default())
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611
612 #[test]
613 fn test_basic_approx_min_cut() {
614 let mut approx = ApproxMinCut::default();
615
616 approx.insert_edge(0, 1, 1.0);
617 approx.insert_edge(1, 2, 1.0);
618 approx.insert_edge(2, 0, 1.0);
619
620 let result = approx.min_cut();
621 assert!(result.value >= 1.8); assert!(result.value <= 2.2);
623 }
624
625 #[test]
626 fn test_triangle_graph() {
627 let mut approx = ApproxMinCut::with_epsilon(0.1);
628
629 approx.insert_edge(0, 1, 1.0);
630 approx.insert_edge(1, 2, 1.0);
631 approx.insert_edge(2, 0, 1.0);
632
633 let value = approx.min_cut_value();
634 assert!((value - 2.0).abs() < 0.5); }
636
637 #[test]
638 fn test_disconnected_graph() {
639 let mut approx = ApproxMinCut::default();
640
641 approx.insert_edge(0, 1, 1.0);
642 approx.insert_edge(2, 3, 1.0);
643
644 assert!(!approx.is_connected());
645 assert_eq!(approx.min_cut_value(), 0.0);
646 }
647
648 #[test]
649 fn test_single_edge() {
650 let mut approx = ApproxMinCut::default();
651
652 approx.insert_edge(0, 1, 5.0);
653
654 let value = approx.min_cut_value();
655 assert!((value - 5.0).abs() < 1.0);
656 }
657
658 #[test]
659 fn test_path_graph() {
660 let mut approx = ApproxMinCut::default();
661
662 approx.insert_edge(0, 1, 1.0);
664 approx.insert_edge(1, 2, 1.0);
665 approx.insert_edge(2, 3, 1.0);
666
667 let value = approx.min_cut_value();
668 assert!(value >= 0.5); assert!(value <= 1.5);
670 }
671
672 #[test]
673 fn test_delete_edge() {
674 let mut approx = ApproxMinCut::default();
675
676 approx.insert_edge(0, 1, 1.0);
677 approx.insert_edge(1, 2, 1.0);
678 approx.insert_edge(2, 0, 1.0);
679
680 assert!(approx.is_connected());
681
682 approx.delete_edge(1, 2);
683
684 assert!(approx.is_connected());
685 let value = approx.min_cut_value();
686 assert!(value >= 0.5 && value <= 1.5);
687 }
688
689 #[test]
690 fn test_stats() {
691 let mut approx = ApproxMinCut::default();
692
693 approx.insert_edge(0, 1, 1.0);
694 approx.insert_edge(1, 2, 1.0);
695 approx.delete_edge(0, 1);
696 approx.min_cut_value();
697
698 let stats = approx.stats();
699 assert_eq!(stats.insertions, 2);
700 assert_eq!(stats.deletions, 1);
701 assert_eq!(stats.queries, 1);
702 }
703
704 #[test]
705 fn test_result_bounds() {
706 let mut approx = ApproxMinCut::with_epsilon(0.2);
707
708 approx.insert_edge(0, 1, 2.0);
709 approx.insert_edge(1, 2, 2.0);
710 approx.insert_edge(2, 0, 2.0);
711
712 let result = approx.min_cut();
713 assert!(result.lower_bound <= result.value);
714 assert!(result.value <= result.upper_bound);
715 assert_eq!(result.epsilon, 0.2);
716 }
717
718 #[test]
719 fn test_larger_graph() {
720 let mut approx = ApproxMinCut::with_epsilon(0.15);
721
722 for i in 0..10 {
724 approx.insert_edge(i, (i + 1) % 10, 1.0);
725 }
726
727 let value = approx.min_cut_value();
728 assert!(value >= 1.0); assert!(value <= 3.0);
730 }
731}