1use crate::graph::{DynamicGraph, VertexId, EdgeId, Weight};
60use crate::error::{MinCutError, Result};
61use std::collections::{HashMap, HashSet, VecDeque};
62use std::sync::Arc;
63
64pub type Conductance = f64;
69
70#[derive(Debug, Clone)]
74pub struct ExpanderComponent {
75 pub id: usize,
77 pub vertices: HashSet<VertexId>,
79 pub conductance: Conductance,
81 pub level: usize,
83 pub boundary_edges: Vec<EdgeId>,
85 pub volume: f64,
87}
88
89impl ExpanderComponent {
90 fn new(id: usize, vertices: HashSet<VertexId>, level: usize) -> Self {
92 Self {
93 id,
94 vertices,
95 conductance: 0.0,
96 level,
97 boundary_edges: Vec::new(),
98 volume: 0.0,
99 }
100 }
101
102 pub fn contains(&self, v: VertexId) -> bool {
104 self.vertices.contains(&v)
105 }
106
107 pub fn size(&self) -> usize {
109 self.vertices.len()
110 }
111}
112
113pub struct ExpanderDecomposition {
120 levels: Vec<Vec<ExpanderComponent>>,
122 vertex_to_component: Vec<HashMap<VertexId, usize>>,
124 phi: Conductance,
126 graph: Arc<DynamicGraph>,
128 next_component_id: usize,
130}
131
132impl ExpanderDecomposition {
133 pub fn build(graph: Arc<DynamicGraph>, phi: Conductance) -> Result<Self> {
147 if phi <= 0.0 || phi >= 1.0 {
148 return Err(MinCutError::InvalidParameter(
149 format!("Conductance phi must be in (0, 1), got {}", phi)
150 ));
151 }
152
153 let mut decomp = Self {
154 levels: Vec::new(),
155 vertex_to_component: Vec::new(),
156 phi,
157 graph: graph.clone(),
158 next_component_id: 0,
159 };
160
161 decomp.build_hierarchy()?;
163
164 Ok(decomp)
165 }
166
167 pub fn component_at_level(&self, v: VertexId, level: usize) -> Option<&ExpanderComponent> {
169 if level >= self.levels.len() {
170 return None;
171 }
172
173 let component_id = self.vertex_to_component[level].get(&v)?;
174 self.levels[level].iter().find(|c| c.id == *component_id)
175 }
176
177 pub fn insert_edge(&mut self, u: VertexId, v: VertexId) -> Result<()> {
181 for level in 0..self.levels.len() {
183 if let (Some(u_comp_id), Some(v_comp_id)) = (
184 self.vertex_to_component[level].get(&u),
185 self.vertex_to_component[level].get(&v),
186 ) {
187 if u_comp_id != v_comp_id {
189 if let Some(edge) = self.graph.get_edge(u, v) {
191 for comp in &mut self.levels[level] {
193 if comp.id == *u_comp_id || comp.id == *v_comp_id {
194 comp.boundary_edges.push(edge.id);
195 }
196 }
197 }
198 } else {
199 let comp_id = *u_comp_id;
201 if let Some(comp) = self.levels[level].iter().find(|c| c.id == comp_id) {
203 let vertices = comp.vertices.clone();
204 let conductance = self.compute_conductance(&vertices);
205 let volume = self.compute_volume(&vertices);
206
207 if let Some(comp) = self.levels[level].iter_mut().find(|c| c.id == comp_id) {
209 comp.conductance = conductance;
210 comp.volume = volume;
211 }
212 }
213 }
214 }
215 }
216
217 Ok(())
218 }
219
220 pub fn delete_edge(&mut self, u: VertexId, _v: VertexId) -> Result<()> {
224 for level in 0..self.levels.len() {
226 if let Some(u_comp_id) = self.vertex_to_component[level].get(&u) {
227 let comp_id = *u_comp_id;
228
229 if let Some(comp) = self.levels[level].iter().find(|c| c.id == comp_id) {
232 let vertices = comp.vertices.clone();
233 let is_connected = self.is_connected(&vertices);
234
235 if !is_connected {
236 let _components = self.find_connected_components(&vertices);
238 }
241
242 let conductance = self.compute_conductance(&vertices);
244 let volume = self.compute_volume(&vertices);
245
246 if let Some(comp) = self.levels[level].iter_mut().find(|c| c.id == comp_id) {
248 comp.conductance = conductance;
249 comp.volume = volume;
250 }
251 }
252 }
253 }
254
255 Ok(())
256 }
257
258 pub fn num_levels(&self) -> usize {
260 self.levels.len()
261 }
262
263 fn build_hierarchy(&mut self) -> Result<()> {
265 let all_vertices: HashSet<VertexId> = self.graph.vertices().into_iter().collect();
266
267 if all_vertices.is_empty() {
268 return Ok(());
269 }
270
271 let components = deterministic_decompose(&self.graph, &all_vertices, self.phi);
274
275 let mut level_components = Vec::new();
277 let mut vertex_map = HashMap::new();
278
279 for vertices in components {
280 let comp_id = self.next_component_id;
281 self.next_component_id += 1;
282
283 let mut component = ExpanderComponent::new(comp_id, vertices.clone(), 0);
284 component.conductance = self.compute_conductance(&vertices);
285 component.volume = self.compute_volume(&vertices);
286
287 for &v in &vertices {
289 vertex_map.insert(v, comp_id);
290 }
291
292 level_components.push(component);
293 }
294
295 self.levels.push(level_components);
296 self.vertex_to_component.push(vertex_map);
297
298 Ok(())
299 }
300
301 fn compute_conductance(&self, vertices: &HashSet<VertexId>) -> Conductance {
309 if vertices.is_empty() {
310 return 0.0;
311 }
312
313 let all_vertices: HashSet<VertexId> = self.graph.vertices().into_iter().collect();
314 let complement: HashSet<VertexId> = all_vertices.difference(vertices).copied().collect();
315
316 if complement.is_empty() {
317 return 0.0;
318 }
319
320 let mut cut_value = 0.0;
322 for &u in vertices {
323 for (v, _) in self.graph.neighbors(u) {
324 if !vertices.contains(&v) {
325 if let Some(weight) = self.graph.edge_weight(u, v) {
326 cut_value += weight;
327 }
328 }
329 }
330 }
331
332 let vol_s = self.compute_volume(vertices);
334 let vol_complement = self.compute_volume(&complement);
335
336 if vol_s == 0.0 || vol_complement == 0.0 {
337 return 0.0;
338 }
339
340 let min_vol = vol_s.min(vol_complement);
342 cut_value / min_vol
343 }
344
345 fn compute_volume(&self, vertices: &HashSet<VertexId>) -> f64 {
347 vertices.iter()
348 .map(|&v| self.graph.degree(v) as f64)
349 .sum()
350 }
351
352 fn prune(&self, component: &ExpanderComponent) -> Option<HashSet<VertexId>> {
357 for &start in &component.vertices {
359 if let Some(cut) = self.local_cut_search(start, self.phi, &component.vertices) {
360 let conductance = self.compute_conductance(&cut);
361 if conductance < self.phi {
362 return Some(cut);
363 }
364 }
365 }
366
367 None
368 }
369
370 fn local_cut_search(
375 &self,
376 start: VertexId,
377 target_conductance: Conductance,
378 vertices: &HashSet<VertexId>,
379 ) -> Option<HashSet<VertexId>> {
380 let mut current_set = HashSet::new();
381 current_set.insert(start);
382
383 let mut visited = HashSet::new();
384 visited.insert(start);
385
386 let mut queue = VecDeque::new();
387 queue.push_back(start);
388
389 let mut best_set = current_set.clone();
390 let mut best_conductance = self.compute_conductance(¤t_set);
391
392 while let Some(u) = queue.pop_front() {
394 for (v, _) in self.graph.neighbors(u) {
395 if !vertices.contains(&v) || visited.contains(&v) {
397 continue;
398 }
399
400 visited.insert(v);
401 current_set.insert(v);
402
403 let conductance = self.compute_conductance(¤t_set);
404
405 if conductance < best_conductance {
407 best_conductance = conductance;
408 best_set = current_set.clone();
409 }
410
411 if conductance < target_conductance {
413 return Some(current_set);
414 }
415
416 queue.push_back(v);
417
418 if current_set.len() >= vertices.len() / 2 {
420 break;
421 }
422 }
423 }
424
425 if best_conductance < self.phi {
427 Some(best_set)
428 } else {
429 None
430 }
431 }
432
433 fn is_connected(&self, vertices: &HashSet<VertexId>) -> bool {
435 if vertices.is_empty() {
436 return true;
437 }
438
439 let start = *vertices.iter().next().unwrap();
440 let mut visited = HashSet::new();
441 let mut queue = VecDeque::new();
442
443 visited.insert(start);
444 queue.push_back(start);
445
446 while let Some(u) = queue.pop_front() {
447 for (v, _) in self.graph.neighbors(u) {
448 if vertices.contains(&v) && visited.insert(v) {
449 queue.push_back(v);
450 }
451 }
452 }
453
454 visited.len() == vertices.len()
455 }
456
457 fn find_connected_components(&self, vertices: &HashSet<VertexId>) -> Vec<HashSet<VertexId>> {
459 let mut visited = HashSet::new();
460 let mut components = Vec::new();
461
462 for &start in vertices {
463 if visited.contains(&start) {
464 continue;
465 }
466
467 let mut component = HashSet::new();
468 let mut queue = VecDeque::new();
469
470 component.insert(start);
471 visited.insert(start);
472 queue.push_back(start);
473
474 while let Some(u) = queue.pop_front() {
475 for (v, _) in self.graph.neighbors(u) {
476 if vertices.contains(&v) && visited.insert(v) {
477 component.insert(v);
478 queue.push_back(v);
479 }
480 }
481 }
482
483 components.push(component);
484 }
485
486 components
487 }
488}
489
490pub fn estimate_conductance(graph: &DynamicGraph, vertices: &HashSet<VertexId>) -> Conductance {
495 if vertices.is_empty() {
496 return 0.0;
497 }
498
499 let all_vertices: HashSet<VertexId> = graph.vertices().into_iter().collect();
500 let complement: HashSet<VertexId> = all_vertices.difference(vertices).copied().collect();
501
502 if complement.is_empty() {
503 return 0.0;
504 }
505
506 let mut cut_value = 0.0;
508 for &u in vertices {
509 for (v, _) in graph.neighbors(u) {
510 if !vertices.contains(&v) {
511 if let Some(weight) = graph.edge_weight(u, v) {
512 cut_value += weight;
513 }
514 }
515 }
516 }
517
518 let vol_s: f64 = vertices.iter().map(|&v| graph.degree(v) as f64).sum();
520 let vol_complement: f64 = complement.iter().map(|&v| graph.degree(v) as f64).sum();
521
522 if vol_s == 0.0 || vol_complement == 0.0 {
523 return 0.0;
524 }
525
526 let min_vol = vol_s.min(vol_complement);
528 cut_value / min_vol
529}
530
531pub fn deterministic_decompose(
538 graph: &DynamicGraph,
539 vertices: &HashSet<VertexId>,
540 phi: Conductance,
541) -> Vec<HashSet<VertexId>> {
542 if vertices.is_empty() {
544 return Vec::new();
545 }
546
547 if vertices.len() == 1 {
548 return vec![vertices.clone()];
549 }
550
551 if let Some(cut) = find_low_conductance_cut(graph, vertices, phi) {
553 if !cut.is_empty() && cut.len() < vertices.len() {
555 let complement: HashSet<VertexId> = vertices.difference(&cut).copied().collect();
556
557 let mut result = deterministic_decompose(graph, &cut, phi);
559 result.extend(deterministic_decompose(graph, &complement, phi));
560 return result;
561 }
562 }
563
564 vec![vertices.clone()]
566}
567
568fn find_low_conductance_cut(
570 graph: &DynamicGraph,
571 vertices: &HashSet<VertexId>,
572 phi: Conductance,
573) -> Option<HashSet<VertexId>> {
574 for &start in vertices {
576 if let Some(cut) = bfs_local_search(graph, start, vertices, phi) {
577 let conductance = estimate_conductance(graph, &cut);
578 if conductance < phi {
579 return Some(cut);
580 }
581 }
582 }
583
584 None
585}
586
587fn bfs_local_search(
589 graph: &DynamicGraph,
590 start: VertexId,
591 vertices: &HashSet<VertexId>,
592 target_phi: Conductance,
593) -> Option<HashSet<VertexId>> {
594 let mut current_set = HashSet::new();
595 current_set.insert(start);
596
597 let mut visited = HashSet::new();
598 visited.insert(start);
599
600 let mut queue = VecDeque::new();
601 queue.push_back(start);
602
603 let mut best_set = current_set.clone();
604 let mut best_conductance = estimate_conductance(graph, ¤t_set);
605
606 while let Some(u) = queue.pop_front() {
607 for (v, _) in graph.neighbors(u) {
608 if !vertices.contains(&v) || visited.contains(&v) {
609 continue;
610 }
611
612 visited.insert(v);
613 current_set.insert(v);
614
615 let conductance = estimate_conductance(graph, ¤t_set);
616
617 if conductance < best_conductance {
618 best_conductance = conductance;
619 best_set = current_set.clone();
620 }
621
622 if conductance < target_phi {
623 return Some(current_set);
624 }
625
626 queue.push_back(v);
627
628 if current_set.len() >= vertices.len() / 2 {
630 break;
631 }
632 }
633 }
634
635 if best_conductance < target_phi {
636 Some(best_set)
637 } else {
638 None
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645
646 fn create_test_graph() -> Arc<DynamicGraph> {
647 let graph = Arc::new(DynamicGraph::new());
648 graph.insert_edge(1, 2, 1.0).unwrap();
650 graph.insert_edge(2, 3, 1.0).unwrap();
651 graph.insert_edge(3, 1, 1.0).unwrap();
652 graph
653 }
654
655 fn create_expander_graph() -> Arc<DynamicGraph> {
656 let graph = Arc::new(DynamicGraph::new());
657 for i in 1..=5 {
659 for j in (i+1)..=5 {
660 graph.insert_edge(i, j, 1.0).unwrap();
661 }
662 }
663 graph
664 }
665
666 fn create_separable_graph() -> Arc<DynamicGraph> {
667 let graph = Arc::new(DynamicGraph::new());
668 graph.insert_edge(1, 2, 1.0).unwrap();
671 graph.insert_edge(2, 3, 1.0).unwrap();
672 graph.insert_edge(3, 1, 1.0).unwrap();
673 graph.insert_edge(3, 4, 1.0).unwrap();
675 graph.insert_edge(4, 5, 1.0).unwrap();
677 graph.insert_edge(5, 6, 1.0).unwrap();
678 graph.insert_edge(6, 4, 1.0).unwrap();
679 graph
680 }
681
682 #[test]
683 fn test_build_simple() {
684 let graph = create_test_graph();
685 let phi = 0.1;
686 let decomp = ExpanderDecomposition::build(graph, phi).unwrap();
687
688 assert!(decomp.num_levels() > 0);
689 }
690
691 #[test]
692 fn test_build_invalid_phi() {
693 let graph = create_test_graph();
694
695 assert!(ExpanderDecomposition::build(graph.clone(), 0.0).is_err());
697
698 assert!(ExpanderDecomposition::build(graph.clone(), 1.0).is_err());
700
701 assert!(ExpanderDecomposition::build(graph.clone(), 1.5).is_err());
703 }
704
705 #[test]
706 fn test_compute_conductance_triangle() {
707 let graph = create_test_graph();
708 let phi = 0.1;
709 let decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
710
711 let mut single_vertex = HashSet::new();
713 single_vertex.insert(1);
714 let conductance = decomp.compute_conductance(&single_vertex);
715
716 assert_eq!(conductance, 1.0);
721 }
722
723 #[test]
724 fn test_compute_conductance_complete() {
725 let graph = create_expander_graph();
726 let phi = 0.1;
727 let decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
728
729 let mut single_vertex = HashSet::new();
731 single_vertex.insert(1);
732 let conductance = decomp.compute_conductance(&single_vertex);
733
734 assert_eq!(conductance, 1.0);
739 }
740
741 #[test]
742 fn test_compute_volume() {
743 let graph = create_test_graph();
744 let phi = 0.1;
745 let decomp = ExpanderDecomposition::build(graph, phi).unwrap();
746
747 let mut vertices = HashSet::new();
748 vertices.insert(1);
749 vertices.insert(2);
750
751 let volume = decomp.compute_volume(&vertices);
754 assert_eq!(volume, 4.0);
755 }
756
757 #[test]
758 fn test_estimate_conductance() {
759 let graph = create_test_graph();
760
761 let mut vertices = HashSet::new();
762 vertices.insert(1);
763
764 let conductance = estimate_conductance(&graph, &vertices);
765 assert_eq!(conductance, 1.0);
766 }
767
768 #[test]
769 fn test_deterministic_decompose_triangle() {
770 let graph = create_test_graph();
771 let vertices: HashSet<VertexId> = graph.vertices().into_iter().collect();
772
773 let phi = 0.5;
777 let components = deterministic_decompose(&graph, &vertices, phi);
778
779 assert!(components.len() >= 1);
782 assert_eq!(components.iter().map(|c| c.len()).sum::<usize>(), 3);
783 }
784
785 #[test]
786 fn test_deterministic_decompose_separable() {
787 let graph = create_separable_graph();
788 let vertices: HashSet<VertexId> = graph.vertices().into_iter().collect();
789
790 let phi = 0.3;
792 let components = deterministic_decompose(&graph, &vertices, phi);
793
794 assert!(components.len() >= 1);
796 }
797
798 #[test]
799 fn test_component_at_level() {
800 let graph = create_test_graph();
801 let phi = 0.1;
802 let decomp = ExpanderDecomposition::build(graph, phi).unwrap();
803
804 if decomp.num_levels() > 0 {
806 let comp = decomp.component_at_level(1, 0);
807 assert!(comp.is_some());
808
809 if let Some(c) = comp {
810 assert!(c.contains(1));
811 }
812 }
813 }
814
815 #[test]
816 fn test_insert_edge() {
817 let graph = create_test_graph();
818 let phi = 0.1;
819 let mut decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
820
821 graph.insert_edge(1, 4, 1.0).unwrap();
823 let result = decomp.insert_edge(1, 4);
824 assert!(result.is_ok());
825 }
826
827 #[test]
828 fn test_delete_edge() {
829 let graph = create_test_graph();
830 let phi = 0.1;
831 let mut decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
832
833 graph.delete_edge(1, 2).unwrap();
835 let result = decomp.delete_edge(1, 2);
836 assert!(result.is_ok());
837 }
838
839 #[test]
840 fn test_is_connected() {
841 let graph = create_test_graph();
842 let phi = 0.1;
843 let decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
844
845 let vertices: HashSet<VertexId> = vec![1, 2, 3].into_iter().collect();
846 assert!(decomp.is_connected(&vertices));
847
848 let disconnected: HashSet<VertexId> = vec![1, 2].into_iter().collect();
850 assert!(decomp.is_connected(&disconnected));
852 }
853
854 #[test]
855 fn test_find_connected_components() {
856 let graph = Arc::new(DynamicGraph::new());
857 graph.insert_edge(1, 2, 1.0).unwrap();
859 graph.insert_edge(3, 4, 1.0).unwrap();
860
861 let phi = 0.1;
862 let decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
863
864 let vertices: HashSet<VertexId> = vec![1, 2, 3, 4].into_iter().collect();
865 let components = decomp.find_connected_components(&vertices);
866
867 assert_eq!(components.len(), 2);
868 }
869
870 #[test]
871 fn test_local_cut_search() {
872 let graph = create_separable_graph();
873 let phi = 0.3;
874 let decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
875
876 let vertices: HashSet<VertexId> = graph.vertices().into_iter().collect();
877
878 if let Some(cut) = decomp.local_cut_search(1, phi, &vertices) {
880 assert!(!cut.is_empty());
881 assert!(cut.len() < vertices.len());
882 }
883 }
884
885 #[test]
886 fn test_prune() {
887 let graph = create_separable_graph();
888 let phi = 0.3;
889 let decomp = ExpanderDecomposition::build(graph.clone(), phi).unwrap();
890
891 if decomp.num_levels() > 0 && !decomp.levels[0].is_empty() {
892 let component = &decomp.levels[0][0];
893
894 let result = decomp.prune(component);
896 assert!(result.is_some() || result.is_none());
898 }
899 }
900
901 #[test]
902 fn test_expander_component_methods() {
903 let mut vertices = HashSet::new();
904 vertices.insert(1);
905 vertices.insert(2);
906 vertices.insert(3);
907
908 let component = ExpanderComponent::new(0, vertices, 0);
909
910 assert_eq!(component.id, 0);
911 assert_eq!(component.level, 0);
912 assert_eq!(component.size(), 3);
913 assert!(component.contains(1));
914 assert!(!component.contains(4));
915 }
916
917 #[test]
918 fn test_empty_graph() {
919 let graph = Arc::new(DynamicGraph::new());
920 let phi = 0.1;
921 let decomp = ExpanderDecomposition::build(graph, phi).unwrap();
922
923 assert_eq!(decomp.num_levels(), 0);
924 }
925
926 #[test]
927 fn test_single_vertex() {
928 let graph = Arc::new(DynamicGraph::new());
929 graph.add_vertex(1);
930
931 let phi = 0.1;
932 let decomp = ExpanderDecomposition::build(graph, phi).unwrap();
933
934 assert!(decomp.num_levels() > 0);
935 }
936
937 #[test]
938 fn test_large_expander() {
939 let graph = Arc::new(DynamicGraph::new());
940
941 for i in 1..=10 {
943 for j in (i+1)..=10 {
944 graph.insert_edge(i, j, 1.0).unwrap();
945 }
946 }
947
948 let phi = 0.1;
949 let decomp = ExpanderDecomposition::build(graph, phi).unwrap();
950
951 assert!(decomp.num_levels() > 0);
952 }
953}