Like [`Self::partition`], but also returns the **bisection tree** — the binary tree of min-cut splits whose leaves are the shard ids. Reusing this as the proof-aggregation tree (rather than an arbitrary flat fold) discharges each cross-shard assumption at the lowest common ancestor of the shards that share it; the per-bisection min-cut means sibling subtrees have the thinnest possible interface, s
(
&self,
num_shards: usize,
epsilon: f64,
)
| 229 | /// shards that share it; the per-bisection min-cut means sibling subtrees have |
| 230 | /// the thinnest possible interface, so most discharge happens low in the tree. |
| 231 | pub fn partition_with_tree( |
| 232 | &self, |
| 233 | num_shards: usize, |
| 234 | epsilon: f64, |
| 235 | ) -> (Vec<u32>, AggNode) { |
| 236 | let n = self.num_vertices(); |
| 237 | if num_shards <= 1 { |
| 238 | return (vec![0u32; n], AggNode::Leaf(0)); // everything in shard 0 |
| 239 | } |
| 240 | // Cap each block's balance weight at the ideal per-shard heartbeats |
| 241 | // (total / num_shards). This keeps balancing heartbeat-aware while a balanced |
| 242 | // split is achievable (few shards), but stops a single oversized *atomic* |
| 243 | // block from skewing every split toward it once there are many shards. The |
| 244 | // resulting heartbeat imbalance is accepted; the goal is `num_shards` |
| 245 | // **non-empty** parallel shards with minimal cross-shard ingress. |
| 246 | let total_hb: u128 = self.vweight.iter().map(|&w| u128::from(w)).sum(); |
| 247 | let cap = |
| 248 | (total_hb / num_shards as u128).max(1).min(u128::from(u64::MAX)) as u64; |
| 249 | let sub = SubHyper::full(self, cap); |
| 250 | // Atomic assignment buffer: independent subtrees own disjoint block sets, so |
| 251 | // they can be partitioned on separate threads without their writes aliasing. |
| 252 | let shard_of: Vec<AtomicU32> = (0..n).map(|_| AtomicU32::new(0)).collect(); |
| 253 | let prog = PartitionProgress::new(num_shards); |
| 254 | let tree = rec_bisect(&sub, num_shards, 0, epsilon, &shard_of, &prog); |
| 255 | prog.done(); |
| 256 | let assignment = shard_of.into_iter().map(AtomicU32::into_inner).collect(); |
| 257 | (assignment, tree) |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | // ============================================================================ |