homemaker-layout/experiments/measure_seed_geometry.py

84 lines
3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Raw-seed fail breakdown: proportion-aware split sizing A/B (homemaker-py-leu.2).
Builds the *constructive* seed (operators.constructive_topology, single-stage,
adjacency-aware) with proportion-aware split sizing OFF then ON, scores each raw
seed with native fitness BEFORE the inner loop, and tallies fails by family. The
claim under test (DESIGN.md §12.2): sizing cuts from target areas drops the
geometry family (size/width/proportion/crinkliness) at the raw seed without
regressing the adjacency/access family that §11.6/§11.7 fixed.
Usage:
URB_NO_OCCLUSION=1 python3 experiments/measure_seed_geometry.py <programme_dir> [n_seeds]
"""
from __future__ import annotations
import copy
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from homemaker_layout import dom, fitness, operators, programme # noqa: E402
GEOM = ("perpendicular", "proportion", "size", "width", "crinkliness")
def _categorise(fails: tuple[str, ...]) -> dict[str, int]:
cats = {"geometry": 0, "access_adj": 0, "missing": 0, "other": 0}
for f in fails:
if f.endswith(GEOM) or "edge too long" in f:
cats["geometry"] += 1
elif "access" in f or "inaccessible" in f or "adjacent" in f or "adjacency" in f:
cats["access_adj"] += 1
elif "missing" in f:
cats["missing"] += 1
else:
cats["other"] += 1
return cats
def main() -> int:
programme_dir = Path(sys.argv[1])
n_seeds = int(sys.argv[2]) if len(sys.argv) > 2 else 10
reqs = programme.load_programme_dir(programme_dir)
types = sorted(reqs) + ["C", "O"]
conf, cost = fitness.load_config(programme_dir)
fit = fitness.Fitness(conf, cost)
seed_file = programme_dir / "init.dom"
seed_root = dom.load(str(seed_file))
print(f"programme : {programme_dir.name} ({len(reqs)} codes)")
print(f"seeds : {n_seeds} (single-stage constructive, adjacency-aware)\n")
agg = {0: {"total": 0, "geometry": 0, "access_adj": 0, "missing": 0, "other": 0},
1: {"total": 0, "geometry": 0, "access_adj": 0, "missing": 0, "other": 0}}
for s in range(n_seeds):
for prop in (False, True):
rng = np.random.default_rng(s)
topo = operators.constructive_topology(
seed_root, reqs, rng, types,
adjacency_aware=True, proportion_aware=prop)
_score, fails = fit.score_with_fails(copy.deepcopy(topo))
cats = _categorise(fails)
agg[int(prop)]["total"] += len(fails)
for k, v in cats.items():
agg[int(prop)][k] += v
def _avg(d, k):
return d[k] / n_seeds
print(f"{'family':<12} {'PROP=0 (before)':>16} {'PROP=1 (after)':>16}")
for k in ("geometry", "access_adj", "missing", "other", "total"):
print(f"{k:<12} {_avg(agg[0], k):>16.1f} {_avg(agg[1], k):>16.1f}")
return 0
if __name__ == "__main__":
sys.exit(main())