ruvector-mincut 2.0.6

World's first subpolynomial dynamic min-cut: self-healing networks, AI optimization, real-time graph analysis
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
//! Property-based tests for paper algorithm implementations
//!
//! Tests the correctness of:
//! - DeterministicLocalKCut (Theorem 4.1)
//! - Fragmentation with Trim (Theorem 5.1)
//! - ThreeLevelHierarchy (expander→precluster→cluster)

use ruvector_mincut::cluster::hierarchy::{HierarchyConfig, ThreeLevelHierarchy};
use ruvector_mincut::fragmentation::{Fragmentation, FragmentationConfig};
use ruvector_mincut::localkcut::deterministic::{
    DeterministicLocalKCut, EdgeColor, EdgeColoring, GreedyForestPacking,
};
use std::collections::HashSet;

// ============================================================================
// Helper Functions
// ============================================================================

/// Brute-force minimum cut for small graphs using exhaustive subset enumeration
fn brute_force_min_cut(adjacency: &[(u64, u64, f64)], vertices: &[u64]) -> f64 {
    if vertices.len() <= 1 {
        return f64::INFINITY;
    }

    let vertex_set: HashSet<_> = vertices.iter().copied().collect();
    let n = vertices.len();
    let mut min_cut = f64::INFINITY;

    // Enumerate all non-empty proper subsets (2^n - 2 subsets)
    // Only practical for small n
    for mask in 1..(1 << n) - 1 {
        let mut subset: HashSet<u64> = HashSet::new();
        for (i, &v) in vertices.iter().enumerate() {
            if (mask >> i) & 1 == 1 {
                subset.insert(v);
            }
        }

        // Compute cut value
        let mut cut_value = 0.0;
        for &(u, v, w) in adjacency {
            let u_in = subset.contains(&u);
            let v_in = subset.contains(&v);
            if u_in != v_in {
                cut_value += w;
            }
        }

        min_cut = min_cut.min(cut_value);
    }

    min_cut
}

/// Check if graph is connected using BFS
fn is_connected(adjacency: &[(u64, u64, f64)], vertices: &[u64]) -> bool {
    if vertices.is_empty() {
        return true;
    }

    use std::collections::VecDeque;

    let mut visited: HashSet<u64> = HashSet::new();
    let mut queue = VecDeque::new();

    queue.push_back(vertices[0]);
    visited.insert(vertices[0]);

    while let Some(v) = queue.pop_front() {
        for &(u, w, _) in adjacency {
            if u == v && !visited.contains(&w) {
                visited.insert(w);
                queue.push_back(w);
            }
            if w == v && !visited.contains(&u) {
                visited.insert(u);
                queue.push_back(u);
            }
        }
    }

    visited.len() == vertices.len()
}

// ============================================================================
// DeterministicLocalKCut Tests
// ============================================================================

#[test]
fn test_localkcut_finds_small_cuts() {
    // Test that LocalKCut finds cuts when they exist
    let mut lkc = DeterministicLocalKCut::new(10, 100, 2);

    // Build two cliques connected by a single edge (barbell graph)
    // Clique 1: vertices 1,2,3
    lkc.insert_edge(1, 2, 1.0);
    lkc.insert_edge(2, 3, 1.0);
    lkc.insert_edge(1, 3, 1.0);

    // Clique 2: vertices 4,5,6
    lkc.insert_edge(4, 5, 1.0);
    lkc.insert_edge(5, 6, 1.0);
    lkc.insert_edge(4, 6, 1.0);

    // Bridge
    lkc.insert_edge(3, 4, 1.0);

    // Query from vertex 1
    let cuts = lkc.query(1);

    // Should find at least one cut
    assert!(!cuts.is_empty(), "Should find at least one cut");

    // At least one cut should have value <= 1 (the bridge)
    let has_small_cut = cuts.iter().any(|c| c.cut_value <= 2.0);
    assert!(has_small_cut, "Should find a small cut (the bridge)");
}

#[test]
fn test_localkcut_respects_volume_bound() {
    let mut lkc = DeterministicLocalKCut::new(10, 5, 2); // Small volume bound

    // Build a star graph (high degree center)
    for i in 2..=10 {
        lkc.insert_edge(1, i, 1.0);
    }

    // Query from center (vertex 1)
    let cuts = lkc.query(1);

    // All cuts should respect volume bound
    for cut in cuts {
        assert!(cut.volume <= 5, "Cut volume {} exceeds bound 5", cut.volume);
    }
}

#[test]
fn test_forest_packing_no_cycles() {
    let mut packing = GreedyForestPacking::new(3);

    // Insert edges that form a cycle
    packing.insert_edge(1, 2);
    packing.insert_edge(2, 3);
    packing.insert_edge(3, 4);

    // This edge would close a cycle
    let forest = packing.insert_edge(1, 4);

    // Should still be assigned (to a different forest)
    assert!(
        forest.is_some(),
        "Cycle-closing edge should fit in some forest"
    );

    // Verify no single forest has a cycle
    for f in 0..3 {
        let edges = packing.forest_edges(f);
        // A forest on n vertices has at most n-1 edges
        // With 4 vertices, each forest should have <= 3 edges
        assert!(edges.len() <= 3, "Forest {} has too many edges", f);
    }
}

#[test]
fn test_edge_coloring_deterministic() {
    // Same edges should get same colors
    let mut coloring1 = EdgeColoring::new(2, 5);
    let mut coloring2 = EdgeColoring::new(2, 5);

    // Set same colors
    coloring1.set(1, 2, EdgeColor::Red);
    coloring1.set(2, 3, EdgeColor::Blue);
    coloring2.set(1, 2, EdgeColor::Red);
    coloring2.set(2, 3, EdgeColor::Blue);

    assert_eq!(coloring1.get(1, 2), coloring2.get(1, 2));
    assert_eq!(coloring1.get(2, 3), coloring2.get(2, 3));

    // Order shouldn't matter
    assert_eq!(coloring1.get(1, 2), coloring1.get(2, 1));
}

// ============================================================================
// Fragmentation Tests
// ============================================================================

#[test]
fn test_fragmentation_covers_all_vertices() {
    let mut frag = Fragmentation::new(FragmentationConfig {
        min_fragment_size: 2,
        max_fragment_size: 10,
        ..Default::default()
    });

    // Build a path graph
    for i in 0..15 {
        frag.insert_edge(i, i + 1, 1.0);
    }

    let roots = frag.fragment();
    assert!(!roots.is_empty(), "Should have at least one fragment");

    // Collect all vertices from leaf fragments
    let mut covered: HashSet<u64> = HashSet::new();
    for fragment in frag.leaf_fragments() {
        covered.extend(&fragment.vertices);
    }

    // All vertices should be covered
    for i in 0..=15 {
        assert!(covered.contains(&i), "Vertex {} not covered", i);
    }
}

#[test]
fn test_fragmentation_boundary_sparse() {
    let config = FragmentationConfig {
        boundary_sparsity: 0.5,
        min_fragment_size: 2,
        ..Default::default()
    };
    let mut frag = Fragmentation::new(config);

    // Build two cliques connected by single edge
    for i in 1..=4 {
        for j in i + 1..=4 {
            frag.insert_edge(i, j, 1.0);
        }
    }
    for i in 5..=8 {
        for j in i + 1..=8 {
            frag.insert_edge(i, j, 1.0);
        }
    }
    frag.insert_edge(4, 5, 1.0);

    frag.fragment();

    // Leaf fragments should have reasonable boundary sparsity
    for fragment in frag.leaf_fragments() {
        let sparsity = fragment.boundary_sparsity();
        // Sparsity should be bounded (not guaranteed to be below threshold due to greedy)
        assert!(
            sparsity <= 2.0,
            "Fragment has very high sparsity: {}",
            sparsity
        );
    }
}

#[test]
fn test_trim_produces_valid_cut() {
    let mut frag = Fragmentation::with_defaults();

    // Build a path graph
    for i in 0..10 {
        frag.insert_edge(i, i + 1, 1.0);
    }

    let vertices: Vec<u64> = (0..=10).collect();
    let result = frag.trim(&vertices);

    if result.success {
        // Trimmed vertices should be a proper subset
        assert!(result.trimmed_vertices.len() < vertices.len());
        assert!(!result.trimmed_vertices.is_empty());

        // Cut edges should connect trimmed to non-trimmed
        for (u, v) in &result.cut_edges {
            let u_trimmed = result.trimmed_vertices.contains(u);
            let v_trimmed = result.trimmed_vertices.contains(v);
            assert!(u_trimmed != v_trimmed, "Cut edge should cross partition");
        }
    }
}

// ============================================================================
// ThreeLevelHierarchy Tests
// ============================================================================

#[test]
fn test_hierarchy_levels_consistent() {
    let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
        min_expander_size: 2,
        ..Default::default()
    });

    // Build graph
    for i in 1..=20 {
        h.insert_edge(i, i + 1, 1.0);
    }
    h.build();

    // Every vertex should be in exactly one expander
    let mut vertex_count: std::collections::HashMap<u64, usize> = std::collections::HashMap::new();
    for expander in h.get_expanders().values() {
        for &v in &expander.vertices {
            *vertex_count.entry(v).or_insert(0) += 1;
        }
    }

    for (v, count) in vertex_count {
        assert_eq!(count, 1, "Vertex {} appears in {} expanders", v, count);
    }
}

#[test]
fn test_hierarchy_global_min_cut_bound() {
    let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
        min_expander_size: 2,
        track_mirror_cuts: true,
        ..Default::default()
    });

    // Build two cliques connected by edges of weight 3
    for i in 1..=4 {
        for j in i + 1..=4 {
            h.insert_edge(i, j, 1.0);
        }
    }
    for i in 5..=8 {
        for j in i + 1..=8 {
            h.insert_edge(i, j, 1.0);
        }
    }
    h.insert_edge(4, 5, 1.0);
    h.insert_edge(3, 6, 1.0);
    h.insert_edge(2, 7, 1.0);

    h.build();

    // Brute force min cut
    let edges: Vec<(u64, u64, f64)> = vec![
        (1, 2, 1.0),
        (1, 3, 1.0),
        (1, 4, 1.0),
        (2, 3, 1.0),
        (2, 4, 1.0),
        (3, 4, 1.0),
        (5, 6, 1.0),
        (5, 7, 1.0),
        (5, 8, 1.0),
        (6, 7, 1.0),
        (6, 8, 1.0),
        (7, 8, 1.0),
        (4, 5, 1.0),
        (3, 6, 1.0),
        (2, 7, 1.0),
    ];
    let vertices: Vec<u64> = (1..=8).collect();
    let brute = brute_force_min_cut(&edges, &vertices);

    // Hierarchy estimate should be <= actual min cut * some factor
    // (it's an upper bound approximation)
    assert!(
        h.global_min_cut <= brute * 2.0 + 0.1 || h.global_min_cut.is_infinite(),
        "Global min cut {} should be close to brute force {}",
        h.global_min_cut,
        brute
    );
}

#[test]
fn test_incremental_update_consistency() {
    let mut h = ThreeLevelHierarchy::with_defaults();

    // Build initial graph
    h.insert_edge(1, 2, 1.0);
    h.insert_edge(2, 3, 1.0);
    h.insert_edge(3, 4, 1.0);
    h.build();

    let initial_vertices = h.stats().num_vertices;

    // Incremental insert
    h.handle_edge_insert(4, 5, 1.0);
    h.handle_edge_insert(5, 6, 1.0);

    // Should have more vertices now
    assert!(h.stats().num_vertices >= initial_vertices);

    // Every new vertex should be assigned
    assert!(h.get_vertex_expander(5).is_some() || h.get_expanders().is_empty());
    assert!(h.get_vertex_expander(6).is_some() || h.get_expanders().is_empty());
}

#[test]
fn test_mirror_cuts_between_expanders() {
    let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
        min_expander_size: 2,
        max_expander_size: 5,
        track_mirror_cuts: true,
        ..Default::default()
    });

    // Build two dense components
    for i in 1..=4 {
        for j in i + 1..=4 {
            h.insert_edge(i, j, 1.0);
        }
    }
    for i in 10..=14 {
        for j in i + 1..=14 {
            h.insert_edge(i, j, 1.0);
        }
    }
    // Connect with bridge
    h.insert_edge(4, 10, 2.0);

    h.build();

    // Check that mirror cuts are being tracked
    let mut has_mirror_cut = false;
    for cluster in h.get_clusters().values() {
        if !cluster.mirror_cuts.is_empty() {
            has_mirror_cut = true;
            // Mirror cut should have the bridge
            for mirror in &cluster.mirror_cuts {
                assert!(
                    mirror.cut_value > 0.0,
                    "Mirror cut should have positive value"
                );
            }
        }
    }

    // If we have multiple expanders, should have mirror cuts
    if h.get_expanders().len() > 1 {
        assert!(
            has_mirror_cut || h.get_clusters().len() > 1,
            "Should track mirror cuts between expanders"
        );
    }
}

// ============================================================================
// Property Tests with Random Graphs
// ============================================================================

#[test]
fn property_fragmentation_idempotent() {
    // Fragmenting twice should give same result
    let mut frag1 = Fragmentation::with_defaults();
    let mut frag2 = Fragmentation::with_defaults();

    // Same graph
    for i in 0..10 {
        frag1.insert_edge(i, i + 1, 1.0);
        frag2.insert_edge(i, i + 1, 1.0);
    }

    frag1.fragment();
    frag2.fragment();

    // Same number of fragments
    assert_eq!(frag1.num_fragments(), frag2.num_fragments());
}

#[test]
fn property_hierarchy_covers_graph() {
    use rand::rngs::StdRng;
    use rand::{Rng, SeedableRng};

    let mut rng = StdRng::seed_from_u64(42);

    for _iteration in 0..10 {
        let mut h = ThreeLevelHierarchy::with_defaults();

        // Random edges
        let n = rng.gen_range(5..20);
        let m = rng.gen_range(n..n * 2);

        for _ in 0..m {
            let u = rng.gen_range(1..=n) as u64;
            let v = rng.gen_range(1..=n) as u64;
            if u != v {
                h.insert_edge(u, v, 1.0);
            }
        }

        h.build();

        // Count vertices in expanders
        let mut in_expanders: HashSet<u64> = HashSet::new();
        for exp in h.get_expanders().values() {
            in_expanders.extend(&exp.vertices);
        }

        // All graph vertices should be covered
        let graph_vertices = h.stats().num_vertices;
        assert_eq!(
            in_expanders.len(),
            graph_vertices,
            "Expanders should cover all {} vertices",
            graph_vertices
        );
    }
}

#[test]
fn property_localkcut_deterministic() {
    // Same graph, same queries, same results
    let mut lkc1 = DeterministicLocalKCut::new(10, 50, 2);
    let mut lkc2 = DeterministicLocalKCut::new(10, 50, 2);

    // Same edges in same order
    for (u, v) in [(1, 2), (2, 3), (3, 4), (4, 1), (1, 3)] {
        lkc1.insert_edge(u, v, 1.0);
        lkc2.insert_edge(u, v, 1.0);
    }

    let cuts1 = lkc1.query(1);
    let cuts2 = lkc2.query(1);

    // Same number of cuts
    assert_eq!(cuts1.len(), cuts2.len(), "Queries should be deterministic");
}

#[test]
fn test_mirror_cut_certification() {
    let mut h = ThreeLevelHierarchy::new(HierarchyConfig {
        min_expander_size: 2,
        max_expander_size: 5,
        track_mirror_cuts: true,
        ..Default::default()
    });

    // Build two well-separated components connected by a bridge
    // Component 1: vertices 1-4
    for i in 1..=4 {
        for j in i + 1..=4 {
            h.insert_edge(i, j, 1.0);
        }
    }
    // Component 2: vertices 10-14
    for i in 10..=14 {
        for j in i + 1..=14 {
            h.insert_edge(i, j, 1.0);
        }
    }
    // Bridge connecting them
    h.insert_edge(4, 10, 2.0);

    h.build();

    // Get counts before certification
    let total_mirror_cuts = h.num_mirror_cuts();

    // Run certification
    h.certify_mirror_cuts();

    // After certification, certified count should be >= 0
    let certified = h.num_certified_mirror_cuts();
    assert!(
        certified <= total_mirror_cuts,
        "Certified {} should be <= total {}",
        certified,
        total_mirror_cuts
    );

    // If we have mirror cuts, certification should have processed them
    if total_mirror_cuts > 0 {
        // At least some should be certified (or all if valid)
        assert!(
            certified >= 0,
            "Certification should not produce negative count"
        );
    }
}

#[test]
fn test_brute_force_matches_known_cut() {
    // Test our brute force helper against a known graph
    // Triangle with vertices 1, 2, 3 - min cut is 2 (remove any vertex)
    let edges = vec![(1, 2, 1.0), (2, 3, 1.0), (1, 3, 1.0)];
    let vertices = vec![1, 2, 3];

    let min_cut = brute_force_min_cut(&edges, &vertices);
    assert!(
        (min_cut - 2.0).abs() < 0.001,
        "Triangle min cut should be 2, got {}",
        min_cut
    );

    // Path graph 1-2-3-4 - min cut is 1
    let path_edges = vec![(1, 2, 1.0), (2, 3, 1.0), (3, 4, 1.0)];
    let path_vertices = vec![1, 2, 3, 4];

    let path_cut = brute_force_min_cut(&path_edges, &path_vertices);
    assert!(
        (path_cut - 1.0).abs() < 0.001,
        "Path min cut should be 1, got {}",
        path_cut
    );
}