From 1bc0df6d03cb2ba2ee3f3a751085eb5cfde997a1 Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Wed, 21 Oct 2026 23:39:37 -0400 Subject: [PATCH] doc: a join of many tools into one target unions them in a balanced tree A Join with one target takes every tool, whichever it meets, but it grew the target by one tool at a time: each union took the whole result so far, and each step first meshed it to see whether the tool met it (which with one target decides nothing). n tools cost n^2 in every per-body step of a boolean (converting the operands, checking the result, exporting it back), whichever kernel runs it: a combine of 64 separate pegs (booleans/3455) took 400 s on SMLib, of 36 lofted blades and a disk (crashes/2992, sweeps/1856) 485 s and 97 s. With one target, the target and the tools are now unioned pairwise, level by level, so each body takes part in about log2(n) unions. The result is the same union under the target's name, with the lineage of every union. Several targets keep the absorption order they need. The doc and engine join and combine tests passed and failed on SMLib as before (measured before the rebase). Not built: base in progress (rebased onto kernel-smlib/no-truck 41adb372). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt --- crates/doc/src/eval.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/crates/doc/src/eval.rs b/crates/doc/src/eval.rs index 83fdf15ff7..e833e3322b 100644 --- a/crates/doc/src/eval.rs +++ b/crates/doc/src/eval.rs @@ -2995,6 +2995,31 @@ fn apply_traced_with( let u = kernel::boolean(&a.body, &b.body, BoolOp::Union)?.ok_or_else(|| DocError::Invalid("join produced nothing".into()))?; Ok(Traced::step(u, &[a, b])) }; + // One target takes every tool, whichever it meets: the result is the union of all of + // them, made in a balanced tree of unions so that each body is in about log2(n) of + // them, instead of the target growing by one tool at a time and taking part, ever + // larger, in every one (n tools cost n²). + if let [keep] = live.as_slice() + && tools.len() > 1 + { + let Some(base) = state.body(keep).map(ModelBody::traced) else { return Ok(()) }; + let mut level: Vec = std::iter::once(base).chain(tools).collect(); + while level.len() > 1 { + let mut next = Vec::with_capacity(level.len().div_ceil(2)); + let mut it = level.into_iter(); + while let Some(a) = it.next() { + next.push(match it.next() { + Some(b) => union(&a, &b)?, + None => a, + }); + } + level = next; + } + if let (Some(acc), Some(mb)) = (level.pop(), state.bodies.iter_mut().find(|b| b.name == *keep)) { + *mb = ModelBody::traced_new(mb.name.clone(), acc, mb.feature); + } + return Ok(()); + } for tool in &tools { let mut met: Vec = Vec::new(); for name in &live {