Files
wifi-densepose/vendor/ruvector/docs/plans/subpolynomial-time-mincut/04-refinement.md

26 KiB

SPARC Phase 4: Refinement - Test-Driven Development Plan

Overview

This phase details the Test-Driven Development (TDD) strategy for implementing the subpolynomial-time dynamic minimum cut algorithm. We follow a rigorous red-green-refactor cycle with comprehensive test coverage.

1. TDD Strategy

1.1 Development Cycles

Cycle Structure:

  1. RED: Write failing test that specifies desired behavior
  2. GREEN: Write minimal code to make test pass
  3. REFACTOR: Improve code quality without changing behavior
  4. VALIDATE: Run full test suite + benchmarks

1.2 Test-First Order

Implement modules in dependency order:

  1. error.rs - Error types (foundation)
  2. graph/representation.rs - Basic graph structure
  3. linkcut/node.rs - Link-cut tree nodes
  4. linkcut/operations.rs - LCT operations
  5. tree/decomposition.rs - Hierarchical tree
  6. algorithm/insert.rs - Edge insertion
  7. algorithm/delete.rs - Edge deletion
  8. algorithm/query.rs - Cut queries
  9. sparsify/sampler.rs - Sparsification
  10. monitoring/callbacks.rs - Monitoring system

2. Unit Tests

2.1 Error Handling Tests

File: tests/unit/error_tests.rs

#[cfg(test)]
mod error_tests {
    use super::*;

    #[test]
    fn test_invalid_vertex_error() {
        let err = MinCutError::InvalidVertex(999);
        assert_eq!(err.to_string(), "Invalid vertex ID: 999");
    }

    #[test]
    fn test_edge_not_found_error() {
        let err = MinCutError::EdgeNotFound(1, 2);
        assert!(err.to_string().contains("Edge (1, 2) does not exist"));
    }

    #[test]
    fn test_error_is_send_sync() {
        fn assert_send_sync<T: Send + Sync>() {}
        assert_send_sync::<MinCutError>();
    }
}

2.2 Graph Representation Tests

File: tests/unit/graph_tests.rs

#[cfg(test)]
mod graph_tests {
    use ruvector_mincut::graph::*;

    #[test]
    fn test_empty_graph() {
        let graph = DynamicGraph::new(0);
        assert_eq!(graph.vertex_count(), 0);
        assert_eq!(graph.edge_count(), 0);
    }

    #[test]
    fn test_add_edge() {
        let mut graph = DynamicGraph::new(3);
        assert!(graph.add_edge(0, 1).is_ok());
        assert_eq!(graph.edge_count(), 1);
        assert!(graph.has_edge(0, 1));
        assert!(graph.has_edge(1, 0)); // Undirected
    }

    #[test]
    fn test_add_duplicate_edge() {
        let mut graph = DynamicGraph::new(3);
        graph.add_edge(0, 1).unwrap();
        let result = graph.add_edge(0, 1);
        assert!(result.is_err());
        assert_eq!(graph.edge_count(), 1);
    }

    #[test]
    fn test_remove_edge() {
        let mut graph = DynamicGraph::new(3);
        graph.add_edge(0, 1).unwrap();
        graph.add_edge(1, 2).unwrap();

        assert!(graph.remove_edge(0, 1).is_ok());
        assert_eq!(graph.edge_count(), 1);
        assert!(!graph.has_edge(0, 1));
    }

    #[test]
    fn test_remove_nonexistent_edge() {
        let mut graph = DynamicGraph::new(3);
        let result = graph.remove_edge(0, 1);
        assert!(result.is_err());
    }

    #[test]
    fn test_neighbors() {
        let mut graph = DynamicGraph::new(4);
        graph.add_edge(0, 1).unwrap();
        graph.add_edge(0, 2).unwrap();
        graph.add_edge(0, 3).unwrap();

        let neighbors: Vec<_> = graph.neighbors(0).collect();
        assert_eq!(neighbors.len(), 3);
        assert!(neighbors.contains(&1));
        assert!(neighbors.contains(&2));
        assert!(neighbors.contains(&3));
    }

    #[test]
    fn test_degree() {
        let mut graph = DynamicGraph::new(4);
        graph.add_edge(0, 1).unwrap();
        graph.add_edge(0, 2).unwrap();

        assert_eq!(graph.degree(0), 2);
        assert_eq!(graph.degree(1), 1);
        assert_eq!(graph.degree(2), 1);
        assert_eq!(graph.degree(3), 0);
    }
}

File: tests/unit/linkcut_tests.rs

#[cfg(test)]
mod linkcut_tests {
    use ruvector_mincut::linkcut::*;

    #[test]
    fn test_make_tree() {
        let mut lct = LinkCutTree::new();
        lct.make_tree(0);
        lct.make_tree(1);

        assert!(!lct.connected(0, 1));
    }

    #[test]
    fn test_link() {
        let mut lct = LinkCutTree::new();
        lct.make_tree(0);
        lct.make_tree(1);

        lct.link(0, 1);
        assert!(lct.connected(0, 1));
    }

    #[test]
    fn test_cut() {
        let mut lct = LinkCutTree::new();
        lct.make_tree(0);
        lct.make_tree(1);
        lct.link(0, 1);

        lct.cut(0, 1);
        assert!(!lct.connected(0, 1));
    }

    #[test]
    fn test_connected_transitive() {
        let mut lct = LinkCutTree::new();
        for i in 0..5 {
            lct.make_tree(i);
        }

        // Build path: 0-1-2-3-4
        lct.link(0, 1);
        lct.link(1, 2);
        lct.link(2, 3);
        lct.link(3, 4);

        assert!(lct.connected(0, 4));
        assert!(lct.connected(1, 3));
    }

    #[test]
    fn test_lca() {
        let mut lct = LinkCutTree::new();
        for i in 0..7 {
            lct.make_tree(i);
        }

        // Build tree:
        //       0
        //      / \
        //     1   2
        //    / \
        //   3   4
        //      / \
        //     5   6

        lct.link(1, 0);
        lct.link(2, 0);
        lct.link(3, 1);
        lct.link(4, 1);
        lct.link(5, 4);
        lct.link(6, 4);

        assert_eq!(lct.lca(3, 4), 1);
        assert_eq!(lct.lca(3, 2), 0);
        assert_eq!(lct.lca(5, 6), 4);
    }

    #[test]
    fn test_path_aggregate() {
        let mut lct = LinkCutTree::new();
        for i in 0..4 {
            lct.make_tree(i);
        }

        lct.link(0, 1);
        lct.link(1, 2);
        lct.link(2, 3);

        // Test aggregate queries on path
        let path_size = lct.path_aggregate(0, 3, |agg| agg.size);
        assert_eq!(path_size, 4);
    }
}

2.4 Decomposition Tree Tests

File: tests/unit/decomposition_tests.rs

#[cfg(test)]
mod decomposition_tests {
    use ruvector_mincut::tree::*;

    #[test]
    fn test_empty_tree() {
        let tree = DecompositionTree::new();
        assert_eq!(tree.height(), 0);
    }

    #[test]
    fn test_build_from_graph() {
        let mut graph = DynamicGraph::new(4);
        graph.add_edge(0, 1).unwrap();
        graph.add_edge(1, 2).unwrap();
        graph.add_edge(2, 3).unwrap();
        graph.add_edge(3, 0).unwrap();

        let tree = DecompositionTree::from_graph(&graph);

        assert_eq!(tree.height(), 2); // log_2(4) = 2
        assert_eq!(tree.leaf_count(), 4);
    }

    #[test]
    fn test_find_leaf() {
        let mut graph = DynamicGraph::new(4);
        graph.add_edge(0, 1).unwrap();
        graph.add_edge(2, 3).unwrap();

        let tree = DecompositionTree::from_graph(&graph);

        let leaf_0 = tree.find_leaf(0);
        assert!(leaf_0.is_some());
        assert_eq!(leaf_0.unwrap().vertices(), &[0]);
    }

    #[test]
    fn test_local_cut_values() {
        // Create complete graph K4
        let mut graph = DynamicGraph::new(4);
        for i in 0..4 {
            for j in i+1..4 {
                graph.add_edge(i, j).unwrap();
            }
        }

        let tree = DecompositionTree::from_graph(&graph);

        // In K4, minimum cut is 3 (any single vertex)
        let root = tree.root();
        assert_eq!(root.local_cut(), 3);
    }
}

3. Integration Tests

3.1 End-to-End Dynamic Updates

File: tests/integration/dynamic_updates_test.rs

#[cfg(test)]
mod dynamic_updates_tests {
    use ruvector_mincut::*;

    #[test]
    fn test_insert_edges_sequential() {
        let mut mincut = DynamicMinCut::new(MinCutConfig::default());

        // Build path graph: 0-1-2-3
        mincut.insert_edge(0, 1).unwrap();
        assert_eq!(mincut.min_cut_value(), 1);

        mincut.insert_edge(1, 2).unwrap();
        assert_eq!(mincut.min_cut_value(), 1);

        mincut.insert_edge(2, 3).unwrap();
        assert_eq!(mincut.min_cut_value(), 1);
    }

    #[test]
    fn test_delete_edges_creates_bottleneck() {
        let mut mincut = DynamicMinCut::new(MinCutConfig::default());

        // Build K4
        for i in 0..4 {
            for j in i+1..4 {
                mincut.insert_edge(i, j).unwrap();
            }
        }
        assert_eq!(mincut.min_cut_value(), 3);

        // Remove edges to create bottleneck
        mincut.delete_edge(0, 2).unwrap();
        mincut.delete_edge(0, 3).unwrap();
        mincut.delete_edge(1, 2).unwrap();
        mincut.delete_edge(1, 3).unwrap();

        // Now min cut is 1 (between {0,1} and {2,3})
        assert_eq!(mincut.min_cut_value(), 1);
    }

    #[test]
    fn test_partition_correctness() {
        let mut mincut = DynamicMinCut::new(MinCutConfig::default());

        // Build dumbbell graph: K3 - single edge - K3
        // Left clique
        mincut.insert_edge(0, 1).unwrap();
        mincut.insert_edge(1, 2).unwrap();
        mincut.insert_edge(2, 0).unwrap();

        // Bridge
        mincut.insert_edge(2, 3).unwrap();

        // Right clique
        mincut.insert_edge(3, 4).unwrap();
        mincut.insert_edge(4, 5).unwrap();
        mincut.insert_edge(5, 3).unwrap();

        let result = mincut.min_cut();
        assert_eq!(result.value, 1);
        assert_eq!(result.cut_edges.len(), 1);
        assert!(result.cut_edges.contains(&(2, 3)) ||
                result.cut_edges.contains(&(3, 2)));

        // Verify partition sizes
        assert_eq!(result.partition_a.len(), 3);
        assert_eq!(result.partition_b.len(), 3);
    }
}

3.2 Correctness Verification

File: tests/integration/correctness_test.rs

#[cfg(test)]
mod correctness_tests {
    use ruvector_mincut::*;
    use ruvector_mincut::testing::*;

    #[test]
    fn test_against_stoer_wagner() {
        // Verify our algorithm matches Stoer-Wagner on various graphs
        for size in [10, 20, 50, 100] {
            for density in [0.1, 0.3, 0.5, 0.7] {
                let graph = generate_random_graph(size, density);

                let mut mincut = DynamicMinCut::from_graph(&graph, Default::default());
                let our_result = mincut.min_cut_value();

                let stoer_wagner = brute_force_mincut(&graph);

                assert_eq!(our_result, stoer_wagner,
                    "Mismatch for n={}, density={}", size, density);
            }
        }
    }

    #[test]
    fn test_random_update_sequences() {
        use rand::Rng;
        let mut rng = rand::thread_rng();

        for trial in 0..100 {
            let mut mincut = DynamicMinCut::new(MinCutConfig::default());
            let n = 20;

            // Random sequence of 100 updates
            for _ in 0..100 {
                let u = rng.gen_range(0..n);
                let v = rng.gen_range(0..n);
                if u == v { continue; }

                if rng.gen_bool(0.5) {
                    // Insert
                    mincut.insert_edge(u, v).ok();
                } else {
                    // Delete
                    mincut.delete_edge(u, v).ok();
                }

                // Verify invariants
                #[cfg(debug_assertions)]
                mincut.validate().unwrap();
            }

            // Final verification against ground truth
            let computed = mincut.min_cut_value();
            let actual = brute_force_mincut(&mincut.graph);
            assert_eq!(computed, actual, "Trial {} failed", trial);
        }
    }
}

3.3 Sparsification Tests

File: tests/integration/sparsification_test.rs

#[cfg(test)]
mod sparsification_tests {
    use ruvector_mincut::*;

    #[test]
    fn test_approximate_within_epsilon() {
        let epsilon = 0.1;
        let config = MinCutConfig {
            epsilon,
            use_sparsification: true,
            ..Default::default()
        };

        for size in [100, 500, 1000] {
            let graph = generate_random_graph(size, 0.1);

            let mut mincut = DynamicMinCut::from_graph(&graph, config);
            let approximate = mincut.min_cut_value() as f64;

            let exact = brute_force_mincut(&graph) as f64;

            let ratio = approximate / exact;
            assert!(ratio >= 1.0 - epsilon && ratio <= 1.0 + epsilon,
                "Approximation ratio {} outside bounds for n={}", ratio, size);
        }
    }

    #[test]
    fn test_sparse_graph_size() {
        let epsilon = 0.05;
        let config = MinCutConfig {
            epsilon,
            use_sparsification: true,
            ..Default::default()
        };

        let n = 1000;
        let graph = generate_random_graph(n, 0.5); // Dense graph

        let mut mincut = DynamicMinCut::from_graph(&graph, config);

        // Sparse graph should have O(n log n / ε²) edges
        let sparse_size = mincut.sparse_graph_size();
        let expected_max = ((n as f64) * (n as f64).ln() / (epsilon * epsilon)) as usize;

        assert!(sparse_size < expected_max * 2,
            "Sparse graph too large: {} > {}", sparse_size, expected_max);
    }
}

4. Performance Tests

4.1 Benchmark Suite

File: benches/mincut_bench.rs

use criterion::{black_box, criterion_group, criterion_main, Criterion, BenchmarkId};
use ruvector_mincut::*;

fn bench_insert_operations(c: &mut Criterion) {
    let mut group = c.benchmark_group("insert_operations");

    for size in [100, 1000, 10000].iter() {
        group.bench_with_input(
            BenchmarkId::new("insert_edge", size),
            size,
            |b, &n| {
                b.iter_batched(
                    || {
                        let mut mincut = DynamicMinCut::new(Default::default());
                        let updates = generate_random_insertions(n, 0.1);
                        (mincut, updates)
                    },
                    |(mut mincut, updates)| {
                        for (u, v) in updates {
                            black_box(mincut.insert_edge(u, v).ok());
                        }
                    },
                    criterion::BatchSize::SmallInput
                );
            }
        );
    }

    group.finish();
}

fn bench_delete_operations(c: &mut Criterion) {
    let mut group = c.benchmark_group("delete_operations");

    for size in [100, 1000, 10000].iter() {
        group.bench_with_input(
            BenchmarkId::new("delete_edge", size),
            size,
            |b, &n| {
                b.iter_batched(
                    || {
                        let graph = generate_random_graph(n, 0.1);
                        let mut mincut = DynamicMinCut::from_graph(&graph, Default::default());
                        let edges: Vec<_> = graph.edges().collect();
                        (mincut, edges)
                    },
                    |(mut mincut, edges)| {
                        for (u, v) in edges.iter().take(100) {
                            black_box(mincut.delete_edge(*u, *v).ok());
                        }
                    },
                    criterion::BatchSize::SmallInput
                );
            }
        );
    }

    group.finish();
}

fn bench_query_operations(c: &mut Criterion) {
    let mut group = c.benchmark_group("query_operations");

    for size in [100, 1000, 10000].iter() {
        group.bench_with_input(
            BenchmarkId::new("min_cut_value", size),
            size,
            |b, &n| {
                let graph = generate_random_graph(n, 0.1);
                let mincut = DynamicMinCut::from_graph(&graph, Default::default());

                b.iter(|| {
                    black_box(mincut.min_cut_value());
                });
            }
        );

        group.bench_with_input(
            BenchmarkId::new("min_cut_partition", size),
            size,
            |b, &n| {
                let graph = generate_random_graph(n, 0.1);
                let mincut = DynamicMinCut::from_graph(&graph, Default::default());

                b.iter(|| {
                    black_box(mincut.min_cut());
                });
            }
        );
    }

    group.finish();
}

fn bench_mixed_workload(c: &mut Criterion) {
    let mut group = c.benchmark_group("mixed_workload");

    for size in [100, 1000, 10000].iter() {
        group.bench_with_input(
            BenchmarkId::new("realistic_workload", size),
            size,
            |b, &n| {
                b.iter_batched(
                    || {
                        let graph = generate_random_graph(n, 0.05);
                        let mut mincut = DynamicMinCut::from_graph(&graph, Default::default());
                        let ops = generate_mixed_operations(1000, n);
                        (mincut, ops)
                    },
                    |(mut mincut, ops)| {
                        for op in ops {
                            match op {
                                Op::Insert(u, v) => {
                                    black_box(mincut.insert_edge(u, v).ok());
                                },
                                Op::Delete(u, v) => {
                                    black_box(mincut.delete_edge(u, v).ok());
                                },
                                Op::Query => {
                                    black_box(mincut.min_cut_value());
                                }
                            }
                        }
                    },
                    criterion::BatchSize::SmallInput
                );
            }
        );
    }

    group.finish();
}

criterion_group!(
    benches,
    bench_insert_operations,
    bench_delete_operations,
    bench_query_operations,
    bench_mixed_workload
);
criterion_main!(benches);

4.2 Performance Regression Tests

File: tests/performance/regression_test.rs

#[cfg(test)]
mod performance_regression_tests {
    use ruvector_mincut::*;
    use std::time::Instant;

    #[test]
    #[ignore] // Run with: cargo test --release -- --ignored
    fn test_update_time_subpolynomial() {
        let n = 10_000;
        let num_updates = 10_000;

        let mut mincut = DynamicMinCut::new(Default::default());
        let updates = generate_random_mixed_operations(num_updates, n);

        let start = Instant::now();
        for op in updates {
            match op {
                Op::Insert(u, v) => mincut.insert_edge(u, v).ok(),
                Op::Delete(u, v) => mincut.delete_edge(u, v).ok(),
                _ => continue,
            };
        }
        let duration = start.elapsed();

        let avg_ns = duration.as_nanos() / num_updates as u128;
        let avg_ms = avg_ns as f64 / 1_000_000.0;

        println!("Average update time: {:.3} ms", avg_ms);

        // Target: <10ms per update for n=10,000
        assert!(avg_ms < 10.0,
            "Update time {} ms exceeds target of 10 ms", avg_ms);
    }

    #[test]
    #[ignore]
    fn test_query_time_constant() {
        let n = 10_000;
        let graph = generate_random_graph(n, 0.1);
        let mincut = DynamicMinCut::from_graph(&graph, Default::default());

        let num_queries = 100_000;
        let start = Instant::now();
        for _ in 0..num_queries {
            black_box(mincut.min_cut_value());
        }
        let duration = start.elapsed();

        let avg_ns = duration.as_nanos() / num_queries;

        println!("Average query time: {} ns", avg_ns);

        // Target: <100ns per query (essentially O(1))
        assert!(avg_ns < 100,
            "Query time {} ns exceeds target of 100 ns", avg_ns);
    }

    #[test]
    #[ignore]
    fn test_throughput_target() {
        let n = 10_000;
        let mut mincut = DynamicMinCut::new(Default::default());
        let updates = generate_random_mixed_operations(10_000, n);

        let start = Instant::now();
        for op in updates {
            match op {
                Op::Insert(u, v) => mincut.insert_edge(u, v).ok(),
                Op::Delete(u, v) => mincut.delete_edge(u, v).ok(),
                _ => continue,
            };
        }
        let duration = start.elapsed();

        let throughput = 10_000.0 / duration.as_secs_f64();

        println!("Throughput: {:.0} updates/second", throughput);

        // Target: >1,000 updates/second for n=10,000
        assert!(throughput > 1000.0,
            "Throughput {} ops/s below target of 1000 ops/s", throughput);
    }
}

5. Property-Based Testing

File: tests/property/quickcheck_tests.rs

use quickcheck::{quickcheck, TestResult};
use ruvector_mincut::*;

#[cfg(test)]
mod property_tests {
    use super::*;

    #[quickcheck]
    fn prop_cut_value_nonnegative(ops: Vec<GraphOp>) -> TestResult {
        if ops.len() > 1000 {
            return TestResult::discard();
        }

        let mut mincut = DynamicMinCut::new(Default::default());

        for op in ops {
            apply_operation(&mut mincut, op);
        }

        TestResult::from_bool(mincut.min_cut_value() >= 0)
    }

    #[quickcheck]
    fn prop_cut_bounded_by_min_degree(ops: Vec<GraphOp>) -> TestResult {
        if ops.len() > 500 {
            return TestResult::discard();
        }

        let mut mincut = DynamicMinCut::new(Default::default());

        for op in ops {
            apply_operation(&mut mincut, op);
        }

        let cut_value = mincut.min_cut_value();
        let min_degree = compute_min_degree(&mincut.graph);

        TestResult::from_bool(cut_value <= min_degree)
    }

    #[quickcheck]
    fn prop_insert_delete_inverse(u: u32, v: u32) -> TestResult {
        if u == v || u > 100 || v > 100 {
            return TestResult::discard();
        }

        let mut mincut = DynamicMinCut::new(Default::default());

        let cut_before = mincut.min_cut_value();

        mincut.insert_edge(u, v).ok();
        let cut_after_insert = mincut.min_cut_value();

        mincut.delete_edge(u, v).ok();
        let cut_after_delete = mincut.min_cut_value();

        TestResult::from_bool(cut_before == cut_after_delete)
    }
}

6. Fuzzing

File: fuzz/fuzz_targets/mincut_fuzz.rs

#![no_main]
use libfuzzer_sys::fuzz_target;
use ruvector_mincut::*;

fuzz_target!(|data: &[u8]| {
    if data.len() < 10 {
        return;
    }

    let mut mincut = DynamicMinCut::new(Default::default());
    let n = 100;

    let mut i = 0;
    while i + 2 < data.len() {
        let u = (data[i] as usize) % n;
        let v = (data[i+1] as usize) % n;
        let op_type = data[i+2] % 3;

        match op_type {
            0 => {
                // Insert
                mincut.insert_edge(u, v).ok();
            },
            1 => {
                // Delete
                mincut.delete_edge(u, v).ok();
            },
            2 => {
                // Query
                let _ = mincut.min_cut_value();
            },
            _ => unreachable!()
        }

        // Verify invariants
        #[cfg(debug_assertions)]
        mincut.validate().expect("Invariant violated");

        i += 3;
    }
});

7. Test Coverage Goals

7.1 Coverage Targets

  • Unit tests: >90% line coverage
  • Integration tests: >80% branch coverage
  • Property tests: >100 successful runs per property
  • Fuzzing: >1 million iterations without crash

7.2 Coverage Measurement

# Install coverage tool
cargo install cargo-tarpaulin

# Run coverage
cargo tarpaulin --out Html --output-dir coverage/

# View coverage report
open coverage/index.html

8. Continuous Integration

8.1 CI Pipeline

# .github/workflows/test.yml
name: Test Suite

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions-rs/toolchain@v1
        with:
          toolchain: stable

      - name: Run unit tests
        run: cargo test --lib

      - name: Run integration tests
        run: cargo test --test '*'

      - name: Run benchmarks (sanity check)
        run: cargo bench --no-run

      - name: Check code coverage
        run: |
          cargo install cargo-tarpaulin
          cargo tarpaulin --out Lcov

      - name: Upload coverage
        uses: codecov/codecov-action@v2

  bench:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Run performance tests
        run: cargo test --release -- --ignored

9. Test Data Generation

File: tests/fixtures/test_graphs.rs

use rand::Rng;

pub fn generate_random_graph(n: usize, density: f64) -> DynamicGraph {
    let mut graph = DynamicGraph::new(n);
    let mut rng = rand::thread_rng();

    for u in 0..n {
        for v in u+1..n {
            if rng.gen_bool(density) {
                graph.add_edge(u, v).unwrap();
            }
        }
    }

    graph
}

pub fn generate_complete_graph(n: usize) -> DynamicGraph {
    let mut graph = DynamicGraph::new(n);
    for u in 0..n {
        for v in u+1..n {
            graph.add_edge(u, v).unwrap();
        }
    }
    graph
}

pub fn generate_path_graph(n: usize) -> DynamicGraph {
    let mut graph = DynamicGraph::new(n);
    for i in 0..n-1 {
        graph.add_edge(i, i+1).unwrap();
    }
    graph
}

pub fn generate_cycle_graph(n: usize) -> DynamicGraph {
    let mut graph = DynamicGraph::new(n);
    for i in 0..n {
        graph.add_edge(i, (i+1) % n).unwrap();
    }
    graph
}

pub fn generate_dumbbell_graph(clique_size: usize) -> DynamicGraph {
    let n = clique_size * 2;
    let mut graph = DynamicGraph::new(n);

    // Left clique
    for u in 0..clique_size {
        for v in u+1..clique_size {
            graph.add_edge(u, v).unwrap();
        }
    }

    // Bridge
    graph.add_edge(clique_size-1, clique_size).unwrap();

    // Right clique
    for u in clique_size..n {
        for v in u+1..n {
            graph.add_edge(u, v).unwrap();
        }
    }

    graph
}

10. Validation & Debugging

10.1 Invariant Checking

impl DynamicMinCut {
    #[cfg(debug_assertions)]
    pub fn validate(&self) -> Result<()> {
        // Check tree structure
        self.tree.validate()?;

        // Check LCT consistency
        self.lct.validate()?;

        // Verify cut value
        let computed = self.current_cut;
        let actual = self.compute_min_cut_brute_force();
        if computed != actual {
            return Err(MinCutError::InvariantViolation(
                format!("Cut value mismatch: {} != {}", computed, actual)
            ));
        }

        Ok(())
    }
}

Next Phase: Proceed to 05-completion.md for integration, deployment, and documentation.