ruvector_mincut/fragmentation/
mod.rs1use crate::graph::{VertexId, Weight};
18use std::collections::{HashMap, HashSet, VecDeque};
19
20#[derive(Debug, Clone)]
22pub struct FragmentationConfig {
23 pub phi: f64,
25 pub max_fragment_size: usize,
27 pub min_fragment_size: usize,
29 pub boundary_sparsity: f64,
31}
32
33impl Default for FragmentationConfig {
34 fn default() -> Self {
35 Self {
36 phi: 0.1,
37 max_fragment_size: 1000,
38 min_fragment_size: 10,
39 boundary_sparsity: 0.5,
40 }
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct Fragment {
47 pub id: u64,
49 pub vertices: HashSet<VertexId>,
51 pub boundary: Vec<(VertexId, VertexId)>,
53 pub internal_edge_count: usize,
55 pub volume: usize,
57 pub parent: Option<u64>,
59 pub children: Vec<u64>,
61 pub is_expander: bool,
63}
64
65impl Fragment {
66 pub fn new(id: u64, vertices: HashSet<VertexId>) -> Self {
68 Self {
69 id,
70 vertices,
71 boundary: Vec::new(),
72 internal_edge_count: 0,
73 volume: 0,
74 parent: None,
75 children: Vec::new(),
76 is_expander: false,
77 }
78 }
79
80 pub fn size(&self) -> usize {
82 self.vertices.len()
83 }
84
85 pub fn contains(&self, v: VertexId) -> bool {
87 self.vertices.contains(&v)
88 }
89
90 pub fn boundary_sparsity(&self) -> f64 {
92 if self.volume == 0 {
93 return 0.0;
94 }
95 self.boundary.len() as f64 / self.volume as f64
96 }
97}
98
99#[derive(Debug, Clone)]
101pub struct TrimResult {
102 pub trimmed_vertices: HashSet<VertexId>,
104 pub cut_edges: Vec<(VertexId, VertexId)>,
106 pub cut_value: f64,
108 pub success: bool,
110}
111
112#[derive(Debug)]
114pub struct Fragmentation {
115 config: FragmentationConfig,
117 fragments: HashMap<u64, Fragment>,
119 vertex_fragment: HashMap<VertexId, u64>,
121 next_id: u64,
123 adjacency: HashMap<VertexId, HashMap<VertexId, Weight>>,
125 roots: Vec<u64>,
127}
128
129impl Fragmentation {
130 pub fn new(config: FragmentationConfig) -> Self {
132 Self {
133 config,
134 fragments: HashMap::new(),
135 vertex_fragment: HashMap::new(),
136 next_id: 1,
137 adjacency: HashMap::new(),
138 roots: Vec::new(),
139 }
140 }
141
142 pub fn with_defaults() -> Self {
144 Self::new(FragmentationConfig::default())
145 }
146
147 pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: Weight) {
149 self.adjacency.entry(u).or_default().insert(v, weight);
150 self.adjacency.entry(v).or_default().insert(u, weight);
151 }
152
153 pub fn delete_edge(&mut self, u: VertexId, v: VertexId) {
155 if let Some(neighbors) = self.adjacency.get_mut(&u) {
156 neighbors.remove(&v);
157 }
158 if let Some(neighbors) = self.adjacency.get_mut(&v) {
159 neighbors.remove(&u);
160 }
161 }
162
163 pub fn vertices(&self) -> Vec<VertexId> {
165 self.adjacency.keys().copied().collect()
166 }
167
168 pub fn neighbors(&self, v: VertexId) -> Vec<(VertexId, Weight)> {
170 self.adjacency
171 .get(&v)
172 .map(|n| n.iter().map(|(&v, &w)| (v, w)).collect())
173 .unwrap_or_default()
174 }
175
176 pub fn degree(&self, v: VertexId) -> usize {
178 self.adjacency.get(&v).map_or(0, |n| n.len())
179 }
180
181 pub fn fragment(&mut self) -> Vec<u64> {
185 let vertices: HashSet<_> = self.vertices().into_iter().collect();
186
187 if vertices.is_empty() {
188 return Vec::new();
189 }
190
191 let root_id = self.create_fragment(vertices);
193 self.roots.push(root_id);
194
195 let mut to_process = vec![root_id];
197
198 while let Some(fragment_id) = to_process.pop() {
199 if let Some(children) = self.try_fragment_recursive(fragment_id) {
200 to_process.extend(children);
201 }
202 }
203
204 self.roots.clone()
205 }
206
207 fn try_fragment_recursive(&mut self, fragment_id: u64) -> Option<Vec<u64>> {
209 let fragment = self.fragments.get(&fragment_id)?;
210
211 if fragment.size() <= self.config.min_fragment_size {
213 return None;
214 }
215
216 if fragment.size() <= self.config.max_fragment_size && fragment.is_expander {
218 return None;
219 }
220
221 let vertices: Vec<_> = fragment.vertices.iter().copied().collect();
223 let trim_result = self.trim(&vertices);
224
225 if !trim_result.success || trim_result.trimmed_vertices.is_empty() {
226 if let Some(f) = self.fragments.get_mut(&fragment_id) {
228 f.is_expander = true;
229 }
230 return None;
231 }
232
233 let remaining: HashSet<_> = fragment
235 .vertices
236 .difference(&trim_result.trimmed_vertices)
237 .copied()
238 .collect();
239
240 if remaining.is_empty() || trim_result.trimmed_vertices.len() == fragment.vertices.len() {
241 return None;
242 }
243
244 let child1_id = self.create_fragment(trim_result.trimmed_vertices);
246 let child2_id = self.create_fragment(remaining);
247
248 if let Some(f) = self.fragments.get_mut(&fragment_id) {
250 f.children = vec![child1_id, child2_id];
251 }
252 if let Some(f) = self.fragments.get_mut(&child1_id) {
253 f.parent = Some(fragment_id);
254 }
255 if let Some(f) = self.fragments.get_mut(&child2_id) {
256 f.parent = Some(fragment_id);
257 }
258
259 Some(vec![child1_id, child2_id])
260 }
261
262 fn create_fragment(&mut self, vertices: HashSet<VertexId>) -> u64 {
264 let id = self.next_id;
265 self.next_id += 1;
266
267 let mut internal_edge_count = 0;
269 let mut boundary = Vec::new();
270 let mut volume = 0;
271
272 for &v in &vertices {
273 let neighbors = self.neighbors(v);
274 volume += neighbors.len();
275
276 for (neighbor, _weight) in neighbors {
277 if vertices.contains(&neighbor) {
278 internal_edge_count += 1;
279 } else {
280 boundary.push((v, neighbor));
281 }
282 }
283 }
284
285 internal_edge_count /= 2;
287
288 let fragment = Fragment {
289 id,
290 vertices: vertices.clone(),
291 boundary,
292 internal_edge_count,
293 volume,
294 parent: None,
295 children: Vec::new(),
296 is_expander: false,
297 };
298
299 for &v in &vertices {
301 self.vertex_fragment.insert(v, id);
302 }
303
304 self.fragments.insert(id, fragment);
305 id
306 }
307
308 pub fn trim(&self, vertices: &[VertexId]) -> TrimResult {
315 if vertices.is_empty() {
316 return TrimResult {
317 trimmed_vertices: HashSet::new(),
318 cut_edges: Vec::new(),
319 cut_value: 0.0,
320 success: false,
321 };
322 }
323
324 let vertex_set: HashSet<_> = vertices.iter().copied().collect();
325
326 let mut sorted_vertices: Vec<_> = vertices.to_vec();
328 sorted_vertices.sort_by_key(|&v| self.degree(v));
329
330 let mut trimmed = HashSet::new();
331 let mut trimmed_volume = 0usize;
332 let mut boundary_count = 0usize;
333
334 for &v in &sorted_vertices {
336 let neighbors = self.neighbors(v);
337 let degree = neighbors.len();
338
339 let mut internal_neighbors = 0usize;
341 let mut external_neighbors = 0usize;
342
343 for (neighbor, _) in &neighbors {
344 if trimmed.contains(neighbor) {
345 internal_neighbors += 1;
346 } else if vertex_set.contains(neighbor) {
347 external_neighbors += 1;
349 }
350 }
351
352 let new_boundary = boundary_count - internal_neighbors + external_neighbors;
356 let new_volume = trimmed_volume + degree;
357
358 let sparsity = if new_volume > 0 {
360 new_boundary as f64 / new_volume as f64
361 } else {
362 0.0
363 };
364
365 if sparsity <= self.config.boundary_sparsity {
366 trimmed.insert(v);
367 trimmed_volume = new_volume;
368 boundary_count = new_boundary;
369 }
370
371 if trimmed.len() >= vertex_set.len() / 2 {
373 break;
374 }
375 }
376
377 if trimmed.is_empty() || trimmed.len() >= vertex_set.len() {
379 return TrimResult {
380 trimmed_vertices: HashSet::new(),
381 cut_edges: Vec::new(),
382 cut_value: 0.0,
383 success: false,
384 };
385 }
386
387 let mut cut_edges = Vec::new();
389 let mut cut_value = 0.0;
390
391 for &v in &trimmed {
392 for (neighbor, weight) in self.neighbors(v) {
393 if !trimmed.contains(&neighbor) && vertex_set.contains(&neighbor) {
394 cut_edges.push((v, neighbor));
395 cut_value += weight;
396 }
397 }
398 }
399
400 TrimResult {
401 trimmed_vertices: trimmed,
402 cut_edges,
403 cut_value,
404 success: true,
405 }
406 }
407
408 pub fn is_expander(&self, fragment_id: u64) -> bool {
413 let fragment = match self.fragments.get(&fragment_id) {
414 Some(f) => f,
415 None => return false,
416 };
417
418 if fragment.vertices.len() <= 2 {
419 return true; }
421
422 let vertices: Vec<_> = fragment.vertices.iter().copied().collect();
424 let trim = self.trim(&vertices);
425
426 if !trim.success {
427 return true; }
429
430 let cut_volume = trim
432 .trimmed_vertices
433 .iter()
434 .map(|&v| self.degree(v))
435 .sum::<usize>();
436
437 let remaining_volume = fragment.volume - cut_volume;
438 let min_volume = cut_volume.min(remaining_volume);
439
440 if min_volume == 0 {
441 return true;
442 }
443
444 let expansion_ratio = trim.cut_edges.len() as f64 / min_volume as f64;
445 expansion_ratio >= self.config.phi
446 }
447
448 pub fn get_fragment(&self, id: u64) -> Option<&Fragment> {
450 self.fragments.get(&id)
451 }
452
453 pub fn get_vertex_fragment(&self, v: VertexId) -> Option<&Fragment> {
455 self.vertex_fragment
456 .get(&v)
457 .and_then(|&id| self.fragments.get(&id))
458 }
459
460 pub fn leaf_fragments(&self) -> Vec<&Fragment> {
462 self.fragments
463 .values()
464 .filter(|f| f.children.is_empty())
465 .collect()
466 }
467
468 pub fn num_fragments(&self) -> usize {
470 self.fragments.len()
471 }
472
473 pub fn max_depth(&self) -> usize {
475 fn depth_of(fragments: &HashMap<u64, Fragment>, id: u64) -> usize {
476 match fragments.get(&id) {
477 Some(f) if f.children.is_empty() => 0,
478 Some(f) => {
479 1 + f
480 .children
481 .iter()
482 .map(|&c| depth_of(fragments, c))
483 .max()
484 .unwrap_or(0)
485 }
486 None => 0,
487 }
488 }
489
490 self.roots
491 .iter()
492 .map(|&r| depth_of(&self.fragments, r))
493 .max()
494 .unwrap_or(0)
495 }
496}
497
498impl Default for Fragmentation {
499 fn default() -> Self {
500 Self::with_defaults()
501 }
502}
503
504#[cfg(test)]
505mod tests {
506 use super::*;
507
508 fn build_path_graph(frag: &mut Fragmentation, n: usize) {
509 for i in 0..n - 1 {
510 frag.insert_edge(i as u64, (i + 1) as u64, 1.0);
511 }
512 }
513
514 fn build_clique(frag: &mut Fragmentation, vertices: &[u64]) {
515 for i in 0..vertices.len() {
516 for j in i + 1..vertices.len() {
517 frag.insert_edge(vertices[i], vertices[j], 1.0);
518 }
519 }
520 }
521
522 #[test]
523 fn test_fragmentation_empty() {
524 let mut frag = Fragmentation::with_defaults();
525 let roots = frag.fragment();
526 assert!(roots.is_empty());
527 }
528
529 #[test]
530 fn test_fragmentation_single_vertex() {
531 let mut frag = Fragmentation::with_defaults();
532 frag.insert_edge(1, 1, 0.0); frag.adjacency.entry(1).or_default(); let mut frag2 = Fragmentation::with_defaults();
537 frag2.insert_edge(1, 2, 1.0);
538 let roots = frag2.fragment();
539 assert_eq!(roots.len(), 1);
540 }
541
542 #[test]
543 fn test_fragmentation_path() {
544 let mut frag = Fragmentation::new(FragmentationConfig {
545 min_fragment_size: 2,
546 max_fragment_size: 5,
547 ..Default::default()
548 });
549
550 build_path_graph(&mut frag, 10);
551 let roots = frag.fragment();
552
553 assert!(!roots.is_empty());
554 assert!(frag.num_fragments() >= 1);
555 }
556
557 #[test]
558 fn test_fragmentation_clique() {
559 let mut frag = Fragmentation::with_defaults();
560 let vertices: Vec<u64> = (1..=6).collect();
561 build_clique(&mut frag, &vertices);
562
563 let roots = frag.fragment();
564
565 assert!(!roots.is_empty());
566 let leaves = frag.leaf_fragments();
568 let leaf = leaves.first().unwrap();
569 assert!(leaf.size() <= 6);
570 }
571
572 #[test]
573 fn test_trim_basic() {
574 let mut frag = Fragmentation::with_defaults();
575 build_path_graph(&mut frag, 10);
576
577 let vertices: Vec<u64> = (0..10).collect();
578 let result = frag.trim(&vertices);
579
580 assert!(result.trimmed_vertices.len() <= vertices.len());
583 }
584
585 #[test]
586 fn test_fragment_properties() {
587 let mut frag = Fragmentation::with_defaults();
588
589 build_clique(&mut frag, &[1, 2, 3, 4]);
591 build_clique(&mut frag, &[5, 6, 7, 8]);
592 frag.insert_edge(4, 5, 1.0); let roots = frag.fragment();
595
596 assert!(!roots.is_empty());
598
599 let f1 = frag.get_vertex_fragment(1);
601 assert!(f1.is_some());
602 }
603
604 #[test]
605 fn test_is_expander() {
606 let mut frag = Fragmentation::new(FragmentationConfig {
607 phi: 0.1,
608 min_fragment_size: 2,
609 ..Default::default()
610 });
611
612 build_clique(&mut frag, &[1, 2, 3, 4, 5]);
614
615 let roots = frag.fragment();
616 assert!(!roots.is_empty());
617
618 let is_exp = frag.is_expander(roots[0]);
620 assert!(is_exp || frag.leaf_fragments().len() > 1);
622 }
623
624 #[test]
625 fn test_hierarchy_depth() {
626 let mut frag = Fragmentation::new(FragmentationConfig {
627 min_fragment_size: 3,
628 max_fragment_size: 10,
629 ..Default::default()
630 });
631
632 build_path_graph(&mut frag, 20);
633 frag.fragment();
634
635 let depth = frag.max_depth();
636 assert!(depth >= 0);
638 }
639}