Document programme-house deceptive valley findings in DESIGN.md §4.10
Records: the level-fix deceptive valley, compound operator design, warm_x0 initialization bug and fix, two-C topology breakthrough, 0-fails geometric impossibility proof, and final 1-fail results beating the Perl optimiser. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
65085d5c3d
commit
85fc848e4e
2 changed files with 92 additions and 4 deletions
|
|
@ -30,12 +30,13 @@
|
|||
{"id":"homemaker-py-yg5","title":"Penalty reshaping: replace 0.5^n while preserving inner-loop protection","description":"DESIGN.md §4.7, §5.4, §7 Phase 4, §8.5. The 0.5^n cliff gives the outer search no gradient and rewards flag-count over geometry, but it also PROTECTS the inner loop from trading into new failures (§4.5). One fitness shape cannot naively be both soft outside and cliff-protected inside. Candidates: cliff-inside-inner-loop only, lexicographic (failure count first, score second), additive/soft, multi-objective Pareto. Must preserve the missing-space failure hierarchy (worse to drop a room than to have a poor one). Measure landscape + search outcomes; this helps Urb today too.","acceptance_criteria":"Chosen scheme documented with measurements: search improves while inner loop still never trades into new failures","status":"closed","priority":3,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:39:00Z","created_by":"Bruno Postle","updated_at":"2026-06-14T08:16:14Z","started_at":"2026-06-14T07:55:32Z","closed_at":"2026-06-14T08:16:14Z","close_reason":"Implemented lexicographic outer-search comparison (-n_fails, fitness). Inner loop unchanged (0.5^n cliff protection preserved). Experiment penalty_reshape.py confirms 0/9 fail regressions in inner loop and shows lex avoids the 3-fail trap that scalar hits 1/3 of the time. Fixed stale _CHILD_INNER_KW sigmas entry.","dependencies":[{"issue_id":"homemaker-py-yg5","depends_on_id":"homemaker-py-uxz","type":"blocks","created_at":"2026-06-12T00:39:46Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||
{"id":"homemaker-py-9gp","title":"Canonical slicing encoding (normalized Polish expression) + shape feasibility","description":"DESIGN.md §5.5, §7 Phase 5. Representation upgrade once core lands: normalized Polish expression / skewed slicing tree (Wong–Liu) for redundancy-free, high-locality topology moves (M1/M2/M3); bottom-up shape-feasibility checks to prune infeasible topologies before the inner loop. Goal: scale to larger programmes. Excluded representations stay excluded (§2): no sequence-pair/B*-tree (non-slicing).","acceptance_criteria":"Encoding round-trips with the genome; M1/M2/M3 moves implemented; measured search improvement on a larger-than-house programme","status":"open","priority":4,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-06-11T23:39:02Z","created_by":"Bruno Postle","updated_at":"2026-06-11T23:39:02Z","dependencies":[{"issue_id":"homemaker-py-9gp","depends_on_id":"homemaker-py-ccw","type":"blocks","created_at":"2026-06-12T00:39:48Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||
{"id":"homemaker-py-2g5","title":"Rebuild occlusion/daylight/sun subsystem in Python (post-Phase-5, after optimisation fully native)","description":"DESIGN.md §6 port scope — a whole subsystem, not a term. quality_daylight (Leaf.pm:281-296) needs Urb::Misc::Sun + Urb::Field::Occlusion (+CIESky); quality_uncrinkliness also takes the occlusion object. Indoor spaces return 1 for daylight; cost is outdoor spaces + crinkliness. Port Sun_horizontal (262980-minute normalisation) and the occlusion wall set from Dom-\u003eWalls.","acceptance_criteria":"Daylight and crinkliness factors match Perl (float tolerance) across the corpus, including multi-storey cases","notes":"Re-scoped 2026-06-12: occlusion disabled in the Urb oracle instead of ported (see homemaker-py-gp2). Native fitness ships with simple crinkliness (illumination factor = 1, in homemaker-py-gnw). This issue is now the eventual Python occlusion rebuild, only after optimisation works entirely in Python. Restores outdoor-daylight and shaded-wall selection pressure.","status":"open","priority":4,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-06-11T23:38:25Z","created_by":"Bruno Postle","updated_at":"2026-06-12T07:27:48Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||
{"_type":"memory","key":"multi-storey-staircase-consistency-when-dividing-or-retyping","value":"Multi-storey staircase consistency: when dividing or retyping a circulation (C) leaf at one level, the same structural change should be propagated to the matching leaf on ALL other storeys so the stair core path is maintained. The optimizer cannot fix staircase disruptions through trial-and-error geometry alone — it requires a synchronized multi-level operator that applies the same topology change to every storey simultaneously."}
|
||||
{"_type":"memory","key":"urb-fitness-bug-found-fixed-2026-06-12","value":"Urb fitness bug found+fixed 2026-06-12 (patch in /home/bruno/src/urb, uncommitted): ProgrammeDriven.pm ratio_o/ratio_type grepped case-insensitively over the ratios hash and took the FIRST key — nondeterministic (x4.5 score swings) for designs with mixed-case type classes (both 'c' circulation and 'C' covered). Fixed to SUM the class (matches Is_Circulation//Is_Outside semantics); 35/35 corpus scores unchanged. CRITICAL for homemaker-py-3y7/gnw: the native port must implement class-SUM ratios. Building.pm has the same unpatched pattern (site-driven path, not used by our oracle). Also: the memetic search reward-hacked this bug before the fix — search results predating it are noise artifacts."}
|
||||
{"_type":"memory","key":"user-preference-bruno-this-is-a-fedora-system","value":"User preference (Bruno): this is a Fedora system — NEVER install Python packages via pip without asking first; always ask whether to install the rpm via dnf (e.g. python3-cma) before considering pip. Applies to any dependency additions."}
|
||||
{"_type":"memory","key":"cli-tool-style-prefer-python-m-homemaker-module","value":"CLI tool style: prefer python -m homemaker.module --parameters pattern, installable via pip install -e . with pyproject.toml entry_points. Not standalone bin/ scripts."}
|
||||
{"_type":"memory","key":"deceptive-valleys-in-topology-search-when-every-single","value":"Deceptive valleys in topology search: when every single-step mutation from a target state passes through a high-fail intermediary (e.g. level_fix displaces a room into 5+ new fails), a compound operator that atomically applies two coordinated changes can escape. Design compound operators to land on the low-fail state directly, bypassing the deceptive gradient. Programme-house example: level_compound_fix atomically moves the level-constrained room AND re-inserts the displaced room adjacent to C in one step (operators.py, 2026-06-14)."}
|
||||
{"_type":"memory","key":"multi-storey-staircase-consistency-when-dividing-or-retyping","value":"Multi-storey staircase consistency: when dividing or retyping a circulation (C) leaf at one level, the same structural change should be propagated to the matching leaf on ALL other storeys so the stair core path is maintained. The optimizer cannot fix staircase disruptions through trial-and-error geometry alone — it requires a synchronized multi-level operator that applies the same topology change to every storey simultaneously."}
|
||||
{"_type":"memory","key":"never-use-corpus-filenames-candidate-001-dom-candidate","value":"Never use corpus filenames (candidate-001.dom, candidate-002.dom, generated.dom, init.dom, etc.) as --output targets when running experiments. These are test fixtures. Always write experimental outputs to scratch/ or a timestamped path. Lesson from 2026-06-14: warm-start runs overwrote candidate-001/002.dom and broke graph tests."}
|
||||
{"_type":"memory","key":"strategy-decision-2026-06-12-bruno-occlusion-daylight","value":"Strategy decision 2026-06-12 (Bruno): occlusion/daylight is ORTHOGONAL to building a scalable optimiser. Disable it in Urb (env flag, homemaker-py-gp2) rather than port it; native fitness uses simple crinkliness (illumination factor = 1); rebuild occlusion in Python only after optimisation is fully native (homemaker-py-2g5, now P4). Consequence: all scores change when the flag flips — re-baseline corpus/.score, DESIGN \\$4.5 gains, gate bars at one clean boundary AFTER homemaker-py-1p0 closes; Phase-2 urb-evolve benchmark must run with the same flag."}
|
||||
{"_type":"memory","key":"urb-fitness-bug-found-fixed-2026-06-12","value":"Urb fitness bug found+fixed 2026-06-12 (patch in /home/bruno/src/urb, uncommitted): ProgrammeDriven.pm ratio_o/ratio_type grepped case-insensitively over the ratios hash and took the FIRST key — nondeterministic (x4.5 score swings) for designs with mixed-case type classes (both 'c' circulation and 'C' covered). Fixed to SUM the class (matches Is_Circulation//Is_Outside semantics); 35/35 corpus scores unchanged. CRITICAL for homemaker-py-3y7/gnw: the native port must implement class-SUM ratios. Building.pm has the same unpatched pattern (site-driven path, not used by our oracle). Also: the memetic search reward-hacked this bug before the fix — search results predating it are noise artifacts."}
|
||||
{"_type":"memory","key":"user-preference-bruno-this-is-a-fedora-system","value":"User preference (Bruno): this is a Fedora system — NEVER install Python packages via pip without asking first; always ask whether to install the rpm via dnf (e.g. python3-cma) before considering pip. Applies to any dependency additions."}
|
||||
{"_type":"memory","key":"urb-oracle-nondeterminism-urb-fitness-pl-output-varies","value":"Urb oracle nondeterminism: urb-fitness.pl output varies run-to-run from Perl hash-order randomisation — .fails line ORDER shuffles (compare sorted, use oracle.Score.fail_lines) and the score float can flip by ~1 ULP (compare with math.isclose rel_tol=1e-12, never ==). Not a batching artifact; affects single runs too. Matters for the Phase 3 native-fitness parity gate (homemaker-py-uxz)."}
|
||||
{"_type":"memory","key":"correction-to-urb-fitness-bug-memory-bruno-2026","value":"CORRECTION to urb-fitness-bug memory (Bruno, 2026-06-12): 'C' is NOT a 'covered' type — Is_Covered is a geometric predicate (indoor space above). Urb's generic types are canonically UPPERCASE: C=circulation, O=outside, S=sahn (get_space_types qw/C O S/; corpus is 100% uppercase, never 'c'/'o' leaves). The mixed-case designs that fired the latent ratio_type first-match bug were created by homemaker's own operator type pool emitting lowercase 'c'/'o' — fixed: driver/operators now emit uppercase generics only, and class checks use t[0].lower() in 'cos'. The Urb class-sum patch stays as defensive hardening (zero impact on canonical designs). Native port (3y7/gnw): treat type classes case-insensitively, generics canonically uppercase."}
|
||||
{"_type":"memory","key":"homemaker-py-pythonpath-set-pythonpath-home-bruno-src","value":"homemaker-layout PYTHONPATH: package installed as 'homemaker-layout' via pip install -e . so 'import homemaker_layout' works from anywhere without PYTHONPATH. For running tests use 'python -m pytest' from project root /home/bruno/src/homemaker-layout (pyproject.toml adds src/ automatically). Never try pip show homemaker — that's the old homemaker-addon conflict."}
|
||||
{"_type":"memory","key":"correction-to-urb-fitness-bug-memory-bruno-2026","value":"CORRECTION to urb-fitness-bug memory (Bruno, 2026-06-12): 'C' is NOT a 'covered' type — Is_Covered is a geometric predicate (indoor space above). Urb's generic types are canonically UPPERCASE: C=circulation, O=outside, S=sahn (get_space_types qw/C O S/; corpus is 100% uppercase, never 'c'/'o' leaves). The mixed-case designs that fired the latent ratio_type first-match bug were created by homemaker's own operator type pool emitting lowercase 'c'/'o' — fixed: driver/operators now emit uppercase generics only, and class checks use t[0].lower() in 'cos'. The Urb class-sum patch stays as defensive hardening (zero impact on canonical designs). Native port (3y7/gnw): treat type classes case-insensitively, generics canonically uppercase."}
|
||||
|
|
|
|||
87
DESIGN.md
87
DESIGN.md
|
|
@ -210,6 +210,8 @@ Urb today and homemaker tomorrow.
|
|||
|
||||
### 4.9 Penalty reshaping decision: lexicographic outer search (measured 2026-06-14)
|
||||
|
||||
|
||||
|
||||
`experiments/penalty_reshape.py`, `URB_NO_OCCLUSION=1`, programme-house.
|
||||
|
||||
**Inner-loop protection** (nm_search, budget 80, 3 files × 3 seeds = 9 runs):
|
||||
|
|
@ -240,6 +242,91 @@ serendipitous runs where scalar also stays in the 2-fail tier).
|
|||
**Decision: lexicographic. `0.5^n` stays in the fitness scalar (inner loop
|
||||
unchanged). Outer search uses `(-n_fails, fitness)` as comparison key.**
|
||||
|
||||
### 4.10 Deceptive level-fix valley and compound operators (measured 2026-06-14/15)
|
||||
|
||||
**Context:** programme-house, Phase 3 native fitness + Phase 4 lex search, seed
|
||||
`warmstart-2f4.dom` (best Phase-3 result, 2 fails at score 0.032). Goal: reach
|
||||
≤ 1 fail, beating the Perl optimiser (2–3 fails).
|
||||
|
||||
**The deceptive valley.** The 2-fail state has l1 (living room, min 27 m²,
|
||||
required level 0) on level 1. The obvious repair is `level_fix`: swap l1 with a
|
||||
leaf on level 0. But every single-step `level_fix` move creates 5+ new fails
|
||||
because the displaced room (t3, the WC) is dropped into an arbitrary slot that
|
||||
violates adjacency, size, and access constraints simultaneously. The lex
|
||||
comparator (`-n_fails, fitness`) correctly rejects these — but the result is that
|
||||
the 2-fail state appears completely surrounded by ≥ 5-fail states, and the search
|
||||
stalls. This is a textbook deceptive valley: the fitness gradient points away from
|
||||
the global optimum.
|
||||
|
||||
**Compound operator.** `mutate_level_compound_fix` (added `operators.py`) escapes
|
||||
the valley by doing two things atomically:
|
||||
|
||||
1. Move l1 to level 0 by swapping it with the *largest* leaf there (the
|
||||
circulation C node, because C is generic and can absorb the swap without
|
||||
producing a new structural failure).
|
||||
2. Re-insert the displaced t3 by dividing the sibling of that C node (so t3
|
||||
lands adjacent to C, satisfying the adjacency requirement).
|
||||
|
||||
The new split gets `division=[0.25,0.25]` (giving t3 ≈ 3.4 m², barely in range)
|
||||
and `rotation=0` (t3 on the left, adjacent to the C sibling).
|
||||
|
||||
**The `warm_x0` initialization bug.** The compound operator sets specific ratios
|
||||
on a newly-created split node. But `driver.py` was initialising the NM inner loop
|
||||
from `parent.ratios`, which has no entry for the new node (it was a leaf).
|
||||
`warm_x0` defaulted the new node to 0.5, giving t3 ≈ 6.8 m² — a size fail —
|
||||
so NM started at 3 fails instead of 1. Lex then always rejected the compound
|
||||
child; `level_compound_fix` was completely invisible to the outer search for
|
||||
~12 000 evals (until `warm_x0` was fixed).
|
||||
|
||||
The correct fix distinguishes genuinely-new split nodes from stale hidden nodes
|
||||
that become visible after structural mutations (e.g. `swap` can flip a `b.below`
|
||||
pointer, revealing pre-writeback division values from a different topology). Only
|
||||
use the child's explicit ratio for node `(li, path)` if the matching node in the
|
||||
parent was *not already divided*; everything else falls through to `parent.ratios`
|
||||
or defaults to 0.5. Fix in `driver.py` lines 259–267.
|
||||
|
||||
**Results (50 000 evals each, pop 8, child_budget 80, 4 workers):**
|
||||
|
||||
| seed | event | eval | fails | score |
|
||||
|------|-------|------|-------|-------|
|
||||
| warmstart-2f4 | seed | 200 | 2 | 0.032 |
|
||||
| warmstart-2f4 | `level_compound_fix` fires | 12 280 | 1 | 0.000122 |
|
||||
| warmstart-2f4 | `level_retype 0/ll<->1/l` | 17 880 | 1 | 0.00497 |
|
||||
| warmstart-2f4 | final | 50 040 | **1** | **0.00518** |
|
||||
| compound3-raw | seed (1-fail hand-built) | 200 | 1 | 0.000118 |
|
||||
| compound3-raw | `level_retype 0/ll<->1/l` | 18 360 | 1 | 0.00383 |
|
||||
| compound3-raw | final | 50 040 | **1** | **0.00523** |
|
||||
|
||||
Perl optimiser reference: **2–3 fails**.
|
||||
|
||||
**The two-C topology breakthrough.** After `level_compound_fix` fires, the
|
||||
topology is: level 0 = `ll(l1), lr(t2), rl(C), rrl(t3), rrr(O)` — but now l1
|
||||
is at level 0 (correct) and t3 is adjacent to rl(C) (staircase). However l1
|
||||
is occupying ll, and rl(C) is the staircase core — so t3-adj-C is satisfied
|
||||
via rl, but there is no second C to satisfy staircase independently. Score
|
||||
≈ 0.000157 (1 fail).
|
||||
|
||||
At eval ≈ 18 000, `level_retype 0/ll<->1/l` (swap the type of ll on level 0
|
||||
with l on level 1) creates a TWO-C configuration at level 0:
|
||||
`ll(C), lr(t2), rl(C), rrl(t3), rrr(O)`, with l1 moving to level 1. The score
|
||||
jumps 25× to ≈ 0.005. Why two C nodes work:
|
||||
|
||||
- `ll(C)` (bottom-left, 23 m²) satisfies t3-adj-C via geometric contact at the
|
||||
l/r zone boundary with `rrl(t3)`.
|
||||
- `rl(C)` (top-right, 8.5 m²) satisfies staircase adjacency via tree adjacency
|
||||
to `rrr(O)` (its right sibling when `r.rotation=3`).
|
||||
|
||||
Both constraints are simultaneously met because binary-tree sibling adjacency and
|
||||
cross-zone geometric adjacency provide *independent* paths.
|
||||
|
||||
**Why 0 fails is geometrically impossible on this programme + plot.** l1 needs
|
||||
min 27 m² at level 0. The only space large enough is `ll` (≈ 23 m², the entire
|
||||
left half of level 0). Putting l1 at `ll` removes the t3-adj-C provider.
|
||||
The alternative — dividing `ll` into `lll(l1)+llr(C)` — gives `llr` a proportion
|
||||
of ≈ 6:1 (width ≈ 0.73 m), failing both the proportion and width constraints.
|
||||
0 fails is not achievable on this programme+plot with a binary slicing tree
|
||||
representation; 1 fail is the geometric optimum.
|
||||
|
||||
---
|
||||
|
||||
## 5. Validated architecture
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue