2026-06-10 21:49:31 +01:00
|
|
|
|
"""Population sweep: warm-start refine every evolved candidate and tally results.
|
|
|
|
|
|
|
|
|
|
|
|
For each real .dom in the example dir, score it, run the solver as a geometry
|
|
|
|
|
|
optimiser (warm-start, no strip), and re-score. Reports how often bottom-up
|
|
|
|
|
|
sizing improves vs regresses total fitness, plus aggregate fail-count change.
|
|
|
|
|
|
|
|
|
|
|
|
This is a breadth check on the solver-as-optimiser role; raw fitness is still
|
|
|
|
|
|
confounded by the 0.5^n failure cliff and any topological defects, so the
|
|
|
|
|
|
fail-count and per-candidate detail matter as much as the win/loss tally.
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
|
|
import sys
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
|
|
|
|
|
2026-06-14 08:18:06 +01:00
|
|
|
|
from homemaker_layout import dom, oracle, programme, solver # noqa: E402
|
2026-06-10 21:49:31 +01:00
|
|
|
|
|
|
|
|
|
|
URB = Path("/home/bruno/src/urb")
|
|
|
|
|
|
EX = URB / "examples/programme-house"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_candidate(p: Path) -> bool:
|
|
|
|
|
|
# real designs: 32-hex hashes or candidate-NNN; skip init and our scratch
|
|
|
|
|
|
name = p.stem
|
|
|
|
|
|
return name not in {"init", "original", "roundtrip", "solved", "refined"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
|
scratch = Path(__file__).resolve().parents[1] / "scratch"
|
|
|
|
|
|
scratch.mkdir(exist_ok=True)
|
|
|
|
|
|
shutil.copy(EX / "patterns.config", scratch / "patterns.config")
|
|
|
|
|
|
targets = programme.load_programme(str(EX / "patterns.config"))
|
|
|
|
|
|
|
|
|
|
|
|
doms = sorted(p for p in EX.glob("*.dom") if _is_candidate(p))
|
|
|
|
|
|
win = loss = tie = 0
|
|
|
|
|
|
fails_before = fails_after = 0
|
|
|
|
|
|
rows = []
|
|
|
|
|
|
for src in doms:
|
|
|
|
|
|
try:
|
|
|
|
|
|
shutil.copy(src, scratch / "orig.dom")
|
|
|
|
|
|
s0 = oracle.score(scratch / "orig.dom", URB)
|
|
|
|
|
|
root = dom.load(str(src))
|
|
|
|
|
|
# gentlest refiner: nudge cut POSITIONS for programme-room area only,
|
|
|
|
|
|
# keep evolved cut angles and leave circulation/shape untouched.
|
|
|
|
|
|
solver.solve_ratios(
|
|
|
|
|
|
root, targets, strip=False, perpendicular=False,
|
|
|
|
|
|
weight_width=0.0, weight_proportion=0.0, min_width_generic=0.0,
|
|
|
|
|
|
)
|
|
|
|
|
|
dom.dump(root, str(scratch / "ref.dom"))
|
|
|
|
|
|
s1 = oracle.score(scratch / "ref.dom", URB)
|
|
|
|
|
|
except Exception as e: # noqa: BLE001
|
|
|
|
|
|
rows.append(f" {src.name:40s} ERROR {e}")
|
|
|
|
|
|
continue
|
|
|
|
|
|
fails_before += s0.n_fails
|
|
|
|
|
|
fails_after += s1.n_fails
|
|
|
|
|
|
if s1.fitness > s0.fitness * 1.001:
|
|
|
|
|
|
win += 1
|
|
|
|
|
|
mark = "+"
|
|
|
|
|
|
elif s1.fitness < s0.fitness * 0.999:
|
|
|
|
|
|
loss += 1
|
|
|
|
|
|
mark = "-"
|
|
|
|
|
|
else:
|
|
|
|
|
|
tie += 1
|
|
|
|
|
|
mark = "="
|
|
|
|
|
|
rows.append(
|
|
|
|
|
|
f" {mark} {src.name:40s} {s0.fitness:.4g} -> {s1.fitness:.4g}"
|
|
|
|
|
|
f" fails {s0.n_fails}->{s1.n_fails}"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
print("\n".join(rows))
|
|
|
|
|
|
n = win + loss + tie
|
|
|
|
|
|
print(f"\n{n} candidates: {win} improved, {loss} regressed, {tie} tied")
|
|
|
|
|
|
print(f"total fails: {fails_before} -> {fails_after}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|