Files
mcm-26-b/docs/algo-3.md
T
2026-09-13 08:29:30 +08:00

23 KiB
Raw Blame History

问题三算法说明:全向多源搜索的覆盖证明、贝叶斯闭环与任务调度(当前实现)

本文汇总当前仓库中问题三的全部实际算法,生产默认策略为 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. 当 0<a/\sqrt3\le R 时,


\max_{g\in\Omega}\min_{s\in S(a)}\lVert g-s\rVert
\le D(a):=
\max\left\{\frac a{\sqrt3},\ \sqrt{R^2+a^2-\sqrt3Ra}\right\},

其中 $R=1800$。证明:任意 g=(r\cos\varphi,r\sin\varphi) 与最近环站的 夹角不超过 $\pi/6$。若 $r\le a/\sqrt3$,选原点;否则选最近环站,由 余弦定理和 \cos\varphi\ge\cos(\pi/6)=\sqrt3/2 得 $\lVert g-s\rVert^2\le r^2+a^2-\sqrt3ar$。右端关于 r 是凸二次函数, 在区间端点取最大值,分别得 a^2/3 和 $R^2+a^2-\sqrt3Ra$。证毕。

代入可验证:a=1200 时 D=968.901572 m;当前 bayes 默认 station_ring_radius_m=1159.7 m 时 D=984.539136 m;两者都小于 1000, 从站上有 15.5 m 以上的几何余量。a 的可行下界为 a_{\min}\approx1122.955832 m(零余量)或 1125.254906 m(1 m 余量)。 七点巡回长度 $6a$;a=1200 时纯覆盖路线约 7200 m。

4.3 49 点基线

网格 S=\{(700i,700j):i,j\in[-3,3]\} 覆盖 $[-2100,2100]^2\supseteq\Omega$; 每个单元的对角线 $700\sqrt2\approx989.95\le1000$,因此任意源都在某个 网格点的 1000 m 内。该证明不需要朝向信息,对问题三的七点方案是更强的 充分条件,但会发现阶段检测次数多一个数量级(频道 × 49 次);问题四仍 需要它作为 legacy 对照(见 algo-4.md)。

4.4 计划可验证性

guarantee/cover.py 提供两套校核:

  • verify_full_disk(points):在 \Omega 内按网格采样,校核 “每个采样位置都在某个点的 1000 m 内”;
  • verify_forward_half_disk(...):第四问语义,问题三不使用。

七点计划走解析证明;49 点计划走 full_disk 校核。运行期不使用未校核的 点集来宣称“已证明无源”。

5. 定位与清除

5.1 问题二选点器的推广(基线与安全后备)

planner.select_measurement_point 对任意 CandidateRegion 工作: 给定当前后验外包多边形 $P$,令其 MEC 为 $B(m,\rho)$,则安全接收盘为


B\left(m,\ 1000-\rho-10^{-3}\right)

(\rho>1000 时退化为探索 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
凸候选集 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. 复现与参考

# 冻结基准集: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。