use crate::graph::VertexId;
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone)]
pub struct ApproxMinCutConfig {
pub epsilon: f64,
pub num_samples: usize,
pub seed: u64,
}
impl Default for ApproxMinCutConfig {
fn default() -> Self {
Self {
epsilon: 0.1,
num_samples: 3,
seed: 42,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct WeightedEdge {
u: VertexId,
v: VertexId,
weight: f64,
}
impl WeightedEdge {
fn new(u: VertexId, v: VertexId, weight: f64) -> Self {
let (u, v) = if u < v { (u, v) } else { (v, u) };
Self { u, v, weight }
}
fn endpoints(&self) -> (VertexId, VertexId) {
(self.u, self.v)
}
}
#[derive(Debug)]
struct SpectralSparsifier {
edges: Vec<WeightedEdge>,
vertices: HashSet<VertexId>,
adj: HashMap<VertexId, Vec<(VertexId, f64)>>,
}
impl SpectralSparsifier {
fn new() -> Self {
Self {
edges: Vec::new(),
vertices: HashSet::new(),
adj: HashMap::new(),
}
}
fn add_edge(&mut self, u: VertexId, v: VertexId, weight: f64) {
self.vertices.insert(u);
self.vertices.insert(v);
self.edges.push(WeightedEdge::new(u, v, weight));
self.adj.entry(u).or_default().push((v, weight));
self.adj.entry(v).or_default().push((u, weight));
}
fn vertex_count(&self) -> usize {
self.vertices.len()
}
fn edge_count(&self) -> usize {
self.edges.len()
}
}
#[derive(Debug)]
pub struct ApproxMinCut {
edges: Vec<WeightedEdge>,
vertices: HashSet<VertexId>,
adj: HashMap<VertexId, Vec<(VertexId, f64)>>,
resistances: HashMap<(VertexId, VertexId), f64>,
config: ApproxMinCutConfig,
cached_min_cut: Option<f64>,
stats: ApproxMinCutStats,
}
#[derive(Debug, Clone, Default)]
pub struct ApproxMinCutStats {
pub insertions: u64,
pub deletions: u64,
pub queries: u64,
pub rebuilds: u64,
}
#[derive(Debug, Clone)]
pub struct ApproxMinCutResult {
pub value: f64,
pub lower_bound: f64,
pub upper_bound: f64,
pub partition: Option<(Vec<VertexId>, Vec<VertexId>)>,
pub epsilon: f64,
}
impl ApproxMinCut {
pub fn new(config: ApproxMinCutConfig) -> Self {
Self {
edges: Vec::new(),
vertices: HashSet::new(),
adj: HashMap::new(),
resistances: HashMap::new(),
config,
cached_min_cut: None,
stats: ApproxMinCutStats::default(),
}
}
pub fn with_epsilon(epsilon: f64) -> Self {
Self::new(ApproxMinCutConfig {
epsilon,
..Default::default()
})
}
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: f64) {
self.stats.insertions += 1;
self.cached_min_cut = None;
let edge = WeightedEdge::new(u, v, weight);
self.edges.push(edge);
self.vertices.insert(u);
self.vertices.insert(v);
self.adj.entry(u).or_default().push((v, weight));
self.adj.entry(v).or_default().push((u, weight));
self.resistances.clear();
}
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
self.stats.deletions += 1;
self.cached_min_cut = None;
let key = if u < v { (u, v) } else { (v, u) };
self.edges.retain(|e| e.endpoints() != key);
if let Some(neighbors) = self.adj.get_mut(&u) {
neighbors.retain(|(neighbor, _)| *neighbor != v);
}
if let Some(neighbors) = self.adj.get_mut(&v) {
neighbors.retain(|(neighbor, _)| *neighbor != u);
}
self.resistances.clear();
}
pub fn min_cut(&mut self) -> ApproxMinCutResult {
self.stats.queries += 1;
if self.edges.is_empty() {
return ApproxMinCutResult {
value: f64::INFINITY,
lower_bound: f64::INFINITY,
upper_bound: f64::INFINITY,
partition: None,
epsilon: self.config.epsilon,
};
}
if let Some(cached) = self.cached_min_cut {
let lower = cached / (1.0 + self.config.epsilon);
let upper = cached * (1.0 + self.config.epsilon);
return ApproxMinCutResult {
value: cached,
lower_bound: lower,
upper_bound: upper,
partition: None,
epsilon: self.config.epsilon,
};
}
let value = self.compute_min_cut_via_sparsifier();
self.cached_min_cut = Some(value);
let lower = value / (1.0 + self.config.epsilon);
let upper = value * (1.0 + self.config.epsilon);
ApproxMinCutResult {
value,
lower_bound: lower,
upper_bound: upper,
partition: self.compute_partition(value),
epsilon: self.config.epsilon,
}
}
pub fn min_cut_value(&mut self) -> f64 {
self.min_cut().value
}
pub fn is_connected(&self) -> bool {
if self.vertices.is_empty() {
return true;
}
let start = *self.vertices.iter().next().unwrap();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(start);
visited.insert(start);
while let Some(current) = queue.pop_front() {
if let Some(neighbors) = self.adj.get(¤t) {
for &(neighbor, _) in neighbors {
if !visited.contains(&neighbor) {
visited.insert(neighbor);
queue.push_back(neighbor);
}
}
}
}
visited.len() == self.vertices.len()
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn stats(&self) -> &ApproxMinCutStats {
&self.stats
}
fn compute_min_cut_via_sparsifier(&mut self) -> f64 {
self.stats.rebuilds += 1;
if !self.is_connected() {
return 0.0;
}
if self.edges.len() <= 50 {
return self.compute_exact_min_cut();
}
self.compute_effective_resistances();
let mut estimates = Vec::new();
for i in 0..self.config.num_samples {
let sparsifier = self.build_sparsifier(self.config.seed + i as u64);
let cut = self.compute_sparsifier_min_cut(&sparsifier);
estimates.push(cut);
}
estimates.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
estimates[estimates.len() / 2]
}
fn compute_effective_resistances(&mut self) {
if !self.resistances.is_empty() {
return;
}
for edge in &self.edges {
let (u, v) = edge.endpoints();
let deg_u = self.adj.get(&u).map_or(1, |n| n.len().max(1));
let deg_v = self.adj.get(&v).map_or(1, |n| n.len().max(1));
let approx_resistance = 2.0 / (deg_u + deg_v) as f64;
self.resistances.insert((u, v), approx_resistance);
}
}
fn build_sparsifier(&self, seed: u64) -> SpectralSparsifier {
let mut sparsifier = SpectralSparsifier::new();
let n = self.vertices.len();
let epsilon = self.config.epsilon;
let target_size = ((n as f64) * (n as f64).ln() / (epsilon * epsilon)).ceil() as usize;
let target_size = target_size.min(self.edges.len()).max(n);
let hash = |i: usize| -> u64 {
let mut h = seed.wrapping_add(i as u64);
h = h.wrapping_mul(0x517cc1b727220a95);
h ^= h >> 32;
h
};
let total_weight: f64 = self.edges.iter().map(|e| e.weight).sum();
for (i, edge) in self.edges.iter().enumerate() {
let (u, v) = edge.endpoints();
let resistance = self.resistances.get(&(u, v)).copied().unwrap_or(1.0);
let prob = (edge.weight * resistance * target_size as f64 / total_weight).min(1.0);
let rand_val = (hash(i) as f64) / (u64::MAX as f64);
if rand_val < prob {
let new_weight = edge.weight / prob;
sparsifier.add_edge(u, v, new_weight);
}
}
self.ensure_sparsifier_connectivity(&mut sparsifier);
sparsifier
}
fn ensure_sparsifier_connectivity(&self, sparsifier: &mut SpectralSparsifier) {
if self.vertices.is_empty() {
return;
}
let start = *self.vertices.iter().next().unwrap();
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(start);
visited.insert(start);
while let Some(current) = queue.pop_front() {
if let Some(neighbors) = self.adj.get(¤t) {
for &(neighbor, weight) in neighbors {
if !visited.contains(&neighbor) {
visited.insert(neighbor);
queue.push_back(neighbor);
if !sparsifier.vertices.contains(&neighbor) {
sparsifier.add_edge(current, neighbor, weight);
}
}
}
}
}
}
fn compute_sparsifier_min_cut(&self, sparsifier: &SpectralSparsifier) -> f64 {
if sparsifier.vertex_count() <= 1 {
return f64::INFINITY;
}
self.stoer_wagner(&sparsifier.adj, &sparsifier.vertices)
}
fn stoer_wagner(
&self,
adj: &HashMap<VertexId, Vec<(VertexId, f64)>>,
vertices: &HashSet<VertexId>,
) -> f64 {
if vertices.len() <= 1 {
return f64::INFINITY;
}
let min_degree: f64 = adj
.iter()
.filter(|(v, _)| vertices.contains(v))
.map(|(_, neighbors)| neighbors.iter().map(|(_, w)| *w).sum::<f64>())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(f64::INFINITY);
if vertices.len() <= 3 {
return min_degree;
}
let verts: Vec<VertexId> = vertices.iter().copied().collect();
let n = verts.len();
let vert_to_idx: HashMap<_, _> = verts.iter().enumerate().map(|(i, &v)| (v, i)).collect();
let mut weights = vec![vec![0.0; n]; n];
for (&v, neighbors) in adj {
if let Some(&i) = vert_to_idx.get(&v) {
for &(neighbor, weight) in neighbors {
if let Some(&j) = vert_to_idx.get(&neighbor) {
weights[i][j] += weight;
}
}
}
}
let mut min_cut = f64::INFINITY;
let mut active: Vec<bool> = vec![true; n];
for phase in 0..n - 1 {
if min_cut == 0.0 {
break;
}
let mut in_a = vec![false; n];
let mut cut_of_phase = vec![0.0; n];
let first = match (0..n).find(|&i| active[i]) {
Some(f) => f,
None => break,
};
in_a[first] = true;
let mut last = first;
let mut before_last = first;
let active_count = active.iter().filter(|&&a| a).count();
for _ in 1..active_count {
for j in 0..n {
if active[j] && !in_a[j] && weights[last][j] > 0.0 {
cut_of_phase[j] += weights[last][j];
}
}
before_last = last;
let mut max_val = f64::NEG_INFINITY;
for j in 0..n {
if active[j] && !in_a[j] && cut_of_phase[j] > max_val {
max_val = cut_of_phase[j];
last = j;
}
}
if max_val == f64::NEG_INFINITY {
break;
}
in_a[last] = true;
}
if cut_of_phase[last] > 0.0 || phase == 0 {
min_cut = min_cut.min(cut_of_phase[last]);
}
active[last] = false;
for j in 0..n {
weights[before_last][j] += weights[last][j];
weights[j][before_last] += weights[j][last];
}
}
min_cut
}
fn compute_exact_min_cut(&self) -> f64 {
self.stoer_wagner(&self.adj, &self.vertices)
}
fn compute_partition(&self, _cut_value: f64) -> Option<(Vec<VertexId>, Vec<VertexId>)> {
if self.vertices.len() <= 1 {
return None;
}
let start = *self.vertices.iter().next()?;
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back(start);
visited.insert(start);
let target = self.vertices.len() / 2;
while visited.len() < target {
if let Some(current) = queue.pop_front() {
if let Some(neighbors) = self.adj.get(¤t) {
for &(neighbor, _) in neighbors {
if !visited.contains(&neighbor) {
visited.insert(neighbor);
queue.push_back(neighbor);
if visited.len() >= target {
break;
}
}
}
}
} else {
break;
}
}
let s: Vec<VertexId> = visited.into_iter().collect();
let t: Vec<VertexId> = self
.vertices
.iter()
.filter(|v| !s.contains(v))
.copied()
.collect();
Some((s, t))
}
}
impl Default for ApproxMinCut {
fn default() -> Self {
Self::new(ApproxMinCutConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_approx_min_cut() {
let mut approx = ApproxMinCut::default();
approx.insert_edge(0, 1, 1.0);
approx.insert_edge(1, 2, 1.0);
approx.insert_edge(2, 0, 1.0);
let result = approx.min_cut();
assert!(result.value >= 1.8); assert!(result.value <= 2.2);
}
#[test]
fn test_triangle_graph() {
let mut approx = ApproxMinCut::with_epsilon(0.1);
approx.insert_edge(0, 1, 1.0);
approx.insert_edge(1, 2, 1.0);
approx.insert_edge(2, 0, 1.0);
let value = approx.min_cut_value();
assert!((value - 2.0).abs() < 0.5); }
#[test]
fn test_disconnected_graph() {
let mut approx = ApproxMinCut::default();
approx.insert_edge(0, 1, 1.0);
approx.insert_edge(2, 3, 1.0);
assert!(!approx.is_connected());
assert_eq!(approx.min_cut_value(), 0.0);
}
#[test]
fn test_single_edge() {
let mut approx = ApproxMinCut::default();
approx.insert_edge(0, 1, 5.0);
let value = approx.min_cut_value();
assert!((value - 5.0).abs() < 1.0);
}
#[test]
fn test_path_graph() {
let mut approx = ApproxMinCut::default();
approx.insert_edge(0, 1, 1.0);
approx.insert_edge(1, 2, 1.0);
approx.insert_edge(2, 3, 1.0);
let value = approx.min_cut_value();
assert!(value >= 0.5); assert!(value <= 1.5);
}
#[test]
fn test_delete_edge() {
let mut approx = ApproxMinCut::default();
approx.insert_edge(0, 1, 1.0);
approx.insert_edge(1, 2, 1.0);
approx.insert_edge(2, 0, 1.0);
assert!(approx.is_connected());
approx.delete_edge(1, 2);
assert!(approx.is_connected());
let value = approx.min_cut_value();
assert!(value >= 0.5 && value <= 1.5);
}
#[test]
fn test_stats() {
let mut approx = ApproxMinCut::default();
approx.insert_edge(0, 1, 1.0);
approx.insert_edge(1, 2, 1.0);
approx.delete_edge(0, 1);
approx.min_cut_value();
let stats = approx.stats();
assert_eq!(stats.insertions, 2);
assert_eq!(stats.deletions, 1);
assert_eq!(stats.queries, 1);
}
#[test]
fn test_result_bounds() {
let mut approx = ApproxMinCut::with_epsilon(0.2);
approx.insert_edge(0, 1, 2.0);
approx.insert_edge(1, 2, 2.0);
approx.insert_edge(2, 0, 2.0);
let result = approx.min_cut();
assert!(result.lower_bound <= result.value);
assert!(result.value <= result.upper_bound);
assert_eq!(result.epsilon, 0.2);
}
#[test]
fn test_larger_graph() {
let mut approx = ApproxMinCut::with_epsilon(0.15);
for i in 0..10 {
approx.insert_edge(i, (i + 1) % 10, 1.0);
}
let value = approx.min_cut_value();
assert!(value >= 1.0); assert!(value <= 3.0);
}
}