git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
684 lines
27 KiB
Markdown
684 lines
27 KiB
Markdown
# ADR-002: Dynamic Hierarchical j-Tree Decomposition for Approximate Cut Structure
|
||
|
||
**Status**: Proposed
|
||
**Date**: 2026-01-25
|
||
**Authors**: ruv.io, RuVector Team
|
||
**Deciders**: Architecture Review Board
|
||
**SDK**: Claude-Flow
|
||
|
||
## Version History
|
||
|
||
| Version | Date | Author | Changes |
|
||
|---------|------|--------|---------|
|
||
| 0.1 | 2026-01-25 | ruv.io | Initial draft based on arXiv:2601.09139 research |
|
||
|
||
---
|
||
|
||
## Plain Language Summary
|
||
|
||
**What is it?**
|
||
|
||
A new algorithmic framework for maintaining an approximate view of a graph's cut structure that updates in near-constant time even as edges are added and removed. It complements our existing exact min-cut implementation by providing a fast "global radar" that can answer approximate cut queries instantly.
|
||
|
||
**Why does it matter?**
|
||
|
||
Our current implementation (arXiv:2512.13105, El-Hayek/Henzinger/Li) excels at **exact** min-cut for superpolylogarithmic cuts but is optimized for a specific cut-size regime. The new j-tree decomposition (arXiv:2601.09139, Goranci/Henzinger/Kiss/Momeni/Zöcklein, January 2026) provides:
|
||
|
||
- **Broader coverage**: Poly-logarithmic approximation for ALL cut-based problems (sparsest cut, multi-way cut, multi-cut, all-pairs min-cuts)
|
||
- **Faster updates**: O(n^ε) amortized for any arbitrarily small ε > 0
|
||
- **Low recourse**: The underlying cut-sparsifier tolerates vertex splits with poly-logarithmic recourse
|
||
|
||
**The Two-Tier Strategy**:
|
||
|
||
| Tier | Algorithm | Purpose | When to Use |
|
||
|------|-----------|---------|-------------|
|
||
| **Tier 1** | j-Tree Decomposition | Fast approximate hierarchy for global structure | Continuous monitoring, routing decisions |
|
||
| **Tier 2** | El-Hayek/Henzinger/Li | Exact deterministic min-cut | When Tier 1 detects critical cuts |
|
||
|
||
Think of it like sonar and radar: the j-tree is your wide-area radar that shows approximate threat positions instantly, while the exact algorithm is your precision sonar that confirms exact details when needed.
|
||
|
||
---
|
||
|
||
## Context
|
||
|
||
### Current State
|
||
|
||
RuVector MinCut implements the December 2025 breakthrough (arXiv:2512.13105) achieving:
|
||
|
||
| Property | Current Implementation |
|
||
|----------|----------------------|
|
||
| **Update Time** | O(n^{o(1)}) amortized |
|
||
| **Approximation** | Exact |
|
||
| **Deterministic** | Yes |
|
||
| **Cut Regime** | Superpolylogarithmic (λ > log^c n) |
|
||
| **Verified Scaling** | n^0.12 empirically |
|
||
|
||
This works excellently for the coherence gate (ADR-001) where we need exact cut values for safety decisions. However, several use cases require:
|
||
|
||
1. **Broader cut-based queries**: Sparsest cut, multi-way cut, multi-cut, all-pairs min-cuts
|
||
2. **Even faster updates**: When monitoring 10K+ updates/second
|
||
3. **Global structure awareness**: Understanding the overall cut landscape, not just the minimum
|
||
|
||
### The January 2026 Breakthrough
|
||
|
||
The paper "Dynamic Hierarchical j-Tree Decomposition and Its Applications" (arXiv:2601.09139, SODA 2026) by Goranci, Henzinger, Kiss, Momeni, and Zöcklein addresses the open question:
|
||
|
||
> "Is there a fully dynamic algorithm for cut-based optimization problems that achieves poly-logarithmic approximation with very small polynomial update time?"
|
||
|
||
**Key Results**:
|
||
|
||
| Result | Complexity | Significance |
|
||
|--------|------------|--------------|
|
||
| **Update Time** | O(n^ε) amortized for any ε ∈ (0,1) | Arbitrarily close to polylog |
|
||
| **Approximation** | Poly-logarithmic | Sufficient for structure detection |
|
||
| **Query Support** | All cut-based problems | Not just min-cut |
|
||
| **Recourse** | Poly-logarithmic total | Sparsifier doesn't explode |
|
||
|
||
### Technical Innovation: Vertex-Split-Tolerant Cut Sparsifier
|
||
|
||
The core innovation is a **dynamic cut-sparsifier** that handles vertex splits with low recourse:
|
||
|
||
```
|
||
Traditional approach: Vertex splits cause O(n) cascading updates
|
||
New approach: Forest packing with lazy repair → poly-log recourse
|
||
```
|
||
|
||
The sparsifier maintains (1±ε) approximation of all cuts while:
|
||
- Tolerating vertex splits (critical for dynamic hierarchies)
|
||
- Adjusting only poly-logarithmically many edges per update
|
||
- Serving as a backbone for the j-tree hierarchy
|
||
|
||
### The (L,j) Hierarchy
|
||
|
||
The j-tree hierarchy reflects increasingly coarse views of the graph's cut landscape:
|
||
|
||
```
|
||
Level 0: Original graph G
|
||
Level 1: Contracted graph with j-tree quality α
|
||
Level 2: Further contracted with quality α²
|
||
...
|
||
Level L: Root (O(1) vertices)
|
||
|
||
L = O(log n / log α)
|
||
```
|
||
|
||
Each level preserves cut structure within an α^ℓ factor, enabling:
|
||
- **Fast approximate queries**: Traverse O(log n) levels
|
||
- **Local updates**: Changes propagate through O(log n) levels
|
||
- **Multi-scale view**: See both fine and coarse structure
|
||
|
||
---
|
||
|
||
## Decision
|
||
|
||
### Adopt Two-Tier Dynamic Cut Architecture
|
||
|
||
We will implement the j-tree decomposition as a complementary layer to our existing exact min-cut, creating a two-tier system:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────┐
|
||
│ TWO-TIER DYNAMIC CUT ARCHITECTURE │
|
||
├─────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ TIER 1: J-TREE HIERARCHY (NEW) │ │
|
||
│ │ │ │
|
||
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
|
||
│ │ │ Level L │ │ Level L-1 │ │ Level 0 │ │ │
|
||
│ │ │ (Root) │◄───│ (Coarse) │◄───│ (Original) │ │ │
|
||
│ │ │ O(1) vtx │ │ α^(L-1) cut │ │ Exact cuts │ │ │
|
||
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ Purpose: Fast approximate answers for global structure │ │
|
||
│ │ Update: O(n^ε) amortized for any ε > 0 │ │
|
||
│ │ Query: Poly-log approximation for all cut problems │ │
|
||
│ │ │ │
|
||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ Trigger: Approximate cut below threshold │
|
||
│ ▼ │
|
||
│ ┌────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ TIER 2: EXACT MIN-CUT (EXISTING) │ │
|
||
│ │ │ │
|
||
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
|
||
│ │ │ SubpolynomialMinCut (arXiv:2512.13105) │ │ │
|
||
│ │ │ • O(n^{o(1)}) amortized exact updates │ │ │
|
||
│ │ │ • Verified n^0.12 scaling │ │ │
|
||
│ │ │ • Deterministic, no randomization │ │ │
|
||
│ │ │ • For superpolylogarithmic cuts (λ > log^c n) │ │ │
|
||
│ │ └──────────────────────────────────────────────────────────────┘ │ │
|
||
│ │ │ │
|
||
│ │ Purpose: Exact verification when precision required │ │
|
||
│ │ Trigger: Tier 1 detects potential critical cut │ │
|
||
│ │ │ │
|
||
│ └────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
└─────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### Module Structure
|
||
|
||
```
|
||
ruvector-mincut/
|
||
├── src/
|
||
│ ├── jtree/ # NEW: j-Tree Decomposition
|
||
│ │ ├── mod.rs # Module exports
|
||
│ │ ├── hierarchy.rs # (L,j) hierarchical decomposition
|
||
│ │ ├── sparsifier.rs # Vertex-split-tolerant cut sparsifier
|
||
│ │ ├── forest_packing.rs # Forest packing for sparsification
|
||
│ │ ├── vertex_split.rs # Vertex split handling with low recourse
|
||
│ │ ├── contraction.rs # Graph contraction for hierarchy levels
|
||
│ │ └── queries/ # Cut-based query implementations
|
||
│ │ ├── mod.rs
|
||
│ │ ├── all_pairs_mincut.rs
|
||
│ │ ├── sparsest_cut.rs
|
||
│ │ ├── multiway_cut.rs
|
||
│ │ └── multicut.rs
|
||
│ ├── tiered/ # NEW: Two-tier coordination
|
||
│ │ ├── mod.rs
|
||
│ │ ├── coordinator.rs # Tier 1/Tier 2 routing logic
|
||
│ │ ├── trigger.rs # Escalation trigger policies
|
||
│ │ └── cache.rs # Cross-tier result caching
|
||
│ └── ...existing modules...
|
||
```
|
||
|
||
### Core Data Structures
|
||
|
||
#### j-Tree Hierarchy
|
||
|
||
```rust
|
||
/// Hierarchical j-tree decomposition for approximate cut structure
|
||
pub struct JTreeHierarchy {
|
||
/// Number of levels (L = O(log n / log α))
|
||
levels: usize,
|
||
/// Approximation quality per level
|
||
alpha: f64,
|
||
/// Contracted graphs at each level
|
||
contracted_graphs: Vec<ContractedGraph>,
|
||
/// Cut sparsifier backbone
|
||
sparsifier: DynamicCutSparsifier,
|
||
/// j-trees at each level
|
||
jtrees: Vec<JTree>,
|
||
}
|
||
|
||
/// Single level j-tree
|
||
pub struct JTree {
|
||
/// Tree structure
|
||
tree: DynamicTree,
|
||
/// Mapping from original vertices to tree nodes
|
||
vertex_map: HashMap<VertexId, TreeNodeId>,
|
||
/// Cached cut values between tree nodes
|
||
cut_cache: CutCache,
|
||
/// Level index
|
||
level: usize,
|
||
}
|
||
|
||
impl JTreeHierarchy {
|
||
/// Build hierarchy from graph
|
||
pub fn build(graph: &DynamicGraph, epsilon: f64) -> Self {
|
||
let alpha = compute_alpha(epsilon);
|
||
let levels = (graph.vertex_count() as f64).log(alpha as f64).ceil() as usize;
|
||
|
||
// Build sparsifier first
|
||
let sparsifier = DynamicCutSparsifier::build(graph, epsilon);
|
||
|
||
// Build contracted graphs level by level
|
||
let mut contracted_graphs = Vec::with_capacity(levels);
|
||
let mut current = sparsifier.sparse_graph();
|
||
|
||
for level in 0..levels {
|
||
contracted_graphs.push(current.clone());
|
||
current = contract_to_jtree(¤t, alpha);
|
||
}
|
||
|
||
Self {
|
||
levels,
|
||
alpha,
|
||
contracted_graphs,
|
||
sparsifier,
|
||
jtrees: build_jtrees(&contracted_graphs),
|
||
}
|
||
}
|
||
|
||
/// Insert edge with O(n^ε) amortized update
|
||
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: f64) -> Result<(), Error> {
|
||
// Update sparsifier (handles vertex splits internally)
|
||
self.sparsifier.insert_edge(u, v, weight)?;
|
||
|
||
// Propagate through hierarchy levels
|
||
for level in 0..self.levels {
|
||
self.update_level(level, EdgeUpdate::Insert(u, v, weight))?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Delete edge with O(n^ε) amortized update
|
||
pub fn delete_edge(&mut self, u: VertexId, v: VertexId) -> Result<(), Error> {
|
||
self.sparsifier.delete_edge(u, v)?;
|
||
|
||
for level in 0..self.levels {
|
||
self.update_level(level, EdgeUpdate::Delete(u, v))?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// Query approximate min-cut (poly-log approximation)
|
||
pub fn approximate_min_cut(&self) -> ApproximateCut {
|
||
// Start from root level and refine
|
||
let mut cut = self.jtrees[self.levels - 1].min_cut();
|
||
|
||
for level in (0..self.levels - 1).rev() {
|
||
cut = self.jtrees[level].refine_cut(&cut);
|
||
}
|
||
|
||
ApproximateCut {
|
||
value: cut.value,
|
||
approximation_factor: self.alpha.powi(self.levels as i32),
|
||
partition: cut.partition,
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
#### Vertex-Split-Tolerant Cut Sparsifier
|
||
|
||
```rust
|
||
/// Dynamic cut sparsifier with low recourse under vertex splits
|
||
pub struct DynamicCutSparsifier {
|
||
/// Forest packing for edge sampling
|
||
forest_packing: ForestPacking,
|
||
/// Sparse graph maintaining (1±ε) cut approximation
|
||
sparse_graph: DynamicGraph,
|
||
/// Epsilon parameter
|
||
epsilon: f64,
|
||
/// Recourse counter for complexity verification
|
||
recourse: RecourseTracker,
|
||
}
|
||
|
||
impl DynamicCutSparsifier {
|
||
/// Handle vertex split with poly-log recourse
|
||
pub fn split_vertex(&mut self, v: VertexId, v1: VertexId, v2: VertexId,
|
||
partition: &[EdgeId]) -> Result<RecourseStats, Error> {
|
||
let before_edges = self.sparse_graph.edge_count();
|
||
|
||
// Forest packing handles the split
|
||
let affected_forests = self.forest_packing.split_vertex(v, v1, v2, partition)?;
|
||
|
||
// Lazy repair: only fix forests that actually need it
|
||
for forest_id in affected_forests {
|
||
self.repair_forest(forest_id)?;
|
||
}
|
||
|
||
let recourse = (self.sparse_graph.edge_count() as i64 - before_edges as i64).abs();
|
||
self.recourse.record(recourse as usize);
|
||
|
||
Ok(self.recourse.stats())
|
||
}
|
||
|
||
/// The key insight: forest packing limits cascading updates
|
||
fn repair_forest(&mut self, forest_id: ForestId) -> Result<(), Error> {
|
||
// Only O(log n) edges need adjustment per forest
|
||
// Total forests = O(log n / ε²)
|
||
// Total recourse = O(log² n / ε²) per vertex split
|
||
self.forest_packing.repair(forest_id, &mut self.sparse_graph)
|
||
}
|
||
}
|
||
```
|
||
|
||
### Two-Tier Coordinator
|
||
|
||
```rust
|
||
/// Coordinates between j-tree approximation (Tier 1) and exact min-cut (Tier 2)
|
||
pub struct TwoTierCoordinator {
|
||
/// Tier 1: Fast approximate hierarchy
|
||
jtree: JTreeHierarchy,
|
||
/// Tier 2: Exact min-cut for verification
|
||
exact: SubpolynomialMinCut,
|
||
/// Trigger policy for escalation
|
||
trigger: EscalationTrigger,
|
||
/// Result cache to avoid redundant computation
|
||
cache: TierCache,
|
||
}
|
||
|
||
/// When to escalate from Tier 1 to Tier 2
|
||
pub struct EscalationTrigger {
|
||
/// Approximate cut threshold below which we verify exactly
|
||
critical_threshold: f64,
|
||
/// Maximum approximation factor before requiring exact
|
||
max_approx_factor: f64,
|
||
/// Whether the query requires exact answer
|
||
exact_required: bool,
|
||
}
|
||
|
||
impl TwoTierCoordinator {
|
||
/// Query min-cut with tiered strategy
|
||
pub fn min_cut(&mut self, exact_required: bool) -> CutResult {
|
||
// Check cache first
|
||
if let Some(cached) = self.cache.get() {
|
||
if !exact_required || cached.is_exact {
|
||
return cached.clone();
|
||
}
|
||
}
|
||
|
||
// Tier 1: Fast approximate query
|
||
let approx = self.jtree.approximate_min_cut();
|
||
|
||
// Decide whether to escalate
|
||
let should_escalate = exact_required
|
||
|| approx.value < self.trigger.critical_threshold
|
||
|| approx.approximation_factor > self.trigger.max_approx_factor;
|
||
|
||
if should_escalate {
|
||
// Tier 2: Exact verification
|
||
let exact_value = self.exact.min_cut_value();
|
||
let exact_partition = self.exact.partition();
|
||
|
||
let result = CutResult {
|
||
value: exact_value,
|
||
partition: exact_partition,
|
||
is_exact: true,
|
||
approximation_factor: 1.0,
|
||
tier_used: Tier::Exact,
|
||
};
|
||
|
||
self.cache.store(result.clone());
|
||
result
|
||
} else {
|
||
let result = CutResult {
|
||
value: approx.value,
|
||
partition: approx.partition,
|
||
is_exact: false,
|
||
approximation_factor: approx.approximation_factor,
|
||
tier_used: Tier::Approximate,
|
||
};
|
||
|
||
self.cache.store(result.clone());
|
||
result
|
||
}
|
||
}
|
||
|
||
/// Insert edge, updating both tiers
|
||
pub fn insert_edge(&mut self, u: VertexId, v: VertexId, weight: f64) -> Result<(), Error> {
|
||
self.cache.invalidate();
|
||
|
||
// Update Tier 1 (fast)
|
||
self.jtree.insert_edge(u, v, weight)?;
|
||
|
||
// Update Tier 2 (also fast, but only if we're tracking that edge regime)
|
||
self.exact.insert_edge(u, v, weight)?;
|
||
|
||
Ok(())
|
||
}
|
||
}
|
||
```
|
||
|
||
### Extended Query Support
|
||
|
||
The j-tree hierarchy enables queries beyond min-cut:
|
||
|
||
```rust
|
||
impl JTreeHierarchy {
|
||
/// All-pairs minimum cuts (approximate)
|
||
pub fn all_pairs_min_cuts(&self) -> AllPairsResult {
|
||
// Use hierarchy to avoid O(n²) explicit computation
|
||
// Query time: O(n log n) for all pairs
|
||
let mut results = HashMap::new();
|
||
|
||
for (u, v) in self.vertex_pairs() {
|
||
let cut = self.min_cut_between(u, v);
|
||
results.insert((u, v), cut);
|
||
}
|
||
|
||
AllPairsResult { cuts: results }
|
||
}
|
||
|
||
/// Sparsest cut (approximate)
|
||
pub fn sparsest_cut(&self) -> SparsestCutResult {
|
||
// Leverage hierarchy for O(n^ε) approximate sparsest cut
|
||
let mut best_sparsity = f64::INFINITY;
|
||
let mut best_cut = None;
|
||
|
||
for level in 0..self.levels {
|
||
let candidate = self.jtrees[level].sparsest_cut_candidate();
|
||
let sparsity = candidate.value / candidate.size.min() as f64;
|
||
|
||
if sparsity < best_sparsity {
|
||
best_sparsity = sparsity;
|
||
best_cut = Some(candidate);
|
||
}
|
||
}
|
||
|
||
SparsestCutResult {
|
||
cut: best_cut.unwrap(),
|
||
sparsity: best_sparsity,
|
||
approximation: self.alpha.powi(self.levels as i32),
|
||
}
|
||
}
|
||
|
||
/// Multi-way cut (approximate)
|
||
pub fn multiway_cut(&self, terminals: &[VertexId]) -> MultiwayCutResult {
|
||
// Use j-tree hierarchy to find approximate multiway cut
|
||
// Approximation: O(log k) where k = number of terminals
|
||
self.compute_multiway_cut(terminals)
|
||
}
|
||
|
||
/// Multi-cut (approximate)
|
||
pub fn multicut(&self, pairs: &[(VertexId, VertexId)]) -> MulticutResult {
|
||
// Approximate multicut using hierarchy
|
||
self.compute_multicut(pairs)
|
||
}
|
||
}
|
||
```
|
||
|
||
### Integration with Coherence Gate (ADR-001)
|
||
|
||
The j-tree hierarchy integrates with the Anytime-Valid Coherence Gate:
|
||
|
||
```rust
|
||
/// Enhanced coherence gate using two-tier cut architecture
|
||
pub struct TieredCoherenceGate {
|
||
/// Two-tier cut coordinator
|
||
cut_coordinator: TwoTierCoordinator,
|
||
/// Conformal prediction component
|
||
conformal: ShiftAdaptiveConformal,
|
||
/// E-process evidence accumulator
|
||
evidence: EProcessAccumulator,
|
||
/// Gate thresholds
|
||
thresholds: GateThresholds,
|
||
}
|
||
|
||
impl TieredCoherenceGate {
|
||
/// Fast structural check using Tier 1
|
||
pub fn fast_structural_check(&self, action: &Action) -> QuickDecision {
|
||
// Use j-tree for O(n^ε) approximate check
|
||
let approx_cut = self.cut_coordinator.jtree.approximate_min_cut();
|
||
|
||
if approx_cut.value > self.thresholds.definitely_safe {
|
||
QuickDecision::Permit
|
||
} else if approx_cut.value < self.thresholds.definitely_unsafe {
|
||
QuickDecision::Deny
|
||
} else {
|
||
QuickDecision::NeedsExactCheck
|
||
}
|
||
}
|
||
|
||
/// Full evaluation with exact verification if needed
|
||
pub fn evaluate(&mut self, action: &Action, context: &Context) -> GateDecision {
|
||
// Quick check first
|
||
let quick = self.fast_structural_check(action);
|
||
|
||
match quick {
|
||
QuickDecision::Permit => {
|
||
// Fast path: structure is definitely safe
|
||
self.issue_permit_fast(action)
|
||
}
|
||
QuickDecision::Deny => {
|
||
// Fast path: structure is definitely unsafe
|
||
self.issue_denial_fast(action)
|
||
}
|
||
QuickDecision::NeedsExactCheck => {
|
||
// Invoke Tier 2 for exact verification
|
||
let exact_cut = self.cut_coordinator.min_cut(true);
|
||
self.evaluate_with_exact_cut(action, context, exact_cut)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### Performance Characteristics
|
||
|
||
| Operation | Tier 1 (j-Tree) | Tier 2 (Exact) | Combined |
|
||
|-----------|-----------------|----------------|----------|
|
||
| **Insert Edge** | O(n^ε) | O(n^{o(1)}) | O(n^ε) |
|
||
| **Delete Edge** | O(n^ε) | O(n^{o(1)}) | O(n^ε) |
|
||
| **Min-Cut Query** | O(log n) approx | O(1) exact | O(1) - O(log n) |
|
||
| **All-Pairs Min-Cut** | O(n log n) | N/A | O(n log n) |
|
||
| **Sparsest Cut** | O(n^ε) | N/A | O(n^ε) |
|
||
| **Multi-Way Cut** | O(k log k · n^ε) | N/A | O(k log k · n^ε) |
|
||
|
||
### Recourse Guarantees
|
||
|
||
The vertex-split-tolerant sparsifier provides:
|
||
|
||
| Metric | Guarantee |
|
||
|--------|-----------|
|
||
| **Edges adjusted per update** | O(log² n / ε²) |
|
||
| **Total recourse over m updates** | O(m · log² n / ε²) |
|
||
| **Forest repairs per vertex split** | O(log n) |
|
||
|
||
This is critical for maintaining hierarchy stability under dynamic changes.
|
||
|
||
---
|
||
|
||
## Implementation Phases
|
||
|
||
### Phase 1: Core Sparsifier (Weeks 1-3)
|
||
|
||
- [ ] Implement `ForestPacking` with edge sampling
|
||
- [ ] Implement `DynamicCutSparsifier` with vertex split handling
|
||
- [ ] Add recourse tracking and verification
|
||
- [ ] Unit tests for sparsifier correctness
|
||
|
||
### Phase 2: j-Tree Hierarchy (Weeks 4-6)
|
||
|
||
- [ ] Implement `JTree` single-level structure
|
||
- [ ] Implement `JTreeHierarchy` multi-level decomposition
|
||
- [ ] Add contraction algorithms for level construction
|
||
- [ ] Integration tests for hierarchy maintenance
|
||
|
||
### Phase 3: Query Support (Weeks 7-9)
|
||
|
||
- [ ] Implement approximate min-cut queries
|
||
- [ ] Implement all-pairs min-cut
|
||
- [ ] Implement sparsest cut
|
||
- [ ] Implement multi-way cut and multi-cut
|
||
- [ ] Benchmark query performance
|
||
|
||
### Phase 4: Two-Tier Integration (Weeks 10-12)
|
||
|
||
- [ ] Implement `TwoTierCoordinator`
|
||
- [ ] Define escalation trigger policies
|
||
- [ ] Integrate with coherence gate
|
||
- [ ] End-to-end testing with coherence scenarios
|
||
|
||
---
|
||
|
||
## Feature Flags
|
||
|
||
```toml
|
||
[features]
|
||
# Existing features
|
||
default = ["exact", "approximate"]
|
||
exact = []
|
||
approximate = []
|
||
|
||
# New features
|
||
jtree = [] # j-Tree hierarchical decomposition
|
||
tiered = ["jtree", "exact"] # Two-tier coordinator
|
||
all-cut-queries = ["jtree"] # Sparsest cut, multiway, multicut
|
||
```
|
||
|
||
---
|
||
|
||
## Consequences
|
||
|
||
### Benefits
|
||
|
||
1. **Broader Query Support**: Sparsest cut, multi-way cut, multi-cut, all-pairs - not just minimum cut
|
||
2. **Faster Continuous Monitoring**: O(n^ε) updates enable 10K+ updates/second even on large graphs
|
||
3. **Global Structure Awareness**: Hierarchical view shows cut landscape at multiple scales
|
||
4. **Graceful Degradation**: Approximate answers when exact isn't needed, exact when it is
|
||
5. **Low Recourse**: Sparsifier stability prevents update cascades
|
||
6. **Coherence Gate Enhancement**: Fast structural checks with exact fallback
|
||
|
||
### Risks & Mitigations
|
||
|
||
| Risk | Probability | Impact | Mitigation |
|
||
|------|-------------|--------|------------|
|
||
| Implementation complexity | High | Medium | Phase incrementally, extensive testing |
|
||
| Approximation too loose | Medium | Medium | Tunable α parameter, exact fallback |
|
||
| Memory overhead from hierarchy | Medium | Low | Lazy level construction |
|
||
| Integration complexity with existing code | Medium | Medium | Clean interface boundaries |
|
||
|
||
### Complexity Analysis
|
||
|
||
| Component | Space | Time (Update) | Time (Query) |
|
||
|-----------|-------|---------------|--------------|
|
||
| Forest Packing | O(m log n / ε²) | O(log² n / ε²) | O(1) |
|
||
| j-Tree Level | O(n_ℓ) | O(n_ℓ^ε) | O(log n_ℓ) |
|
||
| Full Hierarchy | O(n log n) | O(n^ε) | O(log n) |
|
||
| Two-Tier Cache | O(n) | O(1) | O(1) |
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
### Primary
|
||
|
||
1. Goranci, G., Henzinger, M., Kiss, P., Momeni, A., & Zöcklein, G. (January 2026). "Dynamic Hierarchical j-Tree Decomposition and Its Applications." *arXiv:2601.09139*. SODA 2026. **[Core paper for this ADR]**
|
||
|
||
### Complementary
|
||
|
||
2. El-Hayek, A., Henzinger, M., & Li, J. (December 2025). "Deterministic and Exact Fully-dynamic Minimum Cut of Superpolylogarithmic Size in Subpolynomial Time." *arXiv:2512.13105*. **[Existing Tier 2 implementation]**
|
||
|
||
3. Mądry, A. (2010). "Fast Approximation Algorithms for Cut-Based Problems in Undirected Graphs." *FOCS 2010*. **[Original j-tree decomposition]**
|
||
|
||
### Background
|
||
|
||
4. Benczúr, A. A., & Karger, D. R. (1996). "Approximating s-t Minimum Cuts in Õ(n²) Time." *STOC*. **[Cut sparsification foundations]**
|
||
|
||
5. Thorup, M. (2007). "Fully-Dynamic Min-Cut." *Combinatorica*. **[Dynamic min-cut foundations]**
|
||
|
||
---
|
||
|
||
## Related Decisions
|
||
|
||
- **ADR-001**: Anytime-Valid Coherence Gate (uses Tier 2 exact min-cut)
|
||
- **ADR-014**: Coherence Engine Architecture (coherence computation)
|
||
- **ADR-CE-001**: Sheaf Laplacian Coherence (structural coherence foundation)
|
||
|
||
---
|
||
|
||
## Appendix: Paper Comparison
|
||
|
||
### El-Hayek/Henzinger/Li (Dec 2025) vs Goranci et al. (Jan 2026)
|
||
|
||
| Aspect | arXiv:2512.13105 | arXiv:2601.09139 |
|
||
|--------|------------------|------------------|
|
||
| **Focus** | Exact min-cut | Approximate cut hierarchy |
|
||
| **Update Time** | O(n^{o(1)}) | O(n^ε) for any ε > 0 |
|
||
| **Approximation** | Exact | Poly-logarithmic |
|
||
| **Cut Regime** | Superpolylogarithmic | All sizes |
|
||
| **Query Types** | Min-cut only | All cut problems |
|
||
| **Deterministic** | Yes | Yes |
|
||
| **Key Technique** | Cluster hierarchy + LocalKCut | j-Tree + vertex-split sparsifier |
|
||
|
||
**Synergy**: The two approaches complement each other perfectly:
|
||
- Use Goranci et al. for fast global monitoring and diverse cut queries
|
||
- Use El-Hayek et al. for exact verification when critical cuts are detected
|
||
|
||
This two-tier strategy provides both breadth (approximate queries on all cut problems) and depth (exact min-cut when needed).
|