1use crate::graph::{DynamicGraph, VertexId};
24use crate::instance::WitnessHandle;
25use roaring::RoaringBitmap;
26use std::collections::{HashSet, VecDeque};
27
28#[derive(Debug, Clone)]
35pub struct LocalKCutQuery {
36 pub seed_vertices: Vec<VertexId>,
41
42 pub budget_k: u64,
47
48 pub radius: usize,
53}
54
55#[derive(Debug, Clone)]
60pub enum LocalKCutResult {
61 Found {
66 witness: WitnessHandle,
68 cut_value: u64,
70 },
71
72 NoneInLocality,
77}
78
79pub trait LocalKCutOracle: Send + Sync {
85 fn search(&self, graph: &DynamicGraph, query: LocalKCutQuery) -> LocalKCutResult;
101}
102
103#[derive(Debug, Clone)]
108pub struct DeterministicFamilyGenerator {
109 max_size: usize,
111}
112
113impl DeterministicFamilyGenerator {
114 pub fn new(max_size: usize) -> Self {
124 Self { max_size }
125 }
126
127 pub fn generate_seeds(&self, graph: &DynamicGraph, v: VertexId) -> Vec<VertexId> {
141 let mut seeds = vec![v];
142
143 let mut neighbors: Vec<_> = graph
145 .neighbors(v)
146 .into_iter()
147 .map(|(neighbor, _)| neighbor)
148 .collect();
149
150 neighbors.sort_unstable();
152
153 for &neighbor in neighbors.iter().take(self.max_size.saturating_sub(1)) {
155 seeds.push(neighbor);
156 }
157
158 seeds
159 }
160}
161
162impl Default for DeterministicFamilyGenerator {
163 fn default() -> Self {
164 Self::new(4)
165 }
166}
167
168#[derive(Debug, Clone)]
190pub struct DeterministicLocalKCut {
191 max_radius: usize,
193
194 #[allow(dead_code)]
196 family_generator: DeterministicFamilyGenerator,
197}
198
199impl DeterministicLocalKCut {
200 pub fn new(max_radius: usize) -> Self {
218 Self {
219 max_radius,
220 family_generator: DeterministicFamilyGenerator::default(),
221 }
222 }
223
224 pub fn with_family_generator(
235 max_radius: usize,
236 family_generator: DeterministicFamilyGenerator,
237 ) -> Self {
238 Self {
239 max_radius,
240 family_generator,
241 }
242 }
243
244 fn deterministic_bfs(
260 &self,
261 graph: &DynamicGraph,
262 seeds: &[VertexId],
263 budget: u64,
264 radius: usize,
265 ) -> Option<(HashSet<VertexId>, u64)> {
266 if seeds.is_empty() {
267 return None;
268 }
269
270 let mut visited = HashSet::new();
271 let mut queue = VecDeque::new();
272 let mut best_cut: Option<(HashSet<VertexId>, u64)> = None;
273
274 for &seed in seeds {
276 if graph.has_vertex(seed) {
277 visited.insert(seed);
278 queue.push_back((seed, 0));
279 }
280 }
281
282 if visited.is_empty() {
283 return None;
284 }
285
286 let mut current_layer = visited.clone();
288
289 for depth in 0..=radius {
291 let boundary_size = self.calculate_boundary(graph, &visited);
293
294 if boundary_size <= budget && !visited.is_empty() {
296 if visited.len() < graph.num_vertices() {
298 let should_update = match &best_cut {
300 None => true,
301 Some((_, prev_boundary)) => boundary_size < *prev_boundary,
302 };
303
304 if should_update {
305 best_cut = Some((visited.clone(), boundary_size));
306 }
307 }
308 }
309
310 if let Some((_, boundary)) = &best_cut {
312 if *boundary == 0 {
313 break;
314 }
315 }
316
317 if depth >= radius {
319 break;
320 }
321
322 let mut next_layer = HashSet::new();
324 let mut layer_vertices: Vec<_> = current_layer.iter().copied().collect();
325 layer_vertices.sort_unstable(); for v in layer_vertices {
328 let mut neighbors: Vec<_> = graph
330 .neighbors(v)
331 .into_iter()
332 .map(|(neighbor, _)| neighbor)
333 .filter(|neighbor| !visited.contains(neighbor))
334 .collect();
335
336 neighbors.sort_unstable();
337
338 for neighbor in neighbors {
339 if visited.insert(neighbor) {
340 next_layer.insert(neighbor);
341 queue.push_back((neighbor, depth + 1));
342 }
343 }
344 }
345
346 current_layer = next_layer;
347
348 if current_layer.is_empty() {
350 break;
351 }
352 }
353
354 best_cut
355 }
356
357 fn calculate_boundary(&self, graph: &DynamicGraph, vertex_set: &HashSet<VertexId>) -> u64 {
370 let mut boundary_edges = HashSet::new();
371
372 for &v in vertex_set {
373 for (neighbor, edge_id) in graph.neighbors(v) {
374 if !vertex_set.contains(&neighbor) {
375 boundary_edges.insert(edge_id);
377 }
378 }
379 }
380
381 boundary_edges.len() as u64
382 }
383
384 fn create_witness(
396 &self,
397 seed: VertexId,
398 vertices: &HashSet<VertexId>,
399 boundary_size: u64,
400 ) -> WitnessHandle {
401 let mut membership = RoaringBitmap::new();
402
403 for &v in vertices {
404 if v <= u32::MAX as u64 {
405 membership.insert(v as u32);
406 }
407 }
408
409 WitnessHandle::new(seed, membership, boundary_size)
410 }
411}
412
413impl LocalKCutOracle for DeterministicLocalKCut {
414 fn search(&self, graph: &DynamicGraph, query: LocalKCutQuery) -> LocalKCutResult {
415 if query.seed_vertices.is_empty() {
417 return LocalKCutResult::NoneInLocality;
418 }
419
420 let radius = query.radius.min(self.max_radius);
422
423 let result = self.deterministic_bfs(graph, &query.seed_vertices, query.budget_k, radius);
425
426 match result {
427 Some((vertices, boundary_size)) => {
428 let seed = query
430 .seed_vertices
431 .iter()
432 .find(|&&s| vertices.contains(&s))
433 .copied()
434 .unwrap_or(query.seed_vertices[0]);
435
436 let witness = self.create_witness(seed, &vertices, boundary_size);
437
438 LocalKCutResult::Found {
439 witness,
440 cut_value: boundary_size,
441 }
442 }
443 None => LocalKCutResult::NoneInLocality,
444 }
445 }
446}
447
448impl Default for DeterministicLocalKCut {
449 fn default() -> Self {
450 Self::new(10)
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457 use std::sync::Arc;
458
459 fn create_simple_graph() -> Arc<DynamicGraph> {
460 let graph = DynamicGraph::new();
461
462 graph.insert_edge(1, 2, 1.0).unwrap();
464 graph.insert_edge(2, 3, 1.0).unwrap();
465 graph.insert_edge(3, 4, 1.0).unwrap();
466
467 Arc::new(graph)
468 }
469
470 fn create_triangle_graph() -> Arc<DynamicGraph> {
471 let graph = DynamicGraph::new();
472
473 graph.insert_edge(1, 2, 1.0).unwrap();
475 graph.insert_edge(2, 3, 1.0).unwrap();
476 graph.insert_edge(3, 1, 1.0).unwrap();
477
478 Arc::new(graph)
479 }
480
481 fn create_dumbbell_graph() -> Arc<DynamicGraph> {
482 let graph = DynamicGraph::new();
483
484 graph.insert_edge(1, 2, 1.0).unwrap();
487 graph.insert_edge(2, 3, 1.0).unwrap();
488 graph.insert_edge(3, 1, 1.0).unwrap();
489
490 graph.insert_edge(3, 4, 1.0).unwrap();
492
493 graph.insert_edge(4, 5, 1.0).unwrap();
495 graph.insert_edge(5, 6, 1.0).unwrap();
496 graph.insert_edge(6, 4, 1.0).unwrap();
497
498 Arc::new(graph)
499 }
500
501 #[test]
502 fn test_local_kcut_query_creation() {
503 let query = LocalKCutQuery {
504 seed_vertices: vec![1, 2, 3],
505 budget_k: 10,
506 radius: 5,
507 };
508
509 assert_eq!(query.seed_vertices.len(), 3);
510 assert_eq!(query.budget_k, 10);
511 assert_eq!(query.radius, 5);
512 }
513
514 #[test]
515 fn test_deterministic_family_generator() {
516 let graph = create_simple_graph();
517 let generator = DeterministicFamilyGenerator::new(3);
518
519 let seeds1 = generator.generate_seeds(&graph, 1);
520 let seeds2 = generator.generate_seeds(&graph, 1);
521
522 assert_eq!(seeds1, seeds2);
524
525 assert!(seeds1.contains(&1));
527 }
528
529 #[test]
530 fn test_deterministic_local_kcut_creation() {
531 let oracle = DeterministicLocalKCut::new(10);
532 assert_eq!(oracle.max_radius, 10);
533
534 let default_oracle = DeterministicLocalKCut::default();
535 assert_eq!(default_oracle.max_radius, 10);
536 }
537
538 #[test]
539 fn test_simple_path_cut() {
540 let graph = create_simple_graph();
541 let oracle = DeterministicLocalKCut::new(5);
542
543 let query = LocalKCutQuery {
544 seed_vertices: vec![1],
545 budget_k: 2,
546 radius: 2,
547 };
548
549 let result = oracle.search(&graph, query);
550
551 match result {
552 LocalKCutResult::Found { cut_value, witness } => {
553 assert!(cut_value <= 2);
554 assert!(witness.contains(1));
555 assert_eq!(witness.boundary_size(), cut_value);
556 }
557 LocalKCutResult::NoneInLocality => {
558 }
560 }
561 }
562
563 #[test]
564 fn test_triangle_no_cut() {
565 let graph = create_triangle_graph();
566 let oracle = DeterministicLocalKCut::new(5);
567
568 let query = LocalKCutQuery {
570 seed_vertices: vec![1],
571 budget_k: 1,
572 radius: 3,
573 };
574
575 let result = oracle.search(&graph, query);
576
577 match result {
578 LocalKCutResult::NoneInLocality => {
579 }
581 LocalKCutResult::Found { cut_value, .. } => {
582 assert!(cut_value <= 1);
584 }
585 }
586 }
587
588 #[test]
589 fn test_dumbbell_bridge_cut() {
590 let graph = create_dumbbell_graph();
591 let oracle = DeterministicLocalKCut::new(10);
592
593 let query = LocalKCutQuery {
595 seed_vertices: vec![1],
596 budget_k: 3,
597 radius: 10,
598 };
599
600 let result = oracle.search(&graph, query);
601
602 match result {
603 LocalKCutResult::Found { cut_value, witness } => {
604 assert_eq!(cut_value, 1);
606 assert!(witness.contains(1));
607
608 let cardinality = witness.cardinality();
610 assert!(cardinality == 3 || cardinality == 4);
611 }
612 LocalKCutResult::NoneInLocality => {
613 panic!("Should find the bridge cut");
614 }
615 }
616 }
617
618 #[test]
619 fn test_determinism() {
620 let graph = create_dumbbell_graph();
621 let oracle = DeterministicLocalKCut::new(10);
622
623 let query = LocalKCutQuery {
624 seed_vertices: vec![1, 2],
625 budget_k: 5,
626 radius: 5,
627 };
628
629 let result1 = oracle.search(&graph, query.clone());
631 let result2 = oracle.search(&graph, query);
632
633 match (result1, result2) {
635 (
636 LocalKCutResult::Found {
637 cut_value: v1,
638 witness: w1,
639 },
640 LocalKCutResult::Found {
641 cut_value: v2,
642 witness: w2,
643 },
644 ) => {
645 assert_eq!(v1, v2);
646 assert_eq!(w1.seed(), w2.seed());
647 assert_eq!(w1.boundary_size(), w2.boundary_size());
648 assert_eq!(w1.cardinality(), w2.cardinality());
649 }
650 (LocalKCutResult::NoneInLocality, LocalKCutResult::NoneInLocality) => {
651 }
653 _ => {
654 panic!("Non-deterministic results!");
655 }
656 }
657 }
658
659 #[test]
660 fn test_empty_seeds() {
661 let graph = create_simple_graph();
662 let oracle = DeterministicLocalKCut::new(5);
663
664 let query = LocalKCutQuery {
665 seed_vertices: vec![],
666 budget_k: 10,
667 radius: 5,
668 };
669
670 let result = oracle.search(&graph, query);
671
672 assert!(matches!(result, LocalKCutResult::NoneInLocality));
673 }
674
675 #[test]
676 fn test_invalid_seed() {
677 let graph = create_simple_graph();
678 let oracle = DeterministicLocalKCut::new(5);
679
680 let query = LocalKCutQuery {
682 seed_vertices: vec![999],
683 budget_k: 10,
684 radius: 5,
685 };
686
687 let result = oracle.search(&graph, query);
688
689 assert!(matches!(result, LocalKCutResult::NoneInLocality));
690 }
691
692 #[test]
693 fn test_zero_radius() {
694 let graph = create_simple_graph();
695 let oracle = DeterministicLocalKCut::new(5);
696
697 let query = LocalKCutQuery {
698 seed_vertices: vec![1],
699 budget_k: 10,
700 radius: 0,
701 };
702
703 let result = oracle.search(&graph, query);
704
705 match result {
707 LocalKCutResult::Found { witness, .. } => {
708 assert_eq!(witness.cardinality(), 1);
709 assert!(witness.contains(1));
710 }
711 LocalKCutResult::NoneInLocality => {
712 }
714 }
715 }
716
717 #[test]
718 fn test_boundary_calculation() {
719 let graph = create_dumbbell_graph();
720 let oracle = DeterministicLocalKCut::new(5);
721
722 let mut vertices = HashSet::new();
724 vertices.insert(1);
725 vertices.insert(2);
726 vertices.insert(3);
727
728 let boundary = oracle.calculate_boundary(&graph, &vertices);
729
730 assert_eq!(boundary, 1);
732 }
733
734 #[test]
735 fn test_witness_creation() {
736 let oracle = DeterministicLocalKCut::new(5);
737
738 let mut vertices = HashSet::new();
739 vertices.insert(1);
740 vertices.insert(2);
741 vertices.insert(3);
742
743 let witness = oracle.create_witness(1, &vertices, 5);
744
745 assert_eq!(witness.seed(), 1);
746 assert_eq!(witness.boundary_size(), 5);
747 assert_eq!(witness.cardinality(), 3);
748 assert!(witness.contains(1));
749 assert!(witness.contains(2));
750 assert!(witness.contains(3));
751 assert!(!witness.contains(4));
752 }
753
754 #[test]
755 fn test_multiple_seeds() {
756 let graph = create_dumbbell_graph();
757 let oracle = DeterministicLocalKCut::new(10);
758
759 let query = LocalKCutQuery {
760 seed_vertices: vec![1, 2, 3],
761 budget_k: 5,
762 radius: 5,
763 };
764
765 let result = oracle.search(&graph, query);
766
767 match result {
768 LocalKCutResult::Found { witness, .. } => {
769 let contains_seed =
771 witness.contains(1) || witness.contains(2) || witness.contains(3);
772 assert!(contains_seed);
773 }
774 LocalKCutResult::NoneInLocality => {
775 }
777 }
778 }
779
780 #[test]
781 fn test_budget_enforcement() {
782 let graph = create_triangle_graph();
783 let oracle = DeterministicLocalKCut::new(5);
784
785 let query = LocalKCutQuery {
786 seed_vertices: vec![1],
787 budget_k: 1,
788 radius: 5,
789 };
790
791 let result = oracle.search(&graph, query);
792
793 if let LocalKCutResult::Found { cut_value, .. } = result {
795 assert!(cut_value <= 1);
796 }
797 }
798
799 #[test]
800 fn test_large_radius() {
801 let graph = create_simple_graph();
802 let oracle = DeterministicLocalKCut::new(5);
803
804 let query = LocalKCutQuery {
806 seed_vertices: vec![1],
807 budget_k: 10,
808 radius: 100,
809 };
810
811 let _result = oracle.search(&graph, query);
813 }
814
815 #[test]
816 fn test_witness_properties() {
817 let graph = create_dumbbell_graph();
818 let oracle = DeterministicLocalKCut::new(10);
819
820 let query = LocalKCutQuery {
821 seed_vertices: vec![1],
822 budget_k: 5,
823 radius: 5,
824 };
825
826 if let LocalKCutResult::Found { witness, cut_value } = oracle.search(&graph, query) {
827 assert_eq!(witness.boundary_size(), cut_value);
829
830 assert!(witness.cardinality() > 0);
832
833 assert!(witness.contains(witness.seed()));
835 }
836 }
837}