research(R6.2.5): multi-subject occupancy union — N=5 hits 100% for 4 occupants; R6 family complete (#730)
Extends R6.2.3 chest-centric placement to union of chest envelopes
across multiple occupants. Practical question: does coverage degrade
gracefully as occupant count grows?
Result: 2D chest-centric + N=5 + multi-subject union = 100% coverage
for households of 1-4 occupants. N=4 knee returns.
| Scenario | # zones | Cov @ N=5 |
|------------|--------:|----------:|
| 1 occupant | 1 | 100% |
| 2 occupants| 2 | 100% |
| 3 occupants| 3 | 100% |
| 4 occupants| 4 | 100% |
4-occupant saturation: N=4 = 99.0% (+26.1 pp marginal), N=5 = 100%,
N=6+ saturated. Knee at N=4 even for 4 occupants.
Cross-eval: single-subject placement gets 70.6% on 4 zones; multi-
subject-optimised gets 100%. +29.4 pp gain from multi-subject
optimisation. CLI MUST accept multiple --target args and compute union.
Why N=4 knee returns: each chest zone is 40x40 cm, fits inside one
Fresnel ellipsoid (~40 cm wide at midpoint of 5 m link). N=4 anchors
give 6 pairwise links, enough to cover 4 disjoint chest zones without
much waste. Chest-centric multi-subject is the SWEET SPOT for Fresnel
envelope geometry.
R6 family complete (9 ticks: R6, R6.1, R6.2, R6.2.1, R6.2.2, R6.2.2.1,
R6.2.3, R6.2.4, R6.2.5). Family's ship recipe:
- 2D chest-centric + multi-subject + N=5 = 100% coverage
Productisation CLI spec (50 LOC over original R6.2):
wifi-densepose plan-antennas
--room W H [Z] # 2D or 3D
--target NAME X Y W H [DX DY DZ] # repeatable
--target-mode {body, chest} # R6.2.3
--freq-ghz F
--n-anchors N # auto-saturation if omitted
--restarts K
Honest scope: 2D only (3D multi-subject = mechanical extension), static
positions, single 5x5 m geometry, greedy with 4 restarts, 4 occupants
max tested.
Composes:
- R6.2 / R6.2.3 direct extension (single -> multi)
- R6.2.2 / R6.2.4 same saturation behaviour
- R14 V1/V2/V3 in households of 2-4 use this recipe
- R3 / ADR-024 per-subject identity + multi-subject placement
- ADR-105/106/107 federation orthogonal
- R12 PABS multi-subject coverage = multi-subject intrusion detection
Coordination: ticks/tick-27.md, no PROGRESS.md edit.
This commit is contained in:
parent
4183ef651f
commit
4e6ef76294
|
|
@ -0,0 +1,129 @@
|
|||
# R6.2.5 — Multi-subject occupancy union: N=5 hits 100% for 4 occupants
|
||||
|
||||
**Status:** clean positive result · **2026-05-22**
|
||||
|
||||
## Premise
|
||||
|
||||
R6.2 / R6.2.3 picked one chest position per zone. Real households have 2-4 occupants who can be in different positions simultaneously. R6.2.5 extends to **union of chest envelopes** across all expected occupant positions. The practical question: does coverage degrade gracefully as occupant count grows?
|
||||
|
||||
## Result: graceful saturation at N=5
|
||||
|
||||
| Scenario | # zones | Total area | Coverage @ N=5 |
|
||||
|---|---:|---:|---:|
|
||||
| 1 occupant (chair) | 1 | 0.16 m² | **100%** |
|
||||
| 2 occupants (chair + bed) | 2 | 0.40 m² | **100%** |
|
||||
| 3 occupants (chair + bed + desk) | 3 | 0.48 m² | **100%** |
|
||||
| 4 occupants (+ 2nd chair) | 4 | 0.64 m² | **100%** |
|
||||
|
||||
**N=5 hits 100% coverage for all configurations up to 4 occupants.** The chest-centric small-zone approach (R6.2.3) generalises trivially to multi-subject.
|
||||
|
||||
## 4-occupant saturation curve
|
||||
|
||||
| N | Coverage | Marginal |
|
||||
|---:|---:|---:|
|
||||
| 2 | 14.5% | +14.5 pp |
|
||||
| 3 | 72.9% | +58.4 pp |
|
||||
| **4** | **99.0%** | **+26.1 pp** |
|
||||
| 5 | 100% | +1.0 pp |
|
||||
| 6 | 100% | +0 pp |
|
||||
| 7 | 100% | +0 pp |
|
||||
|
||||
**Knee returns to N=4** — even for 4 occupants, 4 anchors get us to 99%. This is the **2D chest-centric multi-subject** regime, which is the most demanding 2D configuration tested in the R6 family — and it still hits the knee at N=4.
|
||||
|
||||
## Cross-eval: single-subject placement is bad for multi-subject
|
||||
|
||||
| Placement | Coverage on 4-zone target |
|
||||
|---|---:|
|
||||
| Single-subject-optimised | 70.6% |
|
||||
| Multi-subject-optimised | **100%** |
|
||||
| **Gain from multi-subject optimisation** | **+29.4 pp** |
|
||||
|
||||
The CLI must accept multiple `--target` arguments and optimise for their **union** — not pick a representative zone and hope.
|
||||
|
||||
## Updated CLI recommendation
|
||||
|
||||
```bash
|
||||
wifi-densepose plan-antennas \
|
||||
--room 5 5 \
|
||||
--target chair_chest 3.7 3.7 0.4 0.4 \
|
||||
--target bed_chest 2.2 0.8 0.6 0.4 \
|
||||
--target desk_chest 0.5 2.7 0.4 0.2 \
|
||||
--target chair2_chest 1.0 4.2 0.4 0.4 \
|
||||
--freq-ghz 2.4
|
||||
```
|
||||
|
||||
Output: N=5 anchors hitting 100% coverage of the union.
|
||||
|
||||
## R6 family summary (8 ticks + this)
|
||||
|
||||
| Tick | Configuration | Headline number |
|
||||
|---|---|---:|
|
||||
| R6.2 | 2D body, single-subject | 51% N=5 |
|
||||
| R6.2.1 | 3D body, single-subject | 26% N=2 (mixed-height) |
|
||||
| R6.2.2 | 2D body, N-anchor | 97% N=5 |
|
||||
| R6.2.2.1 | 3D body, N-anchor | 49% N=5 |
|
||||
| R6.2.3 | 2D chest, single-subject | 82% N=5 |
|
||||
| R6.2.4 | 3D chest, N-anchor | 77% N=5 / 82% N=6 |
|
||||
| **R6.2.5 (this)** | **2D chest, multi-subject (1-4)** | **100% N=5** |
|
||||
|
||||
The R6 family's headline finding: **2D chest-centric + multi-subject + N=5 = 100% coverage**. This is the placement recipe to ship.
|
||||
|
||||
## Composes with prior threads
|
||||
|
||||
- **R6.2 / R6.2.3**: directly extends — single-subject → multi-subject union
|
||||
- **R6.2.2 / R6.2.4**: same saturation behaviour at the multi-subject level
|
||||
- **R14 (empathic appliances)**: V1 lighting / V2 HVAC / V3 attention in households of 2-4 occupants → use multi-subject placement
|
||||
- **R3 / ADR-024**: per-subject identity (AETHER) + multi-subject placement = full empathic-appliance stack
|
||||
- **ADR-105 / ADR-106 / ADR-107**: federation operates on the same model across occupant counts; placement is orthogonal
|
||||
- **R12 PABS**: works per-subject within the union; multi-subject coverage = multi-subject intrusion detection
|
||||
|
||||
## Why N=4 knee returns for multi-subject
|
||||
|
||||
Each chest zone is small (40×40 cm) and fits inside a single Fresnel ellipsoid (which is ~40 cm wide at midpoint of a 5 m link). With N=4 anchors, we get 6 pairwise links — enough Fresnel ellipsoids to cover 4 disjoint 40×40 cm zones without much waste. Beyond N=4 the marginal gain drops to <1 pp.
|
||||
|
||||
This is *more saturated* than the single-subject R6.2 setup (which used 3 m² bed footprint and couldn't be covered fully even at N=8 with body-centric zones). **Chest-centric multi-subject is the sweet spot for the Fresnel envelope geometry.**
|
||||
|
||||
## Honest scope
|
||||
|
||||
- **2D only** — multi-subject 3D not benchmarked (extension is mechanical; expect N=6 to retain the chest-centric N=5 advantage).
|
||||
- **Static positions** — real occupants move; the union should be conservative (larger than any instantaneous configuration).
|
||||
- **Single 5×5 m geometry** — larger or oddly-shaped rooms need separate benchmarks.
|
||||
- **Greedy + 4 restarts** — global optimum may be 1-2 pp higher.
|
||||
- **4 occupants** — beyond 4-5 the coverage may degrade. Extreme density (e.g. classroom with 20 people) is a different regime.
|
||||
|
||||
## What this DOES enable
|
||||
|
||||
1. **A clean cap on the placement complexity story**: 4-occupant households are fully sensable at N=5 with multi-subject-aware placement.
|
||||
2. **A required CLI feature**: support multiple `--target` arguments.
|
||||
3. **An updated installer recipe**: for households of 1-4, the same N=5 chest-centric placement works.
|
||||
4. **R6 family closes with a positive result** that ships directly.
|
||||
|
||||
## What this DOES NOT enable
|
||||
|
||||
- Beyond 4-5 occupants — separate regime, not tested.
|
||||
- Time-varying occupancy (people moving between zones) — would benefit from pose-trajectory data (out of scope).
|
||||
- 3D multi-subject — mechanical extension, not done here.
|
||||
|
||||
## Final R6.2 CLI surface
|
||||
|
||||
After this tick, the productisation of R6.2 should support:
|
||||
|
||||
```
|
||||
wifi-densepose plan-antennas
|
||||
--room W H [Z] # 2D or 3D
|
||||
--target NAME X Y W H [DX DY DZ] # repeatable
|
||||
--target-mode {body, chest} # R6.2.3
|
||||
--freq-ghz F # 2.4, 5.0, 6.0
|
||||
--n-anchors N # auto-saturation if omitted
|
||||
--restarts K # 4 default
|
||||
```
|
||||
|
||||
This covers the R6.2 / R6.2.1 / R6.2.2 / R6.2.2.1 / R6.2.3 / R6.2.4 / R6.2.5 use cases in a single CLI tool. ~50 LOC over the original R6.2.
|
||||
|
||||
## Connection back
|
||||
|
||||
- **R6 / R6.1**: physical foundation
|
||||
- **R6.2 / R6.2.3**: single-subject body / chest
|
||||
- **R6.2.1 / R6.2.2 / R6.2.2.1 / R6.2.4**: 3D / N-anchor / composition
|
||||
- **R6.2.5 (this)**: multi-subject completes the matrix
|
||||
- **R14**: empathic-appliance deployment recipe is now: N=5 + chest-centric + multi-subject-union targets, with mixed-height anchors for full-body coverage when needed
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
# Tick 27 — 2026-05-22 09:32 UTC
|
||||
|
||||
**Thread:** R6.2.5 (multi-subject occupancy union)
|
||||
**Verdict:** Clean positive — **N=5 hits 100% coverage** for households of 1-4 occupants with chest-centric zones. N=4 knee returns. R6 family completes with this tick.
|
||||
|
||||
## What shipped
|
||||
|
||||
- `examples/research-sota/r6_2_5_multi_subject.py`
|
||||
- `examples/research-sota/r6_2_5_multi_subject_results.json`
|
||||
- `docs/research/sota-2026-05-22/R6_2_5-multi-subject-union.md`
|
||||
|
||||
## Headline
|
||||
|
||||
| Scenario | # zones | Coverage @ N=5 |
|
||||
|---|---:|---:|
|
||||
| 1 occupant | 1 | **100%** |
|
||||
| 2 occupants | 2 | **100%** |
|
||||
| 3 occupants | 3 | **100%** |
|
||||
| 4 occupants | 4 | **100%** |
|
||||
|
||||
4-occupant saturation curve:
|
||||
|
||||
| N | Coverage |
|
||||
|---:|---:|
|
||||
| 2 | 14.5% |
|
||||
| 3 | 72.9% |
|
||||
| **4** | **99.0%** ← knee |
|
||||
| 5 | 100% |
|
||||
|
||||
**Knee at N=4** even for 4 occupants. The chest-centric small-zone approach generalises trivially.
|
||||
|
||||
## Cross-eval: multi-subject optimisation matters
|
||||
|
||||
| Placement | Coverage on 4 zones |
|
||||
|---|---:|
|
||||
| Single-subject-optimised | 70.6% |
|
||||
| **Multi-subject-optimised** | **100%** |
|
||||
| **Gain** | **+29.4 pp** |
|
||||
|
||||
CLI must accept multiple `--target` args and compute union.
|
||||
|
||||
## R6 family complete (9 ticks)
|
||||
|
||||
| Tick | Config | Result |
|
||||
|---|---|---:|
|
||||
| R6.2 | 2D body, single | 51% N=5 |
|
||||
| R6.2.1 | 3D body, single | 26% N=2 |
|
||||
| R6.2.2 | 2D body, N-anchor | 97% N=5 |
|
||||
| R6.2.2.1 | 3D body, N-anchor | 49% N=5 |
|
||||
| R6.2.3 | 2D chest, single | 82% N=5 |
|
||||
| R6.2.4 | 3D chest, N-anchor | 77/82% N=5/6 |
|
||||
| **R6.2.5** | **2D chest, multi-subject** | **100% N=5** |
|
||||
|
||||
**R6 family's ship recipe**: 2D chest-centric + multi-subject + N=5 = 100% coverage.
|
||||
|
||||
## Why N=4 knee returns for multi-subject
|
||||
|
||||
Each chest zone is 40×40 cm and fits inside one Fresnel ellipsoid (~40 cm wide at midpoint of 5 m link). N=4 anchors → 6 pairwise links → enough to cover 4 disjoint chest zones without much waste. Beyond N=4 the marginal gain drops to <1 pp.
|
||||
|
||||
**Chest-centric multi-subject is the sweet spot for the Fresnel envelope geometry.**
|
||||
|
||||
## Final R6.2 CLI surface (productisation spec)
|
||||
|
||||
```
|
||||
wifi-densepose plan-antennas
|
||||
--room W H [Z] # 2D or 3D
|
||||
--target NAME X Y W H [DX DY DZ] # repeatable
|
||||
--target-mode {body, chest} # R6.2.3
|
||||
--freq-ghz F # 2.4, 5.0, 6.0
|
||||
--n-anchors N # auto-saturation if omitted
|
||||
--restarts K # 4 default
|
||||
```
|
||||
|
||||
~50 LOC over the original R6.2.
|
||||
|
||||
## Composes with prior threads
|
||||
|
||||
- R6.2 / R6.2.3: direct extension (single → multi)
|
||||
- R6.2.2 / R6.2.4: same saturation behaviour
|
||||
- R14: V1/V2/V3 in households of 2-4 use this recipe
|
||||
- R3 / ADR-024: per-subject identity + multi-subject placement = full empathic-appliance stack
|
||||
- ADR-105/106/107: federation orthogonal to placement
|
||||
- R12 PABS: multi-subject coverage = multi-subject intrusion detection
|
||||
|
||||
## Honest scope
|
||||
|
||||
- 2D only (3D multi-subject is mechanical extension)
|
||||
- Static positions (real movement = conservative union)
|
||||
- Single 5×5 m geometry
|
||||
- Greedy + 4 restarts
|
||||
- 4 occupants; beyond may degrade
|
||||
|
||||
## Coordination
|
||||
|
||||
`ticks/tick-27.md`. No PROGRESS.md edit. Branch `research/sota-r6.2.5-multi-subject`.
|
||||
|
||||
## Remaining loop work
|
||||
|
||||
- R12.1: pose-PABS closed loop (needs Rust integration, out of scope for synthetic ticks)
|
||||
- ADR-108: Kyber substitution (quantum-resistant)
|
||||
- Loop retrospective / 00-summary.md (still ~2.5h until cron stop)
|
||||
|
||||
~2.5h to cron stop. **27 ticks landed.** R6 family + R3 arc both substantively complete.
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env python3
|
||||
"""R6.2.5 — Multi-subject occupancy union.
|
||||
|
||||
See docs/research/sota-2026-05-22/R6_2_5-multi-subject-union.md.
|
||||
|
||||
R6.2 / R6.2.3 picked one chest position per zone. Real households
|
||||
have 2-4 occupants who can be in different positions simultaneously
|
||||
(spouse in bed + child at desk + visitor on chair). R6.2.5 extends to
|
||||
**union of chest envelopes** across all expected occupant positions.
|
||||
|
||||
Practical question: does the optimal placement degrade gracefully
|
||||
when target zones multiply? Does N=5 still hit a useful coverage?
|
||||
|
||||
Pure NumPy.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
|
||||
C = 2.998e8
|
||||
|
||||
|
||||
def wavelength_m(freq_ghz: float) -> float:
|
||||
return C / (freq_ghz * 1e9)
|
||||
|
||||
|
||||
def in_first_fresnel(x, y, tx, rx, wavelength):
|
||||
r1 = np.sqrt((x - tx[0])**2 + (y - tx[1])**2)
|
||||
r2 = np.sqrt((x - rx[0])**2 + (y - rx[1])**2)
|
||||
direct = np.linalg.norm(tx - rx)
|
||||
return (r1 + r2) <= (direct + wavelength / 2)
|
||||
|
||||
|
||||
def union_coverage(anchors, target_x, target_y, wavelength):
|
||||
if len(anchors) < 2: return 0.0
|
||||
covered = np.zeros(len(target_x), dtype=bool)
|
||||
for i in range(len(anchors)):
|
||||
for j in range(i+1, len(anchors)):
|
||||
covered |= in_first_fresnel(target_x, target_y,
|
||||
anchors[i], anchors[j], wavelength)
|
||||
return float(covered.mean())
|
||||
|
||||
|
||||
def rasterise_zones(zones, resolution=0.05):
|
||||
xs, ys = [], []
|
||||
for name, x0, y0, w, h in zones:
|
||||
zx = np.arange(x0, x0 + w, resolution)
|
||||
zy = np.arange(y0, y0 + h, resolution)
|
||||
gx, gy = np.meshgrid(zx, zy)
|
||||
xs.append(gx.ravel())
|
||||
ys.append(gy.ravel())
|
||||
return np.concatenate(xs), np.concatenate(ys)
|
||||
|
||||
|
||||
def candidates(room_w, room_h, step):
|
||||
cands = []
|
||||
for x in np.arange(0, room_w + 0.001, step):
|
||||
cands.append(np.array([x, 0.0]))
|
||||
cands.append(np.array([x, room_h]))
|
||||
for y in np.arange(step, room_h, step):
|
||||
cands.append(np.array([0.0, y]))
|
||||
cands.append(np.array([room_w, y]))
|
||||
return cands
|
||||
|
||||
|
||||
def greedy_search(cands, target_x, target_y, lam, n_anchors, restarts=4, seed=0):
|
||||
rng = np.random.default_rng(seed)
|
||||
best = {"score": -1.0, "anchors": []}
|
||||
for r in range(restarts):
|
||||
idx0, idx1 = rng.choice(len(cands), size=2, replace=False)
|
||||
chosen = [cands[idx0], cands[idx1]]
|
||||
while len(chosen) < n_anchors:
|
||||
best_marg = -1
|
||||
best_idx = None
|
||||
for k, c in enumerate(cands):
|
||||
if any(np.allclose(c, a) for a in chosen): continue
|
||||
s = union_coverage(chosen + [c], target_x, target_y, lam)
|
||||
if s > best_marg:
|
||||
best_marg = s
|
||||
best_idx = k
|
||||
if best_idx is None: break
|
||||
chosen.append(cands[best_idx])
|
||||
score = union_coverage(chosen, target_x, target_y, lam)
|
||||
if score > best["score"]:
|
||||
best = {"score": score, "anchors": [a.tolist() for a in chosen]}
|
||||
return best
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--out", default="examples/research-sota/r6_2_5_multi_subject_results.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
room_w, room_h = 5.0, 5.0
|
||||
freq = 2.4
|
||||
lam = wavelength_m(freq)
|
||||
step = 0.25
|
||||
cands = candidates(room_w, room_h, step)
|
||||
|
||||
# Scenarios with increasing occupant count
|
||||
# Each "chest zone" is a 40x40 cm patch
|
||||
scenarios = {
|
||||
"1 occupant (chair)": [
|
||||
("chair_chest", 3.7, 3.7, 0.4, 0.4),
|
||||
],
|
||||
"2 occupants (chair + bed)": [
|
||||
("chair_chest", 3.7, 3.7, 0.4, 0.4),
|
||||
("bed_chest", 2.2, 0.8, 0.6, 0.4),
|
||||
],
|
||||
"3 occupants (chair + bed + desk)": [
|
||||
("chair_chest", 3.7, 3.7, 0.4, 0.4),
|
||||
("bed_chest", 2.2, 0.8, 0.6, 0.4),
|
||||
("desk_chest", 0.5, 2.7, 0.4, 0.2),
|
||||
],
|
||||
"4 occupants (+ 2nd chair)": [
|
||||
("chair_chest", 3.7, 3.7, 0.4, 0.4),
|
||||
("bed_chest", 2.2, 0.8, 0.6, 0.4),
|
||||
("desk_chest", 0.5, 2.7, 0.4, 0.2),
|
||||
("chair2_chest", 1.0, 4.2, 0.4, 0.4),
|
||||
],
|
||||
}
|
||||
|
||||
print(f"Room {room_w}x{room_h} m, freq {freq} GHz, chest-centric zones")
|
||||
print()
|
||||
|
||||
# For each scenario, find optimum at N=5
|
||||
results = []
|
||||
for name, zones in scenarios.items():
|
||||
tx, ty = rasterise_zones(zones)
|
||||
result = greedy_search(cands, tx, ty, lam, n_anchors=5)
|
||||
# Total zone area
|
||||
zone_area = sum(w * h for _, _, _, w, h in zones)
|
||||
results.append({
|
||||
"scenario": name,
|
||||
"n_zones": len(zones),
|
||||
"total_zone_area_m2": zone_area,
|
||||
"coverage_n5": result["score"],
|
||||
"best_anchors": result["anchors"],
|
||||
})
|
||||
|
||||
print(f"{'Scenario':<40} {'#zones':>6} {'Area':>7} {'Cov@N=5':>9}")
|
||||
print("-" * 75)
|
||||
for r in results:
|
||||
print(f"{r['scenario']:<40} {r['n_zones']:>6} {r['total_zone_area_m2']:>5.2f} m2 {r['coverage_n5']*100:>7.1f}%")
|
||||
print()
|
||||
|
||||
# Stress test: scale N for the 4-occupant scenario
|
||||
print(f"=== 4-occupant scenario, scaling N from 2..7 ===")
|
||||
zones4 = scenarios["4 occupants (+ 2nd chair)"]
|
||||
tx, ty = rasterise_zones(zones4)
|
||||
print(f"{'N':>3} {'Coverage':>9} {'Marginal':>9}")
|
||||
prev = 0.0
|
||||
scale_curve = []
|
||||
for n in range(2, 8):
|
||||
result = greedy_search(cands, tx, ty, lam, n_anchors=n)
|
||||
marg = (result["score"] - prev) * 100
|
||||
print(f"{n:>3} {result['score']*100:>7.1f}% {marg:>+7.1f} pp")
|
||||
scale_curve.append({"n_anchors": n, "coverage": result["score"]})
|
||||
prev = result["score"]
|
||||
print()
|
||||
|
||||
# Cross-eval: how does a single-subject-optimised placement perform on 4 subjects?
|
||||
single_zone = [("chair_chest", 3.7, 3.7, 0.4, 0.4)]
|
||||
tx1, ty1 = rasterise_zones(single_zone)
|
||||
single_opt = greedy_search(cands, tx1, ty1, lam, n_anchors=5)
|
||||
tx4, ty4 = rasterise_zones(zones4)
|
||||
cov_single_on_multi = union_coverage(
|
||||
[np.array(a) for a in single_opt["anchors"]], tx4, ty4, lam
|
||||
)
|
||||
print(f"=== Cross-eval ===")
|
||||
print(f" Single-subject placement on 4-subject zones: {cov_single_on_multi*100:.1f}%")
|
||||
print(f" 4-subject-optimised placement on 4 zones: {results[-1]['coverage_n5']*100:.1f}%")
|
||||
print(f" Gain from multi-subject optimisation: {(results[-1]['coverage_n5'] - cov_single_on_multi)*100:+.1f} pp")
|
||||
print()
|
||||
|
||||
out = {
|
||||
"room": {"width_m": room_w, "height_m": room_h},
|
||||
"freq_ghz": freq,
|
||||
"scenarios_n5": results,
|
||||
"saturation_4subj": scale_curve,
|
||||
"cross_eval": {
|
||||
"single_opt_on_multi": cov_single_on_multi,
|
||||
"multi_opt_on_multi": results[-1]["coverage_n5"],
|
||||
"gain_pp": (results[-1]["coverage_n5"] - cov_single_on_multi) * 100,
|
||||
},
|
||||
}
|
||||
Path(args.out).parent.mkdir(parents=True, exist_ok=True)
|
||||
Path(args.out).write_text(json.dumps(out, indent=2))
|
||||
print(f"Wrote {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
{
|
||||
"room": {
|
||||
"width_m": 5.0,
|
||||
"height_m": 5.0
|
||||
},
|
||||
"freq_ghz": 2.4,
|
||||
"scenarios_n5": [
|
||||
{
|
||||
"scenario": "1 occupant (chair)",
|
||||
"n_zones": 1,
|
||||
"total_zone_area_m2": 0.16000000000000003,
|
||||
"coverage_n5": 1.0,
|
||||
"best_anchors": [
|
||||
[
|
||||
5.0,
|
||||
3.25
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
1.25
|
||||
],
|
||||
[
|
||||
2.0,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
5.0
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "2 occupants (chair + bed)",
|
||||
"n_zones": 2,
|
||||
"total_zone_area_m2": 0.4,
|
||||
"coverage_n5": 1.0,
|
||||
"best_anchors": [
|
||||
[
|
||||
5.0,
|
||||
3.25
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
1.25
|
||||
],
|
||||
[
|
||||
5.0,
|
||||
0.5
|
||||
],
|
||||
[
|
||||
2.0,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "3 occupants (chair + bed + desk)",
|
||||
"n_zones": 3,
|
||||
"total_zone_area_m2": 0.48000000000000004,
|
||||
"coverage_n5": 1.0,
|
||||
"best_anchors": [
|
||||
[
|
||||
5.0,
|
||||
3.25
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
1.25
|
||||
],
|
||||
[
|
||||
2.0,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
5.0,
|
||||
0.5
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario": "4 occupants (+ 2nd chair)",
|
||||
"n_zones": 4,
|
||||
"total_zone_area_m2": 0.6400000000000001,
|
||||
"coverage_n5": 1.0,
|
||||
"best_anchors": [
|
||||
[
|
||||
3.0,
|
||||
0.0
|
||||
],
|
||||
[
|
||||
2.5,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
0.0,
|
||||
3.75
|
||||
],
|
||||
[
|
||||
4.25,
|
||||
5.0
|
||||
],
|
||||
[
|
||||
0.75,
|
||||
5.0
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
"saturation_4subj": [
|
||||
{
|
||||
"n_anchors": 2,
|
||||
"coverage": 0.14516129032258066
|
||||
},
|
||||
{
|
||||
"n_anchors": 3,
|
||||
"coverage": 0.7290322580645161
|
||||
},
|
||||
{
|
||||
"n_anchors": 4,
|
||||
"coverage": 0.9903225806451613
|
||||
},
|
||||
{
|
||||
"n_anchors": 5,
|
||||
"coverage": 1.0
|
||||
},
|
||||
{
|
||||
"n_anchors": 6,
|
||||
"coverage": 1.0
|
||||
},
|
||||
{
|
||||
"n_anchors": 7,
|
||||
"coverage": 1.0
|
||||
}
|
||||
],
|
||||
"cross_eval": {
|
||||
"single_opt_on_multi": 0.7064516129032258,
|
||||
"multi_opt_on_multi": 1.0,
|
||||
"gain_pp": 29.354838709677423
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue