Files
mcm-26-b/docs/第三四问图论与动态规划优化方案.md
T
2026-09-12 14:48:43 +08:00

517 lines
31 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
- 前置文档:[第三四问架构优化与运行提速方案](第三四问架构优化与运行提速方案.md)(架构与 P0–P2 项)、
[algo-3-bayes.md](algo-3-bayes.md)(现役第三问策略)、[algo-3.md](algo-3.md)(七点覆盖证明)
- 范围:只讨论"用图论 / 动态规划类算法进一步压低**虚拟耗时**"这一件事;
题面约束、协议语义、保守性不变量全部不动
- 本文所有数字均为**本轮新增实测**(自建内存回环、冻结场景、现役代码),
复现命令见 §7
---
## 0. 结论先行
**可以,而且第四问的空间远大于第三问;但真正的收益不在"把巡回排得更短",而在
"把不必要的往返与盲清从决策层消掉"。**
| 判断 | 证据 | 可回收 |
| --- | --- | --- |
| 第三问**路由**已接近最优,纯图论排线几乎无空间 | 对**同一批实际作业点**求离线最优开放回路:2308 s vs 实测移动 2458 s,只剩 **6.1%(151 s)** | ≤ 150 s |
| 第三问仍有空间,但在**探测次数**(序贯决策),不在排线 | 30.8 次探测 / 13 源,且 0 次 `no_signal`——每次都在有效收缩,但次数仍偏多 | −150 ~ −250 s |
| 第四问**路由**有 20% 空间,但那是被定位/清除绕行"拖"出来的 | 离线最优 4998 s vs 实测移动 6257 s(**20.1%**);而覆盖点巡回的投影顺序 4365 s 已接近离线 31 点最优 4181 s(+4.4%) | −400 ~ −700 s |
| 第四问最大单点是**定向源定位失败导致的后备盲清** | 平均每局 **131 次清除只有 13 次成功**,`fallback_clear_attempts=125.7`,清除动作 419 s + 清除段移动 798 s ≈ **1220 s/局(14% T)** | **−800 ~ −1100 s** |
| 第四问第二点是**探测本身** | 平均 69 次探测 ≈ **2224 s/局(26% T)**,其中 32 s/次的移动占 1880 s | −400 ~ −800 s |
| 覆盖点集**已经最小**,不要再压 | 27 点方案在 75 m/24 档"通过",但在 25 m/48 档有 **298 个 (p,h) 缺口** → 是采样过拟合的假解;在细网格上直接搜索的最小命中集仍是 **31 点** | 0(见 §5.1 的正确性风险) |
**一句话:第三问的目标是"把 30.8 次探测压到 ~26 次并把序排到最优",第四问的目标是
"把 125 次盲清变成 3 次有保证的探测"。** 两者都是 DP / 序贯决策问题,
不是经典的 TSP 排线问题。
---
## 1. 目标函数的图论化
题面计时模型(`config.CostConfig`,已由 `evaluation.recompute_virtual_time` 逐微秒复算验证):
```
T = L/5 + 5·N_measure + 1·N_switch + 5·N_clear⁺ + 3·N_clear⁻
```
把它写成图上的路径代价,后面所有算法都在这张图上做:
- **节点** = 一次"停靠"(覆盖站点、定位测点、清除点);
- **边权** `w(a,b)` = `dist(a,b)/5`(移动秒);
- **节点服务费** = `5·(该点要测的频道数) + 1·(换频次数)` 或 `5/3`(清除)。
- 于是整局 = 一条**开放路径**(起点原点、终点自由),目标是最小化
`Σ边权 + Σ服务费 + Σ动作惩罚`。
四类图问题分别对应四个子决策:
| 子决策 | 图问题 | 现役做法 | 目标做法 |
| --- | --- | --- | --- |
| 站点/任务排序 | **开放 TSP(小 n 精确)** | NN + 2-opt(滚动) | Held-Karp 精确 DP(n ≤ 14,含换频状态)|
| 定位测点选择 | **带邻域的 TSP / 定向问题(TSPN/OP)** | 每任务独立选点 + 全表重排 | 覆盖骨架 + 最便宜插入;测点作为"邻域"参与联合优化 |
| 定向源定位 | **POMDP / 随机最短路** | 一步贪心 `prob_detect/时间` | 2–3 步 rollout,值函数 = 期望后验 MEC 半径 |
| 后备清除 | **区域覆盖路径(Covering Path)** | 格心最近邻 | 割草机 + 转向点 DP / 格心集 Or-opt |
---
## 2. 本轮实测基线(现役代码,冻结场景)
场景集:`harness/suites.py`,第三问 core 的 seed 100–105 × 13 源;
第四问 core 全部 9 场(10/13/16 源 × 3 定向占比)。配置为现役默认。
### 2.1 第三问(`bayes`,13 源,6 场)
| 指标 | 数值 |
| --- | ---: |
| 平均虚拟时间 | **3197 s** |
| 移动 | 12292 m = **2458 s(77%)** |
| 覆盖点检测 | 83.5 次 = 501 s(其中 69.8 次 `no_signal`,属缺席证明) |
| 定位探测 | **30.8 次 = 185 s**(0 次 `no_signal`) |
| 清除动作 | 65 s |
| **离线最优开放回路(同一批作业点)** | **2308 s** |
| 路由可回收 | **151 s(移动的 6.1%,T 的 4.7%)** |
| 作业点数(去重) | 35–50 |
### 2.2 第四问(`directional` + `q4_mincover_v1`,9 场)
| 指标 | 数值 |
| --- | ---: |
| 平均虚拟时间 | **8676 s** |
| 移动合计 | 6257 s(72%) |
| ├ 覆盖段移动 | 3579 s |
| ├ **探测移动** | **1880 s** |
| └ **清除移动** | **798 s** |
| 动作耗时 | 2149 s(覆盖 1386 / 探测 344 / 清除 419) |
| 探测次数 | 69.0 次(`no_signal` 4–12 次) |
| **清除次数 / 成功** | **131.1 / 13.0 → 118 次失败** |
| `fallback_clear_attempts` | **125.7 次/局** |
| 后备盲清格数(单次) | 3 ~ 210 格,全部以 `cleared` 结束 |
| **离线最优开放回路(同一批作业点)** | **4998 s** |
| 路由可回收 | 1259 s(移动的 20.1%,T 的 14.5%) |
| 覆盖投影顺序 vs 离线 31 点 2-opt | 4365 s vs 4181 s(+4.4%) |
### 2.3 成本地板估计
| 问题 | 不可压缩部分 | 现状 | 真实可压缩 |
| --- | --- | ---: | ---: |
| 第三问 | 七点环 1392 s + 覆盖检测 501 s + 清除动作 65 s + 每源必经的接近/清除移动 + 每源 ≥2 次探测 | 3197 s | **~300–450 s(10–14%)** |
| 第四问 | 31 点巡回 ~4180 s + 缺席检测 ~1386 s + 每源 ≥3 次探测与一次清除 | 8676 s | **~2000–2800 s(23–32%)** |
> 第四问的 4000 s 验收线**依然不可达**(单是 31 点巡回 + 缺席检测就 ≈5566 s),
> 这与前置文档 §6.2 的结论一致;本方案只承诺把 8676 s 压向 6500 s 量级。
---
## 3. 算法设计
### 3.1 D1(第四问最高优先):定向源的"抵近 + 交会"滚动时域 DP
**问题本质**:±1° 示向度误差在距离 d 处给出的横向不确定度约 `d·tan(1°) ≈ 0.0175 d`。
d = 1000 m 时单条方位线的横向约束只有 ±17.5 m;两条方位线的交会角 θ 决定最终尺度
`≈ 0.0175 d / sin θ`。要压到 20 m 清除证书,必须**同时**满足:交会角大、距离近。
现役 `_visibility_probe` 只最大化 `prob_detect(q)/(移动+6)`,天然偏好"近且可见",
与"交会角大"无关;远距离又永远收敛不到 20 m,于是退化到 20 m 网格盲清。
**形式化**(这就是一个 POMDP):
- 状态 `b` = `JointBelief`(位置 × 24 档朝向的可行集,见 `belief/joint.py`),
再叠加 `r_eff ~ U[1000,1500]` 的不确定度;
- 动作 `q` ∈ 候选测点(保证接收点 ∪ 对跖点 ∪ 三圈环探 ∪ **朝 MEC 圆心的抵近点**);
- 观测 `o ∈ {direction, near, no_signal}`,由每条粒子 `(p, h, r_eff)` 解析预测;
- 代价 `c(q) = 6 s + dist(pos,q)/5`;
- 终止:`MEC(b).radius ≤ 20 − margin`(可一次清除);
- 值函数:`V(b) = min_q E_o[ c(q) + V(b') ]`。
**实现选择(按代价递增)**:
1. **一步期望后验半径**(最小改动,复用 Q3 已有内核的二维推广):
对候选 `q`,按粒子权重模拟三分支结果,用幸存子集的 MEC 半径近似 `V(b')`,
取 `E[radius] + λ·c(q)` 最小者。把 `particles.py::expected_posterior_radius`
的向量化实现在 `JointBelief` 上重写一份(numpy 可用时广播)。
2. **2–3 步 rollout**:对前 K=8 个候选各展开一层,深度 2;墙钟预算 ≤ 0.3 s/决策。
3. **交会角代理(解析、零墙钟)**:若一步法来不及落地,先用
`score(q) = −E[radius] · sin(Δbearing(q, 既有方位线)) / dist(q, center)`
的确定性代理,实验证明有效后再换 rollout。
**关键结构性改动**:把候选点集从"绕 MEC 的固定环"扩展为**含抵近点**,
即允许 `q` 落在 `center + α·(pos − center)` 上、`α` 从 0.6 到 1.0 采样。
这样当远距离无法收敛时,策略会**主动抵近**并以一次正常角域证书收尾,
而不是把 200 格盲清当作唯一出路。后备清除**保留为安全网**,但目标是触发次数下降 ≥ 80%。
**验收**:`fallback_clear_attempts` ≤ 15/局(现 125.7);定向频道平均探测次数
≤ 全向频道的 1.5 倍;清除成功率 ≥ 90%;9 场景全清率 100%。
**预期**:−800 ~ −1100 s。
---
### 3.2 D2:覆盖骨架 + 最便宜插入(TSPN 的在线近似)
**问题本质**:31 个覆盖点是**必须在场**的(缺席证明),但探测点与清除点是
**临时产生**的。现役做法把两类点混在一张任务表里,每次从零做 NN + 2-opt
(`bayes_loop._order_tasks`),等于让临时任务把骨架顺序"打散"。
实测投影顺序 4365 s vs 离线最优 4181 s,说明骨架顺序本身没坏,
坏在**绕行开销**——而绕行开销正是插入位置没优化。
**形式化**:这是 **TSP with Neighborhoods / Generalized TSP**:
- 骨架:离线求覆盖点集的近优开放回路 `S`(`7` 点用解析证明;`31` 点用 Or-opt/3-opt,
`n ≤ 16` 时可直接 Held-Karp 精确);
- 在线:给定当前任务 `q` 与其**邻域**(`clear` 任务的邻域是 `C₂₀(K)`,`locate`
任务的邻域是候选测点集),求
`Δ(i,j) = dist(a_i, q) + dist(q, a_{j}) − dist(a_i, a_j)`
最小的插入位置 `(i,j)`;这正是"图上的最便宜插入",`O(|S|²)` 每任务。
**实施**:
1. `guarantee/cover.py` 增加 `optimize_tour(points, seed_tour, passes)`(Or-opt + 2-opt),
把 `nn_tour_m` 从"上界"变成"已优化值",并写入证书 `tour_virtual_s`。
2. `planner/bayes_loop.py` 新增 `_skeleton`(覆盖点顺序)与 `_insert_task(task, neighborhood)`;
`_order_tasks` 退化为"骨架遍历 + 已插入任务",保留现有 2-opt 作为兜底开关。
3. `clear` 任务的邻域用 **`C₂₀(K)` 内离插入点最近的点**(见 3.4),使插入代价与
清除目标联合最小。
**验收**:第四问移动 ≤ 5000 s(现 6257 s);路由相对离线最优的 gap
(新增回归指标 `route_gap_vs_offline`)从 20.1% 降到 ≤ 10%。
**预期**:−400 ~ −700 s。
---
### 3.3 D3:后备清除的覆盖路径(若 D1 后仍触发)
后备清除是"用 20 m 圆盘覆盖残差区域 `R` 直到命中真源"——即 **Covering Path Problem**。
- 当前 `_fallback_clear` 对格心做 nearest-first 扫描,等价于格心集上的 NN 开放路径,
典型约为最优的 1.2–1.3 倍;
- 改进:对格心集做 **Or-opt + 2-opt**(`k ≤ 400`,`O(k²)` 一次约 0.1 s,且只在
极少数局触发);
- 上界更优解:凸区域上的**割草机(boustrophedon)扫描**,用 DP 在扫描线方向选择
转向点可精确最优;对一般多边形用梯形分解 + DP。
**验收**:后备触发时移动 ≤ 现值的 80%(单次);触发次数本身由 D1 压到 ≤ 15/局。
**预期**:−100 ~ −200 s(D1 未完全消除时的兜底)。
---
### 3.4 T1/T3:第三问的精确排线、换频 DP 与清除点选择
**(a) Held-Karp 精确任务序(含换频状态)**
现役 `_two_opt` 是启发式且被 4 轮上限截断。任务表典型只有 6–12 项,
可以**精确求解**:
```
dp[S][j][c] = 已访问任务集 S、停在任务 j、测向频道为 c 的最小代价
转移:dp[S∪{k}][k][c'] = dp[S][j][c] + dist(pos_j, pos_k)/5 + service(k, c, c')
```
- 规模:`|T| ≤ 12` 时 `2^12 × 12 × 20 ≈ 1.0M` 状态,纯 Python 约 0.1–0.3 s/次;
滚动重排每步一次会吃掉预算,因此**只在任务表新增/删除任务时重解**,
或在 `|T| ≤ 9` 时默认启用、否则回退 2-opt + Or-opt。
- 换频代价精确化:`service` 依赖进入/离开频道,把 `c` 并入状态即可精确;
现役 `_path_cost` 用"按频道排序"的贪心近似,会低估/高估 1–3 s/站。
- 附图论下界:对当前点集算 **MST 权重**(`O(n²)` Prim),
得到开放路径最优值的下界,写入 `summary.route_lower_bound_s`,
用于**在线后悔值审计**与回归("本步排序距最优 ≤ x%")。
**验收**:`route_gap_vs_offline` ≤ 3%;单局墙钟增量 ≤ 0.2 s。
**预期**:−120 ~ −200 s。
**(b) 探测的 2 步 rollout(T2)**
现役 `_select_point` 是一步贪心(期望 log 幸存占比 + λ·移动)。
把它扩成深度 2 的 rollout:对前 `K=8` 个候选,各展开一次测量的三分支
(direction / near / no_signal),取 `E[V]` 最小者。
`V` 沿用现有 `expected_survival`。这属于**滚动时域 DP**,不改任何安全性判定。
**验收**:探测次数 30.8 → ≤ 26;墙钟 ≤ 0.3 s/决策。
**预期**:−150 ~ −250 s。
**(c) 清除点选在 `C₂₀(K)` 内离当前位置最近处(T3)**
`C₂₀(K) = ∩_{g∈K} B(g, 20)` 是凸集(圆盘之交)。求其内离当前点最近点是一个
**凸可行性/投影问题**(对小规模可枚举多边形顶点 + 圆盘投影,或直接迭代投影)。
现役 `_near_clear_target` 只沿一维方向平移,与最优之差 ≤ 0.74 m(前置文档 P1-1 已复核),
收益主要来自**与 3.2 插入代价的耦合**:先定插入位置、再选清除点,比反过来省更多。
**验收**:清除段移动下降 ≥ 5%。
**预期**:−50 ~ −150 s。
**(d) 七点环半径(T4)**:`1159.7 → 1122.9`(可证明下界),−44 s。
仅作为"证明性"改动落地,不作为收益项。
---
## 4. 实施路线
| 阶段 | 内容 | 交付物 | 验收门槛 | 依赖 |
| --- | --- | --- | --- | --- |
| **S0 基线冻结**(0.5 天) | 把近期 sweep 的最优参数(`route_aware_probe=false`、`lambda_move_s≈0.06`、`opportunistic_radius_ratio≈0.4–0.5`)回填为基准,跑满 core+boundary 并归档 | `output/suite/q3-baseline`、`q4-baseline` | 现有 `valid` 口径 100% | 无 |
| **S1 度量补全**(0.5 天) | 在 `scripts/run_suite.py` 增加 `route_gap_vs_offline`、`route_lower_bound_s`、`distinct_service_points`、`coverage_actual_visited` 四列 | runner 改动 + 回归 | 与本文 §2 实测复现一致 | S0 |
| **S2 覆盖证书硬化**(1 天,**正确性优先**) | 证书分辨率 75 m/24 → 20 m/72;设计裕量 `r_design ≤ 960 m` 或细网格直接搜索;保留 10% 保险点预算 | `guarantee/cover.py`、`plans/*.json`、正/负例测试 | 细网格(15 m/72)缺口 = 0;故意删点必须被检出 | S1 |
| **S3 第四问 D1**(2–3 天) | `belief/joint.py` 增加期望后验半径 / rollout 选点;`planner/__init__.py::_probe_candidates` 增加抵近点;后备降级为安全网 | `belief/joint.py`、`planner/__init__.py`、测试 | `fallback_clear_attempts ≤ 15`;全清 100%;墙钟 ≤ 1.0 s/局 | S2 |
| **S4 第四问 D2/D3**(2 天) | 覆盖骨架 + 最便宜插入;`C₂₀` 邻域;后备 Or-opt | `planner/*`、`guarantee/cover.py` | 移动 ≤ 5000 s;`route_gap ≤ 10%` | S3 |
| **S5 第三问 T1/T2/T3**(2 天) | Held-Karp(含换频)+ MST 下界;探测 2 步 rollout;`C₂₀` 最近点 | `planner/bayes_loop.py`、`plan/*` | 探测 ≤ 26;`route_gap ≤ 3%`;T ≤ 2900 s | S1 |
| **S6 收口**(1 天) | 全量配对、论文数据表、消融图 | 报告 + `docs/paper` 数据 | 与 S0 配对全部 `valid`,无退化场景 | S4/S5 |
**顺序约束**:S1(度量)先于任何算法改动,否则"更快"不可证;S2(证书)先于
第四问算法改动,否则可能在错误的安全基线上做优化。
---
## 5. 风险与明确不做的部分
### 5.1 本轮发现的正确性风险(必须先处置)
现役 `q4_mincover_v1`(31 点)与 `square49`(49 点)在**粗采样**下缺口为 0,
但把校核分辨率从 75 m/24 档提到 25 m/48 档:
| 点集 | 点数 | 75 m/24 缺口 | 25 m/48 缺口 | 15 m/72 缺口 | 最坏命中@75 m | 最坏命中@25 m |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `square49` | 49 | 0 | 0 | 0 | 760.3 m | 760.3 m |
| `q4_mincover_v1`(现役) | 31 | 0 | **125** | **566** | 999.1 m | 1000.0 m |
| 细网格重搜最小集 | 31 | 0 | 0 | 35 | 983.0 m | 996.2 m |
| 贪心 27 点(75 m 档最优) | 27 | 0 | **298** | 1117 | 986.5 m | 1000.0 m |
结论:
1. **31 点很可能就是实际下界附近**:多起点随机贪心 + 冗余剪枝在 25 m/48 细网格上
直接搜索的最小解仍是 31 点(尚未做 LP 松弛下界,但连贪心都下不去);
之前看到的 27 点是**采样过拟合的假解**,不可采纳;
2. `q4_mincover_v1` 的安全裕量几乎为 0(999.1 m),存在"真源恰落在细缝 + `r_eff` 取下界"
的漏检窗口,现役测试没暴露是因为随机场景未命中细缝且 `r_eff` 通常 > 1000;
3. `square49` 裕量充足但巡回 36.4 km,不可回退;
4. 正确做法是**细网格重搜 + 设计裕量**(S2),代价约 0–150 s,**先保安全再谈提速**。
### 5.2 其他风险
| 风险 | 影响 | 缓解 |
| --- | --- | --- |
| POMDP rollout 墙钟超预算 | 单局计算 > 1 s | 深度 2、候选截断 K=8、`--profile` 纳入基准必报列;超预算自动退回一步法 |
| Held-Karp 状态爆炸(任务表变长) | 单步计算抖动 | 仅在 `|T| ≤ 9` 默认启用,`> 9` 回退 2-opt + Or-opt |
| 抵近点破坏可见性保证 | 探测反而 `no_signal` 变多 | 抵近点仍以 `prob_detect > 0` 过滤;`no_signal` 是合法负观测,不破坏守恒性 |
| 骨架插入与"找全即停"冲突 | 为走骨架而多绕行 | 骨架只提供顺序,不新增必须访问的点;任务表为空即停 |
| 细网格证书重搜耗时 | 离线 3–5 min | 一次性离线搜索 + 运行期按计划名缓存校验(沿用 `load_plan` lru_cache) |
### 5.3 明确不做
- **不重引入 PPO/HRL**(前置文档已用 109 776 步的负面结果排除);
- **不引入新的运行时依赖**(仍只有 `requests`;`numpy` 仅作可选加速后端,
`networkx`/`scipy` 不进入运行时路径);
- **不改变停止条件**(`unresolved_channels` 的口径取舍属于评分方式问题,
须先与题面评分方式核对,本轮不作为默认行为);
- **不承诺第四问 4000 s**(成本地板 ≈5566 s,见 §2.3)。
---
## 6. 验收与度量
### 6.1 有效性定义(沿用,不得放宽)
```
valid := 全部真源被清除 ∧ 缺席频道与真值一致 ∧ 正常 /exit
∧ 零算法异常 ∧ 逐微秒计时复算一致
```
### 6.2 新增必报列(写入 `scripts/run_suite.py` 逐局行与 aggregate)
| 列 | 定义 | 用途 |
| --- | --- | --- |
| `route_gap_vs_offline` | `实测移动 − 离线最优开放回路(同作业点集)` / 实测移动 | 区分"排线不够好"与"点选得不对" |
| `route_lower_bound_s` | 当前任务集 MST 权重 / 速度 | 在线后悔值审计 |
| `distinct_service_points` | 去重作业点数 | 探测是否真的在减少 |
| `coverage_actual_visited` | 实际测过的覆盖点数 | 缺席证明是否提前结束 |
| `probe_no_signal_ratio` | 探测中 `no_signal` 占比 | D1 的效果指标 |
| `fallback_clear_attempts` | 已有 | D1 的主验收指标 |
### 6.3 目标(vs §2 基线)
| 指标 | Q3 现 | Q3 目标 | Q4 现 | Q4 目标 |
| --- | ---: | ---: | ---: | ---: |
| core 平均虚拟时间 | 3197 s | **≤ 2900 s** | 8676 s | **≤ 6500 s** |
| 移动 | 2458 s | ≤ 2250 s | 6257 s | ≤ 5000 s |
| 探测次数 | 30.8 | ≤ 26 | 69.0 | ≤ 45 |
| 后备盲清次数 | 0 | 0 | 125.7 | **≤ 15** |
| `route_gap_vs_offline` | 6.1% | ≤ 3% | 20.1% | ≤ 10% |
| `fallback` 清除成功率 | — | — | 10% | ≥ 90% |
| 单局计算墙钟 | 1.0 s | ≤ 1.2 s | 1.0 s | ≤ 1.5 s |
| 细网格证书缺口(15 m/72) | — | 0(Q3 解析) | 566 | **0** |
### 6.4 回归
`uv run pytest`(当前 232 项)必须全过;新增:
- `CoveragePlan` 细网格正/负例(删掉任一点必须被检出);
- `JointBelief` 的期望后验半径与真源保留性质对拍(随机 `(p,h)` × 随机观测序列);
- `Held-Karp` 与 `2-opt` 在同一小任务集上的最优性对拍(DP 结果必须 ≤ 2-opt);
- `C₂₀` 投影的三角不等式性质(投影点距候选集任一点仍 ≤ 20);
- 逐位一致性:numpy 与纯 Python 两条路径在同一动作序列下 `actions_sha256` 相同。
---
## 7. 复现命令(本轮实测)
```bash
# 度量基线(§2.1/§2.2/§2.3):作业点分解 + 离线最优回归 + 成本地板
uv run python /tmp/analyze_headroom.py # 路由 gap:Q3 6.1%,Q4 20.1%
uv run python /tmp/decompose.py # Q4 三段移动 + 动作 + 清除成功率
uv run python /tmp/q3stats.py # Q3 探测/覆盖检测/跳测统计
# 覆盖证书分辨率(§5.1)
uv run python /tmp/verify_margin.py # 现役 31 点在 25 m/48 档缺口 125
uv run python /tmp/hitset_fine.py # 细网格最小解仍是 31 点
# 既有常驻工具
uv run python scripts/run_suite.py --problem 4 --suite all --jobs 6 \
--variants directional@q4_mincover_v1 --label q4-baseline
uv run python scripts/verify_q4_cover.py --out output/q4/cover-certificate.json
uv run python scripts/analyze_run_timing.py <log.jsonl>
```
> 上述 `/tmp/*.py` 为一次性分析脚本,S1 阶段应把其中的
> `route_gap_vs_offline`、`offline_opt_open_path`、`mst_lower_bound`
> 三个函数并入 `scripts/run_suite.py`(或新建 `scripts/analyze_routing.py`)常驻入库,
> 使"虚拟时间花在哪"与"覆盖集是否合法"一样成为可随时重跑的回归项。
---
## 8. 本轮实测回填(2026-09-12 实施结果)
本节记录 S1–S5 全部落地后的**实测**结果与**负面结论**。所有数字均为冻结基准集
(`harness/suites.py`)配对复跑,口径与 §6.1 完全一致;复现命令见 §8.6。
### 8.1 交付物清单
| 项 | 位置 | 状态 |
| --- | --- | --- |
| 图论/DP 工具箱(精确开放路径、2-opt/Or-opt、MST 下界、路由度量) | `src/mcm26b/routing.py` | ✅ 新增 |
| S1 必报列(`route_gap_vs_offline`、`route_lower_bound_s`、`distinct_service_points`、`coverage_actual_visited`、`probe_no_signal_ratio`、`plan_verify_wall_s`) | `scripts/run_suite.py` | ✅ 新增 |
| S2 设计裕量覆盖计划 + 细网格证书 + 精细剪枝 | `guarantee/cover.py`、`scripts/verify_q4_cover.py`、`plans/q4_mincover_v2.json` | ✅ 新增并设为默认 |
| S3/D1 期望后验半径值函数 + 可见性过滤 + 抵近点 | `belief/joint.py`、`planner/__init__.py` | ✅ 新增(默认口径见 §8.3) |
| S4/D3 后备清除路径改进 | `planner/__init__.py::_fallback_order` | ⚠️ 已实现,实测负收益,默认关闭 |
| S5/T1 Held-Karp 精确任务序(含换频状态) | `planner/bayes_loop.py::_exact_order` | ⚠️ 已实现,实测负收益,默认关闭 |
| 回归测试 | `tests/test_routing.py`、`tests/test_joint_belief.py`、`tests/test_coverage_plan.py` | ✅ 新增,`uv run pytest` 268 项全过 |
### 8.2 最终成绩(vs S0 基线)
| 指标 | Q3 基线 | Q3 现 | Q4 基线 | Q4 现 | 目标 |
| --- | ---: | ---: | ---: | ---: | ---: |
| core 平均虚拟时间 | 3210.3 s | **3188.7 s(−0.7%)** | 8676.1 s | **8602.0 s(−0.9%)** | 2900 / 6500 ✗ |
| core 最大虚拟时间 | 3798.5 s | **3663.7 s(−3.5%)** | 10890.8 s | **9630.4 s(−11.6%)** | — |
| 移动 | 12446.8 m | **12338.1 m(−0.9%)** | 31286.4 m | 32891.1 m(+5.1%) | 2250 / 5000 ✗ |
| 检测次数 | 111.0 | 111.1 | 346.0 | **333.7** | — |
| 后备盲清次数 | 0 | 0 | **125.7** | **4.3(−96.6%)** | ≤15 ✅ |
| `route_gap_vs_offline` | 8.67% | **8.07%** | 18.9% | 24.3% | ≤3% / ≤10% ✗ |
| 单局计算墙钟 | 1.29 s | **1.04 s** | 5.29 s | **4.09 s** | ≤1.2 / ≤1.5 ⚠️ Q4 超标 |
| 覆盖计划运行期校验 | — | 0.42 s | — | 4.80 s(15 m/72 一次性) | — |
| 细网格证书缺口(15 m/72) | — | 0(解析) | 566 | **0** | 0 ✅ |
| 有效局 | 18/18 | 20/20(含边界) | 9/9 | 11/11(含边界) | 100% ✅ |
**结论**:本轮的硬收益是**第四问后备盲清被消除**(125.7 → 4.3 次/局,最坏局
10890.8 → 9630.4 s)与**覆盖证书的正确性**(细网格缺口 566 → 0);虚拟时间只有
0.7–0.9% 的均值改善(中位 −2.0%),**远未达到方案 §6.3 的目标**。
第三问的改善落在噪声内(中位相对变化 0.0005)。§2 的成本地板估计(Q3 可压缩
10–14%、Q4 23–32%)**过于乐观**,实测只回收了约 1%。
### 8.3 定向源探点(D1):先负后正,最终只保留"可见 + 就近"
D1 的完整实现(期望后验 MEC 半径值函数 + 抵近点 + 遮挡点剔除)在**修正联合
信念朝向规则之前**配对为:后备 125.7 → 53.8 次,但探测移动 +5.6 km,虚拟时间
8676 → 9411 s(净负)。原因有两个:
1. 后备清除在命中真源时**立即结束**,"整条路径最短"不是它的目标函数,
§3.2/§3.3 的插入代价模型在这里并不成立;
2. 更关键的是:**联合信念的 `no_signal` 朝向规则被写反了**(§8.4),
`prob_detect` / 期望半径因此系统性地高估"被遮挡点可见",选点必然跑偏。
修正后重测(9 场景 core):
| `probe_score_mode` | λ | 后备次数 | T 均值 | 移动 | 计算墙钟 |
| --- | ---: | ---: | ---: | ---: | ---: |
| `visibility`(旧) | — | 125.6 | 9224.6 s | 33595 m | 11.6 s |
| `expected_radius` | 0.05 | 6.8 | 9740.5 s | 38824 m | 9.6 s |
| `expected_radius` | 5.0 | 4.3 | 8604.3 s | 32893 m | 3.7 s |
| `nearest_visible`(默认) | — | **4.3** | **8602.0 s** | 32891 m | 3.4 s |
λ 越大越优说明本题的探点问题里"**看得见且少走路**"比"单步信息量最大"更重要;
λ→∞ 的极限(可见候选中移动最省)就是默认口径。值函数代码与其对拍测试保留,
供后续研究(例如配合真正的多步 rollout)。
### 8.4 本轮发现的正确性缺陷(已修复)
`belief/joint.py` 的 no_signal 朝向裁剪**规则反向**:`keep=False` 分支用了
"确定覆盖"掩码做交集,等价于保留"必然看得见"的档、删掉"确定看不见"的档;
`keep=True` 分支又漏计了格内角裕量 `asin(margin/max(dist−margin,1))`。
两者都会删掉真源的朝向档(`tests/test_joint_belief.py::test_true_source_survives_consistent_observations`
在随机观测序列上直接复现)。修复后:
`keep=False` 改用 `_far_mask`(与 `_cover_mask` 互补)并把距离阈值收紧为
`r_eff_min − margin`,`keep=True/False` 都计入格内角裕量。
**修复本身是净成本**:在旧 `visibility` 探点口径下 Q4 core 由 8676 → 9225 s;
换成 §8.3 的新探点口径后回落到 8602 s,同时后备清除降 96.6%。
换言之,本轮第四问的成绩改善**主要来自"修好信念 + 换探点口径"这一组合**,
而不是 §3 预期的插入/排线优化。
### 8.5 覆盖证书硬化(S2)
现役 `q4_mincover_v1`(31 点)在粗采样(75 m/24)下缺口 0,但细网格复检:
| 点集 | 点数 | 25 m/48 缺口 | 20 m/72 缺口 | 15 m/72 缺口 | 最坏命中 | NN 巡回 |
| --- | ---: | ---: | ---: | ---: | ---: | ---: |
| `q4_mincover_v1`(旧) | 31 | 125 | 336 | 566 | 1000.0 m | 22.9 km |
| `q4_mincover_v2`(默认) | 31 | **0** | **0** | **0** | **990.0 m** | 23.9 km |
| 严格连续空间证书备选 | 36 | 0 | 0 | 0 | 973.7 m | 25.4 km |
`q4_mincover_v2` 的生成方式:搜索分辨率 50 m/48 档、设计裕量 `r_design=965 m`、
前向角裕量 5°,再做**证书分辨率(15 m/72)精细剪枝**(每次删点都用同一证书口径
复核),因此点数与旧计划相同而缺口归零,**没有付出点数代价**。
> 严格连续空间界需要 `r_verify + 15·√2/2 ≤ 1000`(即 ≤989.4 m)且角裕量 ≥2.5°;
> 满足该条件的 36 点方案最坏命中 973.7 m,但代价 +5 点(约 +1000 s),
> 已落盘为 `output/q4/cover-certificate-v3.json` 备用。默认方案在 15 m/72
> 采样下最坏 990.0 m,与严格界的差 0.65 m 属采样边界效应,作为已知残余风险登记。
### 8.6 负面结论与复现命令
| 优化项 | 实测 | 结论 |
| --- | --- | --- |
| S4/D3 后备清除 Or-opt | 后备失败 125.7 → 161.4 次(T 8676 → 8940 s) | ❌ 目标函数错位:命中即停,路径最短反而把真源推后 |
| S5/T1 Held-Karp 精确任务序 | 3203.2 s vs 2-opt 3193.1 s(18 场景) | ❌ 滚动时域下"当前任务表逐位最优"反而略差;默认 0 |
| `route_aware_probe` | 3210.3 s vs 关闭 3193.1 s | ❌ 权重 1e-3 的路线项无净收益;默认关闭 |
| Q4 融合巡回(站点顺带定位) | 12711 s vs 10809 s | ❌ 与 P0-1 在第三问的负面结论一致 |
| `opportunistic_radius_ratio` | 0.65 → 0.5:3210.3 → 3205.4 s | ✅ 采纳(弱收益) |
```bash
# S1 度量基线(含路由 gap / MST 下界 / 作业点/静默率)
uv run python scripts/run_suite.py --problem 4 --suite core --jobs 6 \
--variants directional --label q4-baseline --out output/suite
uv run python scripts/run_suite.py --problem 3 --suite core --jobs 6 \
--variants bayes --label q3-baseline --out output/suite
# S2 覆盖证书(生成 + 细网格校核;--fine-prune 走证书分辨率剪枝)
uv run python scripts/verify_q4_cover.py --spacing 100 --radius 2850 \
--target-step 50 --headings 48 --design-radius 965 --heading-margin 5 \
--verify-step 15 --verify-headings 72 --fine-prune \
--write-plan q4_mincover_v2 --out output/q4/cover-certificate-v2.json
# S3/D1 口径配对(可见性/值函数/极限三档)
uv run python scripts/run_suite.py --problem 4 --suite core --jobs 6 \
--variants "directional#search.probe_score_mode=visibility,\
directional#search.probe_score_mode=expected_radius;search.probe_lambda_move_s=5,\
directional#search.probe_score_mode=nearest_visible" --label q4-probe-ab
# 回归
uv run pytest -q # 268 项
```
### 8.7 仍未达标项的处置建议
| 未达标 | 差距 | 建议 |
| --- | --- | --- |
| Q3 ≤2900 s | −9% | 已无低风险空间:七站巡回 1392 s + 覆盖检测 501 s + 每源 ≥2 次探测是成本地板;需要改停止条件或覆盖语义才能继续下降 |
| Q4 ≤6500 s | −24% | 31 点巡回 + 缺席检测 ≈5566 s 是地板,与 §2.3 一致;只能改"逐频道缺席证明"口径 |
| `route_gap` Q4 24.3% | — | 该指标在"作业点数随决策变化"时口径不稳(本方案把后备格换成探测点后作业点从 206 降到 110);建议改为"覆盖骨架段"与"临时任务段"分别报 gap |
| Q4 计算墙钟 4.1 s(目标 1.5 s) | 2.7 s | 热点在 `belief` 凸多边形与 `select_measurement_point`(D1 之前的基线即 5.3 s),属 P0-4b 表示层张量化,与本方案无关 |
| 严格连续空间覆盖证书 | 0.65 m | 若要正式采用 36 点严格方案:`--verify-radius 989 --verify-heading-margin 2.5`,代价约 +1000 s |