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
|
|
|
"""Inner-loop search tests against a fake evaluator (no perl, no oracle)."""
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from homemaker import innerloop
|
|
|
|
|
from homemaker.oracle import Score
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FakeEvaluator:
|
|
|
|
|
"""Duck-typed OracleEvaluator over an analytic objective."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, fn):
|
|
|
|
|
self.fn = fn
|
|
|
|
|
self.n_evals = 0
|
|
|
|
|
self.n_oracle_calls = 0
|
|
|
|
|
|
|
|
|
|
def evaluate(self, xs):
|
|
|
|
|
self.n_evals += len(xs)
|
|
|
|
|
self.n_oracle_calls += 1
|
|
|
|
|
return [Score(fitness=self.fn(np.asarray(x)), fails="") for x in xs]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def concave(x):
|
|
|
|
|
# maximum 1.0 at 0.3 in every coordinate
|
|
|
|
|
return float(1.0 - np.sum((x - 0.3) ** 2))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("search", [innerloop.compass_search, innerloop.cma_search])
|
|
|
|
|
def test_search_converges_on_concave(search):
|
2026-06-12 09:46:13 +01:00
|
|
|
# the production configs trade final-digit polish for basin coverage
|
|
|
|
|
# (multi-start sigma ladder), so assert basin convergence, not precision
|
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
|
|
|
ev = FakeEvaluator(concave)
|
|
|
|
|
r = search(ev, np.full(4, 0.7), budget=400)
|
2026-06-12 09:46:13 +01:00
|
|
|
assert r.fitness > 0.99
|
|
|
|
|
assert np.allclose(r.x, 0.3, atol=0.1)
|
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
|
|
|
assert r.x0_fitness == pytest.approx(concave(np.full(4, 0.7)))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize("search", [innerloop.compass_search, innerloop.cma_search])
|
|
|
|
|
def test_search_respects_budget_and_bounds(search):
|
|
|
|
|
seen = []
|
|
|
|
|
|
|
|
|
|
def spy(x):
|
|
|
|
|
seen.append(x.copy())
|
|
|
|
|
return concave(x)
|
|
|
|
|
|
|
|
|
|
ev = FakeEvaluator(spy)
|
|
|
|
|
r = search(ev, np.full(3, 0.5), budget=60)
|
|
|
|
|
# one batch may run slightly over, but never a whole extra cycle
|
|
|
|
|
assert r.n_evals == ev.n_evals <= 60 + 3 * 10
|
|
|
|
|
assert all((x >= innerloop._EPS - 1e-12).all() and (x <= 1 - innerloop._EPS + 1e-12).all()
|
|
|
|
|
for x in seen)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_compass_never_returns_worse_than_start():
|
|
|
|
|
# a hostile objective: best at the start, everything else worse
|
|
|
|
|
x0 = np.full(3, 0.5)
|
|
|
|
|
|
|
|
|
|
def hostile(x):
|
|
|
|
|
return -float(np.sum(np.abs(x - x0)))
|
|
|
|
|
|
|
|
|
|
ev = FakeEvaluator(hostile)
|
|
|
|
|
r = innerloop.compass_search(ev, x0, budget=100)
|
|
|
|
|
assert r.fitness == pytest.approx(0.0)
|
|
|
|
|
assert np.allclose(r.x, x0)
|