Files
wifi-densepose/docs/plans/subpolynomial-time-mincut/02-pseudocode.md
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

631 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.