# 第三、四问:图论与动态规划算法引入方案 - 版本: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 ``` > 上述 `/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 |