Remove two dead paths from the objective
39.24's sweep listed two entries as DEAD rather than suspect -- inert code that reads as live. Neither changes a score or a failure on any corpus artefact, and that is verified rather than asserted: every artefact scores identically to its 39.25 measurement. ratio_public_outside and ratio_private_outside. evaluate_building read both and multiplied a gaussian into the building factor for each. Neither key exists in CONF_DEFAULTS and no patterns.config in the repository declares either, so both branches were guarded and never ran. Removing them also retires what fed them: the four public_length_*/private_length_* tracking keys accumulated per leaf in process_storey, and the _public_length/_private_length helpers, which had no other caller. NOT removed, because they are live: _public_access, _public_access_outside, _public_access_pins and the has_public_access_* tracking flags, which drive real checks and collapse_global's preserve_public_access. Only the length-ratio machinery was dead. The daylight quality factor. evaluate_leaf set factors["daylight"] = 1.0 unconditionally -- pinned since the URB_NO_OCCLUSION descope (6) and unable to be anything else. It was never in _GRADED_FACTORS, so it contributed nothing to the graded signal, and 39.18's geometric mean then had to special-case it in factor_is_asked as a factor that is never asked. A constant that exists only to be excluded is worth deleting. If 2g5 rebuilds occlusion it reintroduces a real daylight factor, which would need factor_is_asked to say True anyway. Two tests referenced the removed factor. test_leaf_grade_ignores_non_graded_keys now names a key that genuinely does not exist; the aggregate underflow test dropped its daylight entry, which would otherwise have been counted as asked and changed the expected geometric mean. Worth doing despite changing no number: 39.20 and 39.25 were both cases where something inert looked live -- a parity test that never ran, a per-level rule switched off in every config -- and in both the misreading cost real time and produced a wrong conclusion. An objective with fewer things in it that do nothing is one where "this term does nothing" is informative rather than routine. Still open on dpt, each needing a ruling or a rate change rather than a measurement: quality_size's upper side, the minimum-internal-area factor as a third statement of "build the rooms", and the 0.5**n_fails curve. 426 passed. Refs homemaker-py-dpt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
This commit is contained in:
parent
73324deff1
commit
99c85ec4ee
5 changed files with 50 additions and 72 deletions
44
DESIGN.md
44
DESIGN.md
|
|
@ -8126,3 +8126,47 @@ where the fraction penalty lifted and fall where a new hard fail landed:
|
||||||
| maple-court | 51→**52**, 65, 52 | −50% (new fail), +1%, +0.4% |
|
| maple-court | 51→**52**, 65, 52 | −50% (new fail), +1%, +0.4% |
|
||||||
| health-centre | 3/9/5 unchanged | +29% … +156% |
|
| health-centre | 3/9/5 unchanged | +29% … +156% |
|
||||||
| programme-house | 1→**2** all seeds | −43%, +24%, −50% |
|
| programme-house | 1→**2** all seeds | −43%, +24%, −50% |
|
||||||
|
|
||||||
|
### 39.26 Two dead paths in the objective (`homemaker-py-dpt`)
|
||||||
|
|
||||||
|
§39.24's sweep listed two entries as DEAD rather than suspect — inert code that
|
||||||
|
reads as live. Both are removed here. Neither changes a score or a failure on
|
||||||
|
any corpus artefact; that is what "dead" meant, and it is verified rather than
|
||||||
|
asserted: every artefact scores identically to its §39.25 measurement.
|
||||||
|
|
||||||
|
**`ratio_public_outside` and `ratio_private_outside`.** `evaluate_building`
|
||||||
|
read both and multiplied a gaussian into the building factor for each. Neither
|
||||||
|
key exists in `CONF_DEFAULTS`, and no `patterns.config` in the repository
|
||||||
|
declares either, so both branches were guarded by `if conf_po and isinstance(...)`
|
||||||
|
and never ran. Removing them also retires what fed them: the four
|
||||||
|
`public_length_*` / `private_length_*` tracking keys accumulated per leaf in
|
||||||
|
`process_storey`, and the `_public_length` / `_private_length` helpers, which
|
||||||
|
had no other caller.
|
||||||
|
|
||||||
|
Note what is **not** removed: `_public_access`, `_public_access_outside` and
|
||||||
|
`_public_access_pins`, and the `has_public_access_inside` / `..._outside`
|
||||||
|
tracking flags. Those are live — they drive real checks and
|
||||||
|
`collapse_global`'s `preserve_public_access`. Only the *length ratio* machinery
|
||||||
|
was dead.
|
||||||
|
|
||||||
|
**The `daylight` quality factor.** `evaluate_leaf` set `factors["daylight"] =
|
||||||
|
1.0` unconditionally, a factor that has been pinned since the URB_NO_OCCLUSION
|
||||||
|
descope (§6) and can never be anything else. It was never in `_GRADED_FACTORS`,
|
||||||
|
so it contributed nothing to the graded signal; §39.18's geometric mean then had
|
||||||
|
to special-case it in `factor_is_asked` as a factor that is never asked. A
|
||||||
|
constant that exists only to be excluded is worth deleting.
|
||||||
|
|
||||||
|
If the occlusion subsystem is ever rebuilt (`homemaker-py-2g5`), it reintroduces
|
||||||
|
a real daylight factor; nothing here forecloses that, and a real one would need
|
||||||
|
`factor_is_asked` to say `True` for it anyway.
|
||||||
|
|
||||||
|
**Why this is worth doing at all**, given neither changes a number: §39.20 and
|
||||||
|
§39.25 were both cases where something inert looked live — a parity test that
|
||||||
|
never ran, a per-level rule switched off in every config — and in both the
|
||||||
|
misreading cost real time and produced a wrong conclusion. An objective with
|
||||||
|
fewer things in it that do nothing is an objective where "this term does
|
||||||
|
nothing" is informative rather than routine.
|
||||||
|
|
||||||
|
Still open on `dpt`, and all three need a ruling or a rate change rather than a
|
||||||
|
measurement: `quality_size`'s upper side, the minimum-internal-area factor as a
|
||||||
|
third statement of "build the rooms", and the `0.5 ** n_fails` curve.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"""Native port of Urb's programme-driven fitness: leaf quality terms + cost model.
|
"""Native port of Urb's programme-driven fitness: leaf quality terms + cost model.
|
||||||
|
|
||||||
Scope (homemaker-py-gnw): per-leaf quality factors (perpendicular, proportion,
|
Scope (homemaker-py-gnw): per-leaf quality factors (perpendicular, proportion,
|
||||||
size, width, crinkliness, daylight, access), the programme-driven parameter
|
size, width, crinkliness, access), the programme-driven parameter
|
||||||
lookup chain (``get_space_params``), value rates, and the cost denominator
|
lookup chain (``get_space_params``), value rates, and the cost denominator
|
||||||
(per-leaf area costs, interior/exterior wall edge costs, boundary costs).
|
(per-leaf area costs, interior/exterior wall edge costs, boundary costs).
|
||||||
Storey/building checks, staircases, failure stacking and final assembly are
|
Storey/building checks, staircases, failure stacking and final assembly are
|
||||||
|
|
@ -1544,9 +1544,6 @@ class Fitness:
|
||||||
factors["crinkliness"] = f
|
factors["crinkliness"] = f
|
||||||
quality *= f
|
quality *= f
|
||||||
|
|
||||||
# Daylight pinned to 1 — URB_NO_OCCLUSION semantics (DESIGN.md §6).
|
|
||||||
factors["daylight"] = 1.0
|
|
||||||
|
|
||||||
if len(self.access(leaf, G)) > 0:
|
if len(self.access(leaf, G)) > 0:
|
||||||
f = 1.0
|
f = 1.0
|
||||||
elif not dom_mod.level_of(leaf) and dom_mod.is_outside(leaf):
|
elif not dom_mod.level_of(leaf) and dom_mod.is_outside(leaf):
|
||||||
|
|
@ -1570,8 +1567,6 @@ class Fitness:
|
||||||
`tests/test_fitness_aggregate.py` asserts the invariant this duplication
|
`tests/test_fitness_aggregate.py` asserts the invariant this duplication
|
||||||
rests on: whenever this returns False, the factor really is 1.0.
|
rests on: whenever this returns False, the factor really is 1.0.
|
||||||
"""
|
"""
|
||||||
if name == "daylight":
|
|
||||||
return False # pinned to 1.0, URB_NO_OCCLUSION §6
|
|
||||||
if name == "size":
|
if name == "size":
|
||||||
return _generic_class(leaf) not in ("o", "s")
|
return _generic_class(leaf) not in ("o", "s")
|
||||||
if name == "crinkliness":
|
if name == "crinkliness":
|
||||||
|
|
@ -1923,38 +1918,6 @@ class Fitness:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _public_length(self, leaf: Node, root: Node) -> float:
|
|
||||||
"""Non-private external boundary metres; mirrors ``Urb::Dom::Public_Length``."""
|
|
||||||
if dom_mod.level_of(leaf) != 0:
|
|
||||||
return 0.0
|
|
||||||
total = 0.0
|
|
||||||
for edge in range(4):
|
|
||||||
bid = geometry.boundary_id(leaf, edge)
|
|
||||||
if bid not in frozenset("abcd"):
|
|
||||||
continue
|
|
||||||
if self._perimeter_type(root, bid).lower() == "private":
|
|
||||||
continue
|
|
||||||
total += geometry.edge_length(leaf, edge)
|
|
||||||
return total
|
|
||||||
|
|
||||||
def _private_length(self, leaf: Node, root: Node) -> float:
|
|
||||||
"""Private external boundary metres; mirrors ``Urb::Dom::Private_Length``."""
|
|
||||||
if dom_mod.level_of(leaf) != 0:
|
|
||||||
return 0.0
|
|
||||||
total = 0.0
|
|
||||||
for edge in range(4):
|
|
||||||
bid = geometry.boundary_id(leaf, edge)
|
|
||||||
if bid not in frozenset("abcd"):
|
|
||||||
continue
|
|
||||||
if self._perimeter_type(root, bid).lower() != "private":
|
|
||||||
continue
|
|
||||||
total += geometry.edge_length(leaf, edge)
|
|
||||||
return total
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------- #
|
|
||||||
# Extended process_storey (adds circ, stair, tracking)
|
|
||||||
# ----------------------------------------------------------------------- #
|
|
||||||
|
|
||||||
def process_storey(
|
def process_storey(
|
||||||
self,
|
self,
|
||||||
level_root: Node,
|
level_root: Node,
|
||||||
|
|
@ -2040,15 +2003,6 @@ class Fitness:
|
||||||
and self._public_access(leaf, root) is not None):
|
and self._public_access(leaf, root) is not None):
|
||||||
tracking["has_public_access_inside"] = True
|
tracking["has_public_access_inside"] = True
|
||||||
|
|
||||||
pub = self._public_length(leaf, root)
|
|
||||||
tracking["public_length_all"] = tracking.get("public_length_all", 0.0) + pub
|
|
||||||
if dom_mod.is_outside(leaf):
|
|
||||||
tracking["public_length_outside"] = tracking.get("public_length_outside", 0.0) + pub
|
|
||||||
priv = self._private_length(leaf, root)
|
|
||||||
tracking["private_length_all"] = tracking.get("private_length_all", 0.0) + priv
|
|
||||||
if dom_mod.is_outside(leaf):
|
|
||||||
tracking["private_length_outside"] = tracking.get("private_length_outside", 0.0) + priv
|
|
||||||
|
|
||||||
for a, b in G.edges():
|
for a, b in G.edges():
|
||||||
cost += self.edge_cost(G, a, b, fail)
|
cost += self.edge_cost(G, a, b, fail)
|
||||||
for leaf in level_root.leaves():
|
for leaf in level_root.leaves():
|
||||||
|
|
@ -2096,19 +2050,6 @@ class Fitness:
|
||||||
f2 = gaussian(actual_internal, 1.0, min_required, min_required * 0.15)
|
f2 = gaussian(actual_internal, 1.0, min_required, min_required * 0.15)
|
||||||
factor *= f2
|
factor *= f2
|
||||||
|
|
||||||
# Public/private ratios (optional config)
|
|
||||||
pub_all = tracking.get("public_length_all", 0.0)
|
|
||||||
pub_ratio = tracking.get("public_length_outside", 0.0) / pub_all if pub_all else 0.0
|
|
||||||
conf_po = self.conf("ratio_public_outside")
|
|
||||||
if conf_po and isinstance(conf_po, list):
|
|
||||||
factor *= gaussian(pub_ratio, 1.0, conf_po[0], conf_po[1])
|
|
||||||
|
|
||||||
priv_all = tracking.get("private_length_all", 0.0)
|
|
||||||
priv_ratio = tracking.get("private_length_outside", 0.0) / priv_all if priv_all else 0.0
|
|
||||||
conf_pr = self.conf("ratio_private_outside")
|
|
||||||
if conf_pr and isinstance(conf_pr, list):
|
|
||||||
factor *= gaussian(priv_ratio, 1.0, conf_pr[0], conf_pr[1])
|
|
||||||
|
|
||||||
# Staircase volume (multi-level only)
|
# Staircase volume (multi-level only)
|
||||||
lvls = dom_mod.levels(root)
|
lvls = dom_mod.levels(root)
|
||||||
if len(lvls) > 1:
|
if len(lvls) > 1:
|
||||||
|
|
@ -2188,10 +2129,6 @@ class Fitness:
|
||||||
tracking: dict = {
|
tracking: dict = {
|
||||||
"has_public_access_outside": False,
|
"has_public_access_outside": False,
|
||||||
"has_public_access_inside": False,
|
"has_public_access_inside": False,
|
||||||
"public_length_all": 0.0,
|
|
||||||
"public_length_outside": 0.0,
|
|
||||||
"private_length_all": 0.0,
|
|
||||||
"private_length_outside": 0.0,
|
|
||||||
"stair_fit": [],
|
"stair_fit": [],
|
||||||
"_failures": failures,
|
"_failures": failures,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,6 @@ def _native_evaluate(src: Path):
|
||||||
tracking: dict = {
|
tracking: dict = {
|
||||||
"has_public_access_outside": False,
|
"has_public_access_outside": False,
|
||||||
"has_public_access_inside": False,
|
"has_public_access_inside": False,
|
||||||
"public_length_all": 0.0,
|
|
||||||
"public_length_outside": 0.0,
|
|
||||||
"private_length_all": 0.0,
|
|
||||||
"private_length_outside": 0.0,
|
|
||||||
"stair_fit": [],
|
"stair_fit": [],
|
||||||
"_failures": failures,
|
"_failures": failures,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -335,8 +335,10 @@ def test_leaf_grade_sums_over_failing_factors():
|
||||||
|
|
||||||
|
|
||||||
def test_leaf_grade_ignores_non_graded_keys():
|
def test_leaf_grade_ignores_non_graded_keys():
|
||||||
# daylight is pinned and never a graded factor even if below threshold.
|
# Only _GRADED_FACTORS contribute; anything else is ignored however low.
|
||||||
assert _leaf_grade({"daylight": 0.0}) == 0.0
|
# (This used to name "daylight", a factor pinned to 1.0 since the
|
||||||
|
# URB_NO_OCCLUSION descope and removed entirely in §39.26.)
|
||||||
|
assert _leaf_grade({"not_a_factor": 0.0}) == 0.0
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ def test_geometric_mean_is_the_product_when_every_factor_is_asked():
|
||||||
fit = Fitness(*load_config(EXAMPLES / "harbor-house"))
|
fit = Fitness(*load_config(EXAMPLES / "harbor-house"))
|
||||||
leaf = dom_mod.Node(type="r")
|
leaf = dom_mod.Node(type="r")
|
||||||
factors = {"perpendicular": 0.9, "proportion": 0.8, "size": 0.5,
|
factors = {"perpendicular": 0.9, "proportion": 0.8, "size": 0.5,
|
||||||
"width": 0.95, "crinkliness": 0.4, "access": 1.0, "daylight": 1.0}
|
"width": 0.95, "crinkliness": 0.4, "access": 1.0}
|
||||||
asked = [v for k, v in factors.items() if fit.factor_is_asked(k, leaf)]
|
asked = [v for k, v in factors.items() if fit.factor_is_asked(k, leaf)]
|
||||||
expected = math.prod(asked) ** (1.0 / len(asked))
|
expected = math.prod(asked) ** (1.0 / len(asked))
|
||||||
assert fit._aggregate_geometric(leaf, factors) == pytest.approx(expected)
|
assert fit._aggregate_geometric(leaf, factors) == pytest.approx(expected)
|
||||||
|
|
@ -111,7 +111,6 @@ def test_it_does_not_underflow_where_the_product_would():
|
||||||
tiny = 1e-60
|
tiny = 1e-60
|
||||||
factors = {k: tiny for k in ("perpendicular", "proportion", "size",
|
factors = {k: tiny for k in ("perpendicular", "proportion", "size",
|
||||||
"width", "crinkliness", "access")}
|
"width", "crinkliness", "access")}
|
||||||
factors["daylight"] = 1.0
|
|
||||||
assert math.prod(factors[k] for k in factors) == 0.0 # product underflows
|
assert math.prod(factors[k] for k in factors) == 0.0 # product underflows
|
||||||
assert fit._aggregate_geometric(leaf, factors) == pytest.approx(tiny, rel=1e-6)
|
assert fit._aggregate_geometric(leaf, factors) == pytest.approx(tiny, rel=1e-6)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue