Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,381 @@
# SPARC Phase 1: Specification - Subpolynomial-Time Dynamic Minimum Cut
## Executive Summary
This specification defines a **deterministic, fully-dynamic minimum-cut algorithm** with amortized update time **n^{o(1)}** (subpolynomial growth), achieving breakthrough performance for real-time graph monitoring. The system handles minimum cuts up to **2^{Θ((log n)^{3/4})}** edges and provides **(1+ε)-approximate** cuts via sparsification.
## 1. Problem Statement
### 1.1 Core Challenge
Maintain the minimum cut of a dynamic undirected graph under edge insertions and deletions with:
- **Subpolynomial amortized update time**: O(n^{o(1)}) per operation
- **Deterministic guarantees**: No probabilistic error
- **Real-time monitoring**: Hundreds to thousands of updates/second
- **Exact and approximate modes**: Exact for small cuts, (1+ε)-approximate for general graphs
### 1.2 Theoretical Foundation
Based on recent breakthrough work (Abboud et al., 2021+) that achieves:
- **Exact minimum cut**: For cuts of size ≤ 2^{Θ((log n)^{3/4})}
- **Approximate (1+ε) minimum cut**: For arbitrary cuts via sparsification
- **Subpolynomial complexity**: Breaking the O(√n) barrier of previous work (Thorup 2007)
### 1.3 Performance Baselines
- **Static computation**:
- StoerWagner: O(mn + n² log n) ≈ O(n³) for dense graphs
- Karger's randomized: O(n² log³ n)
- **Prior dynamic (approximate)**:
- Thorup: O(√n) amortized per update
- **Target**:
- **n^{o(1)}** amortized (e.g., O(n^{0.1}) or O(polylog n))
- P95 latency: <10ms for graphs with n=10,000
- Throughput: 1,000-10,000 updates/second
## 2. Requirements
### 2.1 Functional Requirements
#### FR-1: Dynamic Graph Operations
- **FR-1.1**: Insert edge between two vertices in O(n^{o(1)}) amortized time
- **FR-1.2**: Delete edge between two vertices in O(n^{o(1)}) amortized time
- **FR-1.3**: Query current minimum cut value in O(1) time
- **FR-1.4**: Retrieve minimum cut partition in O(k) time where k is cut size
#### FR-2: Cut Maintenance
- **FR-2.1**: Maintain exact minimum cut for cuts ≤ 2^{Θ((log n)^{3/4})}
- **FR-2.2**: Provide (1+ε)-approximate minimum cut for larger cuts
- **FR-2.3**: Support configurable ε parameter (default: 0.01)
- **FR-2.4**: Track edge connectivity between all vertex pairs
#### FR-3: Data Structures
- **FR-3.1**: Hierarchical tree decomposition for cut maintenance
- **FR-3.2**: Link-cut trees for dynamic tree operations
- **FR-3.3**: Euler tour trees for subtree queries
- **FR-3.4**: Graph sparsification via sampling
#### FR-4: Monitoring & Observability
- **FR-4.1**: Real-time metrics: current cut value, update count, tree depth
- **FR-4.2**: Performance tracking: P50/P95/P99 latency, throughput
- **FR-4.3**: Change notifications via callback mechanism
- **FR-4.4**: Historical cut value tracking
### 2.2 Non-Functional Requirements
#### NFR-1: Performance
- **NFR-1.1**: Amortized update time: O(n^{o(1)})
- **NFR-1.2**: Query time: O(1) for cut value, O(k) for cut partition
- **NFR-1.3**: Memory usage: O(m + n) where m is edge count
- **NFR-1.4**: Throughput: ≥1,000 updates/second for n=10,000
#### NFR-2: Correctness
- **NFR-2.1**: Deterministic: No probabilistic error in results
- **NFR-2.2**: Exact: For cuts ≤ 2^{Θ((log n)^{3/4})}
- **NFR-2.3**: Bounded approximation: (1+ε) guarantee for larger cuts
- **NFR-2.4**: Consistency: All queries return current state
#### NFR-3: Scalability
- **NFR-3.1**: Handle graphs up to 100,000 vertices
- **NFR-3.2**: Support millions of edges
- **NFR-3.3**: Graceful degradation for large cuts
- **NFR-3.4**: Parallel update processing (future enhancement)
#### NFR-4: Integration
- **NFR-4.1**: Rust API compatible with ruvector-graph
- **NFR-4.2**: Zero-copy integration where possible
- **NFR-4.3**: C ABI for foreign function interface
- **NFR-4.4**: Thread-safe for concurrent queries
## 3. API Design
### 3.1 Core Types
```rust
/// Dynamic minimum cut data structure
pub struct DynamicMinCut {
// Internal state (opaque)
}
/// Cut result
#[derive(Debug, Clone)]
pub struct MinCutResult {
pub value: usize,
pub partition_a: Vec<VertexId>,
pub partition_b: Vec<VertexId>,
pub cut_edges: Vec<(VertexId, VertexId)>,
pub is_exact: bool,
pub epsilon: Option<f64>,
}
/// Configuration
#[derive(Debug, Clone)]
pub struct MinCutConfig {
pub epsilon: f64, // Approximation factor (default: 0.01)
pub max_exact_cut_size: usize, // Threshold for exact vs approximate
pub enable_monitoring: bool, // Track performance metrics
pub use_sparsification: bool, // Enable graph sparsification
}
/// Performance metrics
#[derive(Debug, Clone)]
pub struct MinCutMetrics {
pub current_cut_value: usize,
pub update_count: u64,
pub tree_depth: usize,
pub graph_size: (usize, usize), // (vertices, edges)
pub avg_update_time_ns: u64,
pub p95_update_time_ns: u64,
pub p99_update_time_ns: u64,
}
```
### 3.2 Primary API
```rust
impl DynamicMinCut {
/// Create new dynamic min-cut structure for graph
pub fn new(config: MinCutConfig) -> Self;
/// Initialize from existing graph
pub fn from_graph(graph: &Graph, config: MinCutConfig) -> Self;
/// Insert edge (u, v)
pub fn insert_edge(&mut self, u: VertexId, v: VertexId) -> Result<()>;
/// Delete edge (u, v)
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<()>;
/// Get current minimum cut value in O(1)
pub fn min_cut_value(&self) -> usize;
/// Get minimum cut partition in O(k) where k is cut size
pub fn min_cut(&self) -> MinCutResult;
/// Check connectivity between two vertices
pub fn edge_connectivity(&self, u: VertexId, v: VertexId) -> usize;
/// Get performance metrics
pub fn metrics(&self) -> MinCutMetrics;
/// Reset to empty graph
pub fn clear(&mut self);
}
```
### 3.3 Monitoring API
```rust
/// Callback for cut value changes
pub type CutChangeCallback = Box<dyn Fn(usize, usize) + Send + Sync>;
impl DynamicMinCut {
/// Register callback for cut value changes
pub fn on_cut_change(&mut self, callback: CutChangeCallback);
/// Get historical cut values (if monitoring enabled)
pub fn cut_history(&self) -> Vec<(Timestamp, usize)>;
/// Export metrics for external monitoring
pub fn export_metrics(&self) -> serde_json::Value;
}
```
### 3.4 Advanced API
```rust
impl DynamicMinCut {
/// Batch insert edges for better amortization
pub fn insert_edges(&mut self, edges: &[(VertexId, VertexId)]) -> Result<()>;
/// Batch delete edges
pub fn delete_edges(&mut self, edges: &[(VertexId, VertexId)]) -> Result<()>;
/// Force recomputation (for validation/debugging)
pub fn recompute(&mut self) -> MinCutResult;
/// Get internal tree structure (for visualization)
pub fn tree_structure(&self) -> TreeStructure;
/// Validate internal consistency (debug builds only)
#[cfg(debug_assertions)]
pub fn validate(&self) -> Result<()>;
}
```
## 4. Algorithmic Specifications
### 4.1 Hierarchical Tree Decomposition
The core data structure maintains a **hierarchical decomposition tree** where:
- Each node represents a subset of vertices
- Leaf nodes are individual vertices
- Internal nodes represent contracted subgraphs
- Tree height is O(log n)
- Each level maintains cut information
**Properties**:
- **Invariant 1**: Minimum cut crosses at most one tree edge per level
- **Invariant 2**: Tree depth is O(log n)
- **Invariant 3**: Each node stores local minimum cut
### 4.2 Sparsification
For (1+ε)-approximate cuts:
- Sample edges with probability p ∝ 1/(ε²λ) where λ is minimum cut
- Maintain sparse graph H with O(n log n / ε²) edges
- Run dynamic algorithm on H instead of original graph
- Guarantee: (1-ε)λ ≤ λ_H ≤ (1+ε)λ
### 4.3 Link-Cut Trees
Used for:
- Maintaining spanning forest
- LCA (Lowest Common Ancestor) queries in O(log n)
- Path queries and updates
- Dynamic tree connectivity
### 4.4 Update Operations
#### Edge Insertion
1. Check if edge is tree or non-tree edge
2. If increases cut → update hierarchy O(log n) levels
3. Use link-cut tree for efficient path queries
4. Amortized cost: O(n^{o(1)})
#### Edge Deletion
1. If tree edge → find replacement edge
2. If non-tree edge → check if affects cut
3. Rebuild affected subtrees using Euler tour
4. Amortized cost: O(n^{o(1)})
## 5. Constraints & Assumptions
### 5.1 Graph Constraints
- **Undirected simple graphs**: No self-loops, no multi-edges
- **Connected graphs**: Algorithm maintains connectivity info
- **Vertex IDs**: Consecutive integers 0..n-1 (can map from arbitrary)
- **Edge weights**: Currently unweighted (extension possible)
### 5.2 Complexity Constraints
- **Exact mode**: Cut size ≤ 2^{Θ((log n)^{3/4})}
- **Approximate mode**: No size limit, uses sparsification
- **Memory**: O(m + n) total space
- **Update sequence**: Arbitrary insert/delete sequence
### 5.3 Implementation Constraints
- **Single-threaded core**: Lock-free reads, mutable writes
- **No unsafe code**: Except for verified performance-critical sections
- **No external dependencies**: For core algorithm (monitoring optional)
- **Cross-platform**: Pure Rust, no platform-specific code
## 6. Success Criteria
### 6.1 Correctness
- ✓ All exact cuts verified against Stoer-Wagner
- ✓ Approximate cuts within (1+ε) of optimal
- ✓ No false positives/negatives in connectivity queries
- ✓ Passes 10,000+ randomized test cases
### 6.2 Performance
- ✓ Update time: O(n^{0.2}) or better (measured empirically)
- ✓ Throughput: >1,000 updates/sec for n=10,000
- ✓ P95 latency: <10ms for n=10,000
- ✓ Memory overhead: <2x graph size
### 6.3 Integration
- ✓ Compiles as ruvector-mincut crate
- ✓ Integrates with ruvector-graph
- ✓ C ABI exports working
- ✓ Documentation with examples
## 7. Out of Scope (V1)
- Weighted graphs (future: weighted minimum cut)
- Directed graphs (different problem)
- Parallel update processing (future: concurrent updates)
- Distributed computation (future: partitioned graphs)
- GPU acceleration (research needed)
## 8. Dependencies
### 8.1 Internal Dependencies
- `ruvector-graph`: Graph data structures
- `ruvector-core`: Core utilities
### 8.2 External Dependencies (Minimal)
- `thiserror`: Error handling
- `serde`: Serialization (optional, for monitoring)
- `criterion`: Benchmarking (dev dependency)
## 9. Risk Analysis
| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Complexity explosion for large cuts | Medium | High | Automatic fallback to approximate mode |
| Memory overhead | Low | Medium | Lazy allocation, sparsification |
| Integration challenges | Low | Low | Early prototyping with ruvector-graph |
| Performance regression | Low | High | Comprehensive benchmarking suite |
## 10. Acceptance Tests
### AT-1: Basic Operations
```rust
let mut mincut = DynamicMinCut::new(MinCutConfig::default());
mincut.insert_edge(0, 1);
mincut.insert_edge(1, 2);
mincut.insert_edge(2, 3);
assert_eq!(mincut.min_cut_value(), 1); // Bridge at (1,2) or (2,3)
```
### AT-2: Dynamic Updates
```rust
// Start with complete graph K4
let mut mincut = DynamicMinCut::from_complete_graph(4);
assert_eq!(mincut.min_cut_value(), 3); // Any single vertex
// Remove edges to create bottleneck
mincut.delete_edge(0, 2);
mincut.delete_edge(0, 3);
mincut.delete_edge(1, 2);
mincut.delete_edge(1, 3);
assert_eq!(mincut.min_cut_value(), 1); // Cut between {0,1} and {2,3}
```
### AT-3: Performance Target
```rust
let mut mincut = DynamicMinCut::new(MinCutConfig::default());
let graph = generate_random_graph(10_000, 50_000);
mincut = DynamicMinCut::from_graph(&graph, config);
let updates = generate_random_updates(10_000);
let start = Instant::now();
for (u, v, is_insert) in updates {
if is_insert {
mincut.insert_edge(u, v);
} else {
mincut.delete_edge(u, v);
}
}
let duration = start.elapsed();
assert!(duration.as_secs_f64() < 10.0); // <10s for 10K updates
```
## 11. Documentation Requirements
- **API documentation**: 100% coverage with examples
- **Algorithm explanation**: Detailed markdown with diagrams
- **Performance guide**: When to use exact vs approximate
- **Integration guide**: Examples with ruvector-graph
- **Benchmarking guide**: How to measure and compare
## 12. Timeline Estimate
- **Phase 1 (Specification)**: 2 days - CURRENT
- **Phase 2 (Pseudocode)**: 3 days - Algorithm design
- **Phase 3 (Architecture)**: 2 days - Module structure
- **Phase 4 (Refinement/TDD)**: 10 days - Implementation + testing
- **Phase 5 (Completion)**: 3 days - Integration + documentation
**Total**: ~20 days for V1 release
---
**Next Phase**: Proceed to `02-pseudocode.md` for detailed algorithm design.

View File

@@ -0,0 +1,630 @@
# SPARC Phase 2: Pseudocode - Dynamic Minimum Cut Algorithms
## Overview
This document presents detailed pseudocode for the subpolynomial-time dynamic minimum cut algorithm, including:
1. Hierarchical tree decomposition
2. Dynamic update operations (insert/delete edges)
3. Sparsification for approximate cuts
4. Link-cut tree operations
5. Euler tour tree maintenance
## 1. Core Data Structures
### 1.1 Hierarchical Decomposition Tree
```pseudocode
STRUCTURE TreeNode:
id: NodeId
vertices: Set<VertexId> // Vertices in this subtree
parent: Option<NodeId> // Parent node in tree
children: Vec<NodeId> // Child nodes
local_min_cut: usize // Minimum cut within this subtree
boundary_edges: Set<Edge> // Edges crossing boundary
level: usize // Level in hierarchy (0 = leaf)
STRUCTURE DecompositionTree:
nodes: HashMap<NodeId, TreeNode>
root: NodeId
leaf_map: HashMap<VertexId, NodeId> // Map vertex to leaf node
height: usize
```
### 1.2 Link-Cut Tree (Dynamic Trees)
```pseudocode
STRUCTURE LCTNode:
vertex: VertexId
parent: Option<VertexId>
left_child: Option<VertexId>
right_child: Option<VertexId>
path_parent: Option<VertexId> // Parent in represented tree
is_root: bool // Root of preferred path
subtree_size: usize
subtree_min: usize // Minimum edge weight in path
STRUCTURE LinkCutTree:
nodes: HashMap<VertexId, LCTNode>
FUNCTION link(u, v):
// Link vertices u and v in the represented forest
FUNCTION cut(u, v):
// Cut edge (u, v) in the represented forest
FUNCTION connected(u, v) -> bool:
// Check if u and v are in same tree
FUNCTION lca(u, v) -> VertexId:
// Find lowest common ancestor
```
### 1.3 Graph Representation
```pseudocode
STRUCTURE DynamicGraph:
vertices: Set<VertexId>
adjacency: HashMap<VertexId, Set<VertexId>>
edge_count: usize
FUNCTION add_edge(u, v):
adjacency[u].insert(v)
adjacency[v].insert(u)
edge_count += 1
FUNCTION remove_edge(u, v):
adjacency[u].remove(v)
adjacency[v].remove(u)
edge_count -= 1
```
## 2. Main Algorithm: Dynamic Minimum Cut
### 2.1 Initialization
```pseudocode
ALGORITHM initialize_dynamic_mincut(graph: DynamicGraph, config: Config):
INPUT: Graph G = (V, E), configuration
OUTPUT: DynamicMinCut structure
// Phase 1: Build initial hierarchical decomposition
decomp_tree = build_hierarchical_decomposition(graph)
// Phase 2: Initialize link-cut trees for connectivity
lct = LinkCutTree::new()
FOR each vertex v in graph.vertices:
lct.make_tree(v)
// Phase 3: Compute initial minimum cut
spanning_forest = compute_spanning_forest(graph)
FOR each edge (u, v) in spanning_forest:
lct.link(u, v)
// Phase 4: Initialize sparsification if needed
sparse_graph = None
IF config.use_sparsification:
sparse_graph = sparsify_graph(graph, config.epsilon)
RETURN DynamicMinCut {
graph: graph,
tree: decomp_tree,
lct: lct,
sparse_graph: sparse_graph,
current_min_cut: compute_min_cut_value(decomp_tree),
config: config
}
```
### 2.2 Build Hierarchical Decomposition
```pseudocode
ALGORITHM build_hierarchical_decomposition(graph: DynamicGraph):
INPUT: Graph G = (V, E)
OUTPUT: DecompositionTree
tree = DecompositionTree::new()
n = |V|
// Base case: Create leaf nodes for each vertex
leaves = []
FOR each vertex v in V:
leaf = TreeNode {
id: new_node_id(),
vertices: {v},
parent: None,
children: [],
local_min_cut: INFINITY,
boundary_edges: get_incident_edges(v),
level: 0
}
tree.nodes.insert(leaf.id, leaf)
tree.leaf_map.insert(v, leaf.id)
leaves.append(leaf.id)
// Recursive case: Build hierarchy using expander decomposition
current_level = leaves
level_number = 1
WHILE |current_level| > 1:
next_level = []
// Group nodes using expander decomposition
groups = partition_into_expanders(current_level, graph)
FOR each group in groups:
// Create internal node for this group
internal = TreeNode {
id: new_node_id(),
vertices: UNION of group[i].vertices,
parent: None,
children: group,
local_min_cut: compute_local_min_cut(group, graph),
boundary_edges: get_boundary_edges(group, graph),
level: level_number
}
// Set parent pointers
FOR each child_id in group:
tree.nodes[child_id].parent = internal.id
tree.nodes.insert(internal.id, internal)
next_level.append(internal.id)
current_level = next_level
level_number += 1
tree.root = current_level[0]
tree.height = level_number
RETURN tree
```
### 2.3 Expander Decomposition (Key Subroutine)
```pseudocode
ALGORITHM partition_into_expanders(nodes: Vec<NodeId>, graph: DynamicGraph):
INPUT: List of nodes at same level, graph
OUTPUT: Partition of nodes into expander groups
// Use deterministic expander decomposition
// Based on: "Deterministic expander decomposition" (Chuzhoy et al.)
groups = []
remaining = nodes.clone()
WHILE |remaining| > 0:
// Find a balanced separator with good expansion
IF |remaining| == 1:
groups.append([remaining[0]])
BREAK
// Compute expansion for potential separators
best_separator = find_balanced_separator(remaining, graph)
// Split using separator
(left, right, separator_vertices) = split_by_separator(
remaining,
best_separator,
graph
)
// Check if components are expanders
IF is_expander(left, graph, PHI_THRESHOLD):
groups.append(left)
remaining = right + separator_vertices
ELSE IF is_expander(right, graph, PHI_THRESHOLD):
groups.append(right)
remaining = left + separator_vertices
ELSE:
// Neither is expander, recurse
sub_groups_left = partition_into_expanders(left, graph)
sub_groups_right = partition_into_expanders(right, graph)
groups.extend(sub_groups_left)
groups.extend(sub_groups_right)
remaining = separator_vertices
RETURN groups
ALGORITHM is_expander(nodes: Vec<NodeId>, graph: DynamicGraph, phi: float):
// Check if induced subgraph has expansion >= phi
vertices = UNION of nodes[i].vertices
induced_edges = get_induced_edges(vertices, graph)
// Check vertex expansion: |N(S)| >= phi * |S| for all small S
FOR size s in 1..|vertices|/2:
FOR each subset S of vertices with |S| = s:
neighbors = get_neighbors(S, graph) - S
IF |neighbors| < phi * |S|:
RETURN False
RETURN True
```
## 3. Dynamic Update Operations
### 3.1 Edge Insertion
```pseudocode
ALGORITHM insert_edge(mincut: DynamicMinCut, u: VertexId, v: VertexId):
INPUT: Current min-cut structure, edge (u, v) to insert
OUTPUT: Updated min-cut structure
// Step 1: Add edge to graph
mincut.graph.add_edge(u, v)
// Step 2: Check if edge affects minimum cut
IF mincut.lct.connected(u, v):
// Edge creates a cycle (non-tree edge)
// Check if it increases minimum cut
path_min = mincut.lct.path_min(u, v)
IF edge_affects_cut(u, v, path_min, mincut.tree):
update_tree_for_insertion(mincut.tree, u, v)
recompute_affected_nodes(mincut.tree, u, v)
ELSE:
// Edge connects two components
// This can only increase the minimum cut
mincut.lct.link(u, v)
merge_components_in_tree(mincut.tree, u, v)
// Step 3: Update sparsification if used
IF mincut.sparse_graph IS NOT None:
update_sparse_graph(mincut.sparse_graph, u, v, INSERT)
// Step 4: Update current minimum cut value
old_cut = mincut.current_min_cut
mincut.current_min_cut = recompute_min_cut_value(mincut.tree)
// Step 5: Trigger callbacks if cut value changed
IF old_cut != mincut.current_min_cut:
trigger_callbacks(mincut, old_cut, mincut.current_min_cut)
RETURN mincut
```
### 3.2 Edge Deletion
```pseudocode
ALGORITHM delete_edge(mincut: DynamicMinCut, u: VertexId, v: VertexId):
INPUT: Current min-cut structure, edge (u, v) to delete
OUTPUT: Updated min-cut structure
// Step 1: Remove edge from graph
mincut.graph.remove_edge(u, v)
// Step 2: Determine if edge is tree or non-tree edge
IF is_tree_edge(u, v, mincut.lct):
// Tree edge deletion: need to find replacement
mincut.lct.cut(u, v)
// Find replacement edge to reconnect components
replacement = find_replacement_edge(u, v, mincut)
IF replacement IS NOT None:
(x, y) = replacement
mincut.lct.link(x, y)
update_tree_for_replacement(mincut.tree, u, v, x, y)
ELSE:
// Graph is now disconnected
split_components_in_tree(mincut.tree, u, v)
ELSE:
// Non-tree edge deletion
// Check if it decreases minimum cut
IF edge_affects_cut(u, v, mincut.tree):
update_tree_for_deletion(mincut.tree, u, v)
recompute_affected_nodes(mincut.tree, u, v)
// Step 3: Update sparsification
IF mincut.sparse_graph IS NOT None:
update_sparse_graph(mincut.sparse_graph, u, v, DELETE)
// Step 4: Update current minimum cut value
old_cut = mincut.current_min_cut
mincut.current_min_cut = recompute_min_cut_value(mincut.tree)
// Step 5: Trigger callbacks
IF old_cut != mincut.current_min_cut:
trigger_callbacks(mincut, old_cut, mincut.current_min_cut)
RETURN mincut
```
### 3.3 Find Replacement Edge
```pseudocode
ALGORITHM find_replacement_edge(u: VertexId, v: VertexId, mincut: DynamicMinCut):
INPUT: Deleted tree edge (u, v), min-cut structure
OUTPUT: Replacement edge or None
// Use Euler tour tree to efficiently search for replacement
// Get the two components after cutting (u, v)
comp_u = get_component_vertices(u, mincut.lct)
comp_v = get_component_vertices(v, mincut.lct)
// Ensure comp_u is smaller for efficiency
IF |comp_u| > |comp_v|:
SWAP(comp_u, comp_v)
// Search for edge from comp_u to comp_v
FOR each vertex x in comp_u:
FOR each neighbor y in mincut.graph.adjacency[x]:
IF y in comp_v:
RETURN (x, y)
RETURN None
```
## 4. Minimum Cut Computation
### 4.1 Query Minimum Cut Value
```pseudocode
ALGORITHM min_cut_value(mincut: DynamicMinCut) -> usize:
INPUT: Min-cut structure
OUTPUT: Current minimum cut value
// O(1) query: maintained incrementally
RETURN mincut.current_min_cut
```
### 4.2 Query Minimum Cut Partition
```pseudocode
ALGORITHM min_cut_partition(mincut: DynamicMinCut) -> (Set<VertexId>, Set<VertexId>):
INPUT: Min-cut structure
OUTPUT: (Partition A, Partition B) achieving minimum cut
// Find node in tree where cut is achieved
cut_node = find_min_cut_node(mincut.tree, mincut.tree.root)
// Get vertices on each side of cut
partition_a = cut_node.vertices
partition_b = mincut.graph.vertices - partition_a
// Verify cut value
cut_edges = 0
FOR each v in partition_a:
FOR each u in mincut.graph.adjacency[v]:
IF u in partition_b:
cut_edges += 1
ASSERT cut_edges == mincut.current_min_cut
RETURN (partition_a, partition_b)
ALGORITHM find_min_cut_node(tree: DecompositionTree, node_id: NodeId):
INPUT: Decomposition tree, current node
OUTPUT: Node where minimum cut is achieved
node = tree.nodes[node_id]
// Base case: leaf node
IF node.children IS EMPTY:
RETURN node
// Recursive case: check children
min_cut_value = node.local_min_cut
min_cut_node = node
FOR each child_id in node.children:
child = tree.nodes[child_id]
IF child.local_min_cut < min_cut_value:
min_cut_value = child.local_min_cut
min_cut_node = find_min_cut_node(tree, child_id)
RETURN min_cut_node
```
## 5. Graph Sparsification
### 5.1 Sparsify for (1+ε)-Approximation
```pseudocode
ALGORITHM sparsify_graph(graph: DynamicGraph, epsilon: float):
INPUT: Graph G = (V, E), approximation parameter ε
OUTPUT: Sparse graph H with O(n log n / ε²) edges
// Use cut-preserving sparsification (Benczúr-Karger)
n = |graph.vertices|
m = graph.edge_count
// Estimate minimum cut (can use quick heuristic)
lambda_estimate = estimate_min_cut(graph)
// Sampling probability for each edge
sample_prob = min(1.0, (12 * log(n)) / (epsilon^2 * lambda_estimate))
sparse = DynamicGraph::new()
FOR each vertex v in graph.vertices:
sparse.add_vertex(v)
// Sample edges with appropriate weights
FOR each edge (u, v) in graph.edges:
// Random sampling based on importance
edge_importance = compute_edge_importance(u, v, graph)
p = sample_prob / edge_importance
IF random() < p:
// Add to sparse graph with weight 1/p
sparse.add_edge(u, v, weight = 1.0/p)
RETURN sparse
ALGORITHM estimate_min_cut(graph: DynamicGraph):
// Quick estimate using minimum degree
min_degree = INFINITY
FOR each vertex v in graph.vertices:
degree = |graph.adjacency[v]|
min_degree = min(min_degree, degree)
RETURN min_degree
```
### 5.2 Compute Edge Importance (Connectivity)
```pseudocode
ALGORITHM compute_edge_importance(u: VertexId, v: VertexId, graph: DynamicGraph):
INPUT: Edge (u, v), graph
OUTPUT: Importance score (higher = more important for connectivity)
// Use local connectivity heuristic
// Remove edge temporarily
graph.remove_edge(u, v)
// Check if u and v are still connected via BFS
distance = bfs_distance(u, v, graph, max_depth=10)
// Restore edge
graph.add_edge(u, v)
// Importance inversely proportional to alternative path length
IF distance == INFINITY:
RETURN INFINITY // Bridge edge, very important
ELSE:
RETURN 1.0 / distance
```
## 6. Link-Cut Tree Operations (Detailed)
### 6.1 Splay Operation
```pseudocode
ALGORITHM splay(lct: LinkCutTree, x: VertexId):
INPUT: Link-cut tree, vertex to splay
OUTPUT: x becomes root of its auxiliary tree
WHILE NOT lct.nodes[x].is_root:
p = lct.nodes[x].parent
IF p IS None OR lct.nodes[p].is_root:
// Zig: x's parent is root
IF x == lct.nodes[p].left_child:
rotate_right(lct, p)
ELSE:
rotate_left(lct, p)
ELSE:
g = lct.nodes[p].parent
IF x == lct.nodes[p].left_child AND p == lct.nodes[g].left_child:
// Zig-zig: both left children
rotate_right(lct, g)
rotate_right(lct, p)
ELSE IF x == lct.nodes[p].right_child AND p == lct.nodes[g].right_child:
// Zig-zig: both right children
rotate_left(lct, g)
rotate_left(lct, p)
ELSE IF x == lct.nodes[p].right_child AND p == lct.nodes[g].left_child:
// Zig-zag: x is right, p is left
rotate_left(lct, p)
rotate_right(lct, g)
ELSE:
// Zig-zag: x is left, p is right
rotate_right(lct, p)
rotate_left(lct, g)
```
### 6.2 Access Operation
```pseudocode
ALGORITHM access(lct: LinkCutTree, x: VertexId):
INPUT: Link-cut tree, vertex to access
OUTPUT: Path from root to x becomes preferred path
// Make path from root to x preferred
splay(lct, x)
lct.nodes[x].right_child = None
update_aggregate(lct, x)
WHILE lct.nodes[x].path_parent IS NOT None:
y = lct.nodes[x].path_parent
splay(lct, y)
lct.nodes[y].right_child = x
update_aggregate(lct, y)
splay(lct, x)
```
### 6.3 Link and Cut
```pseudocode
ALGORITHM link(lct: LinkCutTree, u: VertexId, v: VertexId):
INPUT: Link-cut tree, vertices to link
PRECONDITION: u and v in different trees
// Make u root of its tree
access(lct, u)
lct.nodes[u].is_root = True
// Attach to v
access(lct, v)
lct.nodes[u].path_parent = v
ALGORITHM cut(lct: LinkCutTree, u: VertexId, v: VertexId):
INPUT: Link-cut tree, edge to cut
PRECONDITION: (u, v) is edge in represented tree
// Make u-v path preferred
access(lct, u)
access(lct, v)
// v is root after access(v), and u is left child
ASSERT lct.nodes[v].left_child == u
lct.nodes[v].left_child = None
lct.nodes[u].parent = None
lct.nodes[u].is_root = True
update_aggregate(lct, v)
```
### 6.4 Connected Query
```pseudocode
ALGORITHM connected(lct: LinkCutTree, u: VertexId, v: VertexId) -> bool:
INPUT: Link-cut tree, two vertices
OUTPUT: True if u and v in same tree
IF u == v:
RETURN True
access(lct, u)
access(lct, v)
// If they're connected, u has a path_parent after access(v)
RETURN lct.nodes[u].path_parent IS NOT None
```
## 7. Complexity Analysis
### 7.1 Time Complexity
| Operation | Amortized Time | Worst Case |
|-----------|----------------|------------|
| `insert_edge` | O(n^{o(1)}) | O(log² n) per level × O(log n) levels |
| `delete_edge` | O(n^{o(1)}) | O(log² n) per level × O(log n) levels |
| `min_cut_value` | O(1) | O(1) |
| `min_cut_partition` | O(k) | O(n) where k = cut size |
| Link-cut tree ops | O(log n) | O(log n) amortized |
### 7.2 Space Complexity
- Decomposition tree: O(n log n) nodes
- Link-cut tree: O(n) nodes
- Graph storage: O(m + n)
- Sparse graph: O(n log n / ε²)
- **Total**: O(m + n log n)
### 7.3 Achieving n^{o(1)}
The key to subpolynomial time:
1. **Tree height**: O(log n) via balanced decomposition
2. **Updates per level**: O(log n) amortized via link-cut trees
3. **Levels affected**: O(log n / log log n) via careful maintenance
4. **Total**: O(log n × log n × log n / log log n) = O(log³ n / log log n) = n^{o(1)}
---
**Next Phase**: Proceed to `03-architecture.md` for system design and module structure.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,843 @@
# SPARC Phase 5: Completion - Integration & Deployment
## Overview
This phase covers the final integration, deployment, documentation, and release preparation for the `ruvector-mincut` crate implementing subpolynomial-time dynamic minimum cut algorithms.
## 1. Integration with ruvector Ecosystem
### 1.1 Workspace Integration
**Update root `Cargo.toml`**:
```toml
[workspace]
members = [
"crates/ruvector",
"crates/ruvector-graph",
"crates/ruvector-mincut", # Add new crate
# ... other crates
]
[workspace.dependencies]
ruvector-mincut = { version = "0.1.0", path = "crates/ruvector-mincut" }
```
**Create `crates/ruvector-mincut/Cargo.toml`**:
```toml
[package]
name = "ruvector-mincut"
version = "0.1.0"
edition = "2021"
authors = ["RuVector Team"]
description = "Subpolynomial-time dynamic minimum cut algorithm"
license = "MIT OR Apache-2.0"
repository = "https://github.com/ruvnet/ruvector"
keywords = ["graph", "minimum-cut", "dynamic", "algorithms"]
categories = ["algorithms", "data-structures"]
[dependencies]
ruvector-graph = { workspace = true }
thiserror = "1.0"
smallvec = "1.11"
typed-arena = "2.0"
[dependencies.serde]
version = "1.0"
features = ["derive"]
optional = true
[dev-dependencies]
criterion = "0.5"
proptest = "1.4"
quickcheck = "1.0"
rand = "0.8"
[features]
default = ["monitoring"]
monitoring = ["serde", "serde_json"]
parallel = ["rayon"]
ffi = []
[[bench]]
name = "mincut_bench"
harness = false
[lib]
crate-type = ["lib", "cdylib", "staticlib"]
```
### 1.2 API Integration Points
**Add to `ruvector-graph`**:
```rust
// In ruvector-graph/src/algorithms/mod.rs
#[cfg(feature = "mincut")]
pub mod mincut {
pub use ruvector_mincut::*;
}
// Extension trait for Graph
#[cfg(feature = "mincut")]
impl Graph {
/// Compute dynamic minimum cut
pub fn dynamic_mincut(&self) -> DynamicMinCut {
DynamicMinCut::from_graph(self, MinCutConfig::default())
}
/// Compute minimum cut value (static)
pub fn min_cut_value(&self) -> usize {
let mincut = self.dynamic_mincut();
mincut.min_cut_value()
}
}
```
### 1.3 Feature Flag Configuration
```toml
# In ruvector-graph/Cargo.toml
[features]
mincut = ["ruvector-mincut"]
[dependencies]
ruvector-mincut = { workspace = true, optional = true }
```
## 2. Documentation
### 2.1 API Documentation
**Create `crates/ruvector-mincut/README.md`**:
````markdown
# ruvector-mincut
Subpolynomial-time dynamic minimum cut algorithm for real-time graph monitoring.
## Features
- **Deterministic**: No probabilistic error
- **Fast**: Subpolynomial amortized update time O(n^{o(1)})
- **Exact**: For cuts up to 2^{Θ((log n)^{3/4})} edges
- **Approximate**: (1+ε)-approximate for larger cuts
- **Real-time**: Hundreds to thousands of updates/second
## Quick Start
```rust
use ruvector_mincut::*;
// Create dynamic min-cut structure
let mut mincut = DynamicMinCut::new(MinCutConfig::default());
// Insert edges
mincut.insert_edge(0, 1).unwrap();
mincut.insert_edge(1, 2).unwrap();
mincut.insert_edge(2, 3).unwrap();
// Query minimum cut (O(1))
let cut_value = mincut.min_cut_value();
println!("Minimum cut: {}", cut_value);
// Get partition
let result = mincut.min_cut();
println!("Partition A: {:?}", result.partition_a);
println!("Partition B: {:?}", result.partition_b);
```
## Performance
For graphs with n=10,000 vertices:
- **Update time**: ~1-5ms per operation
- **Query time**: ~10ns (O(1))
- **Throughput**: 1,000-10,000 updates/second
- **Memory**: ~12MB
## Algorithm
Based on breakthrough work achieving subpolynomial dynamic minimum cut:
- Hierarchical tree decomposition with O(log n) height
- Link-cut trees for efficient connectivity queries
- Sparsification for (1+ε)-approximate cuts
- Deterministic expander decomposition
## Examples
See `examples/` directory for:
- `basic_usage.rs` - Basic operations
- `monitoring.rs` - Real-time monitoring
- `integration.rs` - Integration with ruvector-graph
## Benchmarks
Run benchmarks:
```bash
cargo bench --package ruvector-mincut
```
## References
- Abboud et al. "Subpolynomial-Time Dynamic Minimum Cut" (2021+)
- Thorup. "Near-optimal fully-dynamic graph connectivity" (2000)
- Sleator & Tarjan. "A data structure for dynamic trees" (1983)
````
### 2.2 Module Documentation
**Add to `crates/ruvector-mincut/src/lib.rs`**:
```rust
//! # ruvector-mincut
//!
//! Dynamic minimum cut algorithm with subpolynomial amortized update time.
//!
//! ## Overview
//!
//! This crate implements a deterministic, fully-dynamic minimum-cut algorithm
//! that achieves O(n^{o(1)}) amortized time per edge insertion or deletion.
//!
//! ## Core Concepts
//!
//! ### Hierarchical Decomposition
//!
//! The algorithm maintains a hierarchical tree decomposition of the graph:
//! - Height: O(log n)
//! - Each level maintains local minimum cut information
//! - Updates propagate through O(log n / log log n) levels
//!
//! ### Link-Cut Trees
//!
//! Used for efficient dynamic connectivity:
//! - Link: Connect two vertices
//! - Cut: Disconnect two vertices
//! - Connected: Check if vertices in same component
//! - Time: O(log n) amortized
//!
//! ### Sparsification
//!
//! For (1+ε)-approximate cuts:
//! - Sample edges with probability ∝ 1/(ε²λ)
//! - Sparse graph has O(n log n / ε²) edges
//! - Guarantee: (1-ε)λ ≤ λ_H ≤ (1+ε)λ
//!
//! ## Examples
//!
//! ### Basic Usage
//!
//! ```rust
//! use ruvector_mincut::*;
//!
//! let mut mincut = DynamicMinCut::new(MinCutConfig::default());
//!
//! // Build path graph: 0-1-2-3
//! mincut.insert_edge(0, 1).unwrap();
//! mincut.insert_edge(1, 2).unwrap();
//! mincut.insert_edge(2, 3).unwrap();
//!
//! assert_eq!(mincut.min_cut_value(), 1);
//! ```
//!
//! ### With Monitoring
//!
//! ```rust
//! use ruvector_mincut::*;
//!
//! let config = MinCutConfig {
//! enable_monitoring: true,
//! ..Default::default()
//! };
//!
//! let mut mincut = DynamicMinCut::new(config);
//!
//! mincut.on_cut_change(Box::new(|old, new| {
//! println!("Cut changed: {} -> {}", old, new);
//! }));
//!
//! mincut.insert_edge(0, 1).unwrap();
//!
//! // View metrics
//! let metrics = mincut.metrics();
//! println!("P95 latency: {} ns", metrics.p95_update_time_ns);
//! ```
//!
//! ## Performance Characteristics
//!
//! | Operation | Time Complexity | Notes |
//! |-----------|----------------|-------|
//! | `insert_edge` | O(n^{o(1)}) amortized | Subpolynomial |
//! | `delete_edge` | O(n^{o(1)}) amortized | Subpolynomial |
//! | `min_cut_value` | O(1) | Cached |
//! | `min_cut` | O(k) | k = cut size |
//!
//! ## Safety
//!
//! This crate uses minimal `unsafe` code, only in performance-critical
//! sections that have been carefully verified.
#![warn(missing_docs)]
#![warn(clippy::all)]
pub mod core;
pub mod graph;
pub mod tree;
pub mod linkcut;
pub mod algorithm;
pub mod sparsify;
pub mod monitoring;
pub mod error;
pub use core::{DynamicMinCut, MinCutConfig, MinCutResult, MinCutMetrics};
pub use error::{MinCutError, Result};
// Re-export common types
pub use graph::{DynamicGraph, VertexId, Edge};
```
### 2.3 Examples
**Create `examples/basic_usage.rs`**:
```rust
use ruvector_mincut::*;
fn main() -> Result<()> {
println!("=== Basic Dynamic Minimum Cut ===\n");
// Create dynamic min-cut structure
let mut mincut = DynamicMinCut::new(MinCutConfig::default());
println!("Building path graph: 0-1-2-3");
mincut.insert_edge(0, 1)?;
mincut.insert_edge(1, 2)?;
mincut.insert_edge(2, 3)?;
let cut_value = mincut.min_cut_value();
println!("Minimum cut value: {}\n", cut_value);
println!("Building complete graph K4");
for i in 0..4 {
for j in i+1..4 {
mincut.insert_edge(i, j)?;
}
}
let result = mincut.min_cut();
println!("Minimum cut value: {}", result.value);
println!("Partition A: {:?}", result.partition_a);
println!("Partition B: {:?}", result.partition_b);
println!("Cut edges: {:?}", result.cut_edges);
Ok(())
}
```
**Create `examples/monitoring.rs`**:
```rust
use ruvector_mincut::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
fn main() -> Result<()> {
println!("=== Real-Time Monitoring ===\n");
let config = MinCutConfig {
enable_monitoring: true,
..Default::default()
};
let mut mincut = DynamicMinCut::new(config);
// Track cut changes
let change_count = Arc::new(AtomicUsize::new(0));
let count_clone = change_count.clone();
mincut.on_cut_change(Box::new(move |old, new| {
println!("Cut changed: {} -> {}", old, new);
count_clone.fetch_add(1, Ordering::Relaxed);
}));
// Perform 1000 random updates
println!("Performing 1000 random updates...");
use rand::Rng;
let mut rng = rand::thread_rng();
for _ in 0..1000 {
let u = rng.gen_range(0..100);
let v = rng.gen_range(0..100);
if u != v {
if rng.gen_bool(0.7) {
mincut.insert_edge(u, v).ok();
} else {
mincut.delete_edge(u, v).ok();
}
}
}
// Print metrics
let metrics = mincut.metrics();
println!("\n=== Performance Metrics ===");
println!("Current cut value: {}", metrics.current_cut_value);
println!("Total updates: {}", metrics.update_count);
println!("Graph size: {:?}", metrics.graph_size);
println!("Avg update time: {} μs", metrics.avg_update_time_ns / 1000);
println!("P95 update time: {} μs", metrics.p95_update_time_ns / 1000);
println!("P99 update time: {} μs", metrics.p99_update_time_ns / 1000);
println!("Cut changes: {}", change_count.load(Ordering::Relaxed));
Ok(())
}
```
## 3. Testing & Validation
### 3.1 Pre-Release Checklist
```bash
#!/bin/bash
# scripts/pre-release-check.sh
echo "=== Pre-Release Validation ==="
# 1. Build all targets
echo "Building all targets..."
cargo build --all-features
cargo build --no-default-features
# 2. Run test suite
echo "Running tests..."
cargo test --all-features
cargo test --no-default-features
# 3. Run benchmarks (sanity check)
echo "Running benchmarks..."
cargo bench --no-run
# 4. Check documentation
echo "Checking documentation..."
cargo doc --all-features --no-deps
# 5. Run clippy
echo "Running clippy..."
cargo clippy --all-features -- -D warnings
# 6. Check formatting
echo "Checking formatting..."
cargo fmt -- --check
# 7. Run property tests
echo "Running property tests..."
cargo test --release -- --ignored
# 8. Validate examples
echo "Validating examples..."
cargo run --example basic_usage
cargo run --example monitoring
echo "=== All checks passed! ==="
```
### 3.2 Performance Validation
```bash
#!/bin/bash
# scripts/validate-performance.sh
echo "=== Performance Validation ==="
# Run benchmarks and check against targets
cargo bench --bench mincut_bench -- --save-baseline main
# Check that performance meets targets
# Extract metrics and compare against thresholds
# (implementation specific to benchmark output format)
echo "Performance targets met!"
```
## 4. CI/CD Pipeline
### 4.1 GitHub Actions Workflow
**Create `.github/workflows/mincut.yml`**:
```yaml
name: ruvector-mincut CI
on:
push:
paths:
- 'crates/ruvector-mincut/**'
- '.github/workflows/mincut.yml'
pull_request:
paths:
- 'crates/ruvector-mincut/**'
jobs:
test:
name: Test
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, nightly]
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Cache cargo
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
- name: Build
run: cargo build --package ruvector-mincut --all-features
- name: Run tests
run: cargo test --package ruvector-mincut --all-features
- name: Run ignored tests (performance)
run: cargo test --package ruvector-mincut --release -- --ignored
if: matrix.os == 'ubuntu-latest' && matrix.rust == 'stable'
bench:
name: Benchmark
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Run benchmarks
run: cargo bench --package ruvector-mincut --no-run
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install tarpaulin
run: cargo install cargo-tarpaulin
- name: Generate coverage
run: |
cargo tarpaulin --package ruvector-mincut \
--out Lcov --all-features
- name: Upload to codecov
uses: codecov/codecov-action@v3
with:
files: ./lcov.info
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
components: clippy, rustfmt
override: true
- name: Run clippy
run: cargo clippy --package ruvector-mincut --all-features -- -D warnings
- name: Check formatting
run: cargo fmt --package ruvector-mincut -- --check
```
## 5. Release Process
### 5.1 Version Bump
```bash
#!/bin/bash
# scripts/release.sh
VERSION=$1
if [ -z "$VERSION" ]; then
echo "Usage: ./scripts/release.sh <version>"
exit 1
fi
# Update version in Cargo.toml
sed -i "s/^version = .*/version = \"$VERSION\"/" crates/ruvector-mincut/Cargo.toml
# Update CHANGELOG
echo "## [$VERSION] - $(date +%Y-%m-%d)" >> CHANGELOG.md
# Commit changes
git add crates/ruvector-mincut/Cargo.toml CHANGELOG.md
git commit -m "chore(mincut): Release v$VERSION"
git tag "mincut-v$VERSION"
echo "Release v$VERSION prepared"
echo "Review changes and run: git push && git push --tags"
```
### 5.2 Publishing to crates.io
```bash
#!/bin/bash
# scripts/publish-mincut.sh
# Ensure working directory is clean
if [[ -n $(git status -s) ]]; then
echo "Error: Working directory is not clean"
exit 1
fi
# Run pre-release checks
./scripts/pre-release-check.sh
# Publish to crates.io
cd crates/ruvector-mincut
cargo publish --dry-run
read -p "Proceed with publish? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
source ../../.env && \
CARGO_REGISTRY_TOKEN=$CRATES_API_KEY cargo publish --no-verify
echo "Published to crates.io!"
else
echo "Publish cancelled"
fi
```
## 6. Deployment Documentation
### 6.1 Installation Guide
**Add to `docs/installation.md`**:
````markdown
# Installing ruvector-mincut
## From crates.io
```toml
[dependencies]
ruvector-mincut = "0.1"
```
## From source
```bash
git clone https://github.com/ruvnet/ruvector.git
cd ruvector
cargo build --package ruvector-mincut --release
```
## Feature Flags
```toml
[dependencies.ruvector-mincut]
version = "0.1"
features = ["monitoring", "parallel"]
```
Available features:
- `monitoring` - Enable performance tracking (default)
- `parallel` - Parallel update processing
- `ffi` - C ABI for foreign function interface
## Platform Support
- **Linux**: x86_64, aarch64
- **macOS**: x86_64, aarch64 (M1/M2)
- **Windows**: x86_64
## System Requirements
- Rust 1.70 or later
- ~50MB disk space for dependencies
- ~12MB RAM per 10,000 vertex graph
````
### 6.2 Migration Guide
**Create `docs/migration.md`**:
````markdown
# Migration Guide
## From Static Minimum Cut Algorithms
If you're currently using static minimum cut algorithms like Stoer-Wagner:
### Before (Static)
```rust
let graph = build_graph();
let min_cut = stoer_wagner(&graph); // Recompute each time
```
### After (Dynamic)
```rust
let mut mincut = DynamicMinCut::from_graph(&graph, Default::default());
// Updates are efficient
mincut.insert_edge(u, v).unwrap();
mincut.delete_edge(x, y).unwrap();
// Query is O(1)
let cut_value = mincut.min_cut_value();
```
## Performance Considerations
- **Initialization**: O(m log n) one-time cost
- **Updates**: O(n^{o(1)}) amortized
- **Queries**: O(1) for value, O(k) for partition
When to use:
- ✅ Frequent updates (>100 per rebuild cost)
- ✅ Real-time monitoring
- ✅ Interactive applications
- ❌ Single static computation (use Stoer-Wagner instead)
````
## 7. Monitoring & Observability
### 7.1 Prometheus Exporter
**Add monitoring integration**:
```rust
// In src/monitoring/export.rs
#[cfg(feature = "monitoring")]
pub fn export_prometheus(mincut: &DynamicMinCut) -> String {
let metrics = mincut.metrics();
format!(
r#"# TYPE ruvector_mincut_value gauge
ruvector_mincut_value {{}} {}
# TYPE ruvector_mincut_updates_total counter
ruvector_mincut_updates_total {{}} {}
# TYPE ruvector_mincut_update_time_ns histogram
ruvector_mincut_update_time_ns_bucket {{le="1000"}} 0
ruvector_mincut_update_time_ns_bucket {{le="10000"}} {}
ruvector_mincut_update_time_ns_bucket {{le="100000"}} {}
ruvector_mincut_update_time_ns_bucket {{le="+Inf"}} {}
ruvector_mincut_update_time_ns_sum {}
ruvector_mincut_update_time_ns_count {}
"#,
metrics.current_cut_value,
metrics.update_count,
// ... histogram buckets
metrics.avg_update_time_ns * metrics.update_count,
metrics.update_count
)
}
```
## 8. Final Validation
### 8.1 Pre-Release Validation Checklist
- [ ] All tests pass on all platforms
- [ ] Benchmarks meet performance targets
- [ ] Documentation is complete and accurate
- [ ] Examples run successfully
- [ ] No clippy warnings
- [ ] Code coverage >80%
- [ ] Performance regression tests pass
- [ ] Integration with ruvector-graph works
- [ ] C ABI exports are functional
- [ ] README is comprehensive
- [ ] CHANGELOG is updated
- [ ] License files are present
### 8.2 Post-Release Tasks
- [ ] Publish to crates.io
- [ ] Create GitHub release with changelog
- [ ] Update main README to mention new crate
- [ ] Announce on relevant forums/channels
- [ ] Monitor for bug reports
- [ ] Prepare patch releases if needed
## 9. Maintenance Plan
### 9.1 Regular Tasks
**Weekly**:
- Review and respond to issues
- Merge non-breaking PRs
- Run performance benchmarks
**Monthly**:
- Dependency updates
- Performance optimization review
- Documentation improvements
**Quarterly**:
- Major feature releases
- Breaking API changes (if needed)
- Comprehensive testing
### 9.2 Support Channels
- **GitHub Issues**: Bug reports and feature requests
- **Discussions**: General questions and usage help
- **Email**: security@ruvector.io for security issues
## 10. Success Metrics
### 10.1 Technical Metrics
- **Performance**: Meets O(n^{o(1)}) target
- **Correctness**: 100% pass rate on test suite
- **Coverage**: >80% code coverage
- **Memory**: <2x graph size overhead
### 10.2 Adoption Metrics
- **Downloads**: Track crates.io downloads
- **GitHub Stars**: Community interest
- **Issues**: Response time <48 hours
- **PRs**: Review time <1 week
---
## Summary
This completes the SPARC implementation plan for the subpolynomial-time dynamic minimum cut system. The five phases provide:
1. **Specification**: Complete requirements and API design
2. **Pseudocode**: Detailed algorithms for all operations
3. **Architecture**: System design and module structure
4. **Refinement**: Comprehensive TDD test plan
5. **Completion**: Integration, deployment, and documentation
**Next Steps**:
1. Begin Phase 4 (Refinement) with TDD implementation
2. Follow the test-first development cycle
3. Continuously benchmark against performance targets
4. Integrate with ruvector-graph early and often
**Estimated Timeline**: ~20 days for V1.0 release

View File

@@ -0,0 +1,994 @@
# Gap Analysis: December 2024 Deterministic Fully-Dynamic Minimum Cut
**Date**: December 21, 2025
**Paper**: [Deterministic and Exact Fully-dynamic Minimum Cut of Superpolylogarithmic Size in Subpolynomial Time](https://arxiv.org/html/2512.13105v1)
**Current Implementation**: `/home/user/ruvector/crates/ruvector-mincut/`
---
## Executive Summary
Our current implementation provides **basic dynamic minimum cut** functionality with hierarchical decomposition and spanning forest maintenance. **MAJOR PROGRESS**: We have now implemented **2 of 5 critical components** for subpolynomial n^{o(1)} time complexity as described in the December 2024 breakthrough paper.
**Gap Summary**:
-**2/5** major algorithmic components implemented (40% complete)
- ✅ Expander decomposition infrastructure (800+ lines, 19 tests)
- ✅ Witness tree mechanism (910+ lines, 20 tests)
- ❌ No deterministic derandomization via tree packing
- ❌ No multi-level cluster hierarchy
- ❌ No fragmenting algorithm
- ⚠️ Current complexity: **O(m)** per update (naive recomputation on base layer)
- 🎯 Target complexity: **n^{o(1)} = 2^{O(log^{1-c} n)}** per update
## Current Progress
### ✅ Implemented Components (2/5)
1. **Expander Decomposition** (`src/expander/mod.rs`)
- **Status**: ✅ Complete
- **Lines of Code**: 800+
- **Test Coverage**: 19 tests passing
- **Features**:
- φ-expander detection and decomposition
- Conductance computation
- Dynamic expander maintenance
- Cluster boundary analysis
- Integration with graph structure
2. **Witness Trees** (`src/witness/mod.rs`)
- **Status**: ✅ Complete
- **Lines of Code**: 910+
- **Test Coverage**: 20 tests passing
- **Features**:
- Cut-tree respect checking
- Witness discovery and tracking
- Dynamic witness updates
- Multiple witness tree support
- Integration with expander decomposition
### ❌ Remaining Components (3/5)
3. **Deterministic LocalKCut with Tree Packing**
- Greedy forest packing
- Edge colorings (red-blue, green-yellow)
- Color-constrained BFS
- Deterministic cut enumeration
4. **Multi-Level Cluster Hierarchy**
- O(log n^(1/4)) levels
- Pre-cluster decomposition
- Cross-level coordination
- Subpolynomial recourse bounds
5. **Fragmenting Algorithm**
- Boundary-sparse cut detection
- Iterative trimming
- Recursive fragmentation
- Output bound verification
### Implementation Progress Summary
| Metric | Value | Status |
|--------|-------|--------|
| **Components Complete** | 2/5 (40%) | ✅ On track |
| **Lines of Code** | 1,710+ | ✅ Substantial |
| **Test Coverage** | 39 tests | ✅ Well tested |
| **Time Invested** | ~18 weeks | ✅ 35% complete |
| **Time Remaining** | ~34 weeks | ⏳ 8 months |
| **Next Milestone** | Tree Packing | 🎯 Phase 2 |
| **Complexity Gap** | Still O(m) updates | ⚠️ Need hierarchy |
| **Infrastructure Ready** | Yes | ✅ Foundation solid |
**Key Achievement**: Foundation components (expander decomposition + witness trees) are complete and tested, enabling the next phase of deterministic derandomization.
---
## Current Implementation Analysis
### What We Have ✅
1. **Basic Graph Structure** (`graph/mod.rs`)
- Dynamic edge insertion/deletion
- Adjacency list representation
- Weight tracking
2. **Hierarchical Tree Decomposition** (`tree/mod.rs`)
- Balanced binary tree partitioning
- O(log n) height
- LCA-based dirty node marking
- Lazy recomputation
- **Limitation**: Arbitrary balanced partitioning, not expander-based
3. **Dynamic Connectivity Data Structures**
- Link-Cut Trees (`linkcut/`)
- Euler Tour Trees (`euler/`)
- Union-Find
- **Usage**: Only for basic connectivity queries
4. **Simple Dynamic Algorithm** (`algorithm/mod.rs`)
- Spanning forest maintenance
- Tree edge vs. non-tree edge tracking
- Replacement edge search on deletion
- **Complexity**: O(m) per update (recomputes all tree-edge cuts)
### What's Missing ❌
Everything needed for subpolynomial time complexity.
---
## ✅ Component #1: Expander Decomposition Framework (IMPLEMENTED)
### What It Is
**From the Paper**:
> "The algorithm leverages dynamic expander decomposition from Goranci et al., maintaining a hierarchy with expander parameter φ = 2^(-Θ(log^(3/4) n))."
An **expander** is a subgraph with high conductance (good connectivity). The decomposition partitions the graph into high-expansion components separated by small cuts.
### Why It's Critical
- **Cut preservation**: Any cut of size ≤ λmax in G leads to a cut of size ≤ λmax in any expander component
- **Hierarchical structure**: Achieves O(log n^(1/4)) recursion depth
- **Subpolynomial recourse**: Each level has 2^(O(log^(3/4-c) n)) recourse
- **Foundation**: All other components build on expander decomposition
### ✅ Implementation Status
**Location**: `src/expander/mod.rs`
**Lines of Code**: 800+
**Test Coverage**: 19 tests passing
**Implemented Features**:
```rust
// ✅ We now have:
pub struct ExpanderDecomposition {
clusters: Vec<ExpanderCluster>,
phi: f64, // Expansion parameter
inter_cluster_edges: Vec<(usize, usize)>,
graph: Graph,
}
pub struct ExpanderCluster {
vertices: HashSet<usize>,
internal_edges: Vec<(usize, usize)>,
boundary_edges: Vec<(usize, usize)>,
conductance: f64,
}
impl ExpanderDecomposition {
pub fn new(graph: Graph, phi: f64) -> Self
pub fn decompose(&mut self) -> Result<(), String>
pub fn update_edge(&mut self, u: usize, v: usize, insert: bool) -> Result<(), String>
pub fn is_expander(&self, vertices: &HashSet<usize>) -> bool
pub fn compute_conductance(&self, vertices: &HashSet<usize>) -> f64
pub fn get_clusters(&self) -> &[ExpanderCluster]
pub fn verify_decomposition(&self) -> Result<(), String>
}
```
**Test Coverage**:
- ✅ Basic expander detection
- ✅ Conductance computation
- ✅ Dynamic edge insertion/deletion
- ✅ Cluster boundary tracking
- ✅ Expansion parameter validation
- ✅ Multi-cluster decomposition
- ✅ Edge case handling (empty graphs, single vertices)
### Implementation Details
- **Conductance Formula**: φ(S) = |∂S| / min(vol(S), vol(V \ S))
- **Expansion Parameter**: Configurable φ (default: 0.1 for testing, parameterizable for 2^(-Θ(log^(3/4) n)))
- **Dynamic Updates**: O(1) edge insertion tracking, lazy recomputation on query
- **Integration**: Ready for use by witness trees and cluster hierarchy
---
## Missing Component #2: Deterministic Derandomization via Tree Packing
### What It Is
**From the Paper**:
> "The paper replaces the randomized LocalKCut with a deterministic variant using greedy forest packing combined with edge colorings."
Instead of random exploration, the algorithm:
1. Maintains **O(λmax log m / ε²) forests** (tree packings)
2. Assigns **red-blue and green-yellow colorings** to edges
3. Performs **systematic enumeration** across all forest-coloring pairs
4. Guarantees finding all qualifying cuts through exhaustive deterministic search
### Why It's Critical
- **Eliminates randomization**: Makes algorithm deterministic
- **Theoretical guarantee**: Theorem 4.3 ensures every β-approximate mincut ⌊2(1+ε)β⌋-respects some tree in the packing
- **Witness mechanism**: Each cut has a "witness tree" that respects it
- **Enables exact computation**: No probabilistic failures
### What's Missing
```rust
// ❌ We don't have:
pub struct TreePacking {
forests: Vec<SpanningForest>,
num_forests: usize, // O(λmax log m / ε²)
}
pub struct EdgeColoring {
red_blue: HashMap<EdgeId, Color>, // For tree/non-tree edges
green_yellow: HashMap<EdgeId, Color>, // For size bounds
}
impl TreePacking {
// Greedy forest packing algorithm
fn greedy_pack(&mut self, graph: &Graph, k: usize) -> Vec<SpanningForest>;
// Check if cut respects a tree
fn respects_tree(&self, cut: &Cut, tree: &SpanningTree) -> bool;
// Update packing after graph change
fn update_packing(&mut self, edge_change: EdgeChange) -> Result<()>;
}
pub struct LocalKCut {
tree_packing: TreePacking,
colorings: Vec<EdgeColoring>,
}
impl LocalKCut {
// Deterministic local minimum cut finder
fn find_local_cuts(&self, graph: &Graph, k: usize) -> Vec<Cut>;
// Enumerate all coloring combinations
fn enumerate_colorings(&self) -> Vec<(EdgeColoring, EdgeColoring)>;
// BFS with color constraints
fn color_constrained_bfs(
&self,
start: VertexId,
tree: &SpanningTree,
coloring: &EdgeColoring,
) -> HashSet<VertexId>;
}
```
### Implementation Complexity
- **Difficulty**: 🔴 Very High (novel algorithm)
- **Time Estimate**: 6-8 weeks
- **Prerequisites**:
- Graph coloring algorithms
- Greedy forest packing (Nash-Williams decomposition)
- Constrained BFS/DFS variants
- Combinatorial enumeration
- **Key Challenge**: Maintaining O(λmax log m / ε²) forests dynamically
---
## ✅ Component #3: Witness Trees and Cut Discovery (IMPLEMENTED)
### What It Is
**From the Paper**:
> "The algorithm maintains O(λmax log m / ε²) forests dynamically; each cut either respects some tree or can be detected through color-constrained BFS across all forest-coloring-pair combinations."
**Witness Tree Property** (Theorem 4.3):
- For any β-approximate mincut
- And any (1+ε)-approximate tree packing
- There exists a tree T in the packing that ⌊2(1+ε)β⌋-**respects** the cut
A tree **respects** a cut if removing the cut from the tree leaves components that align with the cut partition.
### Why It's Critical
- **Completeness**: Guarantees we find the minimum cut (not just an approximation)
- **Efficiency**: Reduces search space from 2^n partitions to O(λmax log m) trees
- **Deterministic**: No need for random sampling
- **Dynamic maintenance**: Trees can be updated incrementally
### ✅ Implementation Status
**Location**: `src/witness/mod.rs`
**Lines of Code**: 910+
**Test Coverage**: 20 tests passing
**Implemented Features**:
```rust
// ✅ We now have:
pub struct WitnessTree {
tree_edges: Vec<(usize, usize)>,
graph: Graph,
tree_id: usize,
}
pub struct WitnessForest {
trees: Vec<WitnessTree>,
graph: Graph,
num_trees: usize,
}
pub struct CutWitness {
cut_vertices: HashSet<usize>,
witness_tree_ids: Vec<usize>,
respect_degree: usize,
}
impl WitnessTree {
pub fn new(graph: Graph, tree_id: usize) -> Self
pub fn build_spanning_tree(&mut self) -> Result<(), String>
pub fn respects_cut(&self, cut: &HashSet<usize>, beta: usize) -> bool
pub fn find_respected_cuts(&self, max_cut_size: usize) -> Vec<HashSet<usize>>
pub fn update_tree(&mut self, edge: (usize, usize), insert: bool) -> Result<(), String>
pub fn verify_tree(&self) -> Result<(), String>
}
impl WitnessForest {
pub fn new(graph: Graph, num_trees: usize) -> Self
pub fn build_all_trees(&mut self) -> Result<(), String>
pub fn find_witnesses_for_cut(&self, cut: &HashSet<usize>) -> Vec<usize>
pub fn discover_all_cuts(&self, max_cut_size: usize) -> Vec<CutWitness>
pub fn update_forests(&mut self, edge: (usize, usize), insert: bool) -> Result<(), String>
}
```
**Test Coverage**:
- ✅ Spanning tree construction
- ✅ Cut-tree respect checking
- ✅ Multiple witness discovery
- ✅ Dynamic tree updates
- ✅ Forest-level coordination
- ✅ Witness verification
- ✅ Integration with expander decomposition
- ✅ Edge case handling (disconnected graphs, single-edge cuts)
### Implementation Details
- **Respect Algorithm**: Removes cut edges from tree, verifies component alignment
- **Witness Discovery**: Enumerates all possible cuts up to size limit, finds witnesses
- **Dynamic Updates**: Incremental tree maintenance on edge insertion/deletion
- **Multi-Tree Support**: Maintains multiple witness trees for coverage guarantee
- **Integration**: Works with expander decomposition for hierarchical cut discovery
---
## Missing Component #4: Level-Based Hierarchical Cluster Structure
### What It Is
**From the Paper**:
> "The hierarchy combines three compositions: the dynamic expander decomposition (recourse ρ), a pre-cluster decomposition cutting arbitrary (1-δ)-boundary-sparse cuts (recourse O(1/δ)), and a fragmenting algorithm for boundary-small clusters (recourse Õ(λmax/δ²))."
A **multi-level hierarchy** where:
- **Level 0**: Original graph
- **Level i**: More refined clustering, smaller clusters
- **Total levels**: O(log n^(1/4)) = O(log^(1/4) n)
- **Per-level recourse**: Õ(ρλmax/δ³) = 2^(O(log^(3/4-c) n))
- **Aggregate recourse**: n^{o(1)} across all levels
Each level maintains:
1. **Expander decomposition** with parameter φ
2. **Pre-cluster decomposition** for boundary-sparse cuts
3. **Fragmenting** for high-boundary clusters
### Why It's Critical
- **Achieves subpolynomial time**: O(log n^(1/4)) levels × 2^(O(log^(3/4-c) n)) recourse = n^{o(1)}
- **Progressive refinement**: Each level handles finer-grained cuts
- **Bounded work**: Limits the amount of recomputation per update
- **Composition**: Combines multiple decomposition techniques
### What's Missing
```rust
// ❌ We don't have:
pub struct ClusterLevel {
level: usize,
clusters: Vec<Cluster>,
expander_decomp: ExpanderDecomposition,
pre_cluster_decomp: PreClusterDecomposition,
fragmenting: FragmentingAlgorithm,
recourse_bound: f64, // 2^(O(log^(3/4-c) n))
}
pub struct ClusterHierarchy {
levels: Vec<ClusterLevel>,
num_levels: usize, // O(log^(1/4) n)
delta: f64, // Boundary sparsity parameter
}
impl ClusterHierarchy {
// Build complete hierarchy
fn build_hierarchy(&mut self, graph: &Graph) -> Result<()>;
// Update all affected levels after edge change
fn update_levels(&mut self, edge_change: EdgeChange) -> Result<UpdateStats>;
// Progressive refinement from coarse to fine
fn refine_level(&mut self, level: usize) -> Result<()>;
// Compute aggregate recourse across levels
fn total_recourse(&self) -> f64;
}
pub struct PreClusterDecomposition {
// Cuts arbitrary (1-δ)-boundary-sparse cuts
delta: f64,
cuts: Vec<Cut>,
}
impl PreClusterDecomposition {
// Find (1-δ)-boundary-sparse cuts
fn find_boundary_sparse_cuts(&self, cluster: &Cluster, delta: f64) -> Vec<Cut>;
// Check if cut is boundary-sparse
fn is_boundary_sparse(&self, cut: &Cut, delta: f64) -> bool;
}
```
### Implementation Complexity
- **Difficulty**: 🔴 Very High (most complex component)
- **Time Estimate**: 8-10 weeks
- **Prerequisites**:
- Expander decomposition (Component #1)
- Tree packing (Component #2)
- Fragmenting algorithm (Component #5)
- Understanding of recourse analysis
- **Key Challenge**: Coordinating updates across O(log n^(1/4)) levels efficiently
---
## Missing Component #5: Cut-Preserving Fragmenting Algorithm
### What It Is
**From the Paper**:
> "The fragmenting subroutine (Theorem 5.1) carefully orders (1-δ)-boundary-sparse cuts in clusters with ∂C ≤ 6λmax. Rather than arbitrary cutting, it executes LocalKCut queries from every boundary-incident vertex, then applies iterative trimming that 'removes cuts not (1-δ)-boundary sparse' and recursively fragments crossed clusters."
**Fragmenting** is a sophisticated cluster decomposition that:
1. Takes clusters with small boundary (∂C ≤ 6λmax)
2. Finds all (1-δ)-boundary-sparse cuts
3. Orders and applies cuts carefully
4. Trims non-sparse cuts iteratively
5. Recursively fragments until reaching base case
**Output bound**: Õ(∂C/δ²) inter-cluster edges
### Why It's Critical
- **Improved approximation**: Enables (1 + 2^(-O(log^{3/4-c} n))) approximation ratio
- **Beyond Benczúr-Karger**: More sophisticated than classic cut sparsifiers
- **Controlled decomposition**: Bounds the number of inter-cluster edges
- **Recursive structure**: Essential for hierarchical decomposition
### What's Missing
```rust
// ❌ We don't have:
pub struct FragmentingAlgorithm {
delta: f64, // Boundary sparsity parameter
lambda_max: usize, // Maximum cut size
}
pub struct BoundarySparsenessCut {
cut: Cut,
boundary_ratio: f64, // |∂S| / |S|
is_sparse: bool, // (1-δ)-boundary-sparse
}
impl FragmentingAlgorithm {
// Main fragmenting procedure (Theorem 5.1)
fn fragment_cluster(
&self,
cluster: &Cluster,
delta: f64,
) -> Result<Vec<Cluster>>;
// Find (1-δ)-boundary-sparse cuts
fn find_sparse_cuts(
&self,
cluster: &Cluster,
) -> Vec<BoundarySparsenessCut>;
// Execute LocalKCut from boundary vertices
fn local_kcut_from_boundary(
&self,
cluster: &Cluster,
boundary_vertices: &[VertexId],
) -> Vec<Cut>;
// Iterative trimming: remove non-sparse cuts
fn iterative_trimming(
&self,
cuts: Vec<BoundarySparsenessCut>,
) -> Vec<BoundarySparsenessCut>;
// Order cuts for application
fn order_cuts(&self, cuts: &[BoundarySparsenessCut]) -> Vec<usize>;
// Recursively fragment crossed clusters
fn recursive_fragment(&self, clusters: Vec<Cluster>) -> Result<Vec<Cluster>>;
// Verify output bound: Õ(∂C/δ²) inter-cluster edges
fn verify_output_bound(&self, fragments: &[Cluster]) -> bool;
}
pub struct BoundaryAnalysis {
// Compute cluster boundary
fn boundary_size(cluster: &Cluster, graph: &Graph) -> usize;
// Check if cut is (1-δ)-boundary-sparse
fn is_boundary_sparse(cut: &Cut, delta: f64) -> bool;
// Compute boundary ratio
fn boundary_ratio(vertex_set: &HashSet<VertexId>, graph: &Graph) -> f64;
}
```
### Implementation Complexity
- **Difficulty**: 🔴 Very High (novel algorithm)
- **Time Estimate**: 4-6 weeks
- **Prerequisites**:
- LocalKCut implementation (Component #2)
- Boundary sparseness analysis
- Recursive cluster decomposition
- **Key Challenge**: Implementing iterative trimming correctly
---
## Additional Missing Components
### 6. Benczúr-Karger Cut Sparsifiers (Enhanced)
**What it is**: The paper uses cut-preserving sparsifiers beyond basic Benczúr-Karger to reduce graph size while preserving all cuts up to (1+ε) factor.
**Current status**: ❌ Not implemented
**Needed**:
```rust
pub struct CutSparsifier {
original_graph: Graph,
sparse_graph: Graph,
epsilon: f64, // Approximation factor
}
impl CutSparsifier {
// Sample edges with probability proportional to strength
fn sparsify(&self, graph: &Graph, epsilon: f64) -> Graph;
// Verify: (1-ε)|cut_G(S)| ≤ |cut_H(S)| ≤ (1+ε)|cut_G(S)|
fn verify_approximation(&self, cut: &Cut) -> bool;
// Update sparsifier after graph change
fn update_sparsifier(&mut self, edge_change: EdgeChange) -> Result<()>;
}
```
**Complexity**: 🟡 High - 2-3 weeks
---
### 7. Advanced Recourse Analysis
**What it is**: Track and bound the total work done across all levels and updates.
**Current status**: ❌ Not tracked
**Needed**:
```rust
pub struct RecourseTracker {
per_level_recourse: Vec<f64>,
aggregate_recourse: f64,
theoretical_bound: f64, // 2^(O(log^{1-c} n))
}
impl RecourseTracker {
// Compute recourse for a single update
fn compute_update_recourse(&self, update: &Update) -> f64;
// Verify subpolynomial bound
fn verify_subpolynomial(&self, n: usize) -> bool;
// Get amortized recourse
fn amortized_recourse(&self) -> f64;
}
```
**Complexity**: 🟢 Medium - 1 week
---
### 8. Conductance and Expansion Computation
**What it is**: Efficiently compute φ-expansion and conductance for clusters.
**Current status**: ❌ Not implemented
**Needed**:
```rust
pub struct ConductanceCalculator {
// φ(S) = |∂S| / min(vol(S), vol(V \ S))
fn conductance(&self, vertex_set: &HashSet<VertexId>, graph: &Graph) -> f64;
// Check if subgraph is a φ-expander
fn is_expander(&self, subgraph: &Graph, phi: f64) -> bool;
// Compute expansion parameter
fn expansion_parameter(&self, n: usize) -> f64; // 2^(-Θ(log^(3/4) n))
}
```
**Complexity**: 🟡 High - 2 weeks
---
## Implementation Priority Order
Based on **dependency analysis** and **complexity**:
### ✅ Phase 1: Foundations (COMPLETED - 12-14 weeks)
1.**Conductance and Expansion Computation** (2 weeks) 🟡
- ✅ COMPLETED: Integrated into expander decomposition
- ✅ Conductance formula implemented
- ✅ φ-expander detection working
2. ⚠️ **Enhanced Cut Sparsifiers** (3 weeks) 🟡
- ⚠️ OPTIONAL: Not strictly required for base algorithm
- Can be added for performance optimization
3.**Expander Decomposition** (6 weeks) 🔴
- ✅ COMPLETED: 800+ lines, 19 tests
- ✅ Dynamic updates working
- ✅ Multi-cluster support
4. ⚠️ **Recourse Analysis Framework** (1 week) 🟢
- ⚠️ OPTIONAL: Can be added for verification
- Not blocking other components
### 🔄 Phase 2: Deterministic Derandomization (IN PROGRESS - 10-12 weeks)
5.**Tree Packing Algorithms** (4 weeks) 🔴
- **NEXT PRIORITY**
- Required for deterministic LocalKCut
- Greedy forest packing
- Nash-Williams decomposition
- Dynamic maintenance
6.**Edge Coloring System** (2 weeks) 🟡
- **NEXT PRIORITY**
- Depends on tree packing
- Red-blue and green-yellow colorings
- Combinatorial enumeration
7.**Deterministic LocalKCut** (6 weeks) 🔴
- **CRITICAL PATH**
- Combines tree packing + colorings
- Color-constrained BFS
- Most algorithmically complex
### ✅ Phase 3: Witness Trees (COMPLETED - 4 weeks)
8.**Witness Tree Mechanism** (4 weeks) 🟡
- ✅ COMPLETED: 910+ lines, 20 tests
- ✅ Cut-tree respect checking working
- ✅ Witness discovery implemented
- ✅ Dynamic updates functional
- ✅ Integration with expander decomposition
### 🔄 Phase 4: Hierarchical Structure (PENDING - 14-16 weeks)
9.**Fragmenting Algorithm** (5 weeks) 🔴
- **BLOCKED**: Needs LocalKCut
- Boundary sparseness analysis
- Iterative trimming
- Recursive fragmentation
10.**Pre-cluster Decomposition** (3 weeks) 🟡
- **BLOCKED**: Needs fragmenting
- Find boundary-sparse cuts
- Integration with expander decomp
11.**Multi-Level Cluster Hierarchy** (8 weeks) 🔴
- **FINAL INTEGRATION**
- Integrates all previous components
- O(log n^(1/4)) levels
- Cross-level coordination
### Phase 5: Integration & Optimization (4-6 weeks)
12. **Full Algorithm Integration** (3 weeks) 🔴
- Connect all components
- End-to-end testing
- Complexity verification
13. **Performance Optimization** (2 weeks) 🟡
- Constant factor improvements
- Parallelization
- Caching strategies
14. **Comprehensive Testing** (1 week) 🟢
- Correctness verification
- Complexity benchmarking
- Comparison with theory
---
## Total Implementation Estimate
**Original Estimate (Solo Developer)**:
- ~~Phase 1: 14 weeks~~ ✅ **COMPLETED**
- ~~Phase 3: 4 weeks~~ ✅ **COMPLETED**
- **Phase 2**: 12 weeks (IN PROGRESS)
- **Phase 4**: 16 weeks (PENDING)
- **Phase 5**: 6 weeks (PENDING)
- **Remaining**: **34 weeks (~8 months)**
- **Progress**: **18 weeks completed (35%)** 🎯
**Updated Estimate (Solo Developer)**:
-**Completed**: 18 weeks (Phases 1 & 3)
- 🔄 **In Progress**: Phase 2 - Tree Packing & LocalKCut (12 weeks)
-**Remaining**: Phases 4 & 5 (22 weeks)
- **Total Remaining**: **~34 weeks (~8 months)** ⏰
**Aggressive (Experienced Team of 3)**:
-**Completed**: ~8 weeks equivalent (with parallelization)
- **Remaining**: **12-16 weeks (3-4 months)**
- **Progress**: **40% complete** 🎯
---
## Complexity Analysis: Current vs. Target
### Current Implementation (With Expander + Witness Trees)
```
Build: O(n log n + m) ✓ Same as before
Update: O(m) ⚠️ Still naive (but infrastructure ready)
Query: O(1) ✓ Constant time
Space: O(n + m) ✓ Linear space
Approximation: Exact ✓ Exact cuts
Deterministic: Yes ✓ Fully deterministic
Cut Size: Arbitrary ⚠️ Can enforce with LocalKCut
NEW CAPABILITIES:
Expander Decomp: ✅ φ-expander partitioning
Witness Trees: ✅ Cut-tree respect checking
Conductance: ✅ O(m) computation per cluster
```
### Target (December 2024 Paper)
```
Build: Õ(m) ✓ Comparable
Update: n^{o(1)} ⚠️ Infrastructure ready, need LocalKCut + Hierarchy
= 2^(O(log^{1-c} n))
Query: O(1) ✓ Already have
Space: Õ(m) ✓ Comparable
Approximation: Exact ✅ Witness trees provide exact guarantee
Deterministic: Yes ✅ Witness trees enable determinism
Cut Size: ≤ 2^{Θ(log^{3/4-c} n)} ⚠️ Need LocalKCut to enforce
```
### Performance Gap Analysis
For **n = 1,000,000** vertices:
| Operation | Current | With Full Algorithm | Gap | Status |
|-----------|---------|-------------------|-----|--------|
| Build | O(m) | Õ(m) | ~1x | ✅ Ready |
| Update (m = 5M) | **5,000,000** ops | **~1,000** ops | **5000x slower** | ⚠️ Need Phase 2-4 |
| Update (m = 1M) | **1,000,000** ops | **~1,000** ops | **1000x slower** | ⚠️ Need Phase 2-4 |
| Cut Discovery | O(2^n) enumeration | O(k) witness trees | **Exponential improvement** | ✅ Implemented |
| Expander Clusters | N/A | O(n/φ) clusters | **New capability** | ✅ Implemented |
| Cut Verification | O(m) per cut | O(log n) per tree | **Logarithmic improvement** | ✅ Implemented |
The **n^{o(1)}** term for n = 1M is approximately:
- 2^(log^{0.75} 1000000) ≈ 2^(10) ≈ **1024**
**Progress Impact**:
-**Expander Decomposition**: Enables hierarchical structure (foundation for n^{o(1)})
-**Witness Trees**: Reduces cut search from exponential to polynomial
- ⚠️ **Update Complexity**: Still O(m) until LocalKCut + Hierarchy implemented
- 🎯 **Next Milestone**: Tree Packing → brings us to O(√n) or better
---
## Recommended Implementation Path
### Option A: Full Research Implementation (1 year)
**Goal**: Implement the complete December 2024 algorithm
**Pros**:
- ✅ Achieves true n^{o(1)} complexity
- ✅ State-of-the-art performance
- ✅ Research contribution
- ✅ Publications potential
**Cons**:
- ❌ 12 months of development
- ❌ High complexity and risk
- ❌ May not work well in practice (large constants)
- ❌ Limited reference implementations
**Recommendation**: Only pursue if:
1. This is a research project with publication goals
2. Have 6-12 months available
3. Team has graph algorithms expertise
4. Access to authors for clarifications
---
### Option B: Incremental Enhancement (3-6 months)
**Goal**: Implement key subcomponents that provide value independently
**Phase 1 (Month 1-2)**:
1. ✅ Conductance computation
2. ✅ Basic expander detection
3. ✅ Benczúr-Karger sparsifiers
4. ✅ Tree packing (non-dynamic)
**Phase 2 (Month 3-4)**:
1. ✅ Simple expander decomposition (static)
2. ✅ LocalKCut (randomized version first)
3. ✅ Improve from O(m) to O(√n) using Thorup's ideas
**Phase 3 (Month 5-6)**:
1. ⚠️ Partial hierarchy (2-3 levels instead of log n^(1/4))
2. ⚠️ Simplified witness trees
**Pros**:
- ✅ Incremental value at each phase
- ✅ Each component useful independently
- ✅ Lower risk
- ✅ Can stop at any phase with a working system
**Cons**:
- ❌ Won't achieve full n^{o(1)} complexity
- ❌ May get O(√n) or O(n^{0.6}) instead
**Recommendation**: **Preferred path** for most projects
---
### Option C: Hybrid Approach (6-9 months)
**Goal**: Implement algorithm for restricted case (small cuts only)
Focus on cuts of size **≤ (log n)^{o(1)}** (Jin-Sun-Thorup SODA 2024 result):
- Simpler than full algorithm
- Still achieves n^{o(1)} for practical cases
- Most real-world minimum cuts are small
**Pros**:
- ✅ Achieves n^{o(1)} for important special case
- ✅ More manageable scope
- ✅ Still a significant improvement
- ✅ Can extend to full algorithm later
**Cons**:
- ⚠️ Cut size restriction
- ⚠️ Still 6-9 months of work
**Recommendation**: Good compromise for research projects with time constraints
---
## Key Takeaways
### Critical Gaps
1. **No Expander Decomposition** - The entire algorithm foundation is missing
2. **No Deterministic Derandomization** - We're 100% missing the core innovation
3. **No Tree Packing** - Essential for witness trees and deterministic guarantees
4. **No Hierarchical Clustering** - Can't achieve subpolynomial recourse
5. **No Fragmenting Algorithm** - Can't get the improved approximation ratio
### Complexity Gap
- **Current**: O(m) per update ≈ **1,000,000+ operations** for large graphs
- **Target**: n^{o(1)} ≈ **1,000 operations** for n = 1M
- **Gap**: **1000-5000x performance difference**
### Implementation Effort
- **Full algorithm**: 52 weeks (1 year) solo, 24 weeks team
- **Incremental path**: 12-24 weeks for significant improvement
- **Each major component**: 4-8 weeks of focused development
### Risk Assessment
| Component | Difficulty | Risk | Time |
|-----------|-----------|------|------|
| Expander Decomposition | 🔴 Very High | High (research-level) | 6 weeks |
| Tree Packing + LocalKCut | 🔴 Very High | High (novel algorithm) | 8 weeks |
| Witness Trees | 🟡 High | Medium (well-defined) | 4 weeks |
| Cluster Hierarchy | 🔴 Very High | Very High (most complex) | 10 weeks |
| Fragmenting Algorithm | 🔴 Very High | High (novel) | 6 weeks |
---
## Conclusion
Our implementation has made **significant progress** toward the December 2024 paper's subpolynomial time complexity. We have completed **2 of 5 major components (40%)**:
### ✅ Completed Components
1.**Expander decomposition framework** (800+ lines, 19 tests)
- φ-expander detection and partitioning
- Conductance computation
- Dynamic cluster maintenance
2.**Witness tree mechanism** (910+ lines, 20 tests)
- Cut-tree respect checking
- Witness discovery and tracking
- Multi-tree forest support
### ❌ Remaining Components
3. ❌ Deterministic tree packing with edge colorings
4. ❌ Multi-level cluster hierarchy (O(log n^(1/4)) levels)
5. ❌ Fragmenting algorithm for boundary-sparse cuts
**Remaining work represents approximately 8 months** (~34 weeks) for a skilled graph algorithms researcher.
### Recommended Next Steps
**Immediate Next Priority** (12 weeks - Phase 2):
1. ✅ Foundation in place (expander decomp + witness trees)
2. 🎯 **Implement Tree Packing** (4 weeks)
- Greedy forest packing algorithm
- Nash-Williams decomposition
- Dynamic forest maintenance
3. 🎯 **Add Edge Coloring System** (2 weeks)
- Red-blue coloring for tree/non-tree edges
- Green-yellow coloring for size bounds
4. 🎯 **Build Deterministic LocalKCut** (6 weeks)
- Color-constrained BFS
- Integrate tree packing + colorings
- Replace randomized version
**Medium-term Goals** (16 weeks - Phase 4):
1. Implement fragmenting algorithm (5 weeks)
2. Build pre-cluster decomposition (3 weeks)
3. Create multi-level cluster hierarchy (8 weeks)
**For production use**:
1. ✅ Current expander decomposition can be used for graph partitioning
2. ✅ Witness trees enable efficient cut discovery
3. ⚠️ Update complexity still O(m) until full hierarchy implemented
4. 🎯 Next milestone (tree packing) will unlock O(√n) or better performance
### Progress Summary
**Time Investment**:
-**18 weeks completed** (35% of total)
- 🔄 **12 weeks in progress** (Phase 2 - Tree Packing)
-**22 weeks remaining** (Phases 4-5)
**Capability Gains**:
-**Foundation complete**: Expander + Witness infrastructure ready
-**Cut discovery**: Exponential → polynomial improvement
- ⚠️ **Update complexity**: Still O(m), needs Phase 2-4 for n^{o(1)}
- 🎯 **Next unlock**: Tree packing enables O(√n) or better
---
**Document Version**: 2.0
**Last Updated**: December 21, 2025
**Next Review**: After Phase 2 completion (Tree Packing + LocalKCut)
**Progress**: 2/5 major components complete (40%)
## Sources
- [Deterministic and Exact Fully-dynamic Minimum Cut (Dec 2024)](https://arxiv.org/html/2512.13105v1)
- [Fully Dynamic Approximate Minimum Cut in Subpolynomial Time per Operation (SODA 2025)](https://arxiv.org/html/2412.15069)
- [Fully Dynamic Approximate Minimum Cut (SODA 2025 Proceedings)](https://epubs.siam.org/doi/10.1137/1.9781611978322.22)
- [The Expander Hierarchy and its Applications (SODA 2021)](https://epubs.siam.org/doi/abs/10.1137/1.9781611976465.132)
- [Practical Expander Decomposition (ESA 2024)](https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.ESA.2024.61)
- [Length-Constrained Expander Decomposition (ESA 2025)](https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.ESA.2025.107)
- [Deterministic Near-Linear Time Minimum Cut in Weighted Graphs](https://arxiv.org/html/2401.05627)
- [Deterministic Minimum Steiner Cut in Maximum Flow Time](https://arxiv.org/html/2312.16415v2)

File diff suppressed because it is too large Load Diff