2026-07-19 20:35:18 +01:00
|
|
|
"""Tests for the IN-SEARCH global collapse (homemaker-py-qpk, DESIGN.md §17
|
|
|
|
|
follow-on): running Fitness.collapse_global inside every fitness eval instead
|
|
|
|
|
of once at finish time.
|
|
|
|
|
|
|
|
|
|
Covers:
|
|
|
|
|
- default-OFF guarantee + conf-driven knobs (adjacency, iters)
|
|
|
|
|
- _evaluate_full wiring: collapse_global is invoked (with the right kwargs)
|
|
|
|
|
when the flag is on, never when it is off
|
|
|
|
|
- end-to-end effect on a real evolved layout, cross-checked against the
|
|
|
|
|
documented 94g finish-time result (DESIGN.md §17: 15 -> 12 fails on
|
|
|
|
|
evolved-3M-nols-3.dom)
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import copy
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import patch
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
§39.7: access requirements become a declared `usage:` attribute (homemaker-py-sel)
Closes the second namespace sharing a first character with programme codes: the
usage prefixes b/t/l/k, under which a room silently inherited another room's
connectivity rules from its spelling.
usage is a plain, MANDATORY attribute of the space definition -- not a lookup
table. An interim design proposed a top-level usage_classes: table binding
author-coined names to behaviour; withdrawn, because an indirect name->behaviour
mapping living apart from the thing it describes is exactly the shape of the
prefix rule §39 exists to remove, it would be the only such table in a schema
where every other space property is a plain attribute, and the need it served
was already met -- "building specific" is about what a room is CALLED, and
name: is already free text.
Rule that settles it: a usage value exists iff the engine treats it differently
somewhere. Config selects among behaviours; it cannot invent them.
- programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the
behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS /
SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code,
from BOTH parse paths.
- Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a
retype changes the class automatically. 51 sites assign leaf.type, and
share/share_type plus the r5a resurrection are the precedent for why
leaf-level attributes rot.
- graph.has_circulation takes the usage map and trims on declared class;
fitness.access and the public-access check likewise. fitness._t0 is DELETED --
no first-character type test remains anywhere in the codebase.
- utility is distinct from bedroom (same access requirements today) because it
is a different use and gives derive_interchange_classes an axis to relax on.
- A toilet now keeps its edge to a terminal room -- the Brand adjacency, which
the old b-before-t loop ordering severed.
- All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments
and layout preserved.
MEASURED -- the connectivity model was ~4x too permissive. `none` is not
neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of
52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served
as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each:
harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4
health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3
maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5
Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now
reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected.
The count rose because the objective got honest -- those failures were always
true of the layout and the old model could not see them. Every harbor number
before this was measured against a graph crediting routes through store
cupboards.
Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now
the deleted corridors were not missed because storage stood in for them. With
that substitution gone, homemaker-py-2v1 is the remaining half -- and now
measurable, because the fails it should prevent actually fire.
350 passed (+5 new), same 7 pre-existing fixture failures, lint unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 13:39:41 +00:00
|
|
|
from _helpers import with_usage
|
2026-07-19 20:35:18 +01:00
|
|
|
from homemaker_layout import dom as dom_mod
|
|
|
|
|
from homemaker_layout.dom import Node, _link_subtree
|
|
|
|
|
from homemaker_layout.fitness import Fitness, load_config
|
|
|
|
|
|
|
|
|
|
HARBOR = Path(__file__).parent.parent / "examples" / "harbor-house"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _two_leaf_root(t_left: str, t_right: str, side: float = 6.0, div: float = 0.4):
|
|
|
|
|
from homemaker_layout import geometry
|
|
|
|
|
geometry.clear_cache()
|
|
|
|
|
root = Node(
|
|
|
|
|
node=[[0, 0], [side, 0], [side, side], [0, side]],
|
|
|
|
|
rotation=0, division=[div, div],
|
|
|
|
|
left=Node(type=t_left), right=Node(type=t_right),
|
|
|
|
|
)
|
|
|
|
|
_link_subtree(root, None, "")
|
|
|
|
|
return root
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
# Defaults + conf-driven knobs
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
|
|
|
|
def test_collapse_insearch_default_off():
|
|
|
|
|
fit = Fitness()
|
|
|
|
|
assert fit._collapse_insearch is False
|
|
|
|
|
assert fit._collapse_insearch_adjacency is True
|
|
|
|
|
assert fit._collapse_insearch_iters == 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_collapse_insearch_flag_on():
|
|
|
|
|
fit = Fitness(conf={"collapse_insearch": True})
|
|
|
|
|
assert fit._collapse_insearch is True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_collapse_insearch_adjacency_knob_off():
|
|
|
|
|
fit = Fitness(conf={"collapse_insearch": True,
|
|
|
|
|
"collapse_insearch_adjacency": False})
|
|
|
|
|
assert fit._collapse_insearch_adjacency is False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_collapse_insearch_iters_knob():
|
|
|
|
|
fit = Fitness(conf={"collapse_insearch": True, "collapse_insearch_iters": 5})
|
|
|
|
|
assert fit._collapse_insearch_iters == 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
# _evaluate_full wiring
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
|
|
|
|
def test_evaluate_full_calls_collapse_global_when_on():
|
|
|
|
|
fit = Fitness(conf={"collapse_insearch": True, "collapse_insearch_iters": 2,
|
§39.7: access requirements become a declared `usage:` attribute (homemaker-py-sel)
Closes the second namespace sharing a first character with programme codes: the
usage prefixes b/t/l/k, under which a room silently inherited another room's
connectivity rules from its spelling.
usage is a plain, MANDATORY attribute of the space definition -- not a lookup
table. An interim design proposed a top-level usage_classes: table binding
author-coined names to behaviour; withdrawn, because an indirect name->behaviour
mapping living apart from the thing it describes is exactly the shape of the
prefix rule §39 exists to remove, it would be the only such table in a schema
where every other space property is a plain attribute, and the need it served
was already met -- "building specific" is about what a room is CALLED, and
name: is already free text.
Rule that settles it: a usage value exists iff the engine treats it differently
somewhere. Config selects among behaviours; it cannot invent them.
- programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the
behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS /
SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code,
from BOTH parse paths.
- Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a
retype changes the class automatically. 51 sites assign leaf.type, and
share/share_type plus the r5a resurrection are the precedent for why
leaf-level attributes rot.
- graph.has_circulation takes the usage map and trims on declared class;
fitness.access and the public-access check likewise. fitness._t0 is DELETED --
no first-character type test remains anywhere in the codebase.
- utility is distinct from bedroom (same access requirements today) because it
is a different use and gives derive_interchange_classes an axis to relax on.
- A toilet now keeps its edge to a terminal room -- the Brand adjacency, which
the old b-before-t loop ordering severed.
- All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments
and layout preserved.
MEASURED -- the connectivity model was ~4x too permissive. `none` is not
neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of
52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served
as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each:
harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4
health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3
maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5
Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now
reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected.
The count rose because the objective got honest -- those failures were always
true of the layout and the old model could not see them. Every harbor number
before this was measured against a graph crediting routes through store
cupboards.
Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now
the deleted corridors were not missed because storage stood in for them. With
that substitution gone, homemaker-py-2v1 is the remaining half -- and now
measurable, because the fails it should prevent actually fire.
350 passed (+5 new), same 7 pre-existing fixture failures, lint unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 13:39:41 +00:00
|
|
|
"spaces": with_usage({"b1": {"size": [16.0, 4.0], "count": 2}})})
|
2026-07-19 20:35:18 +01:00
|
|
|
root = _two_leaf_root("b1", "b1")
|
|
|
|
|
with patch.object(Fitness, "collapse_global", wraps=fit.collapse_global) as m:
|
|
|
|
|
fit.score_with_fails(root)
|
|
|
|
|
m.assert_called_once()
|
|
|
|
|
_, kw = m.call_args
|
|
|
|
|
assert kw["adjacency"] is True
|
|
|
|
|
assert kw["objective"] == "threshold"
|
|
|
|
|
assert kw["preserve_public_access"] is True
|
|
|
|
|
assert kw["iters"] == 2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_evaluate_full_does_not_call_collapse_global_when_off():
|
§39.7: access requirements become a declared `usage:` attribute (homemaker-py-sel)
Closes the second namespace sharing a first character with programme codes: the
usage prefixes b/t/l/k, under which a room silently inherited another room's
connectivity rules from its spelling.
usage is a plain, MANDATORY attribute of the space definition -- not a lookup
table. An interim design proposed a top-level usage_classes: table binding
author-coined names to behaviour; withdrawn, because an indirect name->behaviour
mapping living apart from the thing it describes is exactly the shape of the
prefix rule §39 exists to remove, it would be the only such table in a schema
where every other space property is a plain attribute, and the need it served
was already met -- "building specific" is about what a room is CALLED, and
name: is already free text.
Rule that settles it: a usage value exists iff the engine treats it differently
somewhere. Config selects among behaviours; it cannot invent them.
- programme.USAGES (living/kitchen/bedroom/toilet/utility/none) plus the
behaviour groupings PRIVATE_USAGES / PRIVATE_STRIPS / TOILET_STRIPS /
SOCIABLE_USAGES. Missing or unknown usage is a load error naming the code,
from BOTH parse paths.
- Code-level, never leaf-level: usage_of(leaf.type) is looked up fresh, so a
retype changes the class automatically. 51 sites assign leaf.type, and
share/share_type plus the r5a resurrection are the precedent for why
leaf-level attributes rot.
- graph.has_circulation takes the usage map and trims on declared class;
fitness.access and the public-access check likewise. fitness._t0 is DELETED --
no first-character type test remains anywhere in the codebase.
- utility is distinct from bedroom (same access requirements today) because it
is a different use and gives derive_interchange_classes an axis to relax on.
- A toilet now keeps its edge to a terminal room -- the Brand adjacency, which
the old b-before-t loop ordering severed.
- All 107 corpus entries migrated by experiments/migrate_usage_key.py, comments
and layout preserved.
MEASURED -- the connectivity model was ~4x too permissive. `none` is not
neutral: nothing is trimmed, so the graph may route THROUGH the room, and 34 of
52 codes had no class (Dental Surgery, Records Room, Utilities Closet all served
as corridors). Edges trimmed, prefix-inferred vs declared, 3 seeds each:
harbor-house 18 (9%) -> 79 (39%) inaccessible fails 0 -> 4
health-centre 12 (8%) -> 59 (40%) inaccessible fails 2 -> 3
maple-court 53 (17%) -> 123 (39%) inaccessible fails 1 -> 5
Re-baseline (seed 1, 20k, harbor): 58 fails (15h/43s) -> 61 (16h/45s), now
reporting 1-inaccessible-usable-space x2 plus level 0 and level 1 not connected.
The count rose because the objective got honest -- those failures were always
true of the layout and the old model could not see them. Every harbor number
before this was measured against a graph crediting routes through store
cupboards.
Sharpens §38.2: the objective pays x60-85 to delete circulation, and until now
the deleted corridors were not missed because storage stood in for them. With
that substitution gone, homemaker-py-2v1 is the remaining half -- and now
measurable, because the fails it should prevent actually fire.
350 passed (+5 new), same 7 pre-existing fixture failures, lint unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-26 13:39:41 +00:00
|
|
|
fit = Fitness(conf={"spaces": with_usage({"b1": {"size": [16.0, 4.0], "count": 2}})})
|
2026-07-19 20:35:18 +01:00
|
|
|
root = _two_leaf_root("b1", "b1")
|
|
|
|
|
with patch.object(Fitness, "collapse_global", wraps=fit.collapse_global) as m:
|
|
|
|
|
fit.score_with_fails(root)
|
|
|
|
|
m.assert_not_called()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
# End-to-end: matches the documented 94g finish-time result
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
Make both failing tests assert their intent, not stale artefacts
The suite is green for the first time this session: 376 passed, 0 failed.
test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both
endpoints of the 17 result -- 15 fails before collapse, 12 after. Those
were measured before 39.4, when harbor's effective programme was silently
32 instances because codes like cr1 were read as generic circulation; the
same layout now scores 82. But the guarantee the test exists to protect,
per its own docstring, is that in-search collapse reaches the SAME layout
as finish-time collapse on fixed geometry -- and two independent constants
never checked that. They can both drift and stay equal, or both hold and
mask an inequality.
Rewritten to compute both sides live and assert they agree, plus that
collapse does not make the layout worse. Measured: 82 -> 58 in-search, and
finish-time collapse independently reaches 58 at iters=3 and iters=6. The
invariant holds; only the constants were stale. Restating the reference
figure itself remains homemaker-py-ut5.
test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and
asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the
scorer writes beside a .dom -- so its docstring described files that by
design never exist in the repo, and it passed only on a machine that had
already run the scorer. Split into: a test that GENERATES fails by scoring
three corpus layouts picked for breadth (requiring >= 8 distinct kinds so
it cannot silently narrow); a test that an unclassifiable string actually
raises; and an opportunistic .fails sweep that never requires them.
Verified by moving every .fails out of the tree and re-running.
Closes homemaker-py-1ue.
Lint at parity (46).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
|
|
|
def test_collapse_insearch_matches_finish_time_collapse():
|
|
|
|
|
"""in-search collapse must reach the SAME layout as finish-time collapse.
|
|
|
|
|
|
|
|
|
|
That equality is the actual guarantee: `collapse_insearch` runs the same
|
|
|
|
|
`collapse_global` earlier in the same pipeline (before the Phase-1 checks
|
|
|
|
|
rather than after the whole search), so on a FIXED geometry the two must
|
|
|
|
|
agree. Both sides are computed here rather than hard-coded.
|
|
|
|
|
|
|
|
|
|
This test previously asserted the §17 constants directly -- `15` fails
|
|
|
|
|
before collapse and `12` after. Those were measured before §39.4, when
|
|
|
|
|
harbor's effective programme was silently 32 instances because codes like
|
Replace the stale 15-fail acceptance target with the cold-start baseline
DESIGN.md 38.7 pinned Phase 9's acceptance test to "harbor-house reaches its
known 15-fail floor". That figure was measured before 39.4, against harbor's
*effective* programme of 32 instances -- cr1/of/st1/st2 were being read as
generic circulation and silently dropped. Against the real 37-instance
programme the same artefact scores 89, so the target is not measurable; and
2v1, the fix it was the acceptance test for, closed NULL (39.8), so there is
no combined fix left to accept.
New 39.12 records:
* the five evolved-3M*.dom artefacts rescored under the current objective
(69/85/87/89/145), and why they are not a floor -- they were evolved under
one objective and are scored under another;
* that the bead's migration premise is stale: experiments/migrate_ju3_rename.py
does not exist, because ju3 tightened the matching rule at source (39.3)
instead, so the old artefacts parse correctly with no migration;
* the 4x3-seed 500k cold-start baseline (~430 h) as the reference from here,
with each programme's sd and its minimum detectable difference at n=3 --
harbor mean 39.3, sd 5.5, MDD 13.7;
* zero missing-space fails in all twelve runs: the dominant term in the 3M
artefacts is not one the live search still fails on;
* crinkliness at 112 of the 321 corpus fails (35%), all soft, all in the
regime 9gj says quality_uncrinkliness returns a flat 0.0 for -- the largest
single component of the residual is one the objective cannot descend;
* 66 of the 84 hard fails as one access-topology family (not-adjacent-to,
inaccessible usable space, not connected), mechanism in 39.9.
38.7's acceptance paragraph is annotated in place rather than rewritten. The
connectivity clause is demoted to a separately tracked standing defect: it
appears in 10 of the 12 baseline runs.
tests/test_collapse_insearch.py carried its own stale "82 -> 58"; the same
layout now scores 89 -> 64, so the docstring dates the figure instead of
asserting a current one.
Closes homemaker-py-ut5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-04 17:16:10 +00:00
|
|
|
`cr1` were being read as generic circulation; immediately after §39.4 the
|
|
|
|
|
same layout scored 82 -> 58, and it has moved again since. Pinning the
|
|
|
|
|
endpoints made a live invariant fail whenever the
|
Make both failing tests assert their intent, not stale artefacts
The suite is green for the first time this session: 376 passed, 0 failed.
test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both
endpoints of the 17 result -- 15 fails before collapse, 12 after. Those
were measured before 39.4, when harbor's effective programme was silently
32 instances because codes like cr1 were read as generic circulation; the
same layout now scores 82. But the guarantee the test exists to protect,
per its own docstring, is that in-search collapse reaches the SAME layout
as finish-time collapse on fixed geometry -- and two independent constants
never checked that. They can both drift and stay equal, or both hold and
mask an inequality.
Rewritten to compute both sides live and assert they agree, plus that
collapse does not make the layout worse. Measured: 82 -> 58 in-search, and
finish-time collapse independently reaches 58 at iters=3 and iters=6. The
invariant holds; only the constants were stale. Restating the reference
figure itself remains homemaker-py-ut5.
test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and
asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the
scorer writes beside a .dom -- so its docstring described files that by
design never exist in the repo, and it passed only on a machine that had
already run the scorer. Split into: a test that GENERATES fails by scoring
three corpus layouts picked for breadth (requiring >= 8 distinct kinds so
it cannot silently narrow); a test that an unclassifiable string actually
raises; and an opportunistic .fails sweep that never requires them.
Verified by moving every .fails out of the tree and re-running.
Closes homemaker-py-1ue.
Lint at parity (46).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
|
|
|
programme or the objective legitimately changed, while not actually
|
|
|
|
|
checking the invariant at all (two independent constants can both drift and
|
Replace the stale 15-fail acceptance target with the cold-start baseline
DESIGN.md 38.7 pinned Phase 9's acceptance test to "harbor-house reaches its
known 15-fail floor". That figure was measured before 39.4, against harbor's
*effective* programme of 32 instances -- cr1/of/st1/st2 were being read as
generic circulation and silently dropped. Against the real 37-instance
programme the same artefact scores 89, so the target is not measurable; and
2v1, the fix it was the acceptance test for, closed NULL (39.8), so there is
no combined fix left to accept.
New 39.12 records:
* the five evolved-3M*.dom artefacts rescored under the current objective
(69/85/87/89/145), and why they are not a floor -- they were evolved under
one objective and are scored under another;
* that the bead's migration premise is stale: experiments/migrate_ju3_rename.py
does not exist, because ju3 tightened the matching rule at source (39.3)
instead, so the old artefacts parse correctly with no migration;
* the 4x3-seed 500k cold-start baseline (~430 h) as the reference from here,
with each programme's sd and its minimum detectable difference at n=3 --
harbor mean 39.3, sd 5.5, MDD 13.7;
* zero missing-space fails in all twelve runs: the dominant term in the 3M
artefacts is not one the live search still fails on;
* crinkliness at 112 of the 321 corpus fails (35%), all soft, all in the
regime 9gj says quality_uncrinkliness returns a flat 0.0 for -- the largest
single component of the residual is one the objective cannot descend;
* 66 of the 84 hard fails as one access-topology family (not-adjacent-to,
inaccessible usable space, not connected), mechanism in 39.9.
38.7's acceptance paragraph is annotated in place rather than rewritten. The
connectivity clause is demoted to a separately tracked standing defect: it
appears in 10 of the 12 baseline runs.
tests/test_collapse_insearch.py carried its own stale "82 -> 58"; the same
layout now scores 89 -> 64, so the docstring dates the figure instead of
asserting a current one.
Closes homemaker-py-ut5.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-09-04 17:16:10 +00:00
|
|
|
still be equal, or both hold and mask an inequality). At the time of
|
|
|
|
|
writing the same layout scores 89 -> 64; DESIGN.md §39.12 restates the
|
|
|
|
|
reference figure itself and says why no constant belongs here.
|
Make both failing tests assert their intent, not stale artefacts
The suite is green for the first time this session: 376 passed, 0 failed.
test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both
endpoints of the 17 result -- 15 fails before collapse, 12 after. Those
were measured before 39.4, when harbor's effective programme was silently
32 instances because codes like cr1 were read as generic circulation; the
same layout now scores 82. But the guarantee the test exists to protect,
per its own docstring, is that in-search collapse reaches the SAME layout
as finish-time collapse on fixed geometry -- and two independent constants
never checked that. They can both drift and stay equal, or both hold and
mask an inequality.
Rewritten to compute both sides live and assert they agree, plus that
collapse does not make the layout worse. Measured: 82 -> 58 in-search, and
finish-time collapse independently reaches 58 at iters=3 and iters=6. The
invariant holds; only the constants were stale. Restating the reference
figure itself remains homemaker-py-ut5.
test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and
asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the
scorer writes beside a .dom -- so its docstring described files that by
design never exist in the repo, and it passed only on a machine that had
already run the scorer. Split into: a test that GENERATES fails by scoring
three corpus layouts picked for breadth (requiring >= 8 distinct kinds so
it cannot silently narrow); a test that an unclassifiable string actually
raises; and an opportunistic .fails sweep that never requires them.
Verified by moving every .fails out of the tree and re-running.
Closes homemaker-py-1ue.
Lint at parity (46).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
|
|
|
"""
|
2026-07-19 20:35:18 +01:00
|
|
|
conf, cost = load_config(HARBOR)
|
|
|
|
|
conf_ci, _ = load_config(HARBOR, overrides={"collapse_insearch": True})
|
|
|
|
|
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
|
|
|
|
|
|
Make both failing tests assert their intent, not stale artefacts
The suite is green for the first time this session: 376 passed, 0 failed.
test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both
endpoints of the 17 result -- 15 fails before collapse, 12 after. Those
were measured before 39.4, when harbor's effective programme was silently
32 instances because codes like cr1 were read as generic circulation; the
same layout now scores 82. But the guarantee the test exists to protect,
per its own docstring, is that in-search collapse reaches the SAME layout
as finish-time collapse on fixed geometry -- and two independent constants
never checked that. They can both drift and stay equal, or both hold and
mask an inequality.
Rewritten to compute both sides live and assert they agree, plus that
collapse does not make the layout worse. Measured: 82 -> 58 in-search, and
finish-time collapse independently reaches 58 at iters=3 and iters=6. The
invariant holds; only the constants were stale. Restating the reference
figure itself remains homemaker-py-ut5.
test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and
asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the
scorer writes beside a .dom -- so its docstring described files that by
design never exist in the repo, and it passed only on a machine that had
already run the scorer. Split into: a test that GENERATES fails by scoring
three corpus layouts picked for breadth (requiring >= 8 distinct kinds so
it cannot silently narrow); a test that an unclassifiable string actually
raises; and an opportunistic .fails sweep that never requires them.
Verified by moving every .fails out of the tree and re-running.
Closes homemaker-py-1ue.
Lint at parity (46).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
|
|
|
_, f_base = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
|
|
|
|
|
_, f_ci = Fitness(conf_ci, cost).score_with_fails(copy.deepcopy(root))
|
2026-07-19 20:35:18 +01:00
|
|
|
|
Make both failing tests assert their intent, not stale artefacts
The suite is green for the first time this session: 376 passed, 0 failed.
test_collapse_insearch_reproduces_94g_finish_time_result hard-coded both
endpoints of the 17 result -- 15 fails before collapse, 12 after. Those
were measured before 39.4, when harbor's effective programme was silently
32 instances because codes like cr1 were read as generic circulation; the
same layout now scores 82. But the guarantee the test exists to protect,
per its own docstring, is that in-search collapse reaches the SAME layout
as finish-time collapse on fixed geometry -- and two independent constants
never checked that. They can both drift and stay equal, or both hold and
mask an inequality.
Rewritten to compute both sides live and assert they agree, plus that
collapse does not make the layout worse. Measured: 82 -> 58 in-search, and
finish-time collapse independently reaches 58 at iters=3 and iters=6. The
invariant holds; only the constants were stale. Restating the reference
figure itself remains homemaker-py-ut5.
test_classify_fail_tier_covers_full_corpus globbed examples/**/*.fails and
asserted checked > 0. Git tracks ZERO .fails -- they are artefacts the
scorer writes beside a .dom -- so its docstring described files that by
design never exist in the repo, and it passed only on a machine that had
already run the scorer. Split into: a test that GENERATES fails by scoring
three corpus layouts picked for breadth (requiring >= 8 distinct kinds so
it cannot silently narrow); a test that an unclassifiable string actually
raises; and an opportunistic .fails sweep that never requires them.
Verified by moving every .fails out of the tree and re-running.
Closes homemaker-py-1ue.
Lint at parity (46).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
2026-08-29 07:11:25 +00:00
|
|
|
# the same collapse, applied once at finish time, scored canonically
|
|
|
|
|
finished = copy.deepcopy(root)
|
|
|
|
|
Fitness(conf, cost).collapse_global(
|
|
|
|
|
finished, adjacency=True, objective="threshold",
|
|
|
|
|
preserve_public_access=True, iters=3)
|
|
|
|
|
_, f_finish = Fitness(conf, cost).score_with_fails(finished)
|
|
|
|
|
|
|
|
|
|
assert len(f_ci) == len(f_finish), (
|
|
|
|
|
"in-search collapse diverged from finish-time collapse on fixed geometry")
|
|
|
|
|
assert len(f_ci) < len(f_base), "collapse must not make the layout worse"
|
2026-07-19 20:35:18 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
|
|
|
|
def test_collapse_insearch_off_reproduces_baseline():
|
|
|
|
|
conf, cost = load_config(HARBOR)
|
|
|
|
|
fit = Fitness(conf, cost)
|
|
|
|
|
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
|
|
|
|
|
s1, f1 = fit.score_with_fails(copy.deepcopy(root))
|
|
|
|
|
s2, f2 = Fitness(conf, cost).score_with_fails(copy.deepcopy(root))
|
|
|
|
|
assert s1 == pytest.approx(s2)
|
|
|
|
|
assert f1 == f2
|