homemaker-layout/experiments/accept_innerloop.py
Bruno Postle 0dcdf1f29f Geometry inner loop: batched full-objective ratio optimiser (CMA-ES)
innerloop.py: optimise(root, programme_dir, x0=None, budget, method) ->
Result, optimising equal-offset free-branch ratios (midpoint projection of
legacy unequal cuts) against full oracle fitness. OracleEvaluator scores
each population in one batched perl call. Methods: cma (default) — multi-
start sigma ladder (0.05 local, 0.15 exploratory) with IPOP-style popsize
doubling and deterministic seeding (pycma treats seed 0 as clock!) — and
compass with Hooke-Jeeves pattern moves, kept for the d0s bake-off.

Acceptance (experiments/accept_innerloop.py, §4.5 bars vs unprojected
originals, within-noise tolerance 1%): x1.65 / x1.66 / x1.58 against bars
x1.24 / x1.67 / x1.59, no new failures, 46 oracle calls vs Nelder-Mead's
200. The two near-bar results are statistically indistinguishable from the
single-NM-draw bars (measured draw spread brackets them); decision approved
by Bruno 2026-06-12.

Also: tests/ scaffold (12 oracle-free unit tests, pytest pythonpath=src),
rebaseline_no_occlusion.py for homemaker-py-gp2, cma>=3.0 dependency
(installed via dnf), dead-variable cleanup in solver.py.

Closes homemaker-py-1p0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 09:42:24 +01:00

77 lines
3 KiB
Python

#!/usr/bin/env python3
"""Acceptance run for the geometry inner loop (homemaker-py-1p0).
Gate (DESIGN.md §4.5, issue acceptance criteria): reproduce or exceed the
Nelder-Mead diagnostic gains — x1.24 / x1.67 / x1.59, no new failures — on
2f45907, candidate-002 and c964435, using the batched compass search.
Usage: accept_innerloop.py [budget] [method] (default: 200 oracle evals, cma)
"""
from __future__ import annotations
import shutil
import sys
import tempfile
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from homemaker import dom, innerloop, oracle # noqa: E402
URB = Path("/home/bruno/src/urb")
EX = URB / "examples" / "programme-house"
# name -> the x-gain the §4.5 Nelder-Mead diagnostic achieved (the bar)
GATE = {
"2f45907abd9accac2a124d311732f749.dom": 1.24,
"candidate-002.dom": 1.67,
"c964435454c459f86c3ed9a5a7621132.dom": 1.59,
}
# The §4.5 bars are single Nelder-Mead draws and the inner loop's per-run
# variance brackets them (candidate-002 drew 0.0117-0.0160 against a 0.0123
# bar). Reproduction within 1% counts as met — decision approved 2026-06-12
# (homemaker-py-1p0); chasing the last fraction with seed rolls would be
# cherry-picking.
NOISE_TOL = 0.99
def main() -> int:
budget = int(sys.argv[1]) if len(sys.argv) > 1 else 200
method = sys.argv[2] if len(sys.argv) > 2 else "cma"
print(f"method={method} budget={budget}")
all_ok = True
with tempfile.TemporaryDirectory(prefix="accept_innerloop_") as tmp:
scratch = Path(tmp)
shutil.copy(EX / "patterns.config", scratch)
for name, bar in GATE.items():
# Baseline = the UNMODIFIED original file, exactly as §4.5 measured
# it. The inner loop's own x0 score is the equal-offset *projection*
# of the original (a==b forced, clipped), which is lower for legacy
# designs with unequal cuts — gains must not be measured from there.
s_orig = oracle.score(shutil.copy(EX / name, scratch), URB)
root = dom.load(str(EX / name))
t0 = time.perf_counter()
r = innerloop.optimise(root, EX, budget=budget, method=method, urb_root=URB)
dt = time.perf_counter() - t0
gain = r.fitness / s_orig.fitness if s_orig.fitness else float("inf")
ok = gain >= bar * NOISE_TOL and r.n_fails <= s_orig.n_fails
all_ok &= ok
print(
f"{name:42s} dof={len(r.x):2d} "
f"orig={s_orig.fitness:.6g}(fails {s_orig.n_fails}) "
f"projected x0={r.x0_fitness:.6g}(fails {r.x0_n_fails}) "
f"opt={r.fitness:.6g}(fails {r.n_fails}) "
f"x{gain:.2f} (bar x{bar:.2f}) "
f"{r.n_evals} evals / {r.n_oracle_calls} oracle calls / {dt:.0f}s "
f"{'PASS' if ok else 'FAIL'}",
flush=True,
)
print("\nGATE " + ("PASSED" if all_ok else "FAILED"))
return 0 if all_ok else 1
if __name__ == "__main__":
sys.exit(main())