pub mod deterministic;
pub mod paper_impl;
pub use paper_impl::{
DeterministicFamilyGenerator, DeterministicLocalKCut, LocalKCutOracle, LocalKCutQuery,
LocalKCutResult,
};
use crate::graph::{DynamicGraph, EdgeId, VertexId, Weight};
use crate::{MinCutError, Result};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct LocalCutResult {
pub cut_value: Weight,
pub cut_set: HashSet<VertexId>,
pub cut_edges: Vec<(VertexId, VertexId)>,
pub is_minimum: bool,
pub iterations: usize,
}
impl LocalCutResult {
pub fn new(
cut_value: Weight,
cut_set: HashSet<VertexId>,
cut_edges: Vec<(VertexId, VertexId)>,
is_minimum: bool,
iterations: usize,
) -> Self {
Self {
cut_value,
cut_set,
cut_edges,
is_minimum,
iterations,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum EdgeColor {
Red,
Blue,
Green,
Yellow,
}
impl EdgeColor {
pub fn from_index(index: usize) -> Self {
match index % 4 {
0 => EdgeColor::Red,
1 => EdgeColor::Blue,
2 => EdgeColor::Green,
_ => EdgeColor::Yellow,
}
}
pub fn to_index(self) -> usize {
match self {
EdgeColor::Red => 0,
EdgeColor::Blue => 1,
EdgeColor::Green => 2,
EdgeColor::Yellow => 3,
}
}
pub fn all() -> [EdgeColor; 4] {
[
EdgeColor::Red,
EdgeColor::Blue,
EdgeColor::Green,
EdgeColor::Yellow,
]
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ColorMask(u8);
impl ColorMask {
pub fn empty() -> Self {
Self(0)
}
pub fn all() -> Self {
Self(0b1111)
}
pub fn from_colors(colors: &[EdgeColor]) -> Self {
let mut mask = 0u8;
for color in colors {
mask |= 1 << color.to_index();
}
Self(mask)
}
pub fn contains(self, color: EdgeColor) -> bool {
(self.0 & (1 << color.to_index())) != 0
}
pub fn insert(&mut self, color: EdgeColor) {
self.0 |= 1 << color.to_index();
}
pub fn colors(self) -> Vec<EdgeColor> {
let mut result = Vec::new();
for color in EdgeColor::all() {
if self.contains(color) {
result.push(color);
}
}
result
}
pub fn count(self) -> usize {
self.0.count_ones() as usize
}
}
pub struct LocalKCut {
k: usize,
graph: Arc<DynamicGraph>,
edge_colors: HashMap<EdgeId, EdgeColor>,
radius: usize,
}
impl LocalKCut {
pub fn new(graph: Arc<DynamicGraph>, k: usize) -> Self {
let radius = Self::compute_radius(k);
let mut finder = Self {
k,
graph,
edge_colors: HashMap::new(),
radius,
};
finder.assign_colors();
finder
}
fn compute_radius(k: usize) -> usize {
if k <= 1 {
1
} else {
let log_k = (k as f64).log2() / 2.0;
log_k.ceil() as usize + 1
}
}
pub fn find_cut(&self, v: VertexId) -> Option<LocalCutResult> {
if !self.graph.has_vertex(v) {
return None;
}
let mut best_cut: Option<LocalCutResult> = None;
let mut iterations = 0;
for depth in 1..=self.radius {
let num_masks = 1 << 4;
for mask_bits in 1..num_masks {
iterations += 1;
let mask = ColorMask(mask_bits as u8);
let reachable = self.color_constrained_bfs(v, mask, depth);
if reachable.is_empty() || reachable.len() >= self.graph.num_vertices() {
continue;
}
if let Some(cut) = self.check_cut(&reachable) {
let should_update = match &best_cut {
None => true,
Some(prev) => cut.cut_value < prev.cut_value,
};
if should_update {
let mut cut = cut;
cut.iterations = iterations;
best_cut = Some(cut);
}
}
}
if let Some(ref cut) = best_cut {
if cut.cut_value <= 1.0 {
break;
}
}
}
best_cut
}
fn color_constrained_bfs(
&self,
start: VertexId,
mask: ColorMask,
max_depth: usize,
) -> HashSet<VertexId> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
queue.push_back((start, 0));
visited.insert(start);
while let Some((v, depth)) = queue.pop_front() {
if depth >= max_depth {
continue;
}
for (neighbor, edge_id) in self.graph.neighbors(v) {
if visited.contains(&neighbor) {
continue;
}
if let Some(&color) = self.edge_colors.get(&edge_id) {
if mask.contains(color) {
visited.insert(neighbor);
queue.push_back((neighbor, depth + 1));
}
}
}
}
visited
}
fn assign_colors(&mut self) {
self.edge_colors.clear();
for edge in self.graph.edges() {
let color = EdgeColor::from_index(edge.id as usize);
self.edge_colors.insert(edge.id, color);
}
}
fn check_cut(&self, vertices: &HashSet<VertexId>) -> Option<LocalCutResult> {
if vertices.is_empty() || vertices.len() >= self.graph.num_vertices() {
return None;
}
let mut cut_edges = Vec::new();
let mut cut_value = 0.0;
for &v in vertices {
for (neighbor, edge_id) in self.graph.neighbors(v) {
if !vertices.contains(&neighbor) {
if let Some(edge) = self.graph.edges().iter().find(|e| e.id == edge_id) {
cut_edges.push((v, neighbor));
cut_value += edge.weight;
}
}
}
}
if cut_value <= self.k as f64 {
Some(LocalCutResult::new(
cut_value,
vertices.clone(),
cut_edges,
false, 0, ))
} else {
None
}
}
pub fn enumerate_paths(&self, v: VertexId, depth: usize) -> Vec<HashSet<VertexId>> {
let mut results = Vec::new();
for mask_bits in 1..16u8 {
let mask = ColorMask(mask_bits);
let reachable = self.color_constrained_bfs(v, mask, depth);
if !reachable.is_empty() {
results.push(reachable);
}
}
results
}
pub fn edge_color(&self, edge_id: EdgeId) -> Option<EdgeColor> {
self.edge_colors.get(&edge_id).copied()
}
pub fn radius(&self) -> usize {
self.radius
}
pub fn max_cut_size(&self) -> usize {
self.k
}
}
pub struct ForestPacking {
num_forests: usize,
forests: Vec<HashSet<(VertexId, VertexId)>>,
}
impl ForestPacking {
pub fn greedy_packing(graph: &DynamicGraph, lambda_max: usize, epsilon: f64) -> Self {
let m = graph.num_edges();
let n = graph.num_vertices();
if m == 0 || n == 0 {
return Self {
num_forests: 0,
forests: Vec::new(),
};
}
let log_m = (m as f64).ln();
let num_forests = ((lambda_max as f64 * log_m) / (epsilon * epsilon)).ceil() as usize;
let num_forests = num_forests.max(1);
let mut forests = Vec::with_capacity(num_forests);
let edges = graph.edges();
for _ in 0..num_forests {
let mut forest = HashSet::new();
let mut components = UnionFind::new(n);
for edge in &edges {
let (u, v) = (edge.source, edge.target);
if components.find(u) != components.find(v) {
forest.insert((u.min(v), u.max(v)));
components.union(u, v);
}
}
forests.push(forest);
}
Self {
num_forests,
forests,
}
}
pub fn witnesses_cut(&self, cut_edges: &[(VertexId, VertexId)]) -> bool {
if self.forests.is_empty() {
return true;
}
let normalized_cut: HashSet<_> = cut_edges
.iter()
.map(|(u, v)| ((*u).min(*v), (*u).max(*v)))
.collect();
for forest in &self.forests {
let has_witness = forest.iter().any(|edge| normalized_cut.contains(edge));
if !has_witness {
return false;
}
}
true
}
pub fn num_forests(&self) -> usize {
self.num_forests
}
pub fn forest(&self, index: usize) -> Option<&HashSet<(VertexId, VertexId)>> {
self.forests.get(index)
}
}
struct UnionFind {
parent: Vec<usize>,
rank: Vec<usize>,
}
impl UnionFind {
fn new(n: usize) -> Self {
Self {
parent: (0..n).collect(),
rank: vec![0; n],
}
}
fn find(&mut self, x: VertexId) -> VertexId {
let x_idx = x as usize % self.parent.len();
let mut idx = x_idx;
while self.parent[idx] != idx {
let parent = self.parent[idx];
self.parent[idx] = self.parent[parent];
idx = parent;
}
idx as VertexId
}
fn union(&mut self, x: VertexId, y: VertexId) {
let root_x = self.find(x);
let root_y = self.find(y);
if root_x == root_y {
return;
}
let rx = root_x as usize % self.parent.len();
let ry = root_y as usize % self.parent.len();
if self.rank[rx] < self.rank[ry] {
self.parent[rx] = ry;
} else if self.rank[rx] > self.rank[ry] {
self.parent[ry] = rx;
} else {
self.parent[ry] = rx;
self.rank[rx] += 1;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_graph() -> Arc<DynamicGraph> {
let graph = DynamicGraph::new();
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(1, 4, 1.0).unwrap();
graph.insert_edge(2, 5, 1.0).unwrap();
graph.insert_edge(3, 6, 1.0).unwrap();
graph.insert_edge(4, 5, 1.0).unwrap();
graph.insert_edge(5, 6, 1.0).unwrap();
Arc::new(graph)
}
#[test]
fn test_edge_color_conversion() {
assert_eq!(EdgeColor::from_index(0), EdgeColor::Red);
assert_eq!(EdgeColor::from_index(1), EdgeColor::Blue);
assert_eq!(EdgeColor::from_index(2), EdgeColor::Green);
assert_eq!(EdgeColor::from_index(3), EdgeColor::Yellow);
assert_eq!(EdgeColor::from_index(4), EdgeColor::Red);
assert_eq!(EdgeColor::Red.to_index(), 0);
assert_eq!(EdgeColor::Blue.to_index(), 1);
assert_eq!(EdgeColor::Green.to_index(), 2);
assert_eq!(EdgeColor::Yellow.to_index(), 3);
}
#[test]
fn test_color_mask() {
let mut mask = ColorMask::empty();
assert_eq!(mask.count(), 0);
mask.insert(EdgeColor::Red);
assert!(mask.contains(EdgeColor::Red));
assert!(!mask.contains(EdgeColor::Blue));
assert_eq!(mask.count(), 1);
mask.insert(EdgeColor::Blue);
assert_eq!(mask.count(), 2);
let all_mask = ColorMask::all();
assert_eq!(all_mask.count(), 4);
assert!(all_mask.contains(EdgeColor::Red));
assert!(all_mask.contains(EdgeColor::Blue));
assert!(all_mask.contains(EdgeColor::Green));
assert!(all_mask.contains(EdgeColor::Yellow));
}
#[test]
fn test_color_mask_from_colors() {
let colors = vec![EdgeColor::Red, EdgeColor::Green];
let mask = ColorMask::from_colors(&colors);
assert!(mask.contains(EdgeColor::Red));
assert!(!mask.contains(EdgeColor::Blue));
assert!(mask.contains(EdgeColor::Green));
assert!(!mask.contains(EdgeColor::Yellow));
assert_eq!(mask.count(), 2);
}
#[test]
fn test_local_kcut_new() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 3);
assert_eq!(local_kcut.max_cut_size(), 3);
assert!(local_kcut.radius() > 0);
assert_eq!(local_kcut.edge_colors.len(), graph.num_edges());
}
#[test]
fn test_compute_radius() {
assert_eq!(LocalKCut::compute_radius(1), 1);
assert_eq!(LocalKCut::compute_radius(4), 2);
assert_eq!(LocalKCut::compute_radius(16), 3);
assert_eq!(LocalKCut::compute_radius(64), 4);
}
#[test]
fn test_assign_colors() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 3);
for edge in graph.edges() {
assert!(local_kcut.edge_color(edge.id).is_some());
}
}
#[test]
fn test_color_constrained_bfs() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 3);
let all_mask = ColorMask::all();
let reachable = local_kcut.color_constrained_bfs(1, all_mask, 10);
assert!(reachable.contains(&1));
assert!(reachable.len() > 1);
}
#[test]
fn test_color_constrained_bfs_limited() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 3);
let all_mask = ColorMask::all();
let reachable = local_kcut.color_constrained_bfs(1, all_mask, 0);
assert_eq!(reachable.len(), 1);
assert!(reachable.contains(&1));
}
#[test]
fn test_find_cut_simple() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 2.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
let local_kcut = LocalKCut::new(graph.clone(), 5);
let result = local_kcut.find_cut(1);
assert!(result.is_some());
if let Some(cut) = result {
assert!(cut.cut_value <= 5.0);
assert!(!cut.cut_set.is_empty());
}
}
#[test]
fn test_check_cut() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 10);
let mut cut_set = HashSet::new();
cut_set.insert(1);
cut_set.insert(2);
let result = local_kcut.check_cut(&cut_set);
assert!(result.is_some());
if let Some(cut) = result {
assert!(cut.cut_value > 0.0);
assert!(!cut.cut_edges.is_empty());
}
}
#[test]
fn test_check_cut_invalid() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 3);
let empty_set = HashSet::new();
assert!(local_kcut.check_cut(&empty_set).is_none());
let all_vertices: HashSet<_> = graph.vertices().into_iter().collect();
assert!(local_kcut.check_cut(&all_vertices).is_none());
}
#[test]
fn test_enumerate_paths() {
let graph = create_test_graph();
let local_kcut = LocalKCut::new(graph.clone(), 3);
let paths = local_kcut.enumerate_paths(1, 2);
assert!(!paths.is_empty());
for path in &paths {
assert!(path.contains(&1));
}
}
#[test]
fn test_forest_packing_empty_graph() {
let graph = DynamicGraph::new();
let packing = ForestPacking::greedy_packing(&graph, 10, 0.1);
assert_eq!(packing.num_forests(), 0);
}
#[test]
fn test_forest_packing_simple() {
let graph = create_test_graph();
let packing = ForestPacking::greedy_packing(&*graph, 10, 0.1);
assert!(packing.num_forests() > 0);
for i in 0..packing.num_forests() {
if let Some(forest) = packing.forest(i) {
assert!(!forest.is_empty());
}
}
}
#[test]
fn test_forest_witnesses_cut() {
let graph = create_test_graph();
let packing = ForestPacking::greedy_packing(&*graph, 5, 0.1);
let cut_edges = vec![(1, 2)];
let witnesses = packing.witnesses_cut(&cut_edges);
let _ = witnesses;
assert!(packing.num_forests() >= 0);
}
#[test]
fn test_union_find() {
let mut uf = UnionFind::new(5);
assert_eq!(uf.find(0), 0);
assert_eq!(uf.find(1), 1);
uf.union(0, 1);
assert_eq!(uf.find(0), uf.find(1));
uf.union(2, 3);
assert_eq!(uf.find(2), uf.find(3));
assert_ne!(uf.find(0), uf.find(2));
uf.union(1, 2);
assert_eq!(uf.find(0), uf.find(3));
}
#[test]
fn test_local_cut_result() {
let mut cut_set = HashSet::new();
cut_set.insert(1);
cut_set.insert(2);
let cut_edges = vec![(1, 3), (2, 4)];
let result = LocalCutResult::new(2.5, cut_set.clone(), cut_edges.clone(), true, 10);
assert_eq!(result.cut_value, 2.5);
assert_eq!(result.cut_set.len(), 2);
assert_eq!(result.cut_edges.len(), 2);
assert!(result.is_minimum);
assert_eq!(result.iterations, 10);
}
#[test]
fn test_deterministic_coloring() {
let graph = create_test_graph();
let lk1 = LocalKCut::new(graph.clone(), 3);
let lk2 = LocalKCut::new(graph.clone(), 3);
for edge in graph.edges() {
assert_eq!(lk1.edge_color(edge.id), lk2.edge_color(edge.id));
}
}
#[test]
fn test_complete_workflow() {
let graph = Arc::new(DynamicGraph::new());
graph.insert_edge(1, 2, 1.0).unwrap();
graph.insert_edge(2, 3, 1.0).unwrap();
graph.insert_edge(3, 1, 1.0).unwrap();
graph.insert_edge(3, 4, 1.0).unwrap();
graph.insert_edge(4, 5, 1.0).unwrap();
graph.insert_edge(5, 6, 1.0).unwrap();
graph.insert_edge(6, 4, 1.0).unwrap();
let local_kcut = LocalKCut::new(graph.clone(), 3);
let result = local_kcut.find_cut(1);
assert!(result.is_some());
if let Some(cut) = result {
assert!(cut.cut_value <= 3.0);
assert!(cut.iterations > 0);
}
let packing = ForestPacking::greedy_packing(&*graph, 3, 0.1);
assert!(packing.num_forests() > 0);
}
}