use crate::graph::{DynamicGraph, VertexId};
use crate::instance::WitnessHandle;
use roaring::RoaringBitmap;
use std::collections::{HashSet, VecDeque};
#[derive(Debug, Clone)]
pub struct LocalKCutQuery {
pub seed_vertices: Vec<VertexId>,
pub budget_k: u64,
pub radius: usize,
}
#[derive(Debug, Clone)]
pub enum LocalKCutResult {
Found {
witness: WitnessHandle,
cut_value: u64,
},
NoneInLocality,
}
pub trait LocalKCutOracle: Send + Sync {
fn search(&self, graph: &DynamicGraph, query: LocalKCutQuery) -> LocalKCutResult;
}
#[derive(Debug, Clone)]
pub struct DeterministicFamilyGenerator {
max_size: usize,
}
impl DeterministicFamilyGenerator {
pub fn new(max_size: usize) -> Self {
Self { max_size }
}
pub fn generate_seeds(&self, graph: &DynamicGraph, v: VertexId) -> Vec<VertexId> {
let mut seeds = vec![v];
let mut neighbors: Vec<_> = graph
.neighbors(v)
.into_iter()
.map(|(neighbor, _)| neighbor)
.collect();
neighbors.sort_unstable();
for &neighbor in neighbors.iter().take(self.max_size.saturating_sub(1)) {
seeds.push(neighbor);
}
seeds
}
}
impl Default for DeterministicFamilyGenerator {
fn default() -> Self {
Self::new(4)
}
}
#[derive(Debug, Clone)]
pub struct DeterministicLocalKCut {
max_radius: usize,
#[allow(dead_code)]
family_generator: DeterministicFamilyGenerator,
}
impl DeterministicLocalKCut {
pub fn new(max_radius: usize) -> Self {
Self {
max_radius,
family_generator: DeterministicFamilyGenerator::default(),
}
}
pub fn with_family_generator(
max_radius: usize,
family_generator: DeterministicFamilyGenerator,
) -> Self {
Self {
max_radius,
family_generator,
}
}
fn deterministic_bfs(
&self,
graph: &DynamicGraph,
seeds: &[VertexId],
budget: u64,
radius: usize,
) -> Option<(HashSet<VertexId>, u64)> {
if seeds.is_empty() {
return None;
}
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut best_cut: Option<(HashSet<VertexId>, u64)> = None;
for &seed in seeds {
if graph.has_vertex(seed) {
visited.insert(seed);
queue.push_back((seed, 0));
}
}
if visited.is_empty() {
return None;
}
let mut current_layer = visited.clone();
for depth in 0..=radius {
let boundary_size = self.calculate_boundary(graph, &visited);
if boundary_size <= budget && !visited.is_empty() {
if visited.len() < graph.num_vertices() {
let should_update = match &best_cut {
None => true,
Some((_, prev_boundary)) => boundary_size < *prev_boundary,
};
if should_update {
best_cut = Some((visited.clone(), boundary_size));
}
}
}
if let Some((_, boundary)) = &best_cut {
if *boundary == 0 {
break;
}
}
if depth >= radius {
break;
}
let mut next_layer = HashSet::new();
let mut layer_vertices: Vec<_> = current_layer.iter().copied().collect();
layer_vertices.sort_unstable();
for v in layer_vertices {
let mut neighbors: Vec<_> = graph
.neighbors(v)
.into_iter()
.map(|(neighbor, _)| neighbor)
.filter(|neighbor| !visited.contains(neighbor))
.collect();
neighbors.sort_unstable();
for neighbor in neighbors {
if visited.insert(neighbor) {
next_layer.insert(neighbor);
queue.push_back((neighbor, depth + 1));
}
}
}
current_layer = next_layer;
if current_layer.is_empty() {
break;
}
}
best_cut
}
fn calculate_boundary(&self, graph: &DynamicGraph, vertex_set: &HashSet<VertexId>) -> u64 {
let mut boundary_edges = HashSet::new();
for &v in vertex_set {
for (neighbor, edge_id) in graph.neighbors(v) {
if !vertex_set.contains(&neighbor) {
boundary_edges.insert(edge_id);
}
}
}
boundary_edges.len() as u64
}
fn create_witness(
&self,
seed: VertexId,
vertices: &HashSet<VertexId>,
boundary_size: u64,
) -> WitnessHandle {
let mut membership = RoaringBitmap::new();
for &v in vertices {
if v <= u32::MAX as u64 {
membership.insert(v as u32);
}
}
WitnessHandle::new(seed, membership, boundary_size)
}
}
impl LocalKCutOracle for DeterministicLocalKCut {
fn search(&self, graph: &DynamicGraph, query: LocalKCutQuery) -> LocalKCutResult {
if query.seed_vertices.is_empty() {
return LocalKCutResult::NoneInLocality;
}
let radius = query.radius.min(self.max_radius);
let result = self.deterministic_bfs(graph, &query.seed_vertices, query.budget_k, radius);
match result {
Some((vertices, boundary_size)) => {
let seed = query
.seed_vertices
.iter()
.find(|&&s| vertices.contains(&s))
.copied()
.unwrap_or(query.seed_vertices[0]);
let witness = self.create_witness(seed, &vertices, boundary_size);
LocalKCutResult::Found {
witness,
cut_value: boundary_size,
}
}
None => LocalKCutResult::NoneInLocality,
}
}
}
impl Default for DeterministicLocalKCut {
fn default() -> Self {
Self::new(10)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn create_simple_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(3, 4, 1.0).unwrap();
Arc::new(graph)
}
fn create_triangle_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(3, 1, 1.0).unwrap();
Arc::new(graph)
}
fn create_dumbbell_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(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();
Arc::new(graph)
}
#[test]
fn test_local_kcut_query_creation() {
let query = LocalKCutQuery {
seed_vertices: vec![1, 2, 3],
budget_k: 10,
radius: 5,
};
assert_eq!(query.seed_vertices.len(), 3);
assert_eq!(query.budget_k, 10);
assert_eq!(query.radius, 5);
}
#[test]
fn test_deterministic_family_generator() {
let graph = create_simple_graph();
let generator = DeterministicFamilyGenerator::new(3);
let seeds1 = generator.generate_seeds(&graph, 1);
let seeds2 = generator.generate_seeds(&graph, 1);
assert_eq!(seeds1, seeds2);
assert!(seeds1.contains(&1));
}
#[test]
fn test_deterministic_local_kcut_creation() {
let oracle = DeterministicLocalKCut::new(10);
assert_eq!(oracle.max_radius, 10);
let default_oracle = DeterministicLocalKCut::default();
assert_eq!(default_oracle.max_radius, 10);
}
#[test]
fn test_simple_path_cut() {
let graph = create_simple_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 2,
radius: 2,
};
let result = oracle.search(&graph, query);
match result {
LocalKCutResult::Found { cut_value, witness } => {
assert!(cut_value <= 2);
assert!(witness.contains(1));
assert_eq!(witness.boundary_size(), cut_value);
}
LocalKCutResult::NoneInLocality => {
}
}
}
#[test]
fn test_triangle_no_cut() {
let graph = create_triangle_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 1,
radius: 3,
};
let result = oracle.search(&graph, query);
match result {
LocalKCutResult::NoneInLocality => {
}
LocalKCutResult::Found { cut_value, .. } => {
assert!(cut_value <= 1);
}
}
}
#[test]
fn test_dumbbell_bridge_cut() {
let graph = create_dumbbell_graph();
let oracle = DeterministicLocalKCut::new(10);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 3,
radius: 10,
};
let result = oracle.search(&graph, query);
match result {
LocalKCutResult::Found { cut_value, witness } => {
assert_eq!(cut_value, 1);
assert!(witness.contains(1));
let cardinality = witness.cardinality();
assert!(cardinality == 3 || cardinality == 4);
}
LocalKCutResult::NoneInLocality => {
panic!("Should find the bridge cut");
}
}
}
#[test]
fn test_determinism() {
let graph = create_dumbbell_graph();
let oracle = DeterministicLocalKCut::new(10);
let query = LocalKCutQuery {
seed_vertices: vec![1, 2],
budget_k: 5,
radius: 5,
};
let result1 = oracle.search(&graph, query.clone());
let result2 = oracle.search(&graph, query);
match (result1, result2) {
(
LocalKCutResult::Found {
cut_value: v1,
witness: w1,
},
LocalKCutResult::Found {
cut_value: v2,
witness: w2,
},
) => {
assert_eq!(v1, v2);
assert_eq!(w1.seed(), w2.seed());
assert_eq!(w1.boundary_size(), w2.boundary_size());
assert_eq!(w1.cardinality(), w2.cardinality());
}
(LocalKCutResult::NoneInLocality, LocalKCutResult::NoneInLocality) => {
}
_ => {
panic!("Non-deterministic results!");
}
}
}
#[test]
fn test_empty_seeds() {
let graph = create_simple_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![],
budget_k: 10,
radius: 5,
};
let result = oracle.search(&graph, query);
assert!(matches!(result, LocalKCutResult::NoneInLocality));
}
#[test]
fn test_invalid_seed() {
let graph = create_simple_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![999],
budget_k: 10,
radius: 5,
};
let result = oracle.search(&graph, query);
assert!(matches!(result, LocalKCutResult::NoneInLocality));
}
#[test]
fn test_zero_radius() {
let graph = create_simple_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 10,
radius: 0,
};
let result = oracle.search(&graph, query);
match result {
LocalKCutResult::Found { witness, .. } => {
assert_eq!(witness.cardinality(), 1);
assert!(witness.contains(1));
}
LocalKCutResult::NoneInLocality => {
}
}
}
#[test]
fn test_boundary_calculation() {
let graph = create_dumbbell_graph();
let oracle = DeterministicLocalKCut::new(5);
let mut vertices = HashSet::new();
vertices.insert(1);
vertices.insert(2);
vertices.insert(3);
let boundary = oracle.calculate_boundary(&graph, &vertices);
assert_eq!(boundary, 1);
}
#[test]
fn test_witness_creation() {
let oracle = DeterministicLocalKCut::new(5);
let mut vertices = HashSet::new();
vertices.insert(1);
vertices.insert(2);
vertices.insert(3);
let witness = oracle.create_witness(1, &vertices, 5);
assert_eq!(witness.seed(), 1);
assert_eq!(witness.boundary_size(), 5);
assert_eq!(witness.cardinality(), 3);
assert!(witness.contains(1));
assert!(witness.contains(2));
assert!(witness.contains(3));
assert!(!witness.contains(4));
}
#[test]
fn test_multiple_seeds() {
let graph = create_dumbbell_graph();
let oracle = DeterministicLocalKCut::new(10);
let query = LocalKCutQuery {
seed_vertices: vec![1, 2, 3],
budget_k: 5,
radius: 5,
};
let result = oracle.search(&graph, query);
match result {
LocalKCutResult::Found { witness, .. } => {
let contains_seed =
witness.contains(1) || witness.contains(2) || witness.contains(3);
assert!(contains_seed);
}
LocalKCutResult::NoneInLocality => {
}
}
}
#[test]
fn test_budget_enforcement() {
let graph = create_triangle_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 1,
radius: 5,
};
let result = oracle.search(&graph, query);
if let LocalKCutResult::Found { cut_value, .. } = result {
assert!(cut_value <= 1);
}
}
#[test]
fn test_large_radius() {
let graph = create_simple_graph();
let oracle = DeterministicLocalKCut::new(5);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 10,
radius: 100,
};
let _result = oracle.search(&graph, query);
}
#[test]
fn test_witness_properties() {
let graph = create_dumbbell_graph();
let oracle = DeterministicLocalKCut::new(10);
let query = LocalKCutQuery {
seed_vertices: vec![1],
budget_k: 5,
radius: 5,
};
if let LocalKCutResult::Found { witness, cut_value } = oracle.search(&graph, query) {
assert_eq!(witness.boundary_size(), cut_value);
assert!(witness.cardinality() > 0);
assert!(witness.contains(witness.seed()));
}
}
}