Adds shapecurve.is_feasible() (a non-mutating refactor of solve()'s check phase) and a shapecurve_prune flag composing the DP's exact feasible/ infeasible verdict with operators.predicted_shape_fails' existing heuristic prune: DP-feasible vetoes a heuristic prune outright; DP-infeasible only hard-prunes when the incumbent already has zero total fails (exact, since infeasible proves the shape-fail floor is >=1); otherwise defers unchanged to today's heuristic threshold. Conservative by design since a wrong prune is unrecoverable. Validated 0/400 false negatives across two structurally distinct plots (harbor-house-l0 + a newly-added programme-house sweep, the first genuinely non-rectangular plot this DP has been checked against). The real driver.search A/B on harbor-house-l0 measured NULL (byte-identical off/on) for a root-caused, pre-existing reason: predicted_shape_fails rarely triggers organically at this scale, so neither new branch had an opening to fire -- not a defect in this change. Full writeup: DESIGN.md §37.5. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LSwQwpEaHFBkeVSDDWd75S
112 lines
4.5 KiB
Python
112 lines
4.5 KiB
Python
"""Tests for the shape-curve DP (homemaker-py-6xh, promoted from
|
|
experiments/shapecurve_spike.py; see DESIGN.md §37.2/§37.4 for the full
|
|
200-topology validation this unit scale is a fast smoke check of)."""
|
|
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from homemaker_layout import dom, driver, fitness as fit_mod, shapecurve, solver
|
|
|
|
HARBOR_L0 = Path(__file__).parent.parent / "examples" / "harbor-house-l0"
|
|
|
|
pytestmark = pytest.mark.skipif(not HARBOR_L0.is_dir(), reason="harbor-house-l0 not available")
|
|
|
|
|
|
def _fit():
|
|
conf, cost = fit_mod.load_config(str(HARBOR_L0))
|
|
return fit_mod.Fitness(conf, cost)
|
|
|
|
|
|
def _small_feasible_topology():
|
|
"""A tiny 2-leaf (C/O only, no size/adjacency constraints to speak of)
|
|
topology on harbor-house-l0's plot -- deterministically shape-feasible
|
|
(verified: driver.random_topology(seed, 2, rng(0), ['C', 'O']))."""
|
|
seed = dom.load(str(HARBOR_L0 / "init.dom"))
|
|
rng = np.random.default_rng(0)
|
|
return driver.random_topology(seed, 2, rng, ["C", "O"])
|
|
|
|
|
|
def test_eligible_guards_multistorey_and_sharing():
|
|
root = dom.load(str(HARBOR_L0 / "generated.dom"))
|
|
assert len(dom.levels(root)) == 1
|
|
assert shapecurve.eligible(root)
|
|
assert not shapecurve.eligible(root, leaf_sharing=True)
|
|
assert not shapecurve.eligible(root, superpose=True)
|
|
assert not shapecurve.eligible(root, max_share=3)
|
|
assert not shapecurve.eligible(root, multi_use=True)
|
|
|
|
seed = dom.load(str(HARBOR_L0 / "init.dom"))
|
|
seed.above = dom.Node(rotation=0) # fake a second storey
|
|
assert len(dom.levels(seed)) == 2
|
|
assert not shapecurve.eligible(seed)
|
|
|
|
|
|
def test_solve_feasible_root_realises_zero_shape_fails(tmp_path):
|
|
"""A small, obviously-feasible topology's DP-realised ratios round-trip
|
|
through dom.dumps/dom.load and independently verify as zero shape fails
|
|
under the real Fitness scorer."""
|
|
root = _small_feasible_topology()
|
|
fit = _fit()
|
|
|
|
feasible, info = shapecurve.solve(root, fit)
|
|
assert feasible is True
|
|
assert info["w_plot"] > 0 and info["h_plot"] > 0
|
|
|
|
out_path = tmp_path / "realised.dom"
|
|
out_path.write_text(dom.dumps(root))
|
|
reloaded = dom.load(str(out_path))
|
|
|
|
_, fails = fit.score_with_fails(reloaded)
|
|
shape_fails = [f for f in fails if f.endswith((" size", " width", " proportion"))]
|
|
assert shape_fails == []
|
|
|
|
|
|
def test_solve_infeasible_topology_leaves_tree_untouched():
|
|
"""A topology with far more leaves than harbor-house-l0's plot can fit
|
|
(each needing its own min width/area) is infeasible; solve() must not
|
|
write partial/bogus ratios in that case."""
|
|
seed = dom.load(str(HARBOR_L0 / "init.dom"))
|
|
rng = np.random.default_rng(0)
|
|
root = driver.random_topology(seed, 60, rng, ["k1", "l1", "b1", "C", "O"])
|
|
fit = _fit()
|
|
|
|
feasible, info = shapecurve.solve(root, fit)
|
|
assert feasible is False
|
|
# infeasible: no realised point to check, but the call must not raise
|
|
# and must report the same plot dims as the feasible case's mechanism
|
|
assert info["w_plot"] > 0 and info["h_plot"] > 0
|
|
|
|
|
|
def test_solve_is_deterministic():
|
|
root = _small_feasible_topology()
|
|
fit = _fit()
|
|
f1, _ = shapecurve.solve(root, fit)
|
|
divisions_1 = [tuple(b.division) for b in solver.free_branches(root)]
|
|
f2, _ = shapecurve.solve(root, fit)
|
|
divisions_2 = [tuple(b.division) for b in solver.free_branches(root)]
|
|
assert f1 == f2 is True
|
|
assert divisions_1 == pytest.approx(divisions_2)
|
|
|
|
|
|
def test_is_feasible_agrees_with_solve_but_never_writes(monkeypatch):
|
|
"""homemaker-py-wkh: the hard-prune caller needs the boolean verdict
|
|
without solve()'s tree mutation, so ``is_feasible`` must (a) agree with
|
|
``solve``'s own verdict and (b) never write ``division`` -- verified on
|
|
both the feasible and infeasible fixtures already exercised above."""
|
|
fit = _fit()
|
|
|
|
feasible_root = _small_feasible_topology()
|
|
before = [tuple(b.division) for b in solver.free_branches(feasible_root)]
|
|
assert shapecurve.is_feasible(feasible_root, fit) is True
|
|
after = [tuple(b.division) for b in solver.free_branches(feasible_root)]
|
|
assert before == after
|
|
|
|
seed = dom.load(str(HARBOR_L0 / "init.dom"))
|
|
rng = np.random.default_rng(0)
|
|
infeasible_root = driver.random_topology(seed, 60, rng, ["k1", "l1", "b1", "C", "O"])
|
|
before = [tuple(b.division) for b in solver.free_branches(infeasible_root)]
|
|
assert shapecurve.is_feasible(infeasible_root, fit) is False
|
|
after = [tuple(b.division) for b in solver.free_branches(infeasible_root)]
|
|
assert before == after
|