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.neighbors(v)
145 .into_iter()
146 .map(|(neighbor, _)| neighbor)
147 .collect();
148
149 neighbors.sort_unstable();
151
152 for &neighbor in neighbors.iter().take(self.max_size.saturating_sub(1)) {
154 seeds.push(neighbor);
155 }
156
157 seeds
158 }
159}
160
161impl Default for DeterministicFamilyGenerator {
162 fn default() -> Self {
163 Self::new(4)
164 }
165}
166
167#[derive(Debug, Clone)]
189pub struct DeterministicLocalKCut {
190 max_radius: usize,
192
193 #[allow(dead_code)]
195 family_generator: DeterministicFamilyGenerator,
196}
197
198impl DeterministicLocalKCut {
199 pub fn new(max_radius: usize) -> Self {
217 Self {
218 max_radius,
219 family_generator: DeterministicFamilyGenerator::default(),
220 }
221 }
222
223 pub fn with_family_generator(
234 max_radius: usize,
235 family_generator: DeterministicFamilyGenerator,
236 ) -> Self {
237 Self {
238 max_radius,
239 family_generator,
240 }
241 }
242
243 fn deterministic_bfs(
259 &self,
260 graph: &DynamicGraph,
261 seeds: &[VertexId],
262 budget: u64,
263 radius: usize,
264 ) -> Option<(HashSet<VertexId>, u64)> {
265 if seeds.is_empty() {
266 return None;
267 }
268
269 let mut visited = HashSet::new();
270 let mut queue = VecDeque::new();
271 let mut best_cut: Option<(HashSet<VertexId>, u64)> = None;
272
273 for &seed in seeds {
275 if graph.has_vertex(seed) {
276 visited.insert(seed);
277 queue.push_back((seed, 0));
278 }
279 }
280
281 if visited.is_empty() {
282 return None;
283 }
284
285 let mut current_layer = visited.clone();
287
288 for depth in 0..=radius {
290 let boundary_size = self.calculate_boundary(graph, &visited);
292
293 if boundary_size <= budget && !visited.is_empty() {
295 if visited.len() < graph.num_vertices() {
297 let should_update = match &best_cut {
299 None => true,
300 Some((_, prev_boundary)) => boundary_size < *prev_boundary,
301 };
302
303 if should_update {
304 best_cut = Some((visited.clone(), boundary_size));
305 }
306 }
307 }
308
309 if let Some((_, boundary)) = &best_cut {
311 if *boundary == 0 {
312 break;
313 }
314 }
315
316 if depth >= radius {
318 break;
319 }
320
321 let mut next_layer = HashSet::new();
323 let mut layer_vertices: Vec<_> = current_layer.iter().copied().collect();
324 layer_vertices.sort_unstable(); for v in layer_vertices {
327 let mut neighbors: Vec<_> = graph.neighbors(v)
329 .into_iter()
330 .map(|(neighbor, _)| neighbor)
331 .filter(|neighbor| !visited.contains(neighbor))
332 .collect();
333
334 neighbors.sort_unstable();
335
336 for neighbor in neighbors {
337 if visited.insert(neighbor) {
338 next_layer.insert(neighbor);
339 queue.push_back((neighbor, depth + 1));
340 }
341 }
342 }
343
344 current_layer = next_layer;
345
346 if current_layer.is_empty() {
348 break;
349 }
350 }
351
352 best_cut
353 }
354
355 fn calculate_boundary(&self, graph: &DynamicGraph, vertex_set: &HashSet<VertexId>) -> u64 {
368 let mut boundary_edges = HashSet::new();
369
370 for &v in vertex_set {
371 for (neighbor, edge_id) in graph.neighbors(v) {
372 if !vertex_set.contains(&neighbor) {
373 boundary_edges.insert(edge_id);
375 }
376 }
377 }
378
379 boundary_edges.len() as u64
380 }
381
382 fn create_witness(
394 &self,
395 seed: VertexId,
396 vertices: &HashSet<VertexId>,
397 boundary_size: u64,
398 ) -> WitnessHandle {
399 let mut membership = RoaringBitmap::new();
400
401 for &v in vertices {
402 if v <= u32::MAX as u64 {
403 membership.insert(v as u32);
404 }
405 }
406
407 WitnessHandle::new(seed, membership, boundary_size)
408 }
409}
410
411impl LocalKCutOracle for DeterministicLocalKCut {
412 fn search(&self, graph: &DynamicGraph, query: LocalKCutQuery) -> LocalKCutResult {
413 if query.seed_vertices.is_empty() {
415 return LocalKCutResult::NoneInLocality;
416 }
417
418 let radius = query.radius.min(self.max_radius);
420
421 let result = self.deterministic_bfs(
423 graph,
424 &query.seed_vertices,
425 query.budget_k,
426 radius,
427 );
428
429 match result {
430 Some((vertices, boundary_size)) => {
431 let seed = query.seed_vertices.iter()
433 .find(|&&s| vertices.contains(&s))
434 .copied()
435 .unwrap_or(query.seed_vertices[0]);
436
437 let witness = self.create_witness(seed, &vertices, boundary_size);
438
439 LocalKCutResult::Found {
440 witness,
441 cut_value: boundary_size,
442 }
443 }
444 None => LocalKCutResult::NoneInLocality,
445 }
446 }
447}
448
449impl Default for DeterministicLocalKCut {
450 fn default() -> Self {
451 Self::new(10)
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use super::*;
458 use std::sync::Arc;
459
460 fn create_simple_graph() -> Arc<DynamicGraph> {
461 let graph = DynamicGraph::new();
462
463 graph.insert_edge(1, 2, 1.0).unwrap();
465 graph.insert_edge(2, 3, 1.0).unwrap();
466 graph.insert_edge(3, 4, 1.0).unwrap();
467
468 Arc::new(graph)
469 }
470
471 fn create_triangle_graph() -> Arc<DynamicGraph> {
472 let graph = DynamicGraph::new();
473
474 graph.insert_edge(1, 2, 1.0).unwrap();
476 graph.insert_edge(2, 3, 1.0).unwrap();
477 graph.insert_edge(3, 1, 1.0).unwrap();
478
479 Arc::new(graph)
480 }
481
482 fn create_dumbbell_graph() -> Arc<DynamicGraph> {
483 let graph = DynamicGraph::new();
484
485 graph.insert_edge(1, 2, 1.0).unwrap();
488 graph.insert_edge(2, 3, 1.0).unwrap();
489 graph.insert_edge(3, 1, 1.0).unwrap();
490
491 graph.insert_edge(3, 4, 1.0).unwrap();
493
494 graph.insert_edge(4, 5, 1.0).unwrap();
496 graph.insert_edge(5, 6, 1.0).unwrap();
497 graph.insert_edge(6, 4, 1.0).unwrap();
498
499 Arc::new(graph)
500 }
501
502 #[test]
503 fn test_local_kcut_query_creation() {
504 let query = LocalKCutQuery {
505 seed_vertices: vec![1, 2, 3],
506 budget_k: 10,
507 radius: 5,
508 };
509
510 assert_eq!(query.seed_vertices.len(), 3);
511 assert_eq!(query.budget_k, 10);
512 assert_eq!(query.radius, 5);
513 }
514
515 #[test]
516 fn test_deterministic_family_generator() {
517 let graph = create_simple_graph();
518 let generator = DeterministicFamilyGenerator::new(3);
519
520 let seeds1 = generator.generate_seeds(&graph, 1);
521 let seeds2 = generator.generate_seeds(&graph, 1);
522
523 assert_eq!(seeds1, seeds2);
525
526 assert!(seeds1.contains(&1));
528 }
529
530 #[test]
531 fn test_deterministic_local_kcut_creation() {
532 let oracle = DeterministicLocalKCut::new(10);
533 assert_eq!(oracle.max_radius, 10);
534
535 let default_oracle = DeterministicLocalKCut::default();
536 assert_eq!(default_oracle.max_radius, 10);
537 }
538
539 #[test]
540 fn test_simple_path_cut() {
541 let graph = create_simple_graph();
542 let oracle = DeterministicLocalKCut::new(5);
543
544 let query = LocalKCutQuery {
545 seed_vertices: vec![1],
546 budget_k: 2,
547 radius: 2,
548 };
549
550 let result = oracle.search(&graph, query);
551
552 match result {
553 LocalKCutResult::Found { cut_value, witness } => {
554 assert!(cut_value <= 2);
555 assert!(witness.contains(1));
556 assert_eq!(witness.boundary_size(), cut_value);
557 }
558 LocalKCutResult::NoneInLocality => {
559 }
561 }
562 }
563
564 #[test]
565 fn test_triangle_no_cut() {
566 let graph = create_triangle_graph();
567 let oracle = DeterministicLocalKCut::new(5);
568
569 let query = LocalKCutQuery {
571 seed_vertices: vec![1],
572 budget_k: 1,
573 radius: 3,
574 };
575
576 let result = oracle.search(&graph, query);
577
578 match result {
579 LocalKCutResult::NoneInLocality => {
580 }
582 LocalKCutResult::Found { cut_value, .. } => {
583 assert!(cut_value <= 1);
585 }
586 }
587 }
588
589 #[test]
590 fn test_dumbbell_bridge_cut() {
591 let graph = create_dumbbell_graph();
592 let oracle = DeterministicLocalKCut::new(10);
593
594 let query = LocalKCutQuery {
596 seed_vertices: vec![1],
597 budget_k: 3,
598 radius: 10,
599 };
600
601 let result = oracle.search(&graph, query);
602
603 match result {
604 LocalKCutResult::Found { cut_value, witness } => {
605 assert_eq!(cut_value, 1);
607 assert!(witness.contains(1));
608
609 let cardinality = witness.cardinality();
611 assert!(cardinality == 3 || cardinality == 4);
612 }
613 LocalKCutResult::NoneInLocality => {
614 panic!("Should find the bridge cut");
615 }
616 }
617 }
618
619 #[test]
620 fn test_determinism() {
621 let graph = create_dumbbell_graph();
622 let oracle = DeterministicLocalKCut::new(10);
623
624 let query = LocalKCutQuery {
625 seed_vertices: vec![1, 2],
626 budget_k: 5,
627 radius: 5,
628 };
629
630 let result1 = oracle.search(&graph, query.clone());
632 let result2 = oracle.search(&graph, query);
633
634 match (result1, result2) {
636 (
637 LocalKCutResult::Found { cut_value: v1, witness: w1 },
638 LocalKCutResult::Found { cut_value: v2, witness: w2 },
639 ) => {
640 assert_eq!(v1, v2);
641 assert_eq!(w1.seed(), w2.seed());
642 assert_eq!(w1.boundary_size(), w2.boundary_size());
643 assert_eq!(w1.cardinality(), w2.cardinality());
644 }
645 (LocalKCutResult::NoneInLocality, LocalKCutResult::NoneInLocality) => {
646 }
648 _ => {
649 panic!("Non-deterministic results!");
650 }
651 }
652 }
653
654 #[test]
655 fn test_empty_seeds() {
656 let graph = create_simple_graph();
657 let oracle = DeterministicLocalKCut::new(5);
658
659 let query = LocalKCutQuery {
660 seed_vertices: vec![],
661 budget_k: 10,
662 radius: 5,
663 };
664
665 let result = oracle.search(&graph, query);
666
667 assert!(matches!(result, LocalKCutResult::NoneInLocality));
668 }
669
670 #[test]
671 fn test_invalid_seed() {
672 let graph = create_simple_graph();
673 let oracle = DeterministicLocalKCut::new(5);
674
675 let query = LocalKCutQuery {
677 seed_vertices: vec![999],
678 budget_k: 10,
679 radius: 5,
680 };
681
682 let result = oracle.search(&graph, query);
683
684 assert!(matches!(result, LocalKCutResult::NoneInLocality));
685 }
686
687 #[test]
688 fn test_zero_radius() {
689 let graph = create_simple_graph();
690 let oracle = DeterministicLocalKCut::new(5);
691
692 let query = LocalKCutQuery {
693 seed_vertices: vec![1],
694 budget_k: 10,
695 radius: 0,
696 };
697
698 let result = oracle.search(&graph, query);
699
700 match result {
702 LocalKCutResult::Found { witness, .. } => {
703 assert_eq!(witness.cardinality(), 1);
704 assert!(witness.contains(1));
705 }
706 LocalKCutResult::NoneInLocality => {
707 }
709 }
710 }
711
712 #[test]
713 fn test_boundary_calculation() {
714 let graph = create_dumbbell_graph();
715 let oracle = DeterministicLocalKCut::new(5);
716
717 let mut vertices = HashSet::new();
719 vertices.insert(1);
720 vertices.insert(2);
721 vertices.insert(3);
722
723 let boundary = oracle.calculate_boundary(&graph, &vertices);
724
725 assert_eq!(boundary, 1);
727 }
728
729 #[test]
730 fn test_witness_creation() {
731 let oracle = DeterministicLocalKCut::new(5);
732
733 let mut vertices = HashSet::new();
734 vertices.insert(1);
735 vertices.insert(2);
736 vertices.insert(3);
737
738 let witness = oracle.create_witness(1, &vertices, 5);
739
740 assert_eq!(witness.seed(), 1);
741 assert_eq!(witness.boundary_size(), 5);
742 assert_eq!(witness.cardinality(), 3);
743 assert!(witness.contains(1));
744 assert!(witness.contains(2));
745 assert!(witness.contains(3));
746 assert!(!witness.contains(4));
747 }
748
749 #[test]
750 fn test_multiple_seeds() {
751 let graph = create_dumbbell_graph();
752 let oracle = DeterministicLocalKCut::new(10);
753
754 let query = LocalKCutQuery {
755 seed_vertices: vec![1, 2, 3],
756 budget_k: 5,
757 radius: 5,
758 };
759
760 let result = oracle.search(&graph, query);
761
762 match result {
763 LocalKCutResult::Found { witness, .. } => {
764 let contains_seed = witness.contains(1)
766 || witness.contains(2)
767 || witness.contains(3);
768 assert!(contains_seed);
769 }
770 LocalKCutResult::NoneInLocality => {
771 }
773 }
774 }
775
776 #[test]
777 fn test_budget_enforcement() {
778 let graph = create_triangle_graph();
779 let oracle = DeterministicLocalKCut::new(5);
780
781 let query = LocalKCutQuery {
782 seed_vertices: vec![1],
783 budget_k: 1,
784 radius: 5,
785 };
786
787 let result = oracle.search(&graph, query);
788
789 if let LocalKCutResult::Found { cut_value, .. } = result {
791 assert!(cut_value <= 1);
792 }
793 }
794
795 #[test]
796 fn test_large_radius() {
797 let graph = create_simple_graph();
798 let oracle = DeterministicLocalKCut::new(5);
799
800 let query = LocalKCutQuery {
802 seed_vertices: vec![1],
803 budget_k: 10,
804 radius: 100,
805 };
806
807 let _result = oracle.search(&graph, query);
809 }
810
811 #[test]
812 fn test_witness_properties() {
813 let graph = create_dumbbell_graph();
814 let oracle = DeterministicLocalKCut::new(10);
815
816 let query = LocalKCutQuery {
817 seed_vertices: vec![1],
818 budget_k: 5,
819 radius: 5,
820 };
821
822 if let LocalKCutResult::Found { witness, cut_value } = oracle.search(&graph, query) {
823 assert_eq!(witness.boundary_size(), cut_value);
825
826 assert!(witness.cardinality() > 0);
828
829 assert!(witness.contains(witness.seed()));
831 }
832 }
833}