MCPcopy Create free account
hub / github.com/argumentcomputer/ix / rec_bisect

Function rec_bisect

crates/kernel/src/shard.rs:528–589  ·  view source on GitHub ↗

Recursively split `sub` into `k` shards (contiguous ids `base..base+k`), writing shard ids into `shard_of` (indexed by global block id). The `k` leaf budget is allocated between the two sides ~proportional to heartbeat mass, but clamped so each side receives at least 1 leaf and at most its own vertex count. That guarantees **every shard is non-empty** whenever the subtree holds at least `k` verti

(
  sub: &SubHyper,
  k: usize,
  base: u32,
  epsilon: f64,
  shard_of: &[AtomicU32],
  prog: &PartitionProgress,
)

Source from the content-addressed store, hash-verified

526 }
527}
528
529/// Recursively split `sub` into `k` shards (contiguous ids `base..base+k`),
530/// writing shard ids into `shard_of` (indexed by global block id).
531///
532/// The `k` leaf budget is allocated between the two sides ~proportional to
533/// heartbeat mass, but clamped so each side receives at least 1 leaf and at most
534/// its own vertex count. That guarantees **every shard is non-empty** whenever
535/// the subtree holds at least `k` vertices (at the top, `#blocks ≥ 2ⁿ`), so the
536/// full parallelism is realized; the heartbeat-proportional split also keeps
537/// per-shard work as even as the atomic blocks allow. The resulting bisection
538/// tree is a (possibly unbalanced) binary tree — still a valid aggregation tree.
539fn rec_bisect(
540 sub: &SubHyper,
541 k: usize,
542 base: u32,
543 epsilon: f64,
544 shard_of: &[AtomicU32],
545 prog: &PartitionProgress,
546) -> AggNode {
547 if k <= 1 || sub.num_vertices() <= 1 {
548 // A leaf (or a subtree with ≤1 vertex): everything here is one shard.
549 for &g in &sub.global {
550 shard_of[g as usize].store(base, Ordering::Relaxed);
551 }
552 prog.leaf();
553 return AggNode::Leaf(base);
554 }
555 prog.bisecting(sub.num_vertices(), k);
556 let side = bisect(sub, epsilon);
557 let keep0: Vec<bool> = side.iter().map(|&s| s == 0).collect();
558 let keep1: Vec<bool> = side.iter().map(|&s| s == 1).collect();
559 let left = sub.induce(&keep0);
560 let right = sub.induce(&keep1);
561 let nl = left.num_vertices();
562 let nr = right.num_vertices();
563 if nl == 0 || nr == 0 {
564 // Degenerate split: `bisect` guarantees both sides non-empty for |sub| ≥ 2
565 // (its initial partition rejects empty-sided candidates), so this is a
566 // defensive backstop only — keep the subtree together as one shard.
567 for &g in &sub.global {
568 shard_of[g as usize].store(base, Ordering::Relaxed);
569 }
570 prog.leaf();
571 return AggNode::Leaf(base);
572 }
573 // Allocate leaves ~proportional to heartbeat mass, clamped to [1, side size].
574 // `lo`/`hi` are feasible (lo ≤ hi) because nl + nr ≥ k with nl, nr ≥ 1.
575 let wl: u128 = left.vw.iter().map(|&w| u128::from(w)).sum();
576 let wr: u128 = right.vw.iter().map(|&w| u128::from(w)).sum();
577 let wsum = (wl + wr).max(1);
578 let ideal = ((k as u128 * wl + wsum / 2) / wsum) as usize;
579 let lo = 1.max(k.saturating_sub(nr));
580 let hi = (k - 1).min(nl);
581 let k_left = ideal.clamp(lo, hi);
582 let rbase = base + k_left as u32;
583 // The two halves are independent (disjoint blocks) — recurse them in parallel
584 // when the work is large enough to amortize the join. The result is identical
585 // to serial execution (each subtree is deterministic). Each call returns its

Callers 1

partition_with_treeMethod · 0.85

Calls 7

bisectFunction · 0.85
num_verticesMethod · 0.80
leafMethod · 0.80
bisectingMethod · 0.80
induceMethod · 0.80
iterMethod · 0.45
maxMethod · 0.45

Tested by

no test coverage detected