Files
mcm-26-b/docs/第三四问架构优化与运行提速方案.md
T
2026-09-12 14:48:43 +08:00

596 lines
43 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 第三、四问系统性架构优化与运行提速方案
- 版本:2026-09-12
- 依据:本仓库运行历史(`session.jsonl`、`practice-p3-*.jlog`、`referee-data/sessions/*`、
`output/hrl/ui/q3-main/*`、`docs/` 全部性能记录)+ 本文档新增的复跑与离线校核实验
- 范围:第三问(全向源)与第四问(混合定向源)的**闭环架构、算法分解、运行治理**,
不改动题面约束、协议层语义与既有保守性不变量
- 结论先行:**虚拟时间的 75–83% 是移动**,移动里约一半不是覆盖巡回而是定位/清除往返;
**第四问的全部失败来自定向源**;旧机器学习层已被证明不如手写基线。
因此本轮优化的主线是"**把定位并入覆盖巡回 + 把发现集压到最小命中集 + 给定向源建位置×朝向联合信念**",
而不是再堆学习算法。
---
## 0. 摘要(TL;DR)
### 0.1 三个待解的结构性浪费
| 编号 | 现象 | 直接证据 | 估计可回收 |
| --- | --- | --- | --- |
| W1 | Q3 定位探点往返 5741 m / 1148 s,与覆盖巡回(6960 m / 1392 s)相当 | 本次复跑(13 源,seed 0):24 次探点,均值 442 m/次 | **−900 ~ −1100 s/局** |
| W2 | Q4 发现集是 700 m 方格 49 点,巡回 36.4 km;同一保证下贪心最小命中集只要 31 点、22.9 km | `scripts/verify_q4_cover.py`:43 032 个 (p,h) 采样,两组点集均 100% 覆盖 | **−2700 s(巡回)+ −1140 s(检测)** |
| W3 | Q4 定向源没有"位置×朝向"联合信念,只能阶梯探点,最后退化到 ≤400 格盲清 | referee 两局:全向 17/17 清除,定向仅 2/6;4/4 漏检全是定向 | **−1500 ~ −3000 s/局** |
### 0.2 预期收益(自建模拟器,同一冻结基准集,配对口径)
| 指标 | Q3 现状 | Q3 目标 | Q4 现状 | Q4 目标 |
| --- | ---: | ---: | ---: | ---: |
| 平均虚拟时间 | 3707 s(本次 6 场景)/ 3536 s(13 源单局) | **≤ 2700 s** | 13 652 s(10 源 3 定向单局)/ 13 165 s(4 场景均值) | **≤ 9000 s** |
| 最大虚拟时间 | 4628 s | **≤ 4000 s** | > 17 000 s(边界压力) | ≤ 12 000 s |
| 每局计算墙钟 | 2.9–6.5 s | **≤ 0.8 s** | 6.5 s | **≤ 1.0 s** |
| 有效局(全清+缺席无误判+零异常+正常退出+计时复算一致) | 6/6 | 基准集 100% | 4/4 | 基准集 100% |
> 4000 s 是本项目 `docs/算法审核与优化-20260911.md` 自设的验收线,不是题面评分线;
> 第四问在 10 源场景下**不承诺**达成 4000 s,理由见 §6.2。
### 0.3 明确不做的事
- 不重新引入 PPO/HRL。运行历史支持"保留手写基线":HRL 训练 4007.9 s / 109 776 步后
仍被 `bayes` 稳定压制 4.3–5.0%(17–18/20 场景,符号检验 p=0.0001),且 94% 的差距来自
多走的移动;其评估集 20 个场景**全部是单源全向**,无法支撑任何第四问结论。
- 不引入新的第三方运行时依赖(仍是 `requests`;`numpy` 仅作**可选加速后端**,缺失时回退纯 Python)。
### 0.4 实施进展与实测回填(2026-09-12)
冻结基准集 `harness/suites.py`(第三问 18 主场景 + 2 边界;第四问 9 + 2),
并行 runner `scripts/run_suite.py`,配对口径同场景同误差种子。
| 项 | 状态 | 实测 | 结论 |
| --- | --- | --- | --- |
| P1-4 安全阀 | ✅ 已实现 | `summary` 新增 `completion_basis` / `absence_evidence` / `redundant_rescan_measures` / `virtual_reserve_s`;`--persistent` 输出单轮上界告警 | 采纳(5 项测试) |
| P0-2 最小命中集 | ✅ 已实现并默认开启 | 第四问 11 场景配对:**14003.0 → 10975.5 s**(均值 −19.3%、中位 −25.4%、10/11 更快);检测 444.5→319.2;请求 569.5→395.6;位移 54.9→44.2 km;最大 17007→12919 s;11/11 valid | **采纳** |
| P0-4 感知层向量化 | ✅ 已实现 | 单局计算 **3.10 → 1.00 s(3.08×)**,最大 5.84 → 1.30 s;20 场景的虚拟时间与动作序列**逐位一致** | **采纳**(目标 ≤0.8 s 未达,余下为 belief 表示层) |
| P0-1 融合巡回 | ❌ 实测无效,已回退默认 | 仅追加站点方位线:3454.6 vs 3426.9 s(+0.8%,检测 +10.8 次、位移仅 −188 m);追加 + 抑制专用探点:**4989.0 vs 3426.9 s(+45.6%,20/20 更慢)** | 关闭(保留开关与代码) |
| P1-1 C₂₀ 最近清除点 | ⊘ 复核后降为非项 | 现有 `_near_clear_target` 已取安全球 `B(c, limit−ρ)` 内离当前位置最远点;与完整 C₂₀ 最优之差 ≤ slack+margin ≈ 0.74 m | 不改 |
| P1-3 基准集/并行 | ✅ 已实现 | 20 场景 × 3 变体 = 60 局 **26.5 s**(6 进程);20 局 **3.9 s** | 采纳 |
| P0-3 联合信念 | ✅ 已实施并于 2026-09-12 修正 | 网格粒子 × 24 档朝向 bin + 可见性选点;**发现并修复 `no_signal` 朝向规则反向缺陷**(原会删掉真源朝向档);配套 D1 探点口径使第四问后备盲清 **125.7 → 4.3 次/局** | 采纳(结论与实测见[图论与 DP 优化方案 §8](第三四问图论与动态规划优化方案.md)) |
**P0-1 为什么失败(重要负面结论)**:七站环上通常只有 2–3 个站能对同一源取得
有效方位线,且站间交会角不足以直接换到 20 m 证书,专用信息增益探点仍然必要;
"顺路测量"于是退化为平白多花检测,一旦抑制专用探点则会白等巡回、远端绕行。
**结论:第三问的定位往返不是可回收的浪费,`bayes` 的滚动时域调度已在做正确的事。**
**第四问现状与目标差距(2026-09-12 图论/DP 轮回填)**:启用硬化覆盖计划
`q4_mincover_v2` 与 D1 探点口径后,第四问 11 场景(core + boundary)均值为
**9069.9 s**、最大 12081.1 s、后备盲清 24.8 次/局(core 9 场景 8602.0 s /
4.3 次),仍远高于 4000 s 验收线。剩余成本主要是最小命中集巡回
(23.9 km ≈ 4777 s)与缺席频道检测,二者都是覆盖证明的固有代价;
本方案 §2.3 的"可压缩 23–32%"在本模拟器上只回收了约 1%(详见图论与 DP 方案 §8)。
---
## 1. 运行历史复核
### 1.1 数据资产清单(本次实际读取)
| 资产 | 内容 | 可用信息 |
| --- | --- | --- |
| `session.jsonl`(31 011 行) | 官方第三问演练 J2WH-2K7D-5ZXS-DNK6 的客户端日志 | 10 323 次 `/measure`、12 次 `/clear`、逐动作计时 |
| `practice-p3-*.jlog` | 同一局的官方行为日志封装 | 仅明文信封(题号 3、队号、案例码);载荷 AES-256-GCM + RSA-OAEP,不可解 |
| `referee-data/sessions/*`(6 局,5 局完整) | 本地评测机 q3×3、q4×2、截断×1 | 逐请求响应、真值、summary、step 事件 |
| `output/hrl/ui/q3-main/` | HRL 训练(109 776 步)+ `eval/at-40k`、`eval/at-final` | monitor CSV、模型元数据、20 场景配对评估 |
| `docs/算法审核与优化-20260911.md`、`docs/algo-3.md`、`docs/algo-3-bayes.md` | 历史 KPI 与消融 | 官方/自建成绩、验证集分档 |
| 本次新增复跑 | `scripts/benchmark_q3_bayes.py`(6 场景)、Q3/Q4 单局计时分解、`cProfile`、站点三角化可行性 | §1.3 中标注"本次复跑"的数字 |
| 本次新增工具(已入库) | `scripts/analyze_run_timing.py`(任意会话日志的计时分相复算)、`scripts/verify_q4_cover.py`(第四问覆盖证书 + 最小命中集搜索) | F4、F9 可复现 |
### 1.2 先确认度量可信(否则一切优化都是自欺)
官方计时模型:`T = L/5 + 5·N_measure + N_switch + 5·N_clear⁺ + 3·N_clear⁻`,
其中 `L` 为累计直线位移,`/clear` **不计**换频。`scripts/verify_q3_practice.py` 对 5 局完整
评测机日志逐微秒复算,与服务器返回的 `virtual_time_s` 完全一致(例:BBM9 复算 3134.642907 = 记录 3134.642907)。
结论:**后续所有"虚拟时间"结论都可用动作日志独立复算**,这是本项目最可靠的一条工程资产,必须保留。
### 1.3 关键事实(F1–F13)
| 编号 | 事实 | 出处 |
| --- | --- | --- |
| F1 | 虚拟时间构成:**移动 75–83%|检测动作 15–21%|换频 2.3–3.7%** | 官方会话 82.30/14.76/2.94;referee q3 80.50/16.78/2.71、q4 78.39/18.63/2.98;本次复跑 Q3 79.9/17.3/2.9、Q4 75.2/21.1/3.7 |
| F2 | 官方第三问演练 J2WH 总虚拟 350 227.7 s,其中 **346 169.9 s(98.84%)是空复扫**:7 个固定站点 × 8 个未清除频道 × 183 轮,0 检出,结束点正好落在 `virtual_reserve_s=10 000` 边界 | `session.jsonl` 相位切分;`planner/__init__.py:236`、`config.py:99` |
| F3 | **第四问的全部失败来自定向源**:referee 两局全向 17/17 清除,定向仅 2/6;4/4 漏检全是定向;每清除源 356.4 s(Q3 274.0 s,+30%) | `referee-data/sessions/{6X58,BAU8}`;`{BBM9,RM7Y,VQKB}` |
| F4 | 本次复跑 Q3(13 源,seed 0):3535.9 s / 109 次检测 / 13 清除;**七站巡回 6960 m(1392 s,39%)+ 探点往返 5741 m(1148 s)+ 清除移动 3668 m(734 s)** | 本次复跑(方法见附录 A) |
| F4b | 评测机 Q3 单局 BBM9:**21 次专用探点占 1138.7 s 移动,等于该局虚拟时间的 36.3%**,而站点扫频 98 次只占 991.2 s | `scripts/analyze_run_timing.py referee-data/sessions/BBM9-.../log.jsonl --plan seven` |
| F5 | 本次复跑 Q4(10 源 3 定向,seed 0):13 652.1 s / 560 次检测 / 18 次清除(8 次失败)/ 51 356 m | 本次复跑 |
| F6 | 感知层是墙钟热点:单局计算 6.04 s;`cProfile` 下 `expected_survival` 占累计时间 **72%**(2808 次调用),`apply_measure` ≈ 20%,`_kill_outside_wedge` 触发 498 万次 `angular_gap` | 本次 `cProfile`(13 源单局) |
| F7 | HRL 运行:109 776/200 000 步、4007.9 s、**27.0 steps/s(6.75/env)**、1.51 s 墙钟/局;被 SIGTERM 截断时回报仍在上升(Q1 1.05 → Q4 6.20) | `output/hrl/ui/q3-main/q3.json` + 4 份 monitor CSV |
| F8 | HRL 评估:20 场景**全部单源全向**;`bayes` 比 HRL 快 4.3–5.0%(17–18/20,p=0.0001),**94% 的差距是多走的移动**;40k→109k 步仅再改善 0.7% | `eval/at-40k|at-final/aggregate.*`、`eval/manifest.json` |
| F9 | 现役 Q4 发现集:700 m 方格 49 点、NN 巡回 **36.4 km**;满足同一"前向半盘命中"保证的贪心最小集 **31 点、22.9 km(−37%)**,覆盖校核 100% | `scripts/verify_q4_cover.py`;本文附录 A |
| F10 | Q3 七站巡回**已经最优**:6 × 1159.7 = 6958 m(观测 6960 m),无可压缩空间;把环半径降到覆盖下界 1122.9 m 仅省 222 m(44 s) | 几何计算 + 本次复跑 |
| F11 | 站点三角化可行:**99.28% 的源位置有 ≥2 个站在 1500 m 内**;最佳交会角 5 分位 58.5°、中位 122.9°;只有 0.72% 的源位置仅 1 个站在射程内 | 本次离线采样(20 000 点) |
| F12 | 墙钟量级差三个数量级:内存回放 **0.22–0.25 ms/请求**(≈0.35 s/局),官方接口 **9.42 ms 均值/请求**(104.49 s / 10 337 请求);本地评测机 24.5–97.9 ms/请求 | referee `session_end`、`runs/*/summary.json`、`session.jsonl elapsed_s` |
| F13 | 历史 KPI 参照:Q3 官方最好 4058.71 s(3BW5,13/13)、5232.13 s(J4ER,12/12);自建 `bayes` 3707.3 s(本次 6 场景)/ 3726.2 s(14 场景消融)/ 验证集 3315.89 s;Q4 自建 13 164.7 s,**无官方第四问演练记录** | `docs/算法审核与优化-20260911.md`、`docs/algo-3-bayes.md`、`README.md:135` |
### 1.4 由事实导出的六处结构性瓶颈
| 编号 | 瓶颈 | 支撑事实 | 归属层 |
| --- | --- | --- | --- |
| B1 | 定位/清除移动没有并入覆盖巡回,探点按"信息增益"选,移动权重 λ=0.0105 几乎不惩罚绕行 | F1、F4、F4b、F11 | 决策层 |
| B2 | Q4 发现集冗余 63%(49 点 vs 30 点),且每个"真缺席"频道要在全部 49 点各测一次 | F5、F9 | 保证层 |
| B3 | Q4 定向源缺少位置×朝向联合信念:`no_signal` 只记"被遮挡过几次",选点靠固定 45° 阶梯,收敛失败后退化到 ≤400 格 20 m 盲清(≤1200 s + ≤400 请求) | F3、`planner/__init__.py:691-768` | 感知层 + 决策层 |
| B4 | 感知层全 Python 容器:每局 20 个频道各建 ~6.1 万格 `set`,`_kill_outside_wedge` 每次遍历全部幸存格,评分核 `expected_survival` 为 7×800 的纯 Python 双循环 | F6 | 感知层 |
| B5 | 研发吞吐低:场景串行、单进程;一局 0.35–6.5 s;HRL 27 steps/s;14–52 场景的配对基准要跑几分钟到十几分钟 | F6、F7、F12 | 实验层 |
| B6 | 运行治理缺失:单源评估集使指标退化(`avg_locate_clear_time_s ≡ virtual_time_s`)、`eval/manifest.json` 三份重复且与 `output/hrl/ui/manifest.json` 不一致(后者缺失)、`persistent_search=True` 可造成 98.84% 的无效开销且无硬性告警 | F2、F8、F13 | 遥测/实验层 |
---
## 2. 目标架构
### 2.1 分层与依赖方向(依赖只能向下)
```
┌───────────────────────────────────────────────┐
L6 实验/遥测 │ bench/ 冻结基准集·并行 runner·配对统计 │
│ telemetry/ manifest·step log·replay·summary │
└───────────────▲───────────────────────────────┘
│ 只读观测
┌───────────────┴───────────────────────────────┐
L5 执行/安全 │ exec/ 动作账本·预算守卫·安全阀·幂等客户端 │
└───────────────▲───────────────────────────────┘
│ Action 请求
┌───────────────┴───────────────────────────────┐
L4 决策 │ plan/ 任务表·滚动时域排序·信息/时间评分 │
└───────────────▲───────────────────────────────┘
│ Task[]
┌───────────────┴───────────────────────────────┐
L3 保证 │ guarantee/ 命中集覆盖计划·清除证书·缺席证明 │
└───────────────▲───────────────────────────────┘
│ 候选集/幸存集
┌───────────────┴───────────────────────────────┐
L2 信念 │ belief/ BeliefBackend:OccupancyTensor(Q3) │
│ JointTensor(位置×朝向, Q4) │
└───────────────▲───────────────────────────────┘
│ Observation
┌───────────────┴───────────────────────────────┐
L1 观测模型 │ model/ 可见性半平面·r_eff 带·有界误差·舍入裕量 │
└───────────────▲───────────────────────────────┘
│ 原始响应
┌───────────────┴───────────────────────────────┐
L0 协议/时钟 │ protocol/ 规则表·计时模型·状态机·客户端(冻结)│
└───────────────────────────────────────────────┘
```
与现状的差别:**用组合取代继承**。现状是
`OmnidirectionalLoop → SevenBatch → BayesLoop → DirectionalExtension` 的单继承链,
调度、信念、清除、预算、报表五种职责混在同一族类里(`planner/__init__.py` 818 行 +
`planner/bayes_loop.py` 513 行),任何一处改动都会跨层影响其它三问。
目标形态是
```python
Episode(
client = SimulatorClient(...), # L0,冻结
belief = OccupancyTensor(...) | JointTensor(...), # L2
plan = CoveragePlan.points, # L3
scorer = InfoGainPerSecond(...), # L4
policy = FusedTourPolicy(...) | DirectionalPolicy(...), # L4
certifier= Certificate(...), # L3
ledger = Telemetry(...), # L6
)
```
三层保证策略矩阵只差**三个可替换部件**,其余代码共享:
| 问题 | 信念后端 | 覆盖计划 | 选点策略 |
| --- | --- | --- | --- |
| Q3 | `OccupancyTensor`(全向) | 七站环(已经最优,冻结) | 融合巡回 |
| Q4 | `JointTensor`(位置×朝向) | **最小命中集**(30 点,离线搜索 + 运行期校验证书) | 可见性约束选点 |
### 2.2 接口契约(关键签名)
```python
# L1 model/observation.py —— 只此一处定义"什么算看见"
@dataclass(frozen=True)
class ObservationModel:
r_eff_min_m: float; r_eff_max_m: float
near_radius_m: float; half_angle_deg: float
directional_half_deg: float = 90.0 # 含边界
def visible(self, src: Vec2, heading_deg: float | None, reader: Vec2) -> bool: ...
def detectable(self, src: Vec2, heading_deg: float | None, reader: Vec2) -> bool: ...
# L2 belief/base.py —— 两种后端实现同一协议
class BeliefBackend(Protocol):
def apply(self, channel: int, reader: Vec2, result: str,
svd_deg: float | None) -> None: ...
def alive(self, channel: int) -> int: ...
def is_absent(self, channel: int) -> bool: ... # 幸存集为空
def certificate(self, channel: int) -> ClearCertificate | None: ...
def guaranteed_no_signal(self, channel: int, reader: Vec2) -> bool: ...
def prob_detect(self, channel: int, reader: Vec2) -> float: ... # 决策用
def version(self, channel: int) -> int: ... # 派生量缓存键
# L3 guarantee/cover.py —— 覆盖计划是"可离线搜索、可在运行期校验"的一等对象
@dataclass(frozen=True)
class CoveragePlan:
problem: int # 3 | 4
points: tuple[Vec2, ...]
certificate: CoverCertificate # 采样分辨率、缺失数、最坏裕量、脚本哈希
version: str
def verify(self, model: ObservationModel, tol_m: float) -> CoverCertificate: ...
# L4 plan/policy.py
class Policy(Protocol):
def tasks(self, belief: BeliefBackend, state: RobotState) -> list[Task]: ...
def order(self, tasks: list[Task], state: RobotState) -> list[Task]: ...
def measured_at_point(self, point: Vec2) -> tuple[int, ...]: ... # 一停多测
```
### 2.3 四条设计原则
1. **保守性不变量不动**:真源永不被删;清除证书始终基于完整 ±1.005° 角域与题面距离带;
所有启发式(均匀先验、朝向先验、信息增益)**只用于选点,不用于安全证明**。
Q4 的朝向联合信念必须自带"朝向未观测时退化为全向"的保守分支。
2. **保证 = 离线搜索 + 运行期校验**:覆盖集、清除点、巡回顺序都可以离线用数值搜索优化,
但运行期必须用同一份 `ObservationModel` 重新校验证书(点集、分辨率、裕量),
校验失败即拒绝启动,而不是相信一个写死的点表。
3. **一个事实一个来源**:题面常量、`r_eff` 带、清除半径、动作耗时只在 `config.py`;
可见性语义只在 `model/observation.py`;训练/部署若将来共存,必须共用同一控制器模块
(这是旧 HRL 用 13 000 行换来的教训:v1 的 5 个特征索引错位、BC 激活函数不一致、
旧基准把 `completed` 当全清)。
4. **每个优化都必须可配对、可复算、可回归**:冻结基准集 + 逐局 `manifest` + 动作日志
+ 逐微秒复算;任何"更快"的声明必须给出配对分布(均值/中位/最大/更快场数),
而不是单个最优场景。
### 2.4 迁移映射(现状 → 目标)
| 现状 | 目标 | 处理 |
| --- | --- | --- |
| `protocol/*`、`evaluation/recompute_virtual_time` | 不变 | 冻结,作为度量基准 |
| `belief/__init__.py`(凸外包) | `belief/convex.py` | 原样搬迁,继续负责精确角域证书 |
| `particles.py`(网格幸存集) | `belief/occupancy.py`(numpy 张量 + 纯 Python 回退) | 重写热核,语义不变,保留 `tests/test_particles.py` 的真源保留性质测试 |
| — | `belief/joint.py`(位置×朝向) | 新增,Q4 专用 |
| `coverage/` | `guarantee/cover.py` | 49 点常量表 → 可校验的 `CoveragePlan` |
| `planner/bayes_loop.py` 的 `_build_tasks/_order_tasks` | `plan/tasks.py`、`plan/schedule.py` | 抽出,Q3/Q4 共用 |
| `planner/bayes_loop.py::_select_point` | `plan/score.py` | 抽出 + 向量化评分 |
| `planner/__init__.py::DirectionalExtension` | `plan/policy.py::DirectionalPolicy` + `belief/joint.py` | 保留后备清除作为最终兜底,但目标是不再触发 |
| `planner/seven.py` | `guarantee/plans.py::SEVEN` | 保留解析证明 |
| 4 个 benchmark 脚本 | `bench/` 统一 runner(并行 + manifest + 配对统计) | 合并,消除指标口径分叉 |
---
## 3. 优化项
### P0-1 第三问:发现—定位融合巡回(消除探点往返)
**现状**:每次收到 `direction` 后进入独立定位子循环,由信息增益选点器在同一站点之外
另选探点;13 源单局共 24 次探点、5741 m 往返、1148 s,与整个覆盖巡回(6960 m / 1392 s)同量级;
评测机单局 BBM9 更极端:**21 次探点花掉 1138.7 s 移动 = 全局限时时间的 36.3%**(F4b)。
评分函数 `score = E[log 幸存占比] + 0.0105 × 移动秒数`,移动项几乎不影响排名。
**方案**:把"已发现未定证"的频道加入**每一个覆盖站点**的批量扫频清单,
使第二/第三条方位线在巡回途中顺路取得;只有站点三角化不成立时才回退到专用探点。
- 巡回仍由 `plan/schedule.py` 滚动排序(现有最近邻 + 2-opt 已足够,`bayes` 的 −39.5% 移动
主要来自这里),新增"同点顺路测量"任务类型:`Task(kind="station", channels=(未知, 未定证))`。
- 站点任务的固有耗时按 `6 s × 频道数 + 1 s × 换频次数` 记账(现有 `_path_cost` 已支持)。
- 保底:`F11` 显示 0.72% 的源位置只有 1 个站在 1500 m 内,对这类频道保留
`_select_point` 的专用探点路径;判定用 `belief.prob_detect` 而非硬编码。
**预期收益(已被实测推翻)**:原估 F4 的 1148 s 探点移动可回收 70–80%,
即 −800 ~ −900 s。**实测结果相反**:仅追加站点方位线 +0.8%,追加并抑制专用探点
+45.6%(20/20 场景更慢),见 §0.4。原因是站间交会角不足以保证 20 m 证书,
专用信息增益探点仍然必要;抑制它只会白等巡回。**本项不采纳,默认关闭。**
**验收**:18 场景验证集平均 ≤ 2700 s、最大 ≤ 4000 s;`measures` 增幅 ≤ 20%;
`move_distance_m` 降幅 ≥ 25%;全部 `valid`。
**风险**:站点距离最远 2 × 1159.7 = 2319 m,可能出现 `no_signal` 浪费 5 s。
缓解:仅在 `prob_detect > 0` 时顺路测量;`no_signal` 同样是有效负观测(进信念,不算异常)。
---
### P0-2 第四问:用最小命中集替换 49 点发现集(离线搜索 + 运行期校验)
**现状**:`coverage_points()` 生成 700 m 方格 49 点(±2100),依据是"单元内任一点到 4 顶点
≤ 700√2 ≈ 989.95 ≤ 1000"。该条件是**充分但远非必要**:它要求单元内任意位置、任意朝向都由
**同一个**顶点命中。实际上,第四问的保证是"对 ∀(p,h) ∈ Ω×S¹,存在被访点位于其前向半盘内",
允许由**多个**顶点共同围住 p。
**方案**:把它当作一个显式的**击中集(hitting set)问题**离线求解,运行期校验:
1. 目标空间离散:`p` 取 Ω 内 75 m 网格,`h` 取 24 个方向 → 43 032 个 (p,h) 单元;
2. 候选点:半径 2850 m 圆盘内 200–300 m 网格(641/293 个候选);
3. 贪心集合覆盖 + 局部改进,**同时把巡回长度作为次级目标**(641/293 个候选中取 31/30 个,
仍有大量同覆盖度的等价解可用于缩短巡回);
4. 生成 `CoveragePlan(points, certificate)`,`certificate` 记录采样分辨率、未覆盖数、
最坏裕量、生成脚本哈希;`CoveragePlan.verify()` 在每次运行前用同一 `ObservationModel`
重新校核,未通过即拒绝启动。
**本次离线校核结果**(附录 A 可复现):
| 点集 | 点数 | (p,h) 覆盖 | NN 巡回 | 折算移动 |
| --- | ---: | ---: | ---: | ---: |
| 现役 700 m 方格 ±2100 | 49 | 100% | 36.4 km | 7280 s |
| 三角格 1000 m(半径 2800 盘) | 31 | 100% | 34.3 km | 6863 s |
| **贪心最小命中集(200 m 候选)** | **31** | **100%** | **22.9 km** | **4587 s** |
| **贪心最小命中集(300 m 候选)** | **30** | **100%** | **23.6 km** | **4719 s** |
**实测收益(2026-09-12,11 场景配对)**:均值 **14003.0 → 10975.5 s(−19.3%)**,
中位 −25.4%,10/11 场景更快;检测 444.5 → 319.2,请求 569.5 → 395.6,
位移 54.9 → 44.2 km,后备清除尝试 113.3 → 64.1,11/11 valid。
低于原估的 −26%,差额来自"缺席频道省下的检测"被定位探点部分抵消。
**验收**:`CoveragePlan.verify()` 通过;10/13/16 源 × 定向 1/半数/仅一个全向 × 边界朝外压力
场景全部全清;请求数、日志体积仍满足 1200 s / 2 MB 预算。
**风险**:数值采样证书弱于解析证明。缓解:(a) 保留解析七站证明用于 Q3;
(b) 证书采样分辨率与裕量写入 summary 并随日志归档;(c) 保留 10% 的额外"保险点"预算,
若演练观测到任何漏检立即回退到 49 点表。
---
### P0-3 第四问:位置×朝向联合信念与可见性选点
**现状**:定向源只被当作"候选空间集合",`no_signal` 只用于累计 `blocked_probes`
(`planner/__init__.py:663-677`),选点沿固定 45° 阶梯试探;梯子用尽后调用
`_fallback_clear`,对候选外包多边形做 20 m 网格覆盖逐格 `/clear`(≤400 格、≤1200 s、≤400 请求)。
运行历史显示这正是第四问的失败点:定向源 2/6 清除,4/4 漏检全是定向。
**方案**:为第四问频道维护 **(x, y, heading) 联合可行集**,把"看不看得见"变成可推理的量:
- 表示:`JointTensor` —— 复用 Q3 的占用网格,每格附加 24 个朝向 bin 的可行性位掩码
(uint8 位域,20 频道 × 6.1 万格 × 3 B ≈ 3.7 MB,可接受)。
- 更新规则(全部保守,仅删"必然看不见"的格):
- `direction`@s:位置删 `>1500`、角向删 `|gap| > δ_eff + 角裕量`;朝向删
"以该格为源、朝向不覆盖 s" 的半盘(保留 ±90° 含边界)。
- `no_signal`@s:对 `|g−s| ≤ 1000` 的格,朝向**只能**落在背向 s 的半盘;
对 `1000 < |g−s| ≤ 1500` 的格,朝向要么背向 s,要么源实际半径 < |g−s|(保持不确定);
对 `|g−s| > 1500` 的格不删。
- `near`@s:位置收缩到 5 m 盘,朝向置为全向(近场无遮挡语义)。
- 决策:`prob_detect(channel, reader)` = 该点能看到该频道任一可行 (p,h) 的概率质量;
选点直接最大化 `prob_detect / (移动秒数 + 6)`,即**保证有信号的信息增益/秒**。
- 定位收敛:位置由多条 `direction` 的角域交会收缩(与 Q3 相同);朝向在 2 次
"一次被遮挡 + 一次可见"后通常已被压到很窄的楔形,此时可**主动选择必然可见的探点**,
用一个正常角域证书结束,而不再进入 400 格盲清。
- 后备清除**保留**(`fallback_clear_cells` 已有格数与预算双硬顶),但目标变为
"只在证书无法建立时触发",并在 summary 中把触发次数作为回归指标。
**预期收益(未实测)**:定向频道的探点次数与后备清除触发次数大幅下降。参考量级:
后备清除上限 400 格 × 3 s = 1200 s + 400 请求/频道;`docs` 记录的 Q4 改进版每局后备
尝试 616/4 局 ≈ 154 次。保守估计 **−1500 ~ −3000 s/局**。
**验收**:10 源 3 定向场景全清率 100%(现状 referee 仅 2/6 定向);
`fallback_clear_attempts` 相对基线下降 ≥ 70%;定向信道平均检测次数 ≤ 全向信道的 1.5 倍。
**风险**:朝向均匀先验是工程假设,官方分布未知。缓解:先验只影响选点顺序,
不影响任何安全性判定;朝向不确定时自动退化为全向分支(`prob_detect` 取全向值)。
---
### P0-4 感知层向量化与并行(研发运行速度)
**现状**(F6):`expected_survival` 占单局 CPU 的 72%,2808 次调用 × (7 代表点 × 800 子采样)
≈ 1570 万次纯 Python 距离/角度运算;`apply_measure` 另有 20%,其中 `_kill_outside_wedge`
每次遍历全部幸存格(498 万次 `angular_gap`)。
**方案**:
1. `belief/occupancy.py` 用 `numpy` 布尔/整数张量替换 `set[int]`:
每频道 248×248 uint8(≈61 KB),20 频道 ≈ 1.2 MB;盘/楔删除用切片 + 布尔掩码,
粗格索引用 `np.add.reduceat`;`numpy` 缺失时回退现有纯 Python 实现(测试保证两条路径同结果)。
2. 采样评分向量化:把 `subsample` 与 `representatives` 预转 `(n,2)` 数组,
一次广播算完距离、角度、角向 gap,`expected_survival` 由 5600 次标量循环变成 2–3 条向量指令。
3. 站点级编译缓存:`(station, channel, version)` → 掩码结果,`version` 不变直接复用;
当前 `_skip_cache` 只缓存"必 no_signal"布尔,可扩展到整块删除掩码。
4. MEC 增量维护:当前 `mec()` 每次版本变化重算全部幸存点(最多 6 万点);改为
版本变化时只对新增删除点做包围圆增量更新,或按 1/8 子采样给出候选、证书判定用精确值。
5. 批量基准并行化:`bench/runner.py` 用 `concurrent.futures.ProcessPoolExecutor`
按场景分发(每进程独立 `LocalSimulator`,真值仍只进测试端),Mac M 系列 8 核可得 ~6–8×。
6. 观测/遥测:单局 summary 写入 `compute_wall_s`、`profile_top`(可选 `--profile`),
纳入基准表的必填列,防止"虚拟时间变快、墙钟悄悄变慢"。
**实测收益(2026-09-12)**:单局计算 **3.10 → 1.00 s(3.08×)**,最坏 5.84 → 1.30 s;
20 场景基准集 12.2 → 3.9 s。虚拟时间与动作序列**逐位一致**(纯 Python 回退路径保留,
`MCM26B_NO_NUMPY=1` 可强制)。未达 ≤0.8 s 的目标:剩余开销在
`apply_measure` 的网格删除与 `ChannelParticles.__init__`,需要 P0-4b(表示层张量化)。
**验收**:`tests/test_particles.py`、`tests/test_bayes_loop_q3.py` 全过;
纯 Python 与 numpy 两条路径在同一动作日志下产出逐位相同的 summary(`actions_sha256` 相同);
单局 `compute_wall_s` ≤ 0.8 s。
---
### P1-1 清除点选在 C₂₀(K) 内离当前位置最近处
现状 `_near_clear_target` 只沿"当前位置 → 候选圆心"方向平移,平移量受
`limit − radius` 限制,是被证明的一维特例。一般解是
`C₂₀(K) = ∩_{g∈K} B(g, 20)`,取其内离当前位置最近的点:
- 对凸多边形候选集,`C₂₀(K)` 仍是凸集(若干圆盘之交),可用"到当前点的最近点"的
简单迭代/投影求得;
- 若 `C₂₀(K)` 非空,则从该点清除的移动代价最小,且三角不等式保证仍在 20 m 内。
**预期收益**:Q3 单局清除移动 3668 m(734 s)中回收 20–35%(**−150 ~ −260 s**);
Q4 同步受益。
**风险**:`C₂₀` 空时退回现有 MEC 圆心逻辑(已实现)。
---
### P1-2 覆盖几何参数改为"离线搜索 + 运行期校验"
- Q3:`station_ring_radius_m` 由 1159.7 的可证明下界 1122.9 重算,并在 `validate_seven_coverage`
中同时校验 **PF 网格覆盖**(现行文档已记录 1159.7 与 Optuna 建议值差 3.3 m 的取舍)。
收益很小(≈44 s),但把"设计性质 vs 1.7% 均值差异"的取舍写成可复算脚本,而不是口口相传。
- Q4:见 P0-2,覆盖集与巡回联合优化(在等价覆盖解中选巡回最短者,
预期可在 23.6 km 基础上再降 5–10%)。
- 所有覆盖计划产物落 `output/<problem>/coverage/<version>.json` + 证书,纳入版本管理。
---
### P1-3 评估与遥测重构(消除 B6)
1. **冻结基准集**(`bench/suites/`,一题一份、版本化、写入 `manifest.json`):
- Q3:10/13/16 源 × 8 个种子 + 2 个圆周边界场景(`r_eff=1000`,扇区接缝);
- Q4:10 源 3 定向、13 源 6 定向、16 源 8 定向 × 每种 3 个种子 + 朝外边界压力场景;
- 场景与真值只进模拟器与校验器,策略端看不到(真值隔离不变量保持不变)。
2. **统一 runner + 逐局 artifact**:`manifest.json` / `results.json` / `aggregate.{json,csv}` /
`runs/<scene>/<variant>/summary.json` + `session.jsonl`,任何一局无效则 `all_valid=false` 且非零退出。
3. **指标口径去重**:目前 `avg_locate_clear_time_s ≡ virtual_time_s`(单源评估集下二者必然相等),
必须在多源基准集上重新定义并标注:
`T_total`(整场虚拟时间)、`T_accounted`(首次证明找全时刻)、`T_last_clear`、
`T_total / cleared`、`T_total / truth`。
4. **运行历史持久化**:`output/` 目前不纳入 git,导致 `output/rl/review-20260911/`、
`output/q3/optimization-20260911/` 等证据在 revert 后无法追溯。
本轮起把**基准集的 summary/aggregate/manifest(压缩,不含大日志)**纳入版本管理,
官方 jlog/日志仍按题面要求单独归档。
---
### P1-4 安全阀与退出可解释性
| 阀门 | 现状 | 改进 |
| --- | --- | --- |
| `persistent_search` | 默认已关闭,但没有任何"这会浪费多少"的提示;历史上已造成一次 346 170 s(98.84%)的无效演练 | 显式开启时输出预估上限(未清除频道数 × 站点数 × 6 s)并要求二次确认;`summary` 记录 `redundant_rescan_measures` |
| `virtual_reserve_s` | 10 000 s 固定 | 改为 `max(10 000, 2 × 最远单动作上界)`,并在接近时把 `stop_reason` 与剩余预算写入 summary |
| 完成判定 | `completed = 无中断 ∧ (清除数≥16 ∨ 每频道 CLEARED/ABSENT)` | 增加 `completion_basis ∈ {count_cap, per_channel_proof}` 字段,避免把"数量上界"与"逐频道证明"混为一谈 |
| 缺席证明 | 覆盖账本 + PF 双通道 | summary 增加 `absence_evidence[channel] ∈ {pf_empty, coverage, count_cap}`,供论文与审计逐条追溯 |
---
### P2-1 选点评分的解析化/查表化
`expected_survival` 的物理含义可用解析近似替代:对给定测点 q 与候选集,
`E[log 幸存占比]` 的一阶近似只依赖 (候选集到 q 的距离分布, 角向展布)。
先在离线基准上验证解析近似与采样评分选出的点一致率 ≥ 90%,再切默认实现。
预计再省 30–50% 的评分开销(P0-4 之后已不是瓶颈,列入 P2)。
---
### P2-2 是否重启学习类方法的门控条件
只有在**下列四条同时满足**时才考虑把 ML 重新引入,且只允许替换"任务排序"这一个部件:
1. P0-1/P0-2/P0-3 全部落地并通过基准集验收;
2. 手写基线仍有 ≥ 10% 的可测改进空间,且已定位到具体的排序子问题;
3. 训练与部署共用同一控制器模块,并有逐状态一致性测试(旧 HRL 的 13 000 行里最有价值的
就是这条纪律:`HRLControllerMixin` 被 env 与 runtime 逐字节共用);
4. 评估集包含多源与定向场景(现役 20 场景单源评估集不足以支撑任何结论)。
---
## 4. 实施路线图
| 阶段 | 内容 | 交付物 | 验收门槛 | 依赖 |
| --- | --- | --- | --- | --- |
| **S1(P0,2–3 天)** | P0-2 最小命中集 + P0-4 并行基准;P1-3 冻结基准集 + manifest;P1-4 安全阀 | `guarantee/cover.py`、`bench/runner.py`、`bench/suites/*`、`docs/` 更新 | Q4 巡回 ≤ 25 km;基准集并行跑完 ≤ 60 s;全部 `valid` | 无 |
| **S2(P0,2–3 天)** | P0-1 融合巡回(Q3);P1-1 清除点 | `plan/schedule.py`、`plan/score.py`、`Episode` 组合化重构 | Q3 均值 ≤ 2700 s、最大 ≤ 4000 s;移动 −25% | S1 的 runner |
| **S3(P0/P1,3–5 天)** | P0-4 信念层 numpy 化;P0-3 联合信念 + 可见性选点(Q4) | `belief/occupancy.py`、`belief/joint.py`、`plan/policy.py` | 单局计算 ≤ 0.8 s;Q4 定向全清 100%、后备触发 −70% | S2 的接口 |
| **S4(P1/P2,2–3 天)** | P1-2 覆盖参数搜索固化;P2-1 评分解析化;论文数据表 | 覆盖计划证书、`docs/paper` 数据 | 与 S3 结果配对不退化 | S3 |
| **S5(演练)** | 官方演练:Q3 用全向、Q4 用混合;先实测单请求延迟与总请求数,核对 1200 s / 2 MB | 官方日志与报告 | 全清 + 正常退出 + 计时复算一致 | 团队排期 |
**顺序约束**:S1 的基准集必须先于任何算法改动落地,否则无法配对;否则"更快"不可证。
---
## 5. 验收与度量
### 5.1 有效局定义(沿用现有基准脚本,不得放宽)
```
valid := 全部真实源被清除
∧ 被判缺席的频道都与真值一致(无误判)
∧ 正常 /exit(exit_reason == user_exit)
∧ 零算法异常(anomalies == [])
∧ 逐动作虚拟时间复算与服务器返回逐微秒一致
```
### 5.2 必报指标(每个变体、每个场景,落盘 + 汇总)
- 虚拟时间:`T_total`、`T_accounted`、`T_last_clear`、`T_total/cleared`,以及
均值/中位/最大/P90 与 **< 4000 s 场次比例**;
- 动作:`measures`、`clears`、`failed_clears`、`fallback_clear_attempts`、
`redundant_rescan_measures`、`total_requests`;
- 移动:`move_distance_m`,以及 move/action/switch 三分项占比;
- 墙钟:`compute_wall_s`、请求吞吐(ms/请求)、批量基准总时长、训练 steps/s(若涉及);
- 配对比较:同场景同误差种子下 `1 − T_variant / T_baseline` 的均值、
更快场数、以及对场景做 10 000 次 bootstrap 的 95% 区间(参照 `summarize_rl_revision.py` 的做法)。
### 5.3 回归
`uv run pytest`(当前 232 项)必须全过;新增:
- `CoveragePlan.verify()` 的正/负例(故意删一个点必须被检出);
- 联合信念的真源保留性质测试(随机 (p,h) × 随机观测序列,真源 (p,h) 永不被删);
- 融合巡回的"每源至少一条站点方位线"性质测试;
- 逐位一致性测试:numpy 后端与纯 Python 后端在同一动作序列下 summary 完全相同。
---
## 6. 风险与明确不承诺的部分
### 6.1 风险登记
| 风险 | 影响 | 缓解 |
| --- | --- | --- |
| 最小命中集证书依赖数值采样,官方分布未知 | 极端 (p,h) 漏检 → 漏源 | 保留解析七站证明(Q3);Q4 证书采样分辨率/裕量写入 summary;保留 10% 保险点预算与 49 点回退开关 |
| 站点三角化在 0.72% 源位置上只有 1 站在射程内 | 这些场景仍需专用探点 | `prob_detect` 触发回退路径,保留现有 `_select_point` |
| 朝向先验为工程假设 | 选点次优 | 先验只影响顺序;不确定时退化为全向;安全性由 20 m 证书 + 覆盖证明负责 |
| numpy 化引入浮点路径差异 | 真源保留性质被破坏 | 两条后端同时保留并对拍;保持整数微秒复算计时 |
| 并行基准引入进程/端口/临时目录问题 | 基准不稳定 | 每进程独立内存回环;不共用端口;`manifest` 记录随机种子 |
### 6.2 不承诺的部分
- **第四问 4000 s**:10 源场景下,仅"发现 + 缺席证明"就约需
最小命中集巡回 4719 s + 缺席检测 ≈1900 s ≈ 6600 s,加之定位清除,4000 s 不可达。
若必须逼近该线,需要改变停止条件(例如接受 `unresolved_channels` 而非逐频道缺席证明),
那是**成绩口径的取舍**,必须先与题面评分方式核对,本轮不作为代码默认行为。
- 本文所有收益数字都是**自建模拟器上的估计或本次复跑观测**,不是官方成绩;
官方分布、r_eff 分布、误差场分布仍是测试假设。
- 不声称 P0-1/P0-3 已在官方演练验证——需新的演练记录。
---
## 附录 A 复现命令与本次新增实验
```bash
# 1) 基线配对(本次复跑:bayes 3707.3 s vs seven_batch 5581.8 s,局部 −32.1%)
uv run python scripts/benchmark_q3_bayes.py --seeds 0,1 --out /tmp/mcmopt/q3
# 2) 运行历史计时分解(已入库):任意会话日志 → 移动/检测/换频分相 + 重复复扫检测
uv run python scripts/analyze_run_timing.py session.jsonl --plan seven
uv run python scripts/analyze_run_timing.py referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl --plan seven
uv run python scripts/analyze_run_timing.py referee-data/sessions/6X58-XQ7U-CJ24-JQQR/log.jsonl --plan 49
# 实测输出(与服务器记录最大偏差 0.000000 s,证明复算口径正确):
# session.jsonl 350227.692 s,移动 82.3%,检测 10323 次,重复率 96.4,
# 末次清除后仍花 346169.9 s / 10225 次空复扫
# BBM9 (Q3) 3134.643 s,移动 75.9%,探点 21 次 / 1138.7 s
# 6X58 (Q4) 3251.021 s,移动 78.3%,检测 112 次
# 3) 第四问覆盖证书与最小命中集(已入库)
uv run python scripts/verify_q4_cover.py --out output/q4/cover-certificate.json
# 实测输出:目标空间 43032 个 (p,h)
# 现役 49 点方格:缺口 0,NN 巡回 36.4 km(7280 s 移动)
# 贪心最小命中集:31 点,缺口 0,NN 巡回 22.9 km(4587 s 移动)
# 4) 感知层剖面(单局 6.04 s 计算中 expected_survival 占 ~72%)
python -m cProfile -s cumulative <单局驱动脚本>
```
> 第 2、3 项已作为常驻工具入库,使"虚拟时间花在哪"与"覆盖集是否仍然合法"
> 成为可随时重跑的回归项;第 1、4 项仍是一次性分析,建议在 S1 阶段并入
> `bench/runner.py` 的必报指标(`compute_wall_s`、`profile_top`)。
## 附录 B 数字出处索引
| 数字 | 出处 |
| --- | --- |
| 移动 80.50% / 16.78% / 2.71%(Q3) | `referee-data/sessions/{BBM9,RM7Y,VQKB}/log.jsonl` 按 `verify_q3_practice.py` 模型复算 |
| 移动 82.30%(官方 J2WH) | `session.jsonl` 求和 |
| 346 169.9 s 空复扫 | `session.jsonl` `/exit` 时刻 350 227.691846 s 减去 12 源首轮清除的 4 057.839 s |
| Q4 定向 2/6、漏检 4/4 全为定向 | `referee-data/sessions/{6X58,BAU8}/meta.json` + `log.jsonl` |
| bayes 3707.3 / seven 5581.8 | 本次复跑 `benchmark_q3_bayes.py --seeds 0,1` |
| 3535.9 s / 109 检测 / 6960 m 巡回 / 5741 m 探点 | 本次复跑计时分解 |
| 13652.1 s / 560 检测 / 51 356 m(Q4) | 本次复跑单局 |
| `expected_survival` 72% | 本次 `cProfile` 单局 |
| HRL 27.0 steps/s、4007.9 s、109 776 步 | `output/hrl/ui/q3-main/q3.json` + `*.monitor.csv` |
| bayes 比 HRL 快 4.3–5.0%、94% 差距为移动 | `output/hrl/ui/q3-main/eval/at-final/aggregate.json` + `runs/*/summary.json` |
| 站点三角化 99.28%、交会角 5 分位 58.5° | 本次离线采样 20 000 点 |
| 49 点 vs 30 点覆盖集 | 本次贪心击中集计算(附录 A 第 5 项) |