A corridor may be corridor-shaped: no aspect cap on circulation
Owner's ruling: "there should be no cap on the proportion of a corridor, especially for big buildings, the crinkliness rule is there to prevent these becoming unpleasant spaces." proportion_circulation was [1.5, 0.5], failing a corridor above aspect 2.57 -- at the 1.97 m minimum width the width factor allows, a corridor 5.1 m long; at the 2.4 m target width, 6.2 m. The corpus shows the consequence: median circulation leaf 14.3 m2 at aspect 1.67, a stubby room rather than a corridor. The justification checks out arithmetically. The unpleasant space the cap was standing in for is a long BURIED corridor -- and crinkliness already sends that to zero, since a buried leaf has crink == 0. A long corridor along a facade scores 0.90. Aspect cannot tell those two apart; crinkliness can, so the cap was duplicating a rule that already exists and does the job better. Shipped: proportion_circulation = None (no aspect requirement). quality_proportion returns 1.0 for circulation, and shapecurve.leaf_constraints yields rmax = inf so the DP agrees with the fitness instead of pruning topologies the objective would accept. A habitable room's aspect target is untouched. The narrow side still holds -- width_circulation keeps a corridor >= 1.97 m, and "edge too long" still caps a single wall at 8 m. Unlike 39.14/39.18/39.19 this DOES change the fail set, which is the point. Across the twelve baseline artefacts it removes exactly 7 corridor proportion fails and adds none: harbor 33/43/42 -> 33/42/40, maple 54/73/55 -> 54/71/54, health-centre 4/9/5 -> 3/9/5, programme-house unchanged. Also recorded in 39.22, and a retraction: hxi was titled "search is rewarded for deleting the circulation spine", which 39.8 had already measured and refuted -- 0 of the 7 connectivity-breaking deletions sampled were rewarded. My own earlier comment on the bead restated that retracted claim; corrected, and the bead retitled. What binds next: removing the cap roughly doubles a corridor leaf's reach, from proportion at ~6.2 m to size_circulation at 12.5 m (2.4 m wide hits the 30 m2 fail edge there). size_circulation's target area is ZERO, the other half of the double-charge -- circulation priced as overhead once in value_circulation = 50 and again in a size factor whose optimum is non-existence. Not changed: it is a distinct parameter with its own rationale, unruled, and 39.16 is a standing reminder about inherited constants. 415 passed. Refs homemaker-py-hxi. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MJ84Feep79Hhm3E4zZJmnB
This commit is contained in:
parent
32973cfe95
commit
9a8852a82b
4 changed files with 199 additions and 3 deletions
85
DESIGN.md
85
DESIGN.md
|
|
@ -7814,3 +7814,88 @@ Next: `homemaker-py-hxi` — circulation at 0.07 return against a room's 0.66,
|
|||
which the owner has ruled needs fixing — and the corpus re-baseline
|
||||
(`homemaker-py-bk9`), which §39.19 made necessary and which every future number
|
||||
depends on.
|
||||
|
||||
### 39.22 A corridor may be corridor-shaped (`homemaker-py-hxi`)
|
||||
|
||||
Asked whether the scoring of outdoor and circulation space is likely correct,
|
||||
the answer for circulation is no — but **not** for the reason the bead carried.
|
||||
|
||||
**First, a retraction of the bead's own premise.** `hxi` was titled "search is
|
||||
rewarded for deleting the circulation spine". §39.8 already measured that and
|
||||
found the opposite: of 64 sampled deletions across harbor and maple, 7 broke
|
||||
connectivity and **0 of those 7 were rewarded** (×0.00 to ×0.58). Severing costs
|
||||
one or two connectivity fails plus the cascade behind them, which outweighs the
|
||||
×6 value gain. The low value rate does not translate into the spine being
|
||||
deleted. (A comment on the bead restating the retracted claim has been
|
||||
corrected, and the bead retitled.)
|
||||
|
||||
**What the parameters actually say.** Over the 111 circulation leaves in the
|
||||
twelve baseline runs, per leaf:
|
||||
|
||||
| factor | mean | fails |
|
||||
|---|---|---|
|
||||
| perpendicular | 0.982 | 0/111 |
|
||||
| proportion | 0.710 | 7/111 |
|
||||
| size | 0.539 | 16/111 |
|
||||
| width | 0.772 | 2/111 |
|
||||
| crinkliness | 0.485 | 29/111 |
|
||||
| access | 1.000 | 0/111 |
|
||||
|
||||
Median corridor 14.3 m², median aspect **1.67** — a stubby room, not a
|
||||
corridor. Two parameters explain that, and both are the double-charge shape
|
||||
§39.14 and §39.18 found elsewhere:
|
||||
|
||||
1. **`proportion_circulation = [1.5, 0.5]`** caps aspect at 2.57. At the 1.97 m
|
||||
minimum width the width factor allows, that is a corridor **5.1 m long**; at
|
||||
the 2.4 m target width, 6.2 m. A corridor is long and thin by nature, and
|
||||
the bigger the building the longer the spine must be.
|
||||
2. **`size_circulation = [0.0, 14.0]`** — the target area of a corridor is
|
||||
**zero**, so a median 14.3 m² corridor scores 0.592 on size purely for
|
||||
existing. Circulation is then priced as overhead twice: once in
|
||||
`value_circulation` = 50 (a sixth of a room) and again in a size factor
|
||||
whose optimum is non-existence.
|
||||
|
||||
**Owner's ruling on the first:** *there should be no cap on the proportion of a
|
||||
corridor, especially for big buildings, the crinkliness rule is there to
|
||||
prevent these becoming unpleasant spaces.*
|
||||
|
||||
That justification is exactly right, and it is checkable. The unpleasant space
|
||||
the aspect cap was being used to prevent is a long *buried* corridor — and
|
||||
crinkliness already sends that to zero, because a buried leaf has `crink == 0`.
|
||||
A long corridor *along a facade* is a perfectly good corridor, and crinkliness
|
||||
scores it **0.90**. The cap was standing in for a rule that already exists and
|
||||
does the job better, since it distinguishes the two cases where aspect cannot.
|
||||
|
||||
**Shipped:** `proportion_circulation = None`, meaning no aspect requirement.
|
||||
`quality_proportion` returns 1.0 for circulation; `shapecurve.leaf_constraints`
|
||||
yields `rmax = inf` so the DP agrees with the fitness rather than pruning
|
||||
topologies the objective would accept. A habitable room's aspect target is
|
||||
untouched — the ruling is about corridors.
|
||||
|
||||
The narrow side is still held: `width_circulation` keeps a corridor ≥ 1.97 m
|
||||
wide, and `edge too long` still caps any single wall at 8 m.
|
||||
|
||||
**Unlike §39.14/§39.18/§39.19, this changes the fail set** — that is the point
|
||||
of it. Across the twelve baseline artefacts it removes exactly **7** corridor
|
||||
proportion fails and adds none:
|
||||
|
||||
| programme | fails before → after |
|
||||
|---|---|
|
||||
| harbor-house | 33/43/42 → 33/42/40 |
|
||||
| maple-court | 54/73/55 → 54/71/54 |
|
||||
| health-centre | 4/9/5 → 3/9/5 |
|
||||
| programme-house | 1/1/1 → unchanged |
|
||||
|
||||
**What binds next, and it is worth knowing before reading too much into this.**
|
||||
Removing the aspect cap roughly doubles how long a single corridor leaf may be:
|
||||
the binding constraint moves from proportion at ~6.2 m to `size_circulation` at
|
||||
**12.5 m** (a 2.4 m wide leaf reaches the 30 m² fail edge there). A longer spine
|
||||
is still representable as several corridor leaves in series, so this is a real
|
||||
relaxation rather than a cosmetic one — but the zero-area size target is still
|
||||
there, and it is the other half of the double-charge above. It has **not** been
|
||||
changed: it is a distinct parameter with its own rationale (circulation is
|
||||
overhead, minimise it), the owner has not ruled on it, and §39.16 is a standing
|
||||
reminder about how often an inherited constant has turned out to be right.
|
||||
|
||||
Whether any of this helps the search is unmeasured and, as with everything since
|
||||
§39.19, gated on the re-baseline (`homemaker-py-bk9`).
|
||||
|
|
|
|||
|
|
@ -233,7 +233,17 @@ CONF_DEFAULTS: dict = {
|
|||
"size_circulation": [0.0, 14.0],
|
||||
"size_inside": [16.0, 3.5],
|
||||
"proportion_outside": [1.5, 50],
|
||||
"proportion_circulation": [1.5, 0.5],
|
||||
# homemaker-py-hxi (DESIGN.md §39.22). Was [1.5, 0.5], which fails a
|
||||
# corridor above aspect 2.57 -- at the 1.97 m minimum width the width factor
|
||||
# allows, that is a corridor 5.1 m long. A corridor is long and thin by
|
||||
# nature, and the longer the building the longer the spine has to be.
|
||||
#
|
||||
# `None` means NO proportion requirement. What stops a corridor becoming an
|
||||
# unpleasant space is crinkliness, not aspect: a long buried corridor has
|
||||
# crink == 0 and fails outright, while a long one along a facade scores
|
||||
# 0.90 -- which is the right answer for both. Width still holds the
|
||||
# narrow side (>= 1.97 m) and `edge too long` still caps a single wall.
|
||||
"proportion_circulation": None,
|
||||
"proportion_inside": [1.5, 0.5],
|
||||
"width_outside": [3.0, 0.3],
|
||||
"width_circulation": [2.4, 0.2],
|
||||
|
|
@ -1182,6 +1192,8 @@ class Fitness:
|
|||
params = self.conf("proportion_outside")
|
||||
elif t0 == "c":
|
||||
params = self.conf("proportion_circulation")
|
||||
if params is None:
|
||||
return 1.0 # no aspect requirement -- §39.22
|
||||
else:
|
||||
params = self.get_space_params(leaf.type, "proportion")
|
||||
co_type = self._leaf_co_type(leaf)
|
||||
|
|
|
|||
|
|
@ -233,6 +233,9 @@ def leaf_constraints(fit, leaf: dom_mod.Node) -> LeafBounds:
|
|||
params = fit.conf("proportion_circulation")
|
||||
else:
|
||||
params = fit.get_space_params(leaf.type, "proportion")
|
||||
if params is None:
|
||||
rmax = math.inf # no aspect requirement (§39.22, circulation)
|
||||
else:
|
||||
target, sigma = params[0], params[1]
|
||||
rmax = max(1.0 + 1e-9, target + _K * sigma)
|
||||
|
||||
|
|
|
|||
96
tests/test_circulation_proportion.py
Normal file
96
tests/test_circulation_proportion.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
"""Corridors have no aspect requirement (homemaker-py-hxi, DESIGN.md §39.22).
|
||||
|
||||
Owner's ruling: "there should be no cap on the proportion of a corridor,
|
||||
especially for big buildings, the crinkliness rule is there to prevent these
|
||||
becoming unpleasant spaces."
|
||||
|
||||
The tests below pin both halves of that — the cap is gone, AND crinkliness
|
||||
still does the job the cap was wrongly doing, so removing it did not leave
|
||||
long buried corridors unpunished.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import math
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from homemaker_layout import dom as dom_mod
|
||||
from homemaker_layout.fitness import (
|
||||
CONF_DEFAULTS, FAIL_THRESHOLD, Fitness, gaussian, load_config,
|
||||
)
|
||||
|
||||
EXAMPLES = Path(__file__).resolve().parent.parent / "examples"
|
||||
pytestmark = pytest.mark.skipif(not (EXAMPLES / "harbor-house").is_dir(),
|
||||
reason="examples absent")
|
||||
|
||||
|
||||
def _fit(**ov):
|
||||
conf, cost = load_config(EXAMPLES / "harbor-house", overrides=ov)
|
||||
return Fitness(conf, cost)
|
||||
|
||||
|
||||
def test_there_is_no_corridor_aspect_requirement():
|
||||
assert CONF_DEFAULTS["proportion_circulation"] is None
|
||||
fit = _fit()
|
||||
assert dom_mod.is_circulation(dom_mod.Node(type="C"))
|
||||
# any aspect at all scores 1.0 -- checked through the real code path
|
||||
for aspect in (1.0, 3.0, 12.0, 40.0):
|
||||
fit_leaf = dom_mod.Node(
|
||||
type="C", node=[[0.0, 0.0], [aspect, 0.0], [aspect, 1.0], [0.0, 1.0]])
|
||||
assert fit.quality_proportion(fit_leaf) == 1.0, aspect
|
||||
|
||||
|
||||
def test_a_room_still_has_one():
|
||||
"""The ruling is about corridors. A habitable room's aspect target stands."""
|
||||
fit = _fit()
|
||||
room = dom_mod.Node(type="r", node=[[0.0, 0.0], [12.0, 0.0], [12.0, 1.0], [0.0, 1.0]])
|
||||
assert fit.quality_proportion(room) < FAIL_THRESHOLD
|
||||
|
||||
|
||||
def test_crinkliness_is_what_punishes_an_unpleasant_corridor():
|
||||
"""The ruling's own justification, as arithmetic.
|
||||
|
||||
A long BURIED corridor is exactly the unpleasant space the aspect cap was
|
||||
being used to prevent; crinkliness sends it to zero. A long corridor along
|
||||
a facade is a perfectly good corridor, and crinkliness likes it.
|
||||
"""
|
||||
target, sigma = CONF_DEFAULTS["uncrinkliness_circulation"]
|
||||
h, width, length = 3.0, 2.4, 30.0
|
||||
area = width * length
|
||||
|
||||
buried = 0.0 # no illuminated wall at all
|
||||
assert buried == 0.0
|
||||
|
||||
lit_one_side = (length * h) / area # the long wall is a facade
|
||||
q = gaussian(1.0 / lit_one_side, 1.0, target, sigma)
|
||||
assert q > 0.85, "a daylit corridor along a facade should score well"
|
||||
|
||||
|
||||
def test_removing_the_cap_only_ever_removes_proportion_fails():
|
||||
"""It cannot introduce a failure, and it touches nothing but corridors."""
|
||||
old = {"proportion_circulation": [1.5, 0.5]}
|
||||
seen = 0
|
||||
for name in ("harbor-house", "maple-court", "health-centre", "programme-house"):
|
||||
d = EXAMPLES / name
|
||||
for p in sorted(d.glob("coldstart-500000-s*.dom")):
|
||||
root = dom_mod.load(str(p))
|
||||
c_old, cost = load_config(d, overrides=old)
|
||||
c_new, _ = load_config(d)
|
||||
_, f_old = Fitness(c_old, cost).score_with_fails(copy.deepcopy(root))
|
||||
_, f_new = Fitness(c_new, cost).score_with_fails(copy.deepcopy(root))
|
||||
assert not (set(f_new) - set(f_old)), "must not add a failure"
|
||||
assert all(f.endswith(" proportion") for f in set(f_old) - set(f_new))
|
||||
seen += 1
|
||||
assert seen >= 4
|
||||
|
||||
|
||||
def test_the_shape_curve_dp_accepts_an_unbounded_aspect():
|
||||
"""`leaf_constraints` feeds rmax into the DP; None must become inf, not crash
|
||||
and not silently fall back to a finite cap."""
|
||||
from homemaker_layout import shapecurve
|
||||
fit = _fit()
|
||||
leaf = dom_mod.Node(type="C", node=[[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [0.0, 4.0]])
|
||||
assert math.isinf(shapecurve.leaf_constraints(fit, leaf).rmax)
|
||||
Loading…
Add table
Reference in a new issue