qi6: graded circulation-connectivity signal (§18)
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
This commit is contained in:
parent
1ae8faac7c
commit
94d4223a55
7 changed files with 256 additions and 25 deletions
File diff suppressed because one or more lines are too long
51
DESIGN.md
51
DESIGN.md
|
|
@ -2414,3 +2414,54 @@ the collapse *inside* search per-eval (rather than finish-time) is `homemaker-py
|
|||
gated on the 9o5 landscape-flattening risk (§13 / `homemaker-py-xi7`) and its own A/B.
|
||||
Tests: `tests/test_collapse_global.py` ×6 (demand-set relabel, level hard constraint, c/o/s
|
||||
exclusion, no-op safety, keep-better/unmerged); 267 pass.
|
||||
|
||||
## 18. Graded circulation-connectivity signal (`homemaker-py-qi6`) — in progress
|
||||
|
||||
**Motivation — the binary fail is flat.** After the §17 collapse, the residual fails on the
|
||||
harbor-house set are dominated by `level N not connected` (2 of the best layout's 12; also on
|
||||
5 of the 6 sweep layouts). That fail comes from `connected_circulation` (`graph.py`): remove
|
||||
every non-circulation vertex from a storey's adjacency graph and require the remaining
|
||||
circulation cells (`C` stairs plus the `cr`/`st` room-codes that collide with the c/s prefix)
|
||||
to form ONE connected component. On the evolved layouts they instead fragment into **4–7
|
||||
components per storey**.
|
||||
|
||||
**Why finish-time repair fails (measured, negative).** The obvious §17-style companion — a
|
||||
finish-time pass that re-types boundary cells to circulation to bridge the components, kept
|
||||
only if the fail count does not rise — was prototyped (Steiner-MST bridge set per disconnected
|
||||
storey, keep-better guard) and measured on the 6 layouts: **195 → 560 fails (+365)**. The
|
||||
`not connected` fail is *binary* (one fail per storey regardless of fragmentation), but each
|
||||
storey needs 3–7 bridge cells, and every needed-room→circulation conversion triggers a
|
||||
missing-room fail cascade (2–5 fails) that dwarfs the single connectivity fail it clears.
|
||||
Keep-better reverts every one → no-op. **Conclusion: connectivity cannot be bought at finish
|
||||
time when every cell is a needed room; it must come from the outer search allocating connected
|
||||
circulation topology.** But the binary fail gives the search *zero gradient* — a 7-component
|
||||
storey scores identically (both in fail count and in the `0.5^n` scalar) to a 2-component one —
|
||||
so the search cannot tell it is making progress.
|
||||
|
||||
**Mechanism — a graded proximity on the same channel §11.4 built.** `graph.circulation_connectivity(G)`
|
||||
returns the fraction of circulation cells in the largest connected circulation component ∈
|
||||
[0,1] (1.0 = a single connected spine, lower = more fragmented, 0.0 = no circulation), measured
|
||||
on the same circ subgraph the fail uses so the two agree at the connected endpoint. Summed over
|
||||
storeys it is the graded proximity scalar `Fitness.score_with_grade` already carries for the
|
||||
outer comparator, gated by the `conn_grade` conf flag: when on it *replaces* the §11.4 leaf
|
||||
quality-proximity on that channel (a distinct, better-motivated use — §11.4 was rejected because
|
||||
within a fail-tier the `0.5^n` scalar is NOT flat there and grade merely displaced a working
|
||||
signal; connectivity is the opposite case, genuinely flat under the binary fail). Like §11.4 it
|
||||
leaves the scalar fitness and fail count **byte-identical** (verified) — it is only the secondary
|
||||
key `(-n_fails, grade, fitness)` (driver `use_lex and use_grade`), strictly beneath fail-count so
|
||||
the §6 missing-space hierarchy and the §5.4 inner-loop cliff are untouched. Among equally-failing
|
||||
neighbours the search now prefers the one whose circulation is closer to one component, restoring
|
||||
the gradient toward connected topologies.
|
||||
|
||||
**Wiring.** `conn_grade` threads through `_overrides_for`/`_fitness_for`/`_evaluate` and the
|
||||
`search` signature; enabling it implies the grade key. `evolve.py` exposes `--conn-grade`
|
||||
(env `HOMEMAKER_CONN_GRADE`, default OFF); the grade is read off the optimised tree, one extra
|
||||
native eval per child.
|
||||
|
||||
**Status / next.** Signal, fitness wiring, CLI, and 9 tests landed (`tests/test_conn_grade.py`:
|
||||
pure-graph fraction contract, non-circ cells ignored, monotone under (dis)connection, and the
|
||||
score/fail-count-invariance of the flag). The A/B — does the gradient actually pull evolve runs
|
||||
toward connected circulation and clear `not connected` fails — needs full-budget runs and is
|
||||
pending (short 60-eval smoke run confirms the plumbing only). If the graded key alone is
|
||||
insufficient, the follow-on is an insert/relocate-circulation mutation operator (mechanism (a),
|
||||
still `homemaker-py-qi6`) that now has a gradient to climb. 276 tests pass.
|
||||
|
|
|
|||
|
|
@ -40,11 +40,14 @@ _CHILD_INNER_KW: dict = {}
|
|||
|
||||
|
||||
def _overrides_for(leaf_sharing: bool, superpose: bool,
|
||||
max_share: int | None = None) -> dict | None:
|
||||
max_share: int | None = None,
|
||||
conn_grade: bool = False) -> dict | None:
|
||||
"""Run-level conf overrides for the native evaluator (None when all off).
|
||||
|
||||
``max_share`` (homemaker-py-kpu) overrides the evaluator's ``leaf_share_max``
|
||||
grain cap for the in-run annealing ramp; ``None`` leaves the config default.
|
||||
``conn_grade`` (homemaker-py-qi6) turns the graded proximity scalar into the
|
||||
circulation-connectivity signal (§18).
|
||||
"""
|
||||
ov: dict = {}
|
||||
if leaf_sharing:
|
||||
|
|
@ -53,13 +56,16 @@ def _overrides_for(leaf_sharing: bool, superpose: bool,
|
|||
ov["superpose"] = True
|
||||
if max_share is not None:
|
||||
ov["leaf_share_max"] = int(max_share)
|
||||
if conn_grade:
|
||||
ov["conn_grade"] = True
|
||||
return ov or None
|
||||
|
||||
|
||||
@functools.lru_cache(maxsize=None)
|
||||
def _fitness_for(programme_dir: str, leaf_sharing: bool = False,
|
||||
superpose: bool = False,
|
||||
max_share: int | None = None) -> "fitness.Fitness":
|
||||
max_share: int | None = None,
|
||||
conn_grade: bool = False) -> "fitness.Fitness":
|
||||
"""Cached Fitness evaluator per (programme dir, leaf_sharing) (config load is
|
||||
the cost).
|
||||
|
||||
|
|
@ -70,7 +76,7 @@ def _fitness_for(programme_dir: str, leaf_sharing: bool = False,
|
|||
inner loop instead of reading the on-disk (sharing-free) patterns.config.
|
||||
Cached per process — workers fork their own copy.
|
||||
"""
|
||||
overrides = _overrides_for(leaf_sharing, superpose, max_share)
|
||||
overrides = _overrides_for(leaf_sharing, superpose, max_share, conn_grade)
|
||||
conf, cost = fitness.load_config(programme_dir, overrides=overrides)
|
||||
return fitness.Fitness(conf, cost)
|
||||
|
||||
|
|
@ -146,7 +152,8 @@ def _evaluate(root: dom.Node, programme_dir, urb_root, x0, budget, inner_kw,
|
|||
best_n_fails: int | None = None,
|
||||
leaf_sharing: bool = False,
|
||||
superpose: bool = False,
|
||||
max_share: int | None = None) -> tuple[Individual, int]:
|
||||
max_share: int | None = None,
|
||||
conn_grade: bool = False) -> tuple[Individual, int]:
|
||||
# §12.3 shape-feasibility pre-filter (homemaker-py-9gp.1): if even the best
|
||||
# achievable (proportion-aware) geometry of this topology already has at least
|
||||
# as many shape fails as the incumbent's TOTAL fails — and exceeds the tunable
|
||||
|
|
@ -154,11 +161,12 @@ def _evaluate(root: dom.Node, programme_dir, urb_root, x0, budget, inner_kw,
|
|||
# eval instead of spending the full inner-loop budget. The best_n_fails guard
|
||||
# makes the proxy safe: a topology whose shape-fail floor is still below the
|
||||
# incumbent is never discarded. Pruned individuals are tagged and never admitted.
|
||||
overrides = _overrides_for(leaf_sharing, superpose, max_share)
|
||||
overrides = _overrides_for(leaf_sharing, superpose, max_share, conn_grade)
|
||||
if (feasibility_max_shape_fails is not None and best_n_fails is not None):
|
||||
pred = operators.predicted_shape_fails(
|
||||
root, _reqs_for(str(programme_dir)),
|
||||
_fitness_for(str(programme_dir), leaf_sharing, superpose, max_share))
|
||||
_fitness_for(str(programme_dir), leaf_sharing, superpose, max_share,
|
||||
conn_grade))
|
||||
if pred > feasibility_max_shape_fails and pred >= best_n_fails:
|
||||
ind = Individual(root=root, fitness=0.0, n_fails=pred, ratios={},
|
||||
lineage=f"pruned/{lineage}", grade=0.0,
|
||||
|
|
@ -173,7 +181,8 @@ def _evaluate(root: dom.Node, programme_dir, urb_root, x0, budget, inner_kw,
|
|||
grade = 0.0
|
||||
if want_grade:
|
||||
_, _, grade = _fitness_for(
|
||||
str(programme_dir), leaf_sharing, superpose, max_share).score_with_grade(
|
||||
str(programme_dir), leaf_sharing, superpose, max_share,
|
||||
conn_grade).score_with_grade(
|
||||
copy.deepcopy(root))
|
||||
ind = Individual(root=root, fitness=r.fitness, n_fails=r.n_fails,
|
||||
ratios=innerloop.ratio_map(root), lineage=lineage,
|
||||
|
|
@ -209,6 +218,7 @@ def search(
|
|||
base_p: float = 1.0,
|
||||
child_probe=None,
|
||||
use_grade: bool = False,
|
||||
conn_grade: bool = False,
|
||||
tournament_k: int = 2,
|
||||
niche_by_signature: bool = False,
|
||||
restart_patience: int | None = None,
|
||||
|
|
@ -300,6 +310,9 @@ def search(
|
|||
# Kept default-off for reproducibility. Strictly beneath -n_fails ⇒ the
|
||||
# missing-space hierarchy (§6) is preserved and the inner-loop cliff (§5.4)
|
||||
# is untouched.
|
||||
# homemaker-py-qi6 §18: the connectivity signal rides the same grade channel,
|
||||
# so enabling it enables the grade secondary key.
|
||||
use_grade = use_grade or conn_grade
|
||||
if use_lex and use_grade:
|
||||
_key = lambda ind: (-ind.n_fails, ind.grade, _rank_fitness(ind))
|
||||
elif use_lex:
|
||||
|
|
@ -402,7 +415,7 @@ def search(
|
|||
best_nf = result.best.n_fails if result.best is not None else None
|
||||
full = [
|
||||
(root, programme_dir, urb_root, x0, budget_, kw_, lin, use_grade,
|
||||
mx, best_nf, leaf_sharing, superpose, max_share)
|
||||
mx, best_nf, leaf_sharing, superpose, max_share, conn_grade)
|
||||
for root, x0, budget_, kw_, lin in tasks
|
||||
]
|
||||
if _pool is not None:
|
||||
|
|
@ -483,7 +496,8 @@ def search(
|
|||
want_grade=use_grade,
|
||||
leaf_sharing=leaf_sharing,
|
||||
superpose=superpose,
|
||||
max_share=max_share)
|
||||
max_share=max_share,
|
||||
conn_grade=conn_grade)
|
||||
n_evals += used
|
||||
admit(seed_ind, pop)
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,16 @@ def _parse_args(argv=None) -> argparse.Namespace:
|
|||
"requirements) form equivalence classes and each candidate "
|
||||
"collapses every superposed leaf to its best in-class usage "
|
||||
"before scoring (default: off)")
|
||||
p.add_argument("--conn-grade", dest="conn_grade",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=_env_bool("HOMEMAKER_CONN_GRADE", False),
|
||||
help="homemaker-py-qi6 (§18): graded circulation-connectivity "
|
||||
"signal. Adds a secondary comparator key (beneath fail "
|
||||
"count, above fitness) = per-level largest-circ-component "
|
||||
"fraction, giving the search a gradient toward connected "
|
||||
"circulation that the binary 'not connected' fail lacks. "
|
||||
"Does not change the scalar fitness or fail count "
|
||||
"(default: off)")
|
||||
p.add_argument("--anneal-grain", type=str,
|
||||
default=os.environ.get("HOMEMAKER_ANNEAL_GRAIN"),
|
||||
metavar="LADDER",
|
||||
|
|
@ -161,6 +171,7 @@ def main(argv=None) -> int:
|
|||
print(f"leaf sharing : {args.leaf_sharing} (factor={args.leaf_share_factor})",
|
||||
file=sys.stderr)
|
||||
print(f"superpose : {args.superpose}", file=sys.stderr)
|
||||
print(f"conn grade : {args.conn_grade}", file=sys.stderr)
|
||||
print(f"output : {out or 'stdout'}", file=sys.stderr, flush=True)
|
||||
|
||||
anneal_ladder = None
|
||||
|
|
@ -209,6 +220,7 @@ def main(argv=None) -> int:
|
|||
leaf_sharing=args.leaf_sharing,
|
||||
leaf_share_factor=args.leaf_share_factor,
|
||||
superpose=args.superpose,
|
||||
conn_grade=args.conn_grade,
|
||||
log=lambda m: print(m, file=sys.stderr, flush=True),
|
||||
)
|
||||
_finish_sharing = args.leaf_sharing
|
||||
|
|
|
|||
|
|
@ -214,6 +214,13 @@ class Fitness:
|
|||
# leaf to its best in-class usage before scoring, so search optimises the
|
||||
# condensed objective directly and the relaxation gap is removed.
|
||||
self._superpose = bool(self.conf("superpose"))
|
||||
# homemaker-py-qi6 graded circulation-connectivity signal (DESIGN.md §18):
|
||||
# default OFF. When on, the graded proximity scalar (want_grade) is the
|
||||
# per-level largest-circ-component fraction instead of the §11.4 leaf
|
||||
# quality-proximity — a secondary comparator key giving the outer search
|
||||
# a gradient the binary "level N not connected" fail lacks. Leaves the
|
||||
# scalar fitness and fail count untouched, exactly like §11.4.
|
||||
self._conn_grade = bool(self.conf("conn_grade"))
|
||||
from .programme import CLASS_CAP as _CLASS_CAP
|
||||
self._class_cap = int(self.conf("superpose_class_cap") or _CLASS_CAP)
|
||||
self._interchange_classes: list | None = None # lazily derived
|
||||
|
|
@ -1453,10 +1460,18 @@ class Fitness:
|
|||
)
|
||||
cost += se.cost
|
||||
value += se.value
|
||||
if want_grade: # §11.4 outer-comparator signal only; off by default
|
||||
if want_grade and not self._conn_grade: # §11.4 signal; off by default
|
||||
for le in se.leaves:
|
||||
grade += _leaf_grade(le.factors)
|
||||
|
||||
# §18 (homemaker-py-qi6): repurpose the grade channel for the graded
|
||||
# circulation-connectivity signal — sum of per-level largest-circ-component
|
||||
# fractions, higher when circulation is closer to a single connected spine.
|
||||
# Secondary comparator key only; score and fail count are untouched.
|
||||
if want_grade and self._conn_grade:
|
||||
for gc in graph_circ:
|
||||
grade += graph_mod.circulation_connectivity(gc)
|
||||
|
||||
building_factor = self.evaluate_building(root, tracking)
|
||||
value *= building_factor
|
||||
|
||||
|
|
|
|||
|
|
@ -188,6 +188,32 @@ def _connected_outside_inplace(G: nx.Graph) -> None:
|
|||
G.remove_nodes_from(to_remove)
|
||||
|
||||
|
||||
def circulation_connectivity(G: nx.Graph) -> float:
|
||||
"""Fraction of circulation cells in the largest connected circulation
|
||||
component — a continuous [0,1] proximity to a single connected circulation
|
||||
spine (1.0 = fully connected, lower = more fragmented, 0.0 = no circulation).
|
||||
|
||||
Companion graded signal for the binary ``level N not connected`` fail
|
||||
(``connected_circulation``, homemaker-py-qi6). That fail fires identically
|
||||
whether a level's circulation is split into 2 components or 7, so it is FLAT
|
||||
across fragmentation and gives the outer search no gradient to climb toward
|
||||
connectivity. This proxy restores the gradient: among equally-failing
|
||||
layouts, the one whose circulation is closer to a single component scores
|
||||
higher. Measured on the same circ subgraph the fail uses (all non-circulation
|
||||
vertices removed), so the two agree at the connected endpoint (proxy == 1.0
|
||||
iff ``connected_circulation`` is True on a non-empty circ set).
|
||||
"""
|
||||
gc = G.copy()
|
||||
gc.remove_nodes_from(
|
||||
[v for v in list(gc.nodes()) if not dom.is_circulation(v)]
|
||||
)
|
||||
n = gc.number_of_nodes()
|
||||
if n == 0:
|
||||
return 0.0
|
||||
largest = max((len(c) for c in nx.connected_components(gc)), default=0)
|
||||
return largest / n
|
||||
|
||||
|
||||
def connected_circulation(G: nx.Graph) -> bool:
|
||||
"""True iff circulation nodes are non-empty and connected; mirrors
|
||||
``Urb::Dom::Connected_Circulation`` (Storey.pm:106).
|
||||
|
|
|
|||
113
tests/test_conn_grade.py
Normal file
113
tests/test_conn_grade.py
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
"""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
|
||||
Loading…
Add table
Reference in a new issue