From 65085d5c3d920f858c3bb3e630059400ff81ca9f Mon Sep 17 00:00:00 2001 From: Bruno Postle Date: Mon, 15 Jun 2026 07:27:03 +0100 Subject: [PATCH] Fix warm_x0 to honour operator-specified ratios on new splits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a compound operator (e.g. level_compound_fix) creates a new internal node and explicitly sets its division ratio, that ratio was silently overridden: warm_x0 received parent.ratios which had no entry for the new node, so Nelder-Mead started at the default 0.5 instead of the operator's intended 0.25 (for rrl/rrr). Result: NM evaluated the compound topology at the wrong geometry and scored 3 fails instead of 1 — so lex always rejected the compound child, making level_compound_fix invisible to the outer search. Fix: for nodes that are genuinely newly divided (not divided in the parent tree at the same path), inherit the child's operator-set ratio rather than defaulting to 0.5. Structural mutations (e.g. swap) can reveal hidden level-N nodes that retain stale pre-writeback ratios — those are correctly excluded by checking parent_node.divided. Co-Authored-By: Claude Sonnet 4.6 --- src/homemaker_layout/driver.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/homemaker_layout/driver.py b/src/homemaker_layout/driver.py index 034c424..4e8d122 100644 --- a/src/homemaker_layout/driver.py +++ b/src/homemaker_layout/driver.py @@ -251,7 +251,20 @@ def search( child_root, desc = operators.mutate(parent.root, rng, types, weights=_MUTATION_WEIGHTS, reqs=reqs) - ratios = parent.ratios + # Carry operator-specified ratios for nodes that are genuinely + # newly divided (existed as leaves in the parent, are now + # divided in the child). Structural mutations (e.g. swap) can + # reveal previously-hidden nodes whose stale pre-writeback + # ratios must NOT be propagated — those default to 0.5. + parent_lvls = dom.levels(parent.root) + new_splits = { + (li, path): val + for (li, path), val in innerloop.ratio_map(child_root).items() + if li >= len(parent_lvls) + or not (pn := parent_lvls[li].by_id(path)) + or not pn.divided + } + ratios = {**new_splits, **parent.ratios} x0 = innerloop.warm_x0(child_root, ratios) tasks.append((child_root, x0, child_budget, inner_kw, desc)) _run_batch(tasks)