Fix benchmark cell arg order and mutate_swap on undivided trees

run_urbevolve took (seed, budget, pop, cell) but cells call
fn(seed, budget, cell, **kw) — every urb-evolve cell died on TypeError,
deferred silently by pool.map. mutate_swap lacked the empty-candidates
noop guard the other operators have, crashing on init.dom-style bare
plots. Regression test: every mutation survives an undivided tree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Bruno Postle 2026-06-12 23:26:22 +01:00
parent e2b3e20070
commit 3bf507a483
3 changed files with 17 additions and 2 deletions

View file

@ -50,7 +50,7 @@ def run_homemaker(seed: str, budget: int, cell: Path) -> dict:
return {"series": series, "best_dom": out, "log_ok": proc.returncode == 0} return {"series": series, "best_dom": out, "log_ok": proc.returncode == 0}
def run_urbevolve(seed: str, budget: int, pop: int, cell: Path) -> dict: def run_urbevolve(seed: str, budget: int, cell: Path, pop: int) -> dict:
shutil.copy(EX / "patterns.config", cell) shutil.copy(EX / "patterns.config", cell)
shutil.copy(EX / seed, cell) shutil.copy(EX / seed, cell)
proc = subprocess.run( proc = subprocess.run(

View file

@ -101,7 +101,10 @@ def mutate_retype(root: dom.Node, rng: np.random.Generator,
def mutate_swap(root: dom.Node, rng: np.random.Generator, def mutate_swap(root: dom.Node, rng: np.random.Generator,
types: list[str]) -> tuple[dom.Node, str]: types: list[str]) -> tuple[dom.Node, str]:
child = copy.deepcopy(root) child = copy.deepcopy(root)
li, n = _pick(rng, _owned_branches(child)) cands = _owned_branches(child)
if not cands: # undivided topology (e.g. a bare plot seed)
return _finalise(child), "swap noop"
li, n = _pick(rng, cands)
n.left, n.right = n.right, n.left n.left, n.right = n.right, n.left
return _finalise(child), f"swap {li}/{n.id or 'root'}" return _finalise(child), f"swap {li}/{n.id or 'root'}"

View file

@ -83,6 +83,18 @@ def test_relink_clears_stale_below_after_base_undivide():
assert base.by_id(cands[0].id) is not None # parent untouched assert base.by_id(cands[0].id) is not None # parent untouched
def test_all_mutations_survive_undivided_tree():
# an undivided plot (init.dom-style seed) must never crash an operator
bare = dom.Node(type="O", node=[[0, 0], [10, 0], [10, 8], [0, 8]],
height=2.7, wall_outer=0.25, wall_inner=0.08)
dom._link(bare)
for name, op in operators.MUTATIONS.items():
for seed in range(3):
child, desc = op(bare, np.random.default_rng(seed), TYPES)
assert desc, name
canonical(child)
def test_crossover_yields_canonical_pair(): def test_crossover_yields_canonical_pair():
a = genome.decode(genome.encode(dom.load(str(CORPUS / FILES[0])))) a = genome.decode(genome.encode(dom.load(str(CORPUS / FILES[0]))))
b = genome.decode(genome.encode(dom.load(str(CORPUS / FILES[1])))) b = genome.decode(genome.encode(dom.load(str(CORPUS / FILES[1]))))