The dominant post-collapse fail is the binary "level N not connected", which is flat across fragmentation (a 7-component storey scores the same as a 2-component one), so the outer search has no gradient toward connected circulation. A finish-time convert-to-circulation repair was prototyped and measured NEGATIVE (195->560 fails: bridging needed rooms costs more missing-room fails than the one binary fail it clears). Instead add graph.circulation_connectivity(G) = largest-circ-component fraction, summed over storeys onto the score_with_grade proximity channel (conf flag conn_grade; replaces the §11.4 leaf-grade there). It is a secondary comparator key only — scalar fitness and fail count stay byte-identical — restoring the gradient the binary fail lacks. Threaded through driver (_overrides_for/_fitness_for/_evaluate/search; enabling it implies the grade key) and evolve --conn-grade (default off). A/B on full-budget runs pending; short smoke run confirms plumbing. Tests: tests/test_conn_grade.py x9 (fraction contract, non-circ ignored, monotone under (dis)connection, score/fail invariance); 276 pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01M8566xAxTnwtJTkpXjYNZm
113 lines
4.5 KiB
Python
113 lines
4.5 KiB
Python
"""Tests for the graded circulation-connectivity signal (homemaker-py-qi6, §18).
|
|
|
|
Covers:
|
|
- graph.circulation_connectivity: largest-circ-component fraction, non-circ
|
|
cells ignored, empty → 0.0, monotone under (dis)connection.
|
|
- Fitness conn_grade wiring: repurposes the graded proximity scalar, leaves the
|
|
scalar fitness and fail count byte-identical (secondary comparator key only).
|
|
"""
|
|
|
|
import copy
|
|
from pathlib import Path
|
|
|
|
import networkx as nx
|
|
import pytest
|
|
|
|
from homemaker_layout import dom as dom_mod
|
|
from homemaker_layout.dom import Node
|
|
from homemaker_layout.graph import circulation_connectivity
|
|
from homemaker_layout.fitness import Fitness, load_config
|
|
|
|
HARBOR = Path(__file__).parent.parent / "examples" / "harbor-house"
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# circulation_connectivity — pure graph contract
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
def _circ(*ids):
|
|
# bare, unlinked nodes: level_of == 0 → is_usable → is_circulation for c/s
|
|
return [Node(type=t) for t in ids]
|
|
|
|
|
|
def test_fully_connected_is_one():
|
|
a, b, c = _circ("C", "C", "S")
|
|
G = nx.Graph([(a, b), (b, c)])
|
|
assert circulation_connectivity(G) == 1.0
|
|
|
|
|
|
def test_two_components_is_half():
|
|
a, b, c, d = _circ("C", "C", "C", "C")
|
|
G = nx.Graph([(a, b), (c, d)]) # two disjoint pairs of 4 circ cells
|
|
assert circulation_connectivity(G) == 0.5
|
|
|
|
|
|
def test_non_circulation_cells_ignored():
|
|
# largest circ component is {a,b} of 3 circ cells → 2/3; the room cells r/s
|
|
# bridging them do NOT count as circulation, so the split stands.
|
|
a, b, lone = _circ("C", "C", "C")
|
|
r1, r2 = Node(type="b1"), Node(type="k1")
|
|
G = nx.Graph([(a, b), (a, r1), (r1, r2), (r2, lone)])
|
|
assert circulation_connectivity(G) == pytest.approx(2 / 3)
|
|
|
|
|
|
def test_no_circulation_is_zero():
|
|
r1, r2 = Node(type="b1"), Node(type="k1")
|
|
G = nx.Graph([(r1, r2)])
|
|
assert circulation_connectivity(G) == 0.0
|
|
assert circulation_connectivity(nx.Graph()) == 0.0
|
|
|
|
|
|
def test_connecting_a_component_raises_the_grade():
|
|
a, b, c, d = _circ("C", "C", "C", "C")
|
|
split = nx.Graph([(a, b), (c, d)]) # 0.5
|
|
joined = nx.Graph([(a, b), (b, c), (c, d)]) # 1.0
|
|
assert circulation_connectivity(joined) > circulation_connectivity(split)
|
|
|
|
|
|
# --------------------------------------------------------------------------- #
|
|
# Fitness conn_grade wiring — must not perturb score or fail count
|
|
# --------------------------------------------------------------------------- #
|
|
|
|
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
|
@pytest.mark.parametrize("name", ["evolved-3M-nols-3.dom", "evolved-3M.dom"])
|
|
def test_conn_grade_leaves_score_and_fails_untouched(name):
|
|
conf, cost = load_config(HARBOR)
|
|
conf_cg, _ = load_config(HARBOR, overrides={"conn_grade": True})
|
|
fit, fit_cg = Fitness(conf, cost), Fitness(conf_cg, cost)
|
|
|
|
root = dom_mod.load(str(HARBOR / name))
|
|
s_base, f_base = fit.score_with_fails(copy.deepcopy(root))
|
|
s_cg, f_cg, grade = fit_cg.score_with_grade(copy.deepcopy(root))
|
|
|
|
assert s_cg == pytest.approx(s_base)
|
|
assert f_cg == f_base
|
|
# grade is the sum of per-level fractions ∈ [0, n_levels]; harbor layouts are
|
|
# partially disconnected, so it is strictly positive and below the level count.
|
|
n_levels = len(dom_mod.levels(dom_mod.load(str(HARBOR / name))))
|
|
assert 0.0 < grade <= n_levels
|
|
|
|
|
|
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
|
def test_more_connected_layout_scores_higher_grade():
|
|
conf_cg, cost = load_config(HARBOR, overrides={"conn_grade": True})
|
|
fit = Fitness(conf_cg, cost)
|
|
|
|
def grade_of(name):
|
|
_, _, g = fit.score_with_grade(dom_mod.load(str(HARBOR / name)))
|
|
return g
|
|
|
|
# evolved-3M has one fully-connected storey; nols-3 is fragmented on both.
|
|
assert grade_of("evolved-3M.dom") > grade_of("evolved-3M-nols-3.dom")
|
|
|
|
|
|
@pytest.mark.skipif(not HARBOR.is_dir(), reason="harbor-house example absent")
|
|
def test_conn_grade_off_uses_leaf_grade_not_connectivity():
|
|
# With the flag off, the grade is the §11.4 leaf quality-proximity, which is a
|
|
# different (smaller, here) scalar — the two channels must not collide.
|
|
conf, cost = load_config(HARBOR)
|
|
conf_cg, _ = load_config(HARBOR, overrides={"conn_grade": True})
|
|
root = dom_mod.load(str(HARBOR / "evolved-3M-nols-3.dom"))
|
|
_, _, g_leaf = Fitness(conf, cost).score_with_grade(copy.deepcopy(root))
|
|
_, _, g_conn = Fitness(conf_cg, cost).score_with_grade(copy.deepcopy(root))
|
|
assert g_leaf != g_conn
|