homemaker-layout/tests/test_conn_grade.py

114 lines
4.5 KiB
Python
Raw Normal View History

"""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