# 问题三算法说明:全向多源搜索的覆盖证明、贝叶斯闭环与任务调度(当前实现) > 本文汇总当前仓库中问题三的全部实际算法,生产默认策略为 > `src/mcm26b/planner/bayes_loop.py::BayesLoop`(CLI 策略名 `bayes`)。 > `grid`(`OmnidirectionalLoop`,49 点)与 `seven` > (`SevenBatch`,七点解析覆盖 + 本站批量)保留为可复现对照。 > 问题三当前生产不包含任何机器学习策略;历史 HRL/RL 代码已移除, > 自建消融中手写基线稳定优于学习策略(见 `docs/第三四问架构优化与运行提速方案.md`)。 ## 1. 问题定位与策略族 问题三中每局有 $10\sim16$ 个全向干扰源,位置未知、频道互异(1..20), 源位于 $\Omega=B(0,1800)$ 内;有效接收半径 $r_{\rm eff}$ 未知,但满足 $1000\le r_{\rm eff}\le1500$ m。策略端只能看到协议响应 (`direction` / `near` / `no_signal`),目标是发现、定位并清除全部源, 使虚拟总耗时尽量小,同时给出“不存在漏源”的证明或明确未完成。 | 策略 | 类 | 发现保证 | 定位/决策 | 角色 | | --- | --- | --- | --- | --- | | `grid` | `OmnidirectionalLoop` | 700 m 方格 49 点,单元对角 $700\sqrt2\approx989.95\le1000$ | 发现后立即就近定位,无信息增益 | 历史基线 | | `seven` | `SevenBatch` | 原点 + 半径 1200 m 六等分环,解析覆盖界 968.90 m | 本站先扫频,再按候选集 MEC 圆心最近服务队列 | A2 中间对照 | | `bayes` | `BayesLoop` | 七站环(默认半径 1159.7 m,界 984.54 m)+ PF 负观测 | 网格粒子滤波 + 信息增益选点 + 滚动任务排序 | **生产默认** | 七站族还有 `SevenImmediate`(A1,发现即离站)与 `SevenSweep` (A3,先扫完全域再统一服务)。它们的差异只在发现/定位的调度顺序, 七点覆盖证明与距离语义相同。 ### 1.1 统一计时模型 忽略微秒量化时,一次动作回合的虚拟耗时为 $$ T=\frac{L}{5}+5N_m+N_s+5N_c^++3N_c^-, $$ 其中 $L$ 为移动距离(速度 5 m/s),$N_m$ 为检测次数,$N_s$ 为换频次数, $N_c^+$ 为成功清除次数,$N_c^-$ 为失败清除次数。`/clear` 本身不切换 测向频道,`/exit` 不增加虚拟时间,访问顺序是起点固定、终点自由的**开放 路径问题**,不是必须回到原点的 TSP 回路。官方内部按整数微秒累计,项目用 `evaluation.recompute_virtual_time` 逐动作复算并校验。 ## 2. 三层在线架构 `BayesLoop` 在 `OmnidirectionalLoop` 的会话/预算/收尾骨架上重构为三层: 1. **保证层**:七点覆盖账本 `CoverageLedger` + 解析覆盖定理;负责 “全测无信号 $\Rightarrow$ 该频道不存在源”。 2. **信念层**: - 凸候选集 `CandidateRegion`(正观测方向/近距离约束,用于清除证书); - 网格幸存集 `ChannelParticles`(正/负观测统一更新,用于缺席证明、 跳测与信息增益); - 两者同读一套响应流,任何一方证毕即结束该频道。 3. **决策层**:统一任务表(站点扫频 / 定位测量 / 清除),开放路径 最近邻 + 2-opt 滚动重排;每执行一个任务后用真实响应重规划。 问题三不复用问题四的(位置 × 朝向)联合信念;`no_signal` 在全向源上 可以排除 $B(s,1000)$。 ## 3. 证据层:逐频道候选集与负观测 ### 3.1 凸候选集 `CandidateRegion` 对每个已收到过有效信号的频道 $c$ 维护凸多边形 $P_c^{\rm out}$: - 初始为 $\Omega$ 的 192 边外切正多边形; - 每次 `direction`: 1. 与 $B(s,1500)$(192 边外切多边形)求交; 2. 与角域 $W(s,\theta,\delta_{\rm eff})$ 求交,$\delta_{\rm eff}=1^\circ+0.005^\circ$; - `near`:与 $B(s,5)$ 外切多边形求交; - `no_signal`:**不收缩凸候选集**。对全向源,负观测信息由 `ChannelParticles` 处理;让凸表示保留所有正观测候选,可以避免因模型误差误删真源。 圆盘外切多边形的径向超出逐次累加到 `slack_m`,所有裁剪容差只向 “保留更多候选”的方向倾斜,因此真源始终包含在 $P_c^{\rm out}$ 内。 清除证书使用 $P_c^{\rm out}$ 的最小包围圆 $B(m_c,\rho_c)$: $$ \rho_c+\text{slack}_c+0.5\le20 \quad\Longrightarrow\quad \text{在 }m_c\text{ 处一次 `/clear` 必命中真源}. $$ 其中 $0.5$ m 是工程余量,slack 是外包多边形的半径余量;该判据是保守的 充分条件,而不是“必须达到的最优半径”。 ### 3.2 网格幸存集 `ChannelParticles` 每个频道维护 $\Omega$ 外扩裕量上的方格网格,间距 $h=14.65$ m,约 4.7 万格;Voronoi 裕量 $$ m=\frac{\sqrt2}{2}h\approx10.359\ {\rm m} $$ 是格心到格内任一点的最大距离。所有删除判定都内缩 $m$:**只有整个格子与 可行集不相交时才删除该格**。 | 观测 | 位置/距离更新 | 实现 | | --- | --- | --- | | `no_signal` @ $s$ | 距离 $>r_{\rm eff}\ge1000$,删除格心距 $s\le1000-m$ 的格 | `_kill_disk(s,1000-m)` | | `direction` @ $s$,读数 $\theta$ | 距离 $\le1500$;角向偏差不超过 $\delta_{\rm eff}$ + 格内角裕量 | 先删距 $s>1500+m$ 的格,再删角向超出的格 | | `near` @ $s$ | 距离 $\le5$ | 删格心距 $s>5+m$ 的格 | **定理(真源保留).** 真源 $P$ 所在格的格心 $g$ 满足 $|g-P|\le m$;与真值一致的观测不会让该格与可行集完全不相交,因此 “整格不相交”判定永不删除该格。于是: - **缺席证明**:幸存集为空 $\Longrightarrow$ 可行集为空 $\Longrightarrow$ 该频道无源; - **跳测规则**:若某测点到所有幸存格心的最小距离 $>1500+m$,则在该点 测量必为 `no_signal` 且不产生任何新排除,可直接跳过,省 5 秒检测和 1 秒换频; - **PF 清除证书**:若幸存集 MEC 半径 $\rho_{\rm pf}$ 满足 $\rho_{\rm pf}+m+0.5\le20$,则以该 MEC 圆心清除必命中真源。 `BayesLoop` 在每次测量后同步更新凸候选集与 PF,PF 只在频道尚未 CLEARED/ABSENT 时接收新证据。 ### 3.3 数量上界证明 题面给定每局源总数 $10\sim16$ 且每频道至多一个源。若 `DETECTED`/`CLEARED` 的不同频道数达到 16,则所有仍未出现有效信号的 `UNKNOWN` 频道都不可能有源,可直接标记为 `ABSENT`,证据来源记为 `count_bound`。这条规则与位置无关,是严格计数证明,不需要等七站扫完。 `summary.completion_basis` 会区分 `count_cap` 与 `per_channel_proof`, 避免把“达到数量上界”误读成“逐频道缺席证明”。 ## 4. 发现保证:七点解析覆盖与覆盖账本 ### 4.1 `full_disk` 语义 全向源在测点 $s$ 返回 `no_signal`,意味着真实距离严格大于 $r_{\rm eff}\ge1000$。因此若集合 $S$ 满足 $$ \Omega\subseteq\bigcup_{s\in S}B(s,1000), $$ 则任何合法源必在某个 $s\in S$ 的 1000 m 内,在该点不可能返回 `no_signal`。于是“某频道在全部 $S$ 点都测得 `no_signal`”构成严格的 缺席证明。 `CoverageLedger` 按 `(channel, point_index)` 二维记录,只有所有点都已测 (或显式跳过)才允许 `coverage_complete(channel)=True`;“路过某点”不算 “测过某频道”。 ### 4.2 七点构造与解析界 取原点加半径 $a$ 的六等分环: $$ S(a)=\{(0,0)\}\cup\left\{a(\cos(k\pi/3),\sin(k\pi/3)):\ k=0,\dots,5\right\}. $$ **定理 1.** 当 $01000$ 时退化为探索 MEC 圆心,可能 `no_signal`)。盘内取 37 个候选点(圆心 + 3 环 × 12 方向),以“边界见证点 × 读数区间端点 + near 分支”的最坏后验 MEC 半径为主指标、移动时间为次指标;选出后再按 1/4 间距加密复核。该函数最初为问题二实现,在问题三中服务于 `SevenBatch`、`OmnidirectionalLoop` 的定位子循环,以及 `BayesLoop` 的阻塞式安全后备 `_localize_and_clear`。 ### 5.2 `bayes` 的信息增益选点 `BayesLoop._select_point` 在 PF 幸存集上选择定位测点,候选集分两种: - 凸候选集安全盘非空:取 `select_measurement_point` 的环形候选,并额外 加入以当前位置到 MEC 圆心方向为轴的对称候选,以及零移动的当前位置; - $\rho>1000$、无保证安全盘:取 PF MEC 的 $1.0/1.5/2.0$ 倍探索环和 PF MEC 圆心。 对候选 $q$,默认评分模式 `entropy` 是期望对数幸存占比: $$ \text{score}(q)= \mathbb E[\log(\text{后验幸存占比})] +\lambda\left(\frac{\lVert p-q\rVert}{5} +\frac{\lVert q-m_{\rm MEC}\rVert}{5}\right), \qquad \lambda=0.05. $$ 其中期望对代表假设(从幸存格均匀抽样)和 $r_{\rm eff}\sim U(1000,1500)$ 解析积分: - `no_signal` 分支(概率 $(d-1000)/500$):幸存格收缩到 $B(q,1000-m)$ 之外; - `direction` 分支:以代表点方位为期望读数,用 $\pm\delta_{\rm eff}$ 角域裁剪后的子采样估计幸存占比; - `near` 分支($d\le5+m$):后验为 5 m 盘,占比趋近零。 可选 `score_mode=radius` 直接最小化期望后验 MEC 半径。 `score_subsample=800`、`mc_representatives=7` 是工程参数;均匀源分布、 零误差代表读数和 $r_{\rm eff}$ 均匀分布都只是**选点评分假设**,不参与 任何安全证明。安全证明只使用题面给定的 $\pm1.005^\circ$ 角域与距离界。 当前配置中以下“更强的耦合”均已实现但实测为净负收益,默认关闭: `fuse_localize_into_tour=False`(站点顺带定位)、`exact_order_limit=0` (Held-Karp 精确任务序)、`route_aware_probe=False`(路线感知探点)。 代码与开关保留,便于复现与后续研究。 ### 5.3 清除动作 `BayesLoop._best_certificate` 的优先级: 1. **凸证书**:`clear_certificate(region)`; 2. **PF 证书**(`grid_cert_enabled=True`):负观测挖洞使 PF 幸存集更紧时, 若 PF MEC 半径 $+m+0.5\le20$,用 PF MEC 圆心作为清除点。 得到证书后,`_near_clear_target` 还会把清除点从证书圆心向当前位置方向 平移一小段:设当前证书半径 $r$、限值 $limit$,最多平移 $\Delta=\min(\text{当前距离},limit-r-10^{-6})$,新目标点仍保证所有候选 源在 $r+\Delta\le limit$ 内。该平移可省去最后一段接近路程,不放松证书。 定位子循环每频道最多 `max_localize_steps=8` 次追加测量;若在安全接收盘内 出现 `no_signal`,记录异常 `no_signal_in_safe_disk`,不静默忽略。 ## 6. 决策层:统一任务表与滚动重排 ### 6.1 任务类型 `BayesLoop._build_tasks` 在每次决策点构建: | 任务 | 位置 | 服务时间 | 触发条件 | | --- | --- | --- | --- | | `station` | 七站之一 | $5\times$待测频道数 + 换频 | 该站仍有 `UNKNOWN` 频道未测,且跳测规则未排除 | | `locate` | 信息增益选点 | 5 s + 可能的换频 | 频道 `DETECTED` 且暂无清除证书,且未委托给站点 | | `clear` | 证书中心(可平移) | 5 s 成功(失败 3 s) | 频道有清除证书且未处于“清除失败后待补测”状态 | 站点任务的服务时间计算当前频道优先:若机器人当前测向频道包含在待测集合, 则少一次换频。清除动作不改变测向频道。 ### 6.2 跳测规则 `guaranteed_no_signal(station)`:若该频道所有幸存格心到站点的距离都 大于 $1500+m$,则该次测量必为 `no_signal` 且不会产生新排除,任务构造时 直接跳过。跳测缓存按 PF `version` 生效;幸存集单调收缩,因此判定单调。 这条规则是负观测给问题三带来的主要检测节省之一。 ### 6.3 开放路径排序 任务表规模通常为 6~12 项,代价包含移动、检测、换频,终点自由。默认: 1. 最近邻构造初始序; 2. 开放路径 2-opt 改进,最多 `two_opt_passes=4` 轮; 3. 换频代价按实际频道状态沿路径逐步传递,而不是固定服务费; 4. 执行排序后的**第一个任务**,拿到响应后立即回到决策点重新构建/排序 (滚动时域)。 可选的 Held-Karp 精确 DP(含“已访问集合 × 结束时频道”状态)与 `route_aware_probe` 路线感知精修均已实现;在当前冻结基准集上, 精确 DP 与 2-opt 差距在噪声内(甚至略差),默认关闭。 ### 6.4 已停靠位置的机会测量 `_measure_at_stop` 在每次决策点评估当前位置是否值得顺带补测已发现频道: 只对尚未在该 0.5 m 点测过、无证书、到凸候选集所有顶点距离均不超过 $1000-10^{-3}$ m、且预测后验半径至少收缩 `opportunistic_radius_ratio=0.5` 的频道追加一次测量。每次仍支付 5 秒与 实际换频成本,只是省去移动;它不是“免费检测”。 ## 7. 停止、预算与异常收尾 ### 7.1 完成证明 循环停止后,`summary.completed` 的依据分两类: - `count_cap`:清除数达到 16(题面源数上界); - `per_channel_proof`:每个频道状态都是 `CLEARED` 或 `ABSENT`。 缺席证据在 `summary.absence_evidence` 中逐频道记录来源: | 证据 | 条件 | 严格性 | | --- | --- | --- | | `pf_empty` | 某频道 PF 幸存集为空 | 严格(Voronoi 真源保留) | | `coverage` | 七点/49 点全部测得 `no_signal`(或计划上显式跳过) | 严格(解析/校核覆盖) | | `count_bound` | 已有 16 个不同频道的有效存在证据 | 严格(题面数量上界) | `persistent_search` **默认 False**:一旦“每频道已清除或证明不存在”就立即 `/exit`,因为虚拟时间是成绩口径。显式 `--persistent` 会继续复扫未清除 频道直到清除数达 16、实机预算耗尽或虚拟安全余量不足;历史官方演练证明 无条件复扫会浪费大量时间,因此该项目只作为可审计的保险开关保留。 ### 7.2 预算与中断 - 虚拟安全余量 `virtual_reserve_s=10000` s:剩余虚拟时间不足以完成 一个保守单动作上界时停止发起新动作,避免触及 100 小时上限; - 现实预算以服务器 `/enter` 返回的 `remaining_real_duration_s` 为准; `DeadlineExceeded` 时 stop_reason 记为 `real_budget_exhausted`; - 传输、协议或守恒性异常都会尽力 `/exit` 并输出未完成 summary, 不静默丢会话;`completed=False` 与具体 `stop_reason` 同时留档。 ## 8. 基线与自建消融 ### 8.1 调度变体 | 版本 | 发现/定位关系 | 说明 | | --- | --- | --- | | A0 `grid` | 49 点,发现后立即离站定位 | 历史生产基线 | | A1 `SevenImmediate` | 七点,发现后立即离站定位 | 只替换覆盖点 | | A2 `SevenBatch` | 七点,本站先扫完再按最近候选圆心服务队列 | 中间对照,曾用于官方演练 | | A3 `SevenSweep` | 七点,先扫完全部必要站点再统一服务 | 批量调度的另一极端 | A2 相对 A0 的收益主要来自“减少站间往返 + 每站批量扫频 + 无源频道只需 7 次而不是 49 次检测”。在早期 12 个自建场景上,A0/A1/A2/A3 平均虚拟 时间分别为 13788.04 / 7476.09 / 5496.20 / 6094.73 秒,A2 逐局平均降幅 58.55%;更晚的 14 场景消融中,`bayes` 相对 A2 再降 30.0% (3726.2 s vs 5352.7 s)。这些数字来自自建模拟器与固定误差场,不是官方成绩。 ### 8.2 冻结基准集当前结果 `project/q3` 在 18 个冻结 core 场景(seeds 100–105 × 10/13/16 源, `harness/suites.py` 版本化清单)上的配对结果: | 方法 | 有效局 | 全清率 | 虚拟时间均值 | 中位 | 最大 | 检测均值 | 请求均值 | 位移均值 | | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | `bayes` | 18/18 | 100% | **3188.7 s** | 3111.3 s | 3663.7 s | 111.1 | 124.1 | 12338 m | | `seven` | 18/18 | 100% | 5361.7 s | 5367.5 s | 6462.4 s | 100.8 | 113.8 | 23508 m | | `grid` | 18/18 | 100% | 14935.7 s | 15275.1 s | 16691.4 s | 430.6 | 443.6 | 61550 m | 配对变化:`bayes` 相对 `grid` 虚拟时间 **−78.4%**,18/18 场景更快; 所有 54 局零缺席误判、零异常、正常退出且计时复算一致。另有边界场景 (源在 $\Omega$ 边界、$r_{\rm eff}=1000$)验证七点覆盖的扇区接缝。 ### 8.3 官方演练记录(历史口径) - 旧 49 点实现通过官方问题三演练案例 `DHFK-PHD2-SR2P-FXK9`: 10/10 全清、540 次检测、10 次清除,虚拟总时间 16217.45 s, 现实 7.629 s; - A2 七点批量通过官方案例 `J4ER-Y9EP-Q379-UPFA`:12/12 全清、 109 次检测、虚拟 5232.13 s、现实 3.406 s; - 后续一次官方问题三演练(`3BW5-9R54-NZNA-MYB9`)记录 13/13 全清、 虚拟 4058.71 s。 以上案例的源场景、实现版本、误差场均不同,不能直接横向比较,也不能把 历史官方数字当成当前 `bayes` 同案例重跑结果。当前默认 `bayes` 的官方向 成绩仍需新的演练验证。 ## 9. 复杂度、参数与保守性边界 | 模块 | 计算量(近似) | | --- | --- | | PF 更新 | 与删除格/角向扫描的幸存格数相关;粗格索引与向量化(numpy 可选)控制常数 | | 七点覆盖 | 最多 $7\times20=140$ 个 `(站, 频道)` 记录 | | 任务排序 | 最近邻 $O(n^2)$ + 2-opt $O(n^2)$/轮;精确 DP 为 $O(2^nn\cdot|C|)$,默认关闭 | | 凸候选集 | 192 边形外切 + $O(m)$ Sutherland–Hodgman 裁剪;MEC 用 Welzl | | 选点评分 | $O(N_q\times(\text{子采样}))$;候选约 37 个,子采样 800 | 保守性边界: - 所有“证毕”结论依赖题面观测语义和保守外包/内缩;若接口语义、 接收半径范围或误差界改变,必须重新校核; - 性能结论依赖自建场景分布、固定误差场和排序启发式,不构成对全部合法 场景的最坏时间保证; - PF 的均匀先验与信息增益评分是选点启发式,不是官方概率模型; - `persistent_search` 关闭时,如果官方场景违反题面数量上界或存在模型外 行为,不能由本算法保证发现;官方接口语义变化时必须记录异常而不是 强行把未完成解释为成功。 ## 10. 与问题一、二、四当前实现的复用关系和差异(核对结果) | 复用/差异 | 具体情况 | | --- | --- | | 问题一几何内核 | `belief/__init__.py` 与 `particles.py` 只从 `geometry.localization` 导入 `smallest_enclosing_circle`;在线 `clip_wedge` 复刻同一半平面公式,但工作在凸多边形裁剪流程,不调用 `solve` | | 问题二选点器 | `select_measurement_point` 直接从问题二推广而来,问题三用它做基线定位与安全后备;`safe_disk_radius` 同样复用 | | 问题一没有的 | 负观测 `no_signal`、PF 非凸挖洞、覆盖缺席证明、数量上界、任务排序、滚动时域、预算/异常治理 | | 问题二没有的 | 多频道数据关联、发现覆盖、清除动作、在线顺序决策;问题二只做一次第二点选择 | | 问题四的差异 | 问题四不能用七点 `full_disk` 缺席证明;`no_signal` 可能由朝向遮挡造成,因此改用 `forward_half_disk` 31 点覆盖 +(位置 × 朝向)联合信念 + 预算化后备清除(见 `algo-4.md`) | 一句话概括:**问题三 = 问题一的保守几何 + 问题二的保证接收选点 + 逐频道非凸负观测 + 覆盖/数量缺席证明 + 开放路径滚动调度**。 ## 11. 复现与参考 ```bash # 冻结基准集:bayes vs seven_batch(runner 内置策略名) uv run python scripts/run_suite.py --problem 3 --suite core --jobs 6 \ --variants bayes,seven_batch --label q3-core # 项目对比框架(含 grid 基线,生成 project/results/q3.{md,json}) uv run python -m project.run --question 3 # 回归测试 uv run pytest tests/test_particles.py tests/test_bayes_loop_q3.py \ tests/test_planner.py tests/test_coverage_plan.py tests/test_safety_valves.py -q ``` 主要文件: - `src/mcm26b/planner/bayes_loop.py`:`bayes` 决策层; - `src/mcm26b/planner/__init__.py`:`OmnidirectionalLoop`、问题二选点器、清除证书; - `src/mcm26b/planner/seven.py`:七点策略族与覆盖校验; - `src/mcm26b/particles.py`:网格幸存集与信息增益; - `src/mcm26b/belief/__init__.py`:凸候选集; - `src/mcm26b/coverage/__init__.py`、`src/mcm26b/guarantee/cover.py`:覆盖账本与校核; - `src/mcm26b/routing.py`:开放路径 DP/2-opt/MST 下界; - `src/mcm26b/planner/bayes_loop.py::_exact_order`:可选 Held-Karp。 延伸阅读:`docs/algo-3-bayes.md`(早期贝叶斯闭环设计与消融细节)、 `docs/第三四问架构优化与运行提速方案.md`(运行历史与瓶颈)、 `docs/第三四问图论与动态规划优化方案.md`(图论/DP 轮实测与负面结论)。 ## 参考文献 1. 全国大学生数学建模竞赛组委会. 2026 年高教社杯全国大学生数学建模竞赛 B 题[Z]. 2026. 本地题面 `issue/B题.pdf`。 2. 模拟器配套材料. 附件 1《模拟器使用说明》、附件 2《通信接口说明及编程指南》[Z]. 2026. 本地 `issue/附件/`。 3. 本项目. `docs/algo-1.md`、`docs/algo-2.md`、`docs/algo-4.md`。 4. 本项目. `docs/算法审核与优化-20260911.md`、`docs/第三四问架构优化与运行提速方案.md`、`docs/第三四问图论与动态规划优化方案.md`。