517 lines
31 KiB
Markdown
517 lines
31 KiB
Markdown
# 第三、四问:图论与动态规划算法引入方案
|
||
|
||
- 版本: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 |
|