c3g: circ-per-room granularity knob (circ_divisor) + A/B harness
Threads circ_divisor (default 3 = unchanged) through operators.constructive_topology/lift_base_to_storeys and driver.search/search_staged; env CIRCDIV in run_staged_search.py. Adds experiments/run_c3g_ab.sh. Motivation (DESIGN.md §12.3 diagnostic): the maple shape residual is over-granular construction (73 small leaves -> crinkliness+size). Cheap raw-seed probe: a coarser spine lowers the SHAPE floor (maple 135->110, harbor 83->66) but raises access/adjacency, leaving the raw TOTAL floor flat-to-worse. Because §12.3 showed shape is the HARD residual and access/adjacency are cheap to repair, only an end-to-end A/B settles whether trading them pays — this is the plumbing for that run. Tests green (default path byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
e700090c5c
commit
e9684ea7ef
5 changed files with 71 additions and 19 deletions
|
|
@ -34,7 +34,7 @@
|
||||||
{"id":"homemaker-py-nyb","title":"High-locality topology operators (mutation + subtree crossover)","description":"DESIGN.md §5, §7 Phase 2, §8.4. Mutation moves: divide/undivide leaf, swap children, rotate cut, retype leaf, per-floor delta edits, storey add/delete (cf. Urb Mutate.pm — but geometry sliding belongs to the inner loop, not the operator set). Crossover: area-matched subtree exchange (a subtree = a contiguous region, so crossover is meaningful — Crossover.pm). Operators must be high-locality: small genome change =\u003e small phenotype change, so warm-started inner loops stay cheap.","acceptance_criteria":"Each operator produces valid genomes (oracle scores them without error); locality measured (mean fitness/geometry perturbation per operator)","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:37:27Z","created_by":"Bruno Postle","updated_at":"2026-06-12T13:07:37Z","started_at":"2026-06-12T12:54:23Z","closed_at":"2026-06-12T13:07:37Z","close_reason":"operators.py lands: 7 mutations + area-matched crossover, valid-by-construction via genome.encode repair. 115/115 oracle-valid children; locality measured: geom-pert 0.07-0.33 per op, fitness-pert 0.68-0.99 (0.5^n cliff flags raw moves — warm restart + penalty reshaping confirmed load-bearing). Also fixed dom._link stale below-links on structural mutation.","dependencies":[{"issue_id":"homemaker-py-nyb","depends_on_id":"homemaker-py-k2g","type":"blocks","created_at":"2026-06-12T00:39:36Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
|
{"id":"homemaker-py-nyb","title":"High-locality topology operators (mutation + subtree crossover)","description":"DESIGN.md §5, §7 Phase 2, §8.4. Mutation moves: divide/undivide leaf, swap children, rotate cut, retype leaf, per-floor delta edits, storey add/delete (cf. Urb Mutate.pm — but geometry sliding belongs to the inner loop, not the operator set). Crossover: area-matched subtree exchange (a subtree = a contiguous region, so crossover is meaningful — Crossover.pm). Operators must be high-locality: small genome change =\u003e small phenotype change, so warm-started inner loops stay cheap.","acceptance_criteria":"Each operator produces valid genomes (oracle scores them without error); locality measured (mean fitness/geometry perturbation per operator)","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:37:27Z","created_by":"Bruno Postle","updated_at":"2026-06-12T13:07:37Z","started_at":"2026-06-12T12:54:23Z","closed_at":"2026-06-12T13:07:37Z","close_reason":"operators.py lands: 7 mutations + area-matched crossover, valid-by-construction via genome.encode repair. 115/115 oracle-valid children; locality measured: geom-pert 0.07-0.33 per op, fitness-pert 0.68-0.99 (0.5^n cliff flags raw moves — warm restart + penalty reshaping confirmed load-bearing). Also fixed dom._link stale below-links on structural mutation.","dependencies":[{"issue_id":"homemaker-py-nyb","depends_on_id":"homemaker-py-k2g","type":"blocks","created_at":"2026-06-12T00:39:36Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":1,"comment_count":0}
|
||||||
{"id":"homemaker-py-k2g","title":"Topology genome: base-floor tree + per-floor deltas + type assignment","description":"DESIGN.md §5.2, §7 Phase 2. Genome = base-floor slicing topology (primary) + per-leaf type assignment + per-floor divide/undivide deltas (Below-inheritance as regulariser; cut owned by lowest storey where its path is divided — §10). Must round-trip to/from dom.py Node trees so the oracle and inner loop consume it directly. Includes storey count and per-floor type overrides.","acceptance_criteria":"Genome \u003c-\u003e .dom round-trip on all 35 corpus files preserves fitness; multi-storey wall stacking preserved","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:37:26Z","created_by":"Bruno Postle","updated_at":"2026-06-12T12:52:34Z","started_at":"2026-06-12T10:55:21Z","closed_at":"2026-06-12T12:52:34Z","close_reason":"genome.py encode/decode lands. 35/35 oracle fitness parity after round-trip (flag-on); genome fixed-point + owned-projection tests. Dead-field discovery: corpus upper storeys carry drifted dead divisions (97) and rotations (187) — canonicalised by decode, validated fitness-neutral.","dependency_count":0,"dependent_count":1,"comment_count":0}
|
{"id":"homemaker-py-k2g","title":"Topology genome: base-floor tree + per-floor deltas + type assignment","description":"DESIGN.md §5.2, §7 Phase 2. Genome = base-floor slicing topology (primary) + per-leaf type assignment + per-floor divide/undivide deltas (Below-inheritance as regulariser; cut owned by lowest storey where its path is divided — §10). Must round-trip to/from dom.py Node trees so the oracle and inner loop consume it directly. Includes storey count and per-floor type overrides.","acceptance_criteria":"Genome \u003c-\u003e .dom round-trip on all 35 corpus files preserves fitness; multi-storey wall stacking preserved","status":"closed","priority":2,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:37:26Z","created_by":"Bruno Postle","updated_at":"2026-06-12T12:52:34Z","started_at":"2026-06-12T10:55:21Z","closed_at":"2026-06-12T12:52:34Z","close_reason":"genome.py encode/decode lands. 35/35 oracle fitness parity after round-trip (flag-on); genome fixed-point + owned-projection tests. Dead-field discovery: corpus upper storeys carry drifted dead divisions (97) and rotations (187) — canonicalised by decode, validated fitness-neutral.","dependency_count":0,"dependent_count":1,"comment_count":0}
|
||||||
{"id":"homemaker-py-d0s","title":"Experiment: inner-loop optimiser bake-off at equal oracle budgets","description":"DESIGN.md §7 Phase 1, §8.3. DOF is only ~rooms-1 (6–7 on corpus). Compare Nelder-Mead vs CMA-ES vs batched multi-start pattern search at equal oracle-call budgets, measuring fitness gained per oracle call and wall-clock (batch-friendliness matters — §4.6). Measure, don't commit blind.","acceptance_criteria":"Table of fitness-per-budget across \u003e=3 candidates; one optimiser chosen and recorded in DESIGN.md","status":"closed","priority":2,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:36:59Z","created_by":"Bruno Postle","updated_at":"2026-06-13T08:48:13Z","started_at":"2026-06-12T21:22:15Z","closed_at":"2026-06-13T08:48:13Z","close_reason":"Bake-off complete: CMA-ES confirmed as Phase 1/2 optimiser. NM wins quality per eval but sequential architecture incompatible with batching (§4.6). Compass stalls on narrow valleys. Results in DESIGN.md §8.3 and experiments/bakeoff_innerloop.*","dependencies":[{"issue_id":"homemaker-py-d0s","depends_on_id":"homemaker-py-1p0","type":"blocks","created_at":"2026-06-12T00:39:35Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
{"id":"homemaker-py-d0s","title":"Experiment: inner-loop optimiser bake-off at equal oracle budgets","description":"DESIGN.md §7 Phase 1, §8.3. DOF is only ~rooms-1 (6–7 on corpus). Compare Nelder-Mead vs CMA-ES vs batched multi-start pattern search at equal oracle-call budgets, measuring fitness gained per oracle call and wall-clock (batch-friendliness matters — §4.6). Measure, don't commit blind.","acceptance_criteria":"Table of fitness-per-budget across \u003e=3 candidates; one optimiser chosen and recorded in DESIGN.md","status":"closed","priority":2,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:36:59Z","created_by":"Bruno Postle","updated_at":"2026-06-13T08:48:13Z","started_at":"2026-06-12T21:22:15Z","closed_at":"2026-06-13T08:48:13Z","close_reason":"Bake-off complete: CMA-ES confirmed as Phase 1/2 optimiser. NM wins quality per eval but sequential architecture incompatible with batching (§4.6). Compass stalls on narrow valleys. Results in DESIGN.md §8.3 and experiments/bakeoff_innerloop.*","dependencies":[{"issue_id":"homemaker-py-d0s","depends_on_id":"homemaker-py-1p0","type":"blocks","created_at":"2026-06-12T00:39:35Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"homemaker-py-c3g","title":"Construction granularity / leaf-shape lever for the geometry residual","description":"HYPOTHESIS with measured motivation (DESIGN.md §12.3 residual diagnostic), unproven — must be A/B'd vs the §12.2 baseline before adoption (same discipline as §11/§12 levers). Finding: maple-court shape fails are UNIFORM (~68/73 leaves fail), at only 0.44 plot utilisation, dominated by crinkliness (perimeter/area) then size (undersize). So the residual is NOT placement-mismatch (no good leaves to place into) and NOT density/area-bound — it is OVER-GRANULAR construction: 73 small leaves for 52 rooms =\u003e high perimeter/area + below-target sizes. Candidate levers (construction side): fewer/larger leaves, merge or share leaves across same-class rooms, coarser circulation spine, or a granularity that trades adjacency coverage for leaf shape. Cheap first experiment: vary the circulation-per-room ratio and/or a min-leaf-area floor in constructive_topology, measure shape-fail floor (operators.predicted_shape_fails) and end-to-end fails on maple+harbor. Alternative outcome to accept: 52 distinct rooms cannot be well-shaped as 52 leaves at this density (geometry floor of the slicing representation). Files: operators.constructive_topology/_grow_leaves/_assign_adjacency_aware.","status":"open","priority":3,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-06-21T19:55:38Z","created_by":"Bruno Postle","updated_at":"2026-06-21T19:55:38Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"homemaker-py-c3g","title":"Construction granularity / leaf-shape lever for the geometry residual","description":"HYPOTHESIS with measured motivation (DESIGN.md §12.3 residual diagnostic), unproven — must be A/B'd vs the §12.2 baseline before adoption (same discipline as §11/§12 levers). Finding: maple-court shape fails are UNIFORM (~68/73 leaves fail), at only 0.44 plot utilisation, dominated by crinkliness (perimeter/area) then size (undersize). So the residual is NOT placement-mismatch (no good leaves to place into) and NOT density/area-bound — it is OVER-GRANULAR construction: 73 small leaves for 52 rooms =\u003e high perimeter/area + below-target sizes. Candidate levers (construction side): fewer/larger leaves, merge or share leaves across same-class rooms, coarser circulation spine, or a granularity that trades adjacency coverage for leaf shape. Cheap first experiment: vary the circulation-per-room ratio and/or a min-leaf-area floor in constructive_topology, measure shape-fail floor (operators.predicted_shape_fails) and end-to-end fails on maple+harbor. Alternative outcome to accept: 52 distinct rooms cannot be well-shaped as 52 leaves at this density (geometry floor of the slicing representation). Files: operators.constructive_topology/_grow_leaves/_assign_adjacency_aware.","status":"in_progress","priority":3,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-21T19:55:38Z","created_by":"Bruno Postle","updated_at":"2026-06-21T19:59:09Z","started_at":"2026-06-21T19:59:09Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"homemaker-py-ld5","title":"Adjacency-aware lift_base_to_storeys + secondary adjacencies","description":"Follow-up to s44 (DESIGN.md §11.6). s44 made constructive_topology cluster rooms around a connected-dominating-set circulation spine (geometric leaf_graph), cutting harbor single-stage fails 110-\u003e90.7 mean and beating the staged §11.3 best of 95. Two gaps remain: (1) lift_base_to_storeys (staged Stage-2 upper floors) still assigns leaf types at RANDOM — port the _assign_adjacency_aware CDS approach to it so staged search benefits too. (2) Secondary adjacencies (k1\u003c-\u003eda1, da1\u003c-\u003eo, etc., ~4 harbor rooms) are not clustered — extend _assign_adjacency_aware to place rooms with non-c adjacency reqs next to their required neighbour after the c-spine is laid.","notes":"DONE positive, DESIGN.md §11.7. Adjacency-aware lift (CDS seeded from inherited core) + secondary-adjacency room placement. Staged harbor 20k evals: ADJ0 mean 99.0 (=§11.4 baseline), ADJ1 mean 85.3 (-14%, best 78). New best harbor overall. operators 22 tests pass.","status":"closed","priority":3,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-19T08:12:11Z","created_by":"Bruno Postle","updated_at":"2026-06-19T10:41:14Z","started_at":"2026-06-19T08:33:43Z","closed_at":"2026-06-19T10:41:14Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"homemaker-py-ld5","title":"Adjacency-aware lift_base_to_storeys + secondary adjacencies","description":"Follow-up to s44 (DESIGN.md §11.6). s44 made constructive_topology cluster rooms around a connected-dominating-set circulation spine (geometric leaf_graph), cutting harbor single-stage fails 110-\u003e90.7 mean and beating the staged §11.3 best of 95. Two gaps remain: (1) lift_base_to_storeys (staged Stage-2 upper floors) still assigns leaf types at RANDOM — port the _assign_adjacency_aware CDS approach to it so staged search benefits too. (2) Secondary adjacencies (k1\u003c-\u003eda1, da1\u003c-\u003eo, etc., ~4 harbor rooms) are not clustered — extend _assign_adjacency_aware to place rooms with non-c adjacency reqs next to their required neighbour after the c-spine is laid.","notes":"DONE positive, DESIGN.md §11.7. Adjacency-aware lift (CDS seeded from inherited core) + secondary-adjacency room placement. Staged harbor 20k evals: ADJ0 mean 99.0 (=§11.4 baseline), ADJ1 mean 85.3 (-14%, best 78). New best harbor overall. operators 22 tests pass.","status":"closed","priority":3,"issue_type":"feature","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-19T08:12:11Z","created_by":"Bruno Postle","updated_at":"2026-06-19T10:41:14Z","started_at":"2026-06-19T08:33:43Z","closed_at":"2026-06-19T10:41:14Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"homemaker-py-n5k","title":"Config inheritance: load parent patterns.config/costs.config as base layer","description":"urb-evolve.pl walks up one directory level and loads ../patterns.config and ../costs.config as a base configuration before merging the programme directory's own files on top (local keys win). homemaker-evolve and fitness.load_config should replicate this: when loading a programme directory, first check the parent for each config file and load it, then deep-merge the local file over the top. This lets shared defaults live in a project root while individual programmes only override what differs.","status":"closed","priority":3,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-06-14T06:22:38Z","created_by":"Bruno Postle","updated_at":"2026-06-14T06:50:27Z","closed_at":"2026-06-14T06:50:27Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
{"id":"homemaker-py-n5k","title":"Config inheritance: load parent patterns.config/costs.config as base layer","description":"urb-evolve.pl walks up one directory level and loads ../patterns.config and ../costs.config as a base configuration before merging the programme directory's own files on top (local keys win). homemaker-evolve and fitness.load_config should replicate this: when loading a programme directory, first check the parent for each config file and load it, then deep-merge the local file over the top. This lets shared defaults live in a project root while individual programmes only override what differs.","status":"closed","priority":3,"issue_type":"feature","owner":"bruno@postle.net","created_at":"2026-06-14T06:22:38Z","created_by":"Bruno Postle","updated_at":"2026-06-14T06:50:27Z","closed_at":"2026-06-14T06:50:27Z","close_reason":"Closed","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"id":"homemaker-py-9t6","title":"Package install: pyproject.toml with entry points","description":"The project currently requires PYTHONPATH=/home/bruno/src/homemaker-py/src and is run via 'python3 experiments/...'. There is no installable package. Add a pyproject.toml with: package discovery for src/homemaker/, a [project.scripts] entry point for homemaker-evolve (homemaker-py-2wc), and minimal metadata. After 'pip install -e .' the tool should be on PATH and importable without PYTHONPATH. Keep the existing pyproject.toml if one exists and extend it.","acceptance_criteria":"'pip install -e .' succeeds; 'homemaker-evolve --help' works from any directory; 'import homemaker' works without PYTHONPATH","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-13T21:52:35Z","created_by":"Bruno Postle","updated_at":"2026-06-14T07:18:42Z","started_at":"2026-06-14T06:52:28Z","closed_at":"2026-06-14T07:18:42Z","close_reason":"pyproject.toml already had entry point; renamed package to homemaker-layout throughout, GitHub repo renamed, pip install -e . verified","dependencies":[{"issue_id":"homemaker-py-9t6","depends_on_id":"homemaker-py-2wc","type":"blocks","created_at":"2026-06-13T22:52:41Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
{"id":"homemaker-py-9t6","title":"Package install: pyproject.toml with entry points","description":"The project currently requires PYTHONPATH=/home/bruno/src/homemaker-py/src and is run via 'python3 experiments/...'. There is no installable package. Add a pyproject.toml with: package discovery for src/homemaker/, a [project.scripts] entry point for homemaker-evolve (homemaker-py-2wc), and minimal metadata. After 'pip install -e .' the tool should be on PATH and importable without PYTHONPATH. Keep the existing pyproject.toml if one exists and extend it.","acceptance_criteria":"'pip install -e .' succeeds; 'homemaker-evolve --help' works from any directory; 'import homemaker' works without PYTHONPATH","status":"closed","priority":3,"issue_type":"task","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-13T21:52:35Z","created_by":"Bruno Postle","updated_at":"2026-06-14T07:18:42Z","started_at":"2026-06-14T06:52:28Z","closed_at":"2026-06-14T07:18:42Z","close_reason":"pyproject.toml already had entry point; renamed package to homemaker-layout throughout, GitHub repo renamed, pip install -e . verified","dependencies":[{"issue_id":"homemaker-py-9t6","depends_on_id":"homemaker-py-2wc","type":"blocks","created_at":"2026-06-13T22:52:41Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||||
|
|
@ -46,17 +46,17 @@
|
||||||
{"id":"homemaker-py-can","title":"Programme width defaults: t3 contradiction (impossible width_inside default)","description":"DESIGN.md §8.2, confirmed in source. t3 (3 m2 WC) has no width spec so inherits width_inside [4.0, 1.0] (Fitness/Base.pm:60) — geometrically impossible; designs 'pass' only by failing size instead. Fix AFTER faithful-port validation (port-faithfully-first policy, §8.1): a sane width default scaled to area (e.g. sqrt(area/proportion)) or per-room widths in patterns.config. Applies to native fitness; optionally upstream to Urb.","acceptance_criteria":"No programme space has a default width incompatible with its target area; corpus re-scored and effect documented","status":"closed","priority":3,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:39:01Z","created_by":"Bruno Postle","updated_at":"2026-06-13T21:21:37Z","started_at":"2026-06-13T21:16:11Z","closed_at":"2026-06-13T21:21:37Z","close_reason":"Fixed in get_space_params: when a programme space has no explicit 'width', derive target from sqrt(size/proportion) instead of falling back to width_inside [4.0, 1.0]. Re-scored 35-file corpus: 32 files improved (+1-121%), 5 files lost spurious width fails. All 109 tests pass. Upstream Perl fix tracked as homemaker-py-8fe.","dependencies":[{"issue_id":"homemaker-py-can","depends_on_id":"homemaker-py-uxz","type":"blocks","created_at":"2026-06-12T00:39:47Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
{"id":"homemaker-py-can","title":"Programme width defaults: t3 contradiction (impossible width_inside default)","description":"DESIGN.md §8.2, confirmed in source. t3 (3 m2 WC) has no width spec so inherits width_inside [4.0, 1.0] (Fitness/Base.pm:60) — geometrically impossible; designs 'pass' only by failing size instead. Fix AFTER faithful-port validation (port-faithfully-first policy, §8.1): a sane width default scaled to area (e.g. sqrt(area/proportion)) or per-room widths in patterns.config. Applies to native fitness; optionally upstream to Urb.","acceptance_criteria":"No programme space has a default width incompatible with its target area; corpus re-scored and effect documented","status":"closed","priority":3,"issue_type":"bug","assignee":"Bruno Postle","owner":"bruno@postle.net","created_at":"2026-06-11T23:39:01Z","created_by":"Bruno Postle","updated_at":"2026-06-13T21:21:37Z","started_at":"2026-06-13T21:16:11Z","closed_at":"2026-06-13T21:21:37Z","close_reason":"Fixed in get_space_params: when a programme space has no explicit 'width', derive target from sqrt(size/proportion) instead of falling back to width_inside [4.0, 1.0]. Re-scored 35-file corpus: 32 files improved (+1-121%), 5 files lost spurious width fails. All 109 tests pass. Upstream Perl fix tracked as homemaker-py-8fe.","dependencies":[{"issue_id":"homemaker-py-can","depends_on_id":"homemaker-py-uxz","type":"blocks","created_at":"2026-06-12T00:39:47Z","created_by":"Bruno Postle","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0}
|
||||||
{"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-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-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.\nReframed 2026-06-17: orthogonal to epic homemaker-py-c4c. This is fitness FIDELITY (restoring daylight + shaded-wall selection pressure to match Perl), not search CAPABILITY — it changes what 'good' means, not the search's ability to find good. It will NOT improve final designs in the sense currently sought. Stays P4, deferred until the topology-search-quality epic lands and optimisation is fully native.","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-17T19:14:48Z","dependency_count":0,"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.\nReframed 2026-06-17: orthogonal to epic homemaker-py-c4c. This is fitness FIDELITY (restoring daylight + shaded-wall selection pressure to match Perl), not search CAPABILITY — it changes what 'good' means, not the search's ability to find good. It will NOT improve final designs in the sense currently sought. Stays P4, deferred until the topology-search-quality epic lands and optimisation is fully native.","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-17T19:14:48Z","dependency_count":0,"dependent_count":0,"comment_count":0}
|
||||||
{"_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":"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":"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":"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":"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":"proportion-aware-constructive-seeding-leu-2-12-2","value":"Proportion-aware constructive seeding (leu.2/§12.2): sizing seed cuts from target AREAS only regresses (thin slivers wreck aspect); you must ALSO pick each cut's rotation for child squareness. It is a convergence ACCELERATOR via a deeper local optimum around the constructed topology: wins where that topology is roughly right and budget is scarce (harbor -13%, maple -10% at 20k evals) but DELAYS small programmes where the seed must be restructured by undivide (programme-house regresses at fixed budget, yet reaches the floor given budget - speed, not asymptote). Default-on. Also: n_storeys must honour storey_minimum, not just level: keys (programme-house storey_minimum:2, all rooms level:0 - was seeded 1 storey short; cq1)."}
|
|
||||||
{"_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":"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":"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":"warm-x0-initialization-bug-pattern-when-a-topology","value":"warm_x0 initialization bug pattern: when a topology operator explicitly sets division ratios on a newly-created node (e.g. compound_fix sets node.division=[0.25,0.25] for t3), parent.ratios has no entry for that node (it was a leaf). warm_x0 defaults it to 0.5, corrupting the inner loop's starting point and making the operator invisible to lex comparison. Fix: only propagate child ratios for nodes where the parent node was NOT already divided; stale hidden nodes revealed by structural mutations (swap flipping b.below) must NOT contribute their pre-writeback values. See driver.py lines 259-267 (fixed 2026-06-14)."}
|
|
||||||
{"_type":"memory","key":"adjacency-in-binary-slicing-tree-is-structural-not","value":"Adjacency in binary slicing tree is structural, not geometric: the inner-loop NM cannot fix topological adjacency failures. Two paths exist: (1) tree-sibling adjacency — a node is adjacent to its sibling in the tree; (2) cross-zone geometric adjacency — leaves from different subtrees that happen to share a boundary. Staircase/adjacency fails require a topology mutation that changes which nodes are siblings or which zones touch. This was proved empirically on programme-house: staircase fail from rot=0 layout could not be fixed by NM but was fixed by level_retype creating a two-C topology (2026-06-14/15)."}
|
|
||||||
{"_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":"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":"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":"programme-house-optimisation-result-2026-06-14-15","value":"Programme-house optimisation result (2026-06-14/15): best achievable is 1 fail (l1 wrong level, score ~0.005). 0 fails is geometrically impossible: l1 (min 27m²) must occupy ll (~23m²) at level 0, which eliminates the t3-adj-C provider; dividing ll into lll(l1)+llr(C) gives llr proportion ~6:1 (fails). Python memetic optimizer achieves 1 fail in 50k evals vs Perl optimiser's 2-3 fails. Winning topology: TWO C nodes at level 0 — ll(C) for t3-adj-C via geometric contact, rl(C) for staircase via tree-sibling adjacency to rrr(O). Best .dom: scratch/from-warmstart-fixed.dom and scratch/from-compound3-fixed.dom."}
|
{"_type":"memory","key":"programme-house-optimisation-result-2026-06-14-15","value":"Programme-house optimisation result (2026-06-14/15): best achievable is 1 fail (l1 wrong level, score ~0.005). 0 fails is geometrically impossible: l1 (min 27m²) must occupy ll (~23m²) at level 0, which eliminates the t3-adj-C provider; dividing ll into lll(l1)+llr(C) gives llr proportion ~6:1 (fails). Python memetic optimizer achieves 1 fail in 50k evals vs Perl optimiser's 2-3 fails. Winning topology: TWO C nodes at level 0 — ll(C) for t3-adj-C via geometric contact, rl(C) for staircase via tree-sibling adjacency to rrr(O). Best .dom: scratch/from-warmstart-fixed.dom and scratch/from-compound3-fixed.dom."}
|
||||||
|
{"_type":"memory","key":"proportion-aware-constructive-seeding-leu-2-12-2","value":"Proportion-aware constructive seeding (leu.2/§12.2): sizing seed cuts from target AREAS only regresses (thin slivers wreck aspect); you must ALSO pick each cut's rotation for child squareness. It is a convergence ACCELERATOR via a deeper local optimum around the constructed topology: wins where that topology is roughly right and budget is scarce (harbor -13%, maple -10% at 20k evals) but DELAYS small programmes where the seed must be restructured by undivide (programme-house regresses at fixed budget, yet reaches the floor given budget - speed, not asymptote). Default-on. Also: n_storeys must honour storey_minimum, not just level: keys (programme-house storey_minimum:2, all rooms level:0 - was seeded 1 storey short; cq1)."}
|
||||||
|
{"_type":"memory","key":"adjacency-in-binary-slicing-tree-is-structural-not","value":"Adjacency in binary slicing tree is structural, not geometric: the inner-loop NM cannot fix topological adjacency failures. Two paths exist: (1) tree-sibling adjacency — a node is adjacent to its sibling in the tree; (2) cross-zone geometric adjacency — leaves from different subtrees that happen to share a boundary. Staircase/adjacency fails require a topology mutation that changes which nodes are siblings or which zones touch. This was proved empirically on programme-house: staircase fail from rot=0 layout could not be fixed by NM but was fixed by level_retype creating a two-C topology (2026-06-14/15)."}
|
||||||
|
{"_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":"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":"warm-x0-initialization-bug-pattern-when-a-topology","value":"warm_x0 initialization bug pattern: when a topology operator explicitly sets division ratios on a newly-created node (e.g. compound_fix sets node.division=[0.25,0.25] for t3), parent.ratios has no entry for that node (it was a leaf). warm_x0 defaults it to 0.5, corrupting the inner loop's starting point and making the operator invisible to lex comparison. Fix: only propagate child ratios for nodes where the parent node was NOT already divided; stale hidden nodes revealed by structural mutations (swap flipping b.below) must NOT contribute their pre-writeback values. See driver.py lines 259-267 (fixed 2026-06-14)."}
|
||||||
|
{"_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":"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":"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":"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":"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)."}
|
||||||
|
|
|
||||||
39
experiments/run_c3g_ab.sh
Executable file
39
experiments/run_c3g_ab.sh
Executable file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# c3g granularity A/B (DESIGN.md §12.3 follow-up): does a coarser circulation
|
||||||
|
# spine (fewer, larger leaves -> lower shape floor) pay end-to-end, given that
|
||||||
|
# §12.3 showed shape fails are the HARD residual and access/adjacency are cheap
|
||||||
|
# to repair? Trade hard-shape for easy-access at the seed and measure.
|
||||||
|
#
|
||||||
|
# Reuses the §12.3 div=3 baseline (maple 126/148/134, harbor 72/81/69); runs
|
||||||
|
# div=6 and div=8 arms + a div=3 seed-0 control to confirm reproduction.
|
||||||
|
set -u
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
BUDGET="${1:-20000}"
|
||||||
|
OUT=scratch/c3g_ab; mkdir -p "$OUT"
|
||||||
|
TSV=scratch/c3g_results.tsv
|
||||||
|
[ -f "$TSV" ] || printf 'programme\tseed\tcircdiv\tfails\ttopologies\telapsed_s\n' > "$TSV"
|
||||||
|
|
||||||
|
run() { # programme seed div
|
||||||
|
local prog="$1" seed="$2" div="$3"
|
||||||
|
local log="$OUT/${prog}_div${div}_s${seed}.log"
|
||||||
|
echo ">>> $prog seed=$seed circdiv=$div"
|
||||||
|
local t0; t0=$(date +%s)
|
||||||
|
env URB_NO_OCCLUSION=1 CIRCDIV="$div" \
|
||||||
|
python3 experiments/run_staged_search.py "examples/$prog" "$BUDGET" "$seed" \
|
||||||
|
"examples/$prog/init.dom" "$OUT/${prog}_div${div}_s${seed}.dom" > "$log" 2>&1
|
||||||
|
local t1; t1=$(date +%s)
|
||||||
|
local fails topos
|
||||||
|
fails=$(grep 're-scored (native)' "$log" | tail -1 | sed -n 's/.*(\([0-9]*\) fails).*/\1/p')
|
||||||
|
topos=$(grep -m1 '^evals' "$log" | sed -n 's/.*across \([0-9]*\) topologies.*/\1/p')
|
||||||
|
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$prog" "$seed" "$div" "${fails:-ERR}" "${topos:-?}" "$((t1-t0))" >> "$TSV"
|
||||||
|
echo " -> ${fails:-ERR} fails, ${topos:-?} topologies, $((t1-t0))s"
|
||||||
|
}
|
||||||
|
|
||||||
|
# control (reproduce §12.3 div=3) then the reduced-granularity arms
|
||||||
|
run maple-court 0 3
|
||||||
|
for seed in 0 1 2; do run maple-court "$seed" 6; run maple-court "$seed" 8; done
|
||||||
|
run harbor-house 0 3
|
||||||
|
for seed in 0 1 2; do run harbor-house "$seed" 6; done
|
||||||
|
|
||||||
|
echo "=== c3g sweep complete ==="
|
||||||
|
column -t -s $'\t' "$TSV"
|
||||||
|
|
@ -61,6 +61,7 @@ def main() -> int:
|
||||||
feas = os.environ.get("FEAS", "0") == "1" # 9gp.1 shape-feasibility pre-filter A/B
|
feas = os.environ.get("FEAS", "0") == "1" # 9gp.1 shape-feasibility pre-filter A/B
|
||||||
_ms = os.environ.get("MAXSHAPE") # 9gp.1 prune threshold (shape-fail count)
|
_ms = os.environ.get("MAXSHAPE") # 9gp.1 prune threshold (shape-fail count)
|
||||||
max_shape = int(_ms) if _ms else None
|
max_shape = int(_ms) if _ms else None
|
||||||
|
circ_div = int(os.environ.get("CIRCDIV", "3")) # c3g circ-per-room granularity knob
|
||||||
|
|
||||||
print(f"programme : {programme_dir.name}")
|
print(f"programme : {programme_dir.name}")
|
||||||
print(f"seed : {seed_file.name}")
|
print(f"seed : {seed_file.name}")
|
||||||
|
|
@ -73,6 +74,7 @@ def main() -> int:
|
||||||
print(f"prop_aware: {prop}")
|
print(f"prop_aware: {prop}")
|
||||||
print(f"reassoc : {reassoc}")
|
print(f"reassoc : {reassoc}")
|
||||||
print(f"feas_filt : {feas} (max_shape={max_shape})")
|
print(f"feas_filt : {feas} (max_shape={max_shape})")
|
||||||
|
print(f"circ_div : {circ_div}")
|
||||||
print(flush=True)
|
print(flush=True)
|
||||||
|
|
||||||
seed_root = dom.load(str(seed_file))
|
seed_root = dom.load(str(seed_file))
|
||||||
|
|
@ -98,6 +100,7 @@ def main() -> int:
|
||||||
enable_reassociate=reassoc,
|
enable_reassociate=reassoc,
|
||||||
feasibility_filter=feas,
|
feasibility_filter=feas,
|
||||||
feasibility_max_shape_fails=max_shape,
|
feasibility_max_shape_fails=max_shape,
|
||||||
|
circ_divisor=circ_div,
|
||||||
)
|
)
|
||||||
|
|
||||||
elapsed = time.perf_counter() - t0
|
elapsed = time.perf_counter() - t0
|
||||||
|
|
|
||||||
|
|
@ -186,6 +186,7 @@ def search(
|
||||||
enable_reassociate: bool = False,
|
enable_reassociate: bool = False,
|
||||||
feasibility_filter: bool = False,
|
feasibility_filter: bool = False,
|
||||||
feasibility_max_shape_fails: int | None = None,
|
feasibility_max_shape_fails: int | None = None,
|
||||||
|
circ_divisor: int = 3,
|
||||||
) -> SearchResult:
|
) -> SearchResult:
|
||||||
"""Run the memetic loop from ``seed_root`` until ``budget`` oracle
|
"""Run the memetic loop from ``seed_root`` until ``budget`` oracle
|
||||||
evaluations are consumed. Returns the best individual found; its ``root``
|
evaluations are consumed. Returns the best individual found; its ``root``
|
||||||
|
|
@ -376,7 +377,8 @@ def search(
|
||||||
topo = operators.constructive_topology(
|
topo = operators.constructive_topology(
|
||||||
seed_root, reqs, rng, types, min_storeys=min_storeys,
|
seed_root, reqs, rng, types, min_storeys=min_storeys,
|
||||||
adjacency_aware=seed_adjacency_aware,
|
adjacency_aware=seed_adjacency_aware,
|
||||||
proportion_aware=seed_proportion_aware)
|
proportion_aware=seed_proportion_aware,
|
||||||
|
circ_divisor=circ_divisor)
|
||||||
return (topo, None, child_budget, {}, f"construct/{tag}")
|
return (topo, None, child_budget, {}, f"construct/{tag}")
|
||||||
n = int(rng.integers(max(1, n_target - 1), n_target + 2))
|
n = int(rng.integers(max(1, n_target - 1), n_target + 2))
|
||||||
return (random_topology(seed_root, n, rng, types), None, child_budget,
|
return (random_topology(seed_root, n, rng, types), None, child_budget,
|
||||||
|
|
@ -500,6 +502,7 @@ def search_staged(
|
||||||
enable_reassociate: bool = False,
|
enable_reassociate: bool = False,
|
||||||
feasibility_filter: bool = False,
|
feasibility_filter: bool = False,
|
||||||
feasibility_max_shape_fails: int | None = None,
|
feasibility_max_shape_fails: int | None = None,
|
||||||
|
circ_divisor: int = 3,
|
||||||
) -> SearchResult:
|
) -> SearchResult:
|
||||||
"""Staged per-floor topology search (DESIGN.md §11.3, ``homemaker-py-c4c.3``).
|
"""Staged per-floor topology search (DESIGN.md §11.3, ``homemaker-py-c4c.3``).
|
||||||
|
|
||||||
|
|
@ -546,7 +549,8 @@ def search_staged(
|
||||||
seed_proportion_aware=seed_proportion_aware,
|
seed_proportion_aware=seed_proportion_aware,
|
||||||
enable_reassociate=enable_reassociate,
|
enable_reassociate=enable_reassociate,
|
||||||
feasibility_filter=feasibility_filter,
|
feasibility_filter=feasibility_filter,
|
||||||
feasibility_max_shape_fails=feasibility_max_shape_fails)
|
feasibility_max_shape_fails=feasibility_max_shape_fails,
|
||||||
|
circ_divisor=circ_divisor)
|
||||||
|
|
||||||
if types is None:
|
if types is None:
|
||||||
types = sorted(reqs) + ["C", "O"]
|
types = sorted(reqs) + ["C", "O"]
|
||||||
|
|
@ -575,6 +579,7 @@ def search_staged(
|
||||||
enable_reassociate=enable_reassociate,
|
enable_reassociate=enable_reassociate,
|
||||||
feasibility_filter=feasibility_filter,
|
feasibility_filter=feasibility_filter,
|
||||||
feasibility_max_shape_fails=feasibility_max_shape_fails,
|
feasibility_max_shape_fails=feasibility_max_shape_fails,
|
||||||
|
circ_divisor=circ_divisor,
|
||||||
)
|
)
|
||||||
best_base = r1.best.root
|
best_base = r1.best.root
|
||||||
_log(f"[staged] stage 1 done: base {r1.best.fitness:.6g} "
|
_log(f"[staged] stage 1 done: base {r1.best.fitness:.6g} "
|
||||||
|
|
@ -591,7 +596,8 @@ def search_staged(
|
||||||
return operators.lift_base_to_storeys(
|
return operators.lift_base_to_storeys(
|
||||||
best_base, upper, rng2, types, reqs=reqs,
|
best_base, upper, rng2, types, reqs=reqs,
|
||||||
adjacency_aware=seed_adjacency_aware,
|
adjacency_aware=seed_adjacency_aware,
|
||||||
proportion_aware=seed_proportion_aware)
|
proportion_aware=seed_proportion_aware,
|
||||||
|
circ_divisor=circ_divisor)
|
||||||
|
|
||||||
_log(f"[staged] stage 2: upper floors as deltas, budget {b2}, base_p {base_p}")
|
_log(f"[staged] stage 2: upper floors as deltas, budget {b2}, base_p {base_p}")
|
||||||
r2 = search(
|
r2 = search(
|
||||||
|
|
@ -608,6 +614,7 @@ def search_staged(
|
||||||
enable_reassociate=enable_reassociate,
|
enable_reassociate=enable_reassociate,
|
||||||
feasibility_filter=feasibility_filter,
|
feasibility_filter=feasibility_filter,
|
||||||
feasibility_max_shape_fails=feasibility_max_shape_fails,
|
feasibility_max_shape_fails=feasibility_max_shape_fails,
|
||||||
|
circ_divisor=circ_divisor,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stitch the two stages into one accounting (total evals, tagged history).
|
# Stitch the two stages into one accounting (total evals, tagged history).
|
||||||
|
|
|
||||||
|
|
@ -567,7 +567,8 @@ def _assign_adjacency_aware(lvl: dom.Node, room_codes: list[str], reqs,
|
||||||
def constructive_topology(seed_root: dom.Node, reqs, rng: np.random.Generator,
|
def constructive_topology(seed_root: dom.Node, reqs, rng: np.random.Generator,
|
||||||
types: list[str], min_storeys: int = 1,
|
types: list[str], min_storeys: int = 1,
|
||||||
adjacency_aware: bool = True,
|
adjacency_aware: bool = True,
|
||||||
proportion_aware: bool = True) -> dom.Node:
|
proportion_aware: bool = True,
|
||||||
|
circ_divisor: int = 3) -> dom.Node:
|
||||||
"""Build a seed that instantiates every required space by construction.
|
"""Build a seed that instantiates every required space by construction.
|
||||||
|
|
||||||
The §11.0 diagnosis: random divide+retype chains leave required programme
|
The §11.0 diagnosis: random divide+retype chains leave required programme
|
||||||
|
|
@ -618,7 +619,8 @@ def constructive_topology(seed_root: dom.Node, reqs, rng: np.random.Generator,
|
||||||
# then assign so every room is adjacent to it (s44). Geometry must be
|
# then assign so every room is adjacent to it (s44). Geometry must be
|
||||||
# available to read the leaf-adjacency graph; _grow_leaves leaves the
|
# available to read the leaf-adjacency graph; _grow_leaves leaves the
|
||||||
# tree finalisable and geometry.leaf_graph derives coords on demand.
|
# tree finalisable and geometry.leaf_graph derives coords on demand.
|
||||||
n_circ = max(1, -(-len(rooms) // 3)) # ceil(rooms / 3)
|
# c3g granularity knob: ~one circ per `circ_divisor` rooms (default 3).
|
||||||
|
n_circ = max(1, -(-len(rooms) // circ_divisor))
|
||||||
_grow_leaves(lvl, len(rooms) + 1 + n_circ, rng)
|
_grow_leaves(lvl, len(rooms) + 1 + n_circ, rng)
|
||||||
dom._link(child)
|
dom._link(child)
|
||||||
_assign_adjacency_aware(lvl, rooms, reqs, rng)
|
_assign_adjacency_aware(lvl, rooms, reqs, rng)
|
||||||
|
|
@ -645,7 +647,8 @@ def constructive_topology(seed_root: dom.Node, reqs, rng: np.random.Generator,
|
||||||
def lift_base_to_storeys(base_root: dom.Node, upper_buckets: list[dict[str, int]],
|
def lift_base_to_storeys(base_root: dom.Node, upper_buckets: list[dict[str, int]],
|
||||||
rng: np.random.Generator, types: list[str],
|
rng: np.random.Generator, types: list[str],
|
||||||
reqs=None, adjacency_aware: bool = True,
|
reqs=None, adjacency_aware: bool = True,
|
||||||
proportion_aware: bool = True) -> dom.Node:
|
proportion_aware: bool = True,
|
||||||
|
circ_divisor: int = 3) -> dom.Node:
|
||||||
"""Stack upper storeys onto an evolved single-storey base (DESIGN.md §11.3).
|
"""Stack upper storeys onto an evolved single-storey base (DESIGN.md §11.3).
|
||||||
|
|
||||||
Stage 2 seeder: the Stage-1 base is the credible ground floor and is left
|
Stage 2 seeder: the Stage-1 base is the credible ground floor and is left
|
||||||
|
|
@ -685,7 +688,7 @@ def lift_base_to_storeys(base_root: dom.Node, upper_buckets: list[dict[str, int]
|
||||||
# 3 rooms, the inherited core counted) and assign rooms around it via
|
# 3 rooms, the inherited core counted) and assign rooms around it via
|
||||||
# the geometric leaf graph, seeding the dominating set from the
|
# the geometric leaf graph, seeding the dominating set from the
|
||||||
# inherited vertical core so the spine grows off the core, not anew.
|
# inherited vertical core so the spine grows off the core, not anew.
|
||||||
n_circ = max(1, -(-len(rooms) // 3)) # ceil(rooms / 3)
|
n_circ = max(1, -(-len(rooms) // circ_divisor)) # c3g granularity knob
|
||||||
target_total = len(rooms) + 1 + n_circ
|
target_total = len(rooms) + 1 + n_circ
|
||||||
n_free_target = target_total - (1 if core_node is not None else 0)
|
n_free_target = target_total - (1 if core_node is not None else 0)
|
||||||
while len(_free()) < n_free_target:
|
while len(_free()) < n_free_target:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue