homemaker-layout/tests/test_collapse_insearch.py
Bruno Postle 2b7a7d2926 qpk: in-search global collapse — run collapse_global per-eval during search
Runs the 94g finish-time cell↔room collapse inside every fitness eval
(collapse_insearch conf flag, default off, bit-identical when off) instead
of once at the end, so search optimises the collapsed objective directly.
Plumbed through fitness.py/driver.py/evolve.py the same way superpose/
conn_grade are; --collapse-insearch CLI flag.

A/B validated against the xi7 protocol (equal budget, both arms finished
with standard finish-time --collapse): POSITIVE, opposite of the 9o5/xi7
prior. harbor-house ON wins 3/3 (mean fails 80.3->72.0); programme-house
mixed 3/5 (mean fails 8.4->7.8). Kept default off pending a larger
programme-house sample; documented as a working opt-in for harbor-house-
scale-or-larger programmes. Full writeup in DESIGN.md §20.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 20:35:18 +01:00

122 lines
4.7 KiB
Python

"""Tests for the IN-SEARCH global collapse (homemaker-py-qpk, DESIGN.md §17
follow-on): running Fitness.collapse_global inside every fitness eval instead
of once at finish time.
Covers:
- default-OFF guarantee + conf-driven knobs (adjacency, iters)
- _evaluate_full wiring: collapse_global is invoked (with the right kwargs)
when the flag is on, never when it is off
- end-to-end effect on a real evolved layout, cross-checked against the
documented 94g finish-time result (DESIGN.md §17: 15 -> 12 fails on
evolved-3M-nols-3.dom)
"""
import copy
from pathlib import Path
from unittest.mock import patch
import pytest
from homemaker_layout import dom as dom_mod
from homemaker_layout.dom import Node, _link_subtree
from homemaker_layout.fitness import Fitness, load_config
HARBOR = Path(__file__).parent.parent / "examples" / "harbor-house"
def _two_leaf_root(t_left: str, t_right: str, side: float = 6.0, div: float = 0.4):
from homemaker_layout import geometry
geometry.clear_cache()
root = Node(
node=[[0, 0], [side, 0], [side, side], [0, side]],
rotation=0, division=[div, div],
left=Node(type=t_left), right=Node(type=t_right),
)
_link_subtree(root, None, "")
return root
# --------------------------------------------------------------------------- #
# Defaults + conf-driven knobs
# --------------------------------------------------------------------------- #
def test_collapse_insearch_default_off():
fit = Fitness()
assert fit._collapse_insearch is False
assert fit._collapse_insearch_adjacency is True
assert fit._collapse_insearch_iters == 3
def test_collapse_insearch_flag_on():
fit = Fitness(conf={"collapse_insearch": True})
assert fit._collapse_insearch is True
def test_collapse_insearch_adjacency_knob_off():
fit = Fitness(conf={"collapse_insearch": True,
"collapse_insearch_adjacency": False})
assert fit._collapse_insearch_adjacency is False
def test_collapse_insearch_iters_knob():
fit = Fitness(conf={"collapse_insearch": True, "collapse_insearch_iters": 5})
assert fit._collapse_insearch_iters == 5
# --------------------------------------------------------------------------- #
# _evaluate_full wiring
# --------------------------------------------------------------------------- #
def test_evaluate_full_calls_collapse_global_when_on():
fit = Fitness(conf={"collapse_insearch": True, "collapse_insearch_iters": 2,
"spaces": {"b1": {"size": [16.0, 4.0], "count": 2}}})
root = _two_leaf_root("b1", "b1")
with patch.object(Fitness, "collapse_global", wraps=fit.collapse_global) as m:
fit.score_with_fails(root)
m.assert_called_once()
_, kw = m.call_args
assert kw["adjacency"] is True
assert kw["objective"] == "threshold"
assert kw["preserve_public_access"] is True
assert kw["iters"] == 2
def test_evaluate_full_does_not_call_collapse_global_when_off():
fit = Fitness(conf={"spaces": {"b1": {"size": [16.0, 4.0], "count": 2}}})
root = _two_leaf_root("b1", "b1")
with patch.object(Fitness, "collapse_global", wraps=fit.collapse_global) as m:
fit.score_with_fails(root)
m.assert_not_called()
# --------------------------------------------------------------------------- #
# End-to-end: matches the documented 94g finish-time result
# --------------------------------------------------------------------------- #
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
def test_collapse_insearch_reproduces_94g_finish_time_result():
# DESIGN.md §17: the finish-time collapse takes this layout 15 -> 12 fails.
# collapse_insearch runs the SAME collapse_global earlier in the SAME
# pipeline (before Phase-1 checks instead of after the whole search), so it
# must reach the identical fail count on this fixed-geometry layout.
conf, cost = load_config(HARBOR)
conf_ci, _ = load_config(HARBOR, overrides={"collapse_insearch": True})
fit, fit_ci = Fitness(conf, cost), Fitness(conf_ci, cost)
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
_, f_base = fit.score_with_fails(copy.deepcopy(root))
_, f_ci = fit_ci.score_with_fails(copy.deepcopy(root))
assert len(f_base) == 15
assert len(f_ci) == 12
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
def test_collapse_insearch_off_reproduces_baseline():
conf, cost = load_config(HARBOR)
fit = Fitness(conf, cost)
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
s1, f1 = fit.score_with_fails(copy.deepcopy(root))
s2, f2 = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
assert s1 == pytest.approx(s2)
assert f1 == f2