use ruvector_mincut::prelude::*;
use std::sync::Arc;
fn stoer_wagner_min_cut(graph: &DynamicGraph) -> f64 {
if graph.num_vertices() == 0 {
return f64::INFINITY;
}
if !graph.is_connected() {
return 0.0;
}
let vertices = graph.vertices();
if vertices.len() <= 1 {
return f64::INFINITY;
}
if vertices.len() == 2 {
if let Some(edge) = graph.get_edge(vertices[0], vertices[1]) {
return edge.weight;
}
return 0.0;
}
let mut min_cut = f64::INFINITY;
for &v in &vertices {
let mut degree_sum = 0.0;
for (_, edge_id) in graph.neighbors(v) {
if let Some(e) = graph.edges().iter().find(|e| e.id == edge_id) {
degree_sum += e.weight;
}
}
min_cut = min_cut.min(degree_sum);
}
min_cut
}
fn build_path_graph(n: usize) -> Arc<DynamicGraph> {
let graph = Arc::new(DynamicGraph::new());
for i in 1..n {
graph.insert_edge(i as u64, (i + 1) as u64, 1.0).unwrap();
}
graph
}
fn build_cycle_graph(n: usize) -> Arc<DynamicGraph> {
let graph = Arc::new(DynamicGraph::new());
for i in 1..=n {
let next = if i == n { 1 } else { i + 1 };
graph.insert_edge(i as u64, next as u64, 1.0).unwrap();
}
graph
}
fn build_complete_graph(n: usize) -> Arc<DynamicGraph> {
let graph = Arc::new(DynamicGraph::new());
for i in 1..=n {
for j in (i + 1)..=n {
graph.insert_edge(i as u64, j as u64, 1.0).unwrap();
}
}
graph
}
#[test]
fn test_geometric_range_factor() {
let base: f64 = 1.2;
for i in 0..20 {
let lambda_min = (base.powi(i)).floor() as u64;
let lambda_max = (base.powi(i + 1)).floor() as u64;
assert!(
lambda_max >= lambda_min,
"Range {} must be valid: min={}, max={}",
i,
lambda_min,
lambda_max
);
if i >= 10 {
let ratio = lambda_max as f64 / lambda_min.max(1) as f64;
assert!(
ratio >= 1.0 && ratio <= 1.5,
"Ratio {} should be close to 1.2: {}",
i,
ratio
);
}
}
}
#[test]
fn test_geometric_range_coverage() {
let base: f64 = 1.2;
let mut ranges = Vec::new();
for i in 0..30 {
let lambda_min = (base.powi(i)).floor() as u64;
let lambda_max = (base.powi(i + 1)).floor() as u64;
ranges.push((lambda_min, lambda_max));
}
for i in 1..ranges.len() {
let prev_max = ranges[i - 1].1;
let curr_min = ranges[i].0;
let gap = curr_min.saturating_sub(prev_max);
assert!(
gap <= 2,
"Gap between ranges too large: {} at index {}",
gap,
i
);
}
}
#[test]
fn test_geometric_range_bounds() {
let base: f64 = 1.2;
let test_cases = vec![
(0, 1, 1), (1, 1, 1), (5, 2, 2), (10, 6, 7), (20, 38, 46), ];
for (i, expected_min, expected_max) in test_cases {
let lambda_min = (base.powi(i)).floor() as u64;
let lambda_max = (base.powi(i + 1)).floor() as u64;
assert_eq!(
lambda_min, expected_min,
"Range {} min should be {}",
i, expected_min
);
assert_eq!(
lambda_max, expected_max,
"Range {} max should be {}",
i, expected_max
);
}
}
#[test]
fn test_disconnected_returns_zero() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 5.0).unwrap();
graph.insert_edge(3, 4, 3.0).unwrap();
assert!(!graph.is_connected(), "Graph should be disconnected");
let mincut = MinCutBuilder::new().exact().build().unwrap();
let mut mincut_dynamic = MinCutBuilder::new()
.with_edges(vec![(1, 2, 5.0), (3, 4, 3.0)])
.build()
.unwrap();
assert_eq!(
mincut_dynamic.min_cut_value(),
0.0,
"Disconnected graph must have min cut = 0"
);
}
#[test]
fn test_disconnected_multiple_components() {
let edges = vec![
(1, 2, 1.0),
(2, 3, 1.0), (10, 11, 2.0),
(11, 12, 2.0), (20, 21, 3.0), ];
let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
assert_eq!(
mincut.min_cut_value(),
0.0,
"Multiple disconnected components must have min cut = 0"
);
assert!(!mincut.is_connected());
}
#[test]
fn test_becomes_disconnected_after_delete() {
let mut mincut = MinCutBuilder::new()
.with_edges(vec![
(1, 2, 1.0),
(2, 3, 1.0), (3, 4, 1.0),
])
.build()
.unwrap();
assert!(mincut.is_connected());
let new_cut = mincut.delete_edge(2, 3).unwrap();
assert_eq!(new_cut, 0.0, "Should become disconnected");
assert!(!mincut.is_connected());
}
#[test]
fn test_single_edge_min_cut() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 3.5).unwrap();
let mincut = MinCutBuilder::new()
.with_edges(vec![(1, 2, 3.5)])
.build()
.unwrap();
assert_eq!(
mincut.min_cut_value(),
3.5,
"Single edge min cut should equal edge weight"
);
assert!(mincut.is_connected());
let brute_force = stoer_wagner_min_cut(&graph);
assert_eq!(
mincut.min_cut_value(),
brute_force,
"Should match brute force: {} vs {}",
mincut.min_cut_value(),
brute_force
);
}
#[test]
fn test_path_graph_min_cut() {
for n in 3..10 {
let graph = build_path_graph(n);
let mincut = MinCutBuilder::new().exact().build().unwrap();
let mut edges = Vec::new();
for i in 1..n {
edges.push((i as u64, (i + 1) as u64, 1.0));
}
let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
assert_eq!(
mincut.min_cut_value(),
1.0,
"Path graph P_{} should have min cut = 1",
n
);
let brute_force = stoer_wagner_min_cut(&graph);
assert_eq!(
mincut.min_cut_value(),
brute_force,
"P_{} should match brute force",
n
);
}
}
#[test]
fn test_cycle_graph_min_cut() {
for n in 3..10 {
let graph = build_cycle_graph(n);
let mut edges = Vec::new();
for i in 1..=n {
let next = if i == n { 1 } else { i + 1 };
edges.push((i as u64, next as u64, 1.0));
}
let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
assert_eq!(
mincut.min_cut_value(),
2.0,
"Cycle C_{} should have min cut = 2",
n
);
let brute_force = stoer_wagner_min_cut(&graph);
assert_eq!(
mincut.min_cut_value(),
brute_force,
"C_{} should match brute force",
n
);
}
}
#[test]
fn test_complete_graph_min_cut() {
for n in 3..=6 {
let graph = build_complete_graph(n);
let mut edges = Vec::new();
for i in 1..=n {
for j in (i + 1)..=n {
edges.push((i as u64, j as u64, 1.0));
}
}
let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
let expected = (n - 1) as f64;
assert_eq!(
mincut.min_cut_value(),
expected,
"Complete graph K_{} should have min cut = {}",
n,
expected
);
let brute_force = stoer_wagner_min_cut(&graph);
assert_eq!(
mincut.min_cut_value(),
brute_force,
"K_{} should match brute force",
n
);
}
}
#[test]
fn test_weighted_graph_correctness() {
let edges = vec![
(1, 2, 5.0),
(2, 3, 3.0),
(3, 4, 7.0),
(4, 1, 2.0),
(1, 3, 4.0), ];
let graph = Arc::new(DynamicGraph::new());
for (u, v, w) in &edges {
graph.insert_edge(*u, *v, *w).unwrap();
}
let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
let brute_force = stoer_wagner_min_cut(&graph);
assert!(
(mincut.min_cut_value() - brute_force).abs() < 0.001,
"Weighted graph should match brute force: {} vs {}",
mincut.min_cut_value(),
brute_force
);
}
#[test]
fn test_insert_before_delete_ordering() {
let mut mincut = MinCutBuilder::new()
.with_edges(vec![(1, 2, 1.0), (2, 3, 1.0)])
.build()
.unwrap();
let initial_edges = mincut.num_edges();
assert_eq!(initial_edges, 2);
mincut.insert_edge(3, 4, 1.0).unwrap();
assert_eq!(mincut.num_edges(), 3, "Insert should happen first");
mincut.delete_edge(1, 2).unwrap();
assert_eq!(mincut.num_edges(), 2, "Delete should happen after insert");
}
#[test]
fn test_operation_sequence_determinism() {
let operations = vec![
("insert", 1, 2, 1.0),
("insert", 2, 3, 1.0),
("insert", 3, 4, 1.0),
("delete", 2, 3, 0.0),
("insert", 1, 4, 2.0),
];
for _run in 0..2 {
let mut mincut = MinCutBuilder::new().build().unwrap();
for (op, u, v, w) in &operations {
match *op {
"insert" => {
let _ = mincut.insert_edge(*u, *v, *w);
}
"delete" => {
let _ = mincut.delete_edge(*u, *v);
}
_ => panic!("Unknown operation"),
}
}
let final_edges = mincut.num_edges();
assert!(
final_edges >= 2 && final_edges <= 4,
"Should have reasonable edge count: {}",
final_edges
);
}
}
#[test]
fn fuzz_random_small_graphs() {
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
let mut rng = StdRng::seed_from_u64(42);
for _iteration in 0..20 {
let n = rng.gen_range(3..8); let m = rng.gen_range(n..=(n * (n - 1) / 2).min(15));
let mut edges = Vec::new();
let mut edge_set = std::collections::HashSet::new();
for _ in 0..m {
let mut attempts = 0;
loop {
let u = rng.gen_range(1..=n) as u64;
let v = rng.gen_range(1..=n) as u64;
if u != v {
let edge_key = if u < v { (u, v) } else { (v, u) };
if edge_set.insert(edge_key) {
let weight = rng.gen_range(1.0..10.0);
edges.push((u, v, weight));
break;
}
}
attempts += 1;
if attempts > 50 {
break;
}
}
}
if edges.is_empty() {
continue;
}
let mincut = MinCutBuilder::new().with_edges(edges.clone()).build();
if let Ok(mc) = mincut {
let value = mc.min_cut_value();
assert!(value >= 0.0, "Min cut must be non-negative");
}
}
}
#[test]
fn fuzz_random_operations_sequence() {
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
let mut rng = StdRng::seed_from_u64(123);
for _iteration in 0..10 {
let mut mincut = MinCutBuilder::new().build().unwrap();
let mut present_edges = std::collections::HashSet::new();
let num_ops = rng.gen_range(5..15);
for _ in 0..num_ops {
let op = rng.gen_range(0..2);
if op == 0 || present_edges.is_empty() {
let u = rng.gen_range(1..6) as u64; let v = rng.gen_range(1..6) as u64;
if u != v {
let key = if u < v { (u, v) } else { (v, u) };
if !present_edges.contains(&key) {
let weight = rng.gen_range(1.0..5.0);
if mincut.insert_edge(u, v, weight).is_ok() {
present_edges.insert(key);
}
}
}
} else {
if let Some(&(u, v)) = present_edges.iter().next() {
present_edges.remove(&(u, v));
let _ = mincut.delete_edge(u, v);
}
}
}
let final_cut = mincut.min_cut_value();
assert!(final_cut >= 0.0, "Cut value must be non-negative");
}
}
#[test]
fn test_delete_maintains_correctness() {
let mut edges = Vec::new();
for i in 1..=5 {
for j in (i + 1)..=5 {
edges.push((i, j, 1.0));
}
}
let mut mincut = MinCutBuilder::new()
.with_edges(edges.clone())
.build()
.unwrap();
assert_eq!(mincut.min_cut_value(), 4.0);
for (u, v, _) in edges.iter().take(5) {
let _ = mincut.delete_edge(*u, *v);
let current_cut = mincut.min_cut_value();
assert!(current_cut >= 0.0, "Cut must be non-negative");
if mincut.is_connected() {
assert!(
current_cut > 0.0 && current_cut < f64::INFINITY,
"Connected graph must have finite positive cut"
);
} else {
assert_eq!(current_cut, 0.0, "Disconnected graph must have cut = 0");
}
}
}
#[test]
fn test_delete_bridge_creates_disconnection() {
let mut mincut = MinCutBuilder::new()
.with_edges(vec![
(1, 2, 1.0),
(2, 3, 1.0),
(3, 1, 1.0), (3, 4, 1.0), (4, 5, 1.0),
(5, 6, 1.0),
(6, 4, 1.0), ])
.build()
.unwrap();
assert!(mincut.is_connected());
assert_eq!(mincut.min_cut_value(), 1.0);
mincut.delete_edge(3, 4).unwrap();
assert!(!mincut.is_connected());
assert_eq!(mincut.min_cut_value(), 0.0);
}
#[test]
fn property_min_cut_bounded_by_min_degree() {
let test_graphs = vec![
vec![(1, 2, 1.0), (2, 3, 1.0), (3, 1, 1.0)],
vec![(1, 2, 2.0), (2, 3, 3.0), (3, 4, 1.0), (4, 1, 2.0)],
vec![(1, 2, 1.0), (1, 3, 1.0), (1, 4, 1.0), (2, 3, 1.0)],
];
for edges in test_graphs {
let graph = Arc::new(DynamicGraph::new());
for (u, v, w) in &edges {
graph.insert_edge(*u, *v, *w).unwrap();
}
let mincut = MinCutBuilder::new().with_edges(edges).build().unwrap();
if mincut.is_connected() {
let mut min_degree = f64::INFINITY;
for &v in &graph.vertices() {
let mut degree_weight = 0.0;
for (_, edge_id) in graph.neighbors(v) {
for e in graph.edges() {
if e.id == edge_id {
degree_weight += e.weight;
}
}
}
min_degree = min_degree.min(degree_weight);
}
assert!(
mincut.min_cut_value() <= min_degree + 0.001,
"Min cut must be ≤ minimum degree: {} vs {}",
mincut.min_cut_value(),
min_degree
);
}
}
}
#[test]
fn property_min_cut_monotonic_on_edge_removal() {
let mut mincut = MinCutBuilder::new()
.with_edges(vec![
(1, 2, 1.0),
(2, 3, 1.0),
(3, 4, 1.0),
(4, 1, 1.0),
(1, 3, 2.0), ])
.build()
.unwrap();
let initial_cut = mincut.min_cut_value();
mincut.delete_edge(1, 3).unwrap();
let after_delete = mincut.min_cut_value();
assert!(
after_delete <= initial_cut,
"Deleting edges cannot increase min cut: {} -> {}",
initial_cut,
after_delete
);
}
#[test]
fn property_symmetry() {
let edges_forward = vec![(1, 2, 1.5), (2, 3, 2.5), (3, 1, 1.0)];
let edges_reverse: Vec<_> = edges_forward.iter().map(|(u, v, w)| (*v, *u, *w)).collect();
let mincut_fwd = MinCutBuilder::new()
.with_edges(edges_forward)
.build()
.unwrap();
let mincut_rev = MinCutBuilder::new()
.with_edges(edges_reverse)
.build()
.unwrap();
assert_eq!(
mincut_fwd.min_cut_value(),
mincut_rev.min_cut_value(),
"Graph should have same min cut regardless of edge direction"
);
}