Files
mcm-26-b/docs/问题一解答.md
T

250 lines
17 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.
# B题问题一 交会定位区域的直径与圆覆盖
问题一可以转化为二维半平面交问题:每次带误差的示向度给出一个前向角域,多次测量的交集就是定位区域。对于非空有界定位多边形,直径等于最远顶点对的距离。**以这个直径为直径的圆不一定覆盖定位区域。**判断是否能覆盖,应检验最远顶点对中点到所有顶点的距离,或直接计算最小包围圆。
代码、合成算例与官方演练观测均已实现和保存。本解答只解决问题一;在官方问题3演练中采集同一频道的三个检测点,用于检验交会计算,没有执行全域搜索清除任务,未使用正式测试。
## 一、题目假设和模型边界
检测点为 $S_i=(s_{ix},s_{iy})$,测得示向度为 $\theta_i$,误差半角为 $\delta=1^\circ$。角度从正东开始逆时针计算。同一频道对应同一干扰源;位置不变时重复检测不会消除误差。
本问求附录2交会法所定义的多边形,因此主模型只取前向角域之交,不额外加入1800米圆域或1500米接收上界。这些先验可用于后续搜索定位,但加入圆盘后边界可能包含圆弧,不能仍按原问题的纯多边形处理。本程序也不以图像视窗作为真实边界。
模拟器示向度保留两位小数,正文规定误差在 $\pm 1^\circ$ 内。本次主计算使用 $\pm 1^\circ$;如需把独立舍入误差也纳入保守预算,可用 `--rounding-margin-deg 0.005` 显式追加半角,输出会记录实际误差半角。这是额外的工程裕量,不是题面规定。
## 二、从测向角域得到线性约束
设二维叉积 $[a,b]=a_xb_y-a_yb_x$,令
$$
u_i^-=(\cos(\theta_i-\delta),\sin(\theta_i-\delta)),\qquad
u_i^+=(\cos(\theta_i+\delta),\sin(\theta_i+\delta)).
$$
未知源位置 $X=(x,y)$ 必须满足
$$
[u_i^-,X-S_i]\ge0,\qquad [u_i^+,X-S_i]\le0.
$$
这两条不等式选择两条边界射线之间的前向角域,而不是无限延伸的两条直线。写为标准形式 $a_jx+b_jy\le c_j$:
$$
\begin{aligned}
\sin(\theta_i-\delta)x-\cos(\theta_i-\delta)y
&\le \sin(\theta_i-\delta)s_{ix}-\cos(\theta_i-\delta)s_{iy},\\
-\sin(\theta_i+\delta)x+\cos(\theta_i+\delta)y
&\le-\sin(\theta_i+\delta)s_{ix}+\cos(\theta_i+\delta)s_{iy}.
\end{aligned}
$$
程序另外保留 $u_i\cdot(X-S_i)\ge 0$ 的前向约束。当 $0<\delta<90^\circ$ 时它是冗余的,但可让 $\delta=0$ 的扩展情形正确表示射线,而不是整条直线。实现支持 $0\le\delta<90^\circ$,题目算例始终使用 $1^\circ$。
定位区域为
$$
P=\bigcap_{i=1}^{n}W_i=\{X:AX\le b\}.
$$
通过向量而不是简单的角度大小比较处理约束,可自然处理 $359^\circ$ 与 $0^\circ$ 跨界问题。
## 三、可复现的算法
1. 验证输入为有限数值,将检测点平移至其均值附近并按尺度归一化,减小大坐标带来的抵消误差。
2. 构造所有半平面;枚举每对不平行边界直线的交点。
3. 只保留满足全部不等式的交点;去重后用单调链算法求凸包,并保留点、线段等退化结果。
4. 判断有无非零方向 $d$ 满足 $Ad\le 0$。若存在,任一可行点沿该方向无限前进仍可行,区域无界,直径为无穷大。
5. 对有界非空区域,枚举凸包顶点对,得到直径及一对最远顶点。
6. 用任意一组最远顶点对中点与半径 $D/2$ 检验圆覆盖;失败时返回圆外顶点及最大超出距离。用 Welzl 随机增量法求最小包围圆,输出 20 米清除半径余量,以区分“直径圆覆盖”和“保证清除”。
非空前向角域的交集不会包含一条双向无限直线,即它是尖的多面集。这保证非空区域存在极点,所以在本模型中可以通过是否存在可行边界交点来判空。无观测时需单独返回整个平面无界,不能依据“没有顶点”误判为空集。
二维非平凡衰退锥若存在,至少有一条边界方向与某个约束边界平行。因此检查每条边界的两个切向方向是否满足 $Ad\le 0$,即可判定无界性。无界情形中输出的有限顶点仅是区域的极点,不构成完整有限多边形。
伪代码如下:
```text
输入同一频道的测量列表 M
若 M 为空:返回无界
H ← 将每次测量转换为前向角域的半平面约束
V ← 所有边界对交点中满足 H 的点
若 V 为空:返回空集
若存在非零 d 满足 H 的齐次约束 A d <= 0:返回无界
V ← 去重并排序为凸包
(D, A, B) ← V 中的最远顶点对
O ← (A+B)/2
覆盖成立 <- max_{v in V} ||v-O|| <= D/2
返回 V、D、A、B、O、覆盖判断和最小包围圆
```
设半平面数为 $k$(本实现中 $k=3n$),最终顶点数为 $m$。枚举交点并检验全部约束需要 $O(k^3)$,直径计算需要 $O(m^2)$,无界性检查需要 $O(k^2)$。可行交点排序的代价上界为 $O(k^2\log k)$,临时交点存储上界为 $O(k^2)$。新增直径圆证据只需 $O(m)$ 遍历。
最小包围圆改为 Welzl 随机增量算法:先打乱点顺序;已有圆包含新点则保留,否则强制新点成为边界点并解更小子问题;二维最多需要 3 个支撑点。3 个强制边界点必须用外接圆,不能误用这 3 点的无约束最小圆。实现以显式栈代替递归,防止大量点超过 Python 递归深度。固定随机种子使输出可复现,理论期望线性指随机排列下的期望,不是该固定种子的最坏复杂度保证。对外函数内部也做坐标归一化,最后重新计算所有点到输出圆心的最大距离,保持数值包含性。旧 $O(m^4)$ 支撑点枚举保留为独立测试基准。
半平面交与直径仍采用容易审查的小规模算法,未为少量测点引入更复杂的旋转卡壳。大规模在线数据可后续替换半平面交,不能把MEC局部提速等同于整个求解器线性复杂度。
## 四、为什么只枚举顶点对就能得到直径
有界凸多边形内任意两点可表示为顶点的凸组合:
$$
x=\sum_i\lambda_i v_i,\quad y=\sum_j\mu_jv_j,\quad
\lambda_i,\mu_j\ge0,\quad\sum_i\lambda_i=\sum_j\mu_j=1.
$$
由三角不等式,
$$
\lVert x-y\rVert=\left\lVert\sum_{i,j}\lambda_i\mu_j(v_i-v_j)\right\rVert
\le\sum_{i,j}\lambda_i\mu_j\lVert v_i-v_j\rVert
\le\max_{i,j}\lVert v_i-v_j\rVert.
$$
最远顶点对本身又属于区域,因此上界可以达到:
$$
\boxed{D(P)=\max_{i,j}\lVert v_i-v_j\rVert.}
$$
点的直径为0,线段的直径为端点距离。对空集,本程序约定直径未定义,输出null;对无界区域,输出 `diameter_infinite=true`,不把Infinity写入不兼容标准的JSON数值。
## 五、直径圆能否覆盖的充分必要条件
取任意最远顶点对 $A,B$,距离为 $D$,令 $O=(A+B)/2$。如果半径为 $D/2$ 的某个圆能够同时包含 $A$ 和 $B$,则
$$
D=\lVert A-B\rVert\le\lVert A-O'\rVert+\lVert O'-B\rVert\le D.
$$
两个不等式只能同时取等号,因此其圆心只能是O。又因为圆盘是凸集,包含全部顶点等价于包含整个多边形。所以
$$
\boxed{\text{存在直径为 }D\text{ 的覆盖圆}\iff
\max_{v\in V}\lVert v-(A+B)/2\rVert\le D/2.}
$$
不必尝试移动直径圆,也不必尝试其他最远点对:若这个唯一可能的圆心不满足覆盖,就不存在直径为 $D$ 的覆盖圆。最小包围圆半径 $\rho$ 满足 $\rho\ge D/2$,当且仅当 $\rho=D/2$ 时存在这样的覆盖圆。
代码的 `diameter_circle.max_excess_m` 为 $\max_{v\in V}\lVert v-O\rVert-D/2$;超出数值容差时返回 `witness_vertex` 和从 0 开始的 `outside_vertex_indices`。因此输出既有判断,也有可独立复核的反例。判定中使用 `coverage_tolerance_m`,边界附近的布尔结果属于浮点容差下结论。
保证清除使用 MEC 半径加数值余量 $\varepsilon_m$,再与 20 米比较。`clearance_certificate.remaining_margin_m` 对应 $20-(\rho+\varepsilon_m)$;非负时支持当前圆心清除,负数时不作此保证。这里的证书以观测误差界正确、真源属于 $P$ 和浮点几何计算可靠为前提,不是实际清除成功记录。数学上所有可行清除中心构成 $\bigcap_{v\in V}B(v,20)$,MEC 圆心是充分选择;后续可在此集合中另选距离机器狗最近的点。
## 六、满足题设 $\pm 1^\circ$ 的具体反例
取三个检测点及示向度:
| 检测点 | x(米) | y(米) | 示向度 |
| --- | ---: | ---: | ---: |
| $S_1$ | $-1000$ | $0$ | $1^\circ$ |
| $S_2$ | $540$ | $-500\sqrt{3}$ | $121^\circ$ |
| $S_3$ | $520$ | $520\sqrt{3}$ | $241^\circ$ |
三个角域的交集恰为等边三角形,顶点是 $(0,0)$、$(40,0)$、$(20,20\sqrt{3})$。每个角域的一条边界对应三角形的一条边,另一条边界在三角形外侧,不切去三角形内部。可将真实源设在三角形中心 $(20,20\sqrt{3}/3)$,其真实方位与上述读数的偏差均小于 $1^\circ$,且三个检测距离都小于 1500 米。因此这不是脱离测向模型的任意图形反例。
它的直径为 $D=40$ 米;直径圆半径为 20 米。以底边两端为最远点对,圆心为 $(20,0)$,到第三个顶点的距离是 $20\sqrt{3}\approx 34.6410$ 米,因此不能覆盖。其最小包围圆为
$$
O_*=(20,20\sqrt{3}/3),\qquad \rho_*=40/\sqrt{3}\approx 23.0940\,\text{米}>20\,\text{米}.
$$
![等边三角形定位区域反例](../output/q1/figures/counterexample-736-light.png)
图中蓝色为定位区域,红色虚线为直径圆,绿色实线为最小包围圆。黑色加号只在合成场景中表示已知真源。
## 七、官方演练数据验证
2026年9月10日,界面明确为“问题3 演练 测试”后,通过公开HTTP接口采集。案例编码为 `U5ZW-P9KN-TCAG-R7ST`,选取频道9。
| 检测点 | x(米) | y(米) | 示向度 |
| --- | ---: | ---: | ---: |
| $S_1$ | $0$ | $0$ | $131.52^\circ$ |
| $S_2$ | $-613.761885$ | $391.530775$ | $34.92^\circ$ |
| $S_3$ | $-314.272134$ | $656.683353$ | $227.19^\circ$ |
重复在 $S_1$ 测量一次仍返回 $131.52^\circ$。这次重复观测只用于核查原地误差固定,没有作为新的独立测点加入模型。
使用 $\pm 1^\circ$ 得到四边形,其顶点按逆时针顺序为:
| 顶点 | x(米) | y(米) |
| --- | ---: | ---: |
| V1 | −460.698306 | 502.411776 |
| V2 | −456.424236 | 497.750714 |
| V3 | −440.569893 | 515.476604 |
| V4 | −441.370356 | 516.413163 |
计算结果:D=23.996723米,最远点对为V1和V3;直径圆中心为(−450.634100,508.944190),半径11.998362米,但最远顶点到这个圆心的距离为12.602364米,所以该圆不能覆盖区域。最小包围圆中心为(−449.976955,507.952835),半径12.068583米。
![官方演练测向所得定位区域](../output/q1/figures/practice-736-light.png)
共发送14个被接受的请求:1次进入、12次检测、1次退出,最终虚拟时间293.602198秒。日志末尾为 `/exit`、`accepted=true`、`exit_reason=user_exit`;界面显示测试正常结束。没有调用 `/clear`,所以这里不能将演练结束解读为“清除了所有源”。官方没有公开该源的坐标真值,本例验证的是观测交会计算、接口适配和几何自洽性,不声称验证了真实位置误差。
原始请求响应保存在 `output/q1/practice/*_actions.json`,保留了复算所需的请求ID、位置、频道与时间;三点观测及其计算结果保存在 `*_observations.json`。没有读取或解密模拟器隐藏场景数据。
## 八、运行方式和代码入口
核心算法仅用Python标准库,支持Python3.10及以上;工程依赖仅 `requests`(测试另需 `pytest`)。在项目根目录运行:
```powershell
uv run python -m mcm26b.geometry examples/q1/triangle.json -o output/q1/triangle_recomputed.json
uv run mcm26b q1 examples/q1/normal.json
uv run python -m mcm26b.geometry examples/q1/normal.json --rounding-margin-deg 0.005
uv run pytest
uv run python scripts/build_q1_demo.py
uv run python scripts/benchmark_q1.py
```
输入JSON的最小格式:
```json
{
"measurements": [
{"x": 0, "y": 0, "bearing_deg": 131.52, "half_angle_deg": 1},
{"x": -613.7618853216438, "y": 391.53077545248124, "bearing_deg": 34.92},
{"x": -314.2721344680094, "y": 656.6833525352392, "bearing_deg": 227.19}
]
}
```
省略 `half_angle_deg` 时默认为1。输出包含区域状态、顶点、直径、最远点对、直径圆覆盖判断、反例证据、最小包围圆和清除余量。CLI的 `error_policy` 记录追加裕量与每条观测最终半角;默认追加0,不改变输入口径。`tolerance_m` 是归一化容差换算回米的尺度;核心算法不接收真源坐标,样例中的 `target` 只用于合成数据的可视化与测试。空集和无界区域不输出有限圆及清除证书。
| 文件 | 职责 |
| --- | --- |
| `src/mcm26b/geometry/localization.py` | 测向角域、半平面、区域分类、直径和包围圆 |
| `src/mcm26b/geometry/planning.py` | 问题二首次测向的距离保证候选圆盘;独立于问题一模型 |
| `src/mcm26b/geometry/__main__.py`、`src/mcm26b/cli.py` | JSON命令行输入输出 |
| `tests/test_geometry.py` | 解析反例、随机几何、独立裁剪对照与退化测试 |
| `tests/test_practice_replay.py` | 离线复算演练计时与观测结果,不连接模拟器 |
| `tests/test_geometry_certificates.py` | 增量圆与穷举法对照、覆盖证据、清除阈值、舍入选项及首次选点证书 |
| `scripts/benchmark_q1.py` | 局部性能对比与官方演练误差界敏感性复算 |
| `scripts/capture_practice.py` | 在已经启动且已核验的演练中采集少量测向 |
| `scripts/build_q1_demo.py` | 用Python算法生成案例、结果和交互图数据 |
| `scripts/assets/q1.template.html` | 可视化源模板;不另写一套求解算法 |
| `output/q1/question1.html` | 可直接打开的离线交互式模拟,所有数据已内嵌 |
如需再次采集,先在模拟器明确启动“问题3演练测试”,等待接口就绪,读取案例编码,然后运行:
```powershell
uv run python scripts/capture_practice.py --robot-id 你的参赛队号 --practice-confirmed --case-code 界面中的演练案例编码
```
接口协议没有返回演练/正式模式的字段,`--practice-confirmed` 是操作者确认已核验界面的显式参数,不能替代界面核查。采集脚本没有启动测试的功能,没有正式模式选项,只会调用 `/enter`、`/measure`、`/exit`。禁止在正式测试会话中运行。
## 九、验证范围和数值限制
20项自动化测试通过,包括100组随机有界案例、60组与独立矩形裁剪实现的对照、250组增量圆与穷举圆对照、1500个共圆点及重复点/共线点、平移旋转缩放不变性,以及已保存演练日志的逐动作计时复算。新增测试还覆盖清除距离20米临界值、显式舍入选项、首次安全候选圆盘边界和零半径情形。已测试一组有限但直径超过10亿米的近乎平行测向,确认不会因为显示范围而被截断成小区域。
本次离线优化报告保存在 `output/q1/optimization_report.json`。同一演练观测的敏感性结果如下,原始记录不改写:
| 误差半角 | 直径 / 米 | MEC半径 / 米 | 直径圆最大超出 / 米 | 含数值裕量的清除余量 / 米 |
| --- | ---: | ---: | ---: | ---: |
| $1^\circ$(默认题设) | 23.996723 | 12.068583 | 0.604002 | 7.931415 |
| $1.005^\circ$(额外舍入) | 24.119088 | 12.130377 | 0.608486 | 7.869621 |
在本机60个共圆点的局部基准中,旧MEC枚举中位约306.5毫秒,新算法约0.096毫秒;4点时旧法约0.022毫秒、新法约0.029毫秒,小规模没有提速优势。这些是特定输入的局部实测,不是普遍倍率保证,也不代表网络、全清闭环或官方成绩。三点演练的完整问题一求解中位约0.099毫秒。运行基准脚本可以重新生成当前机器的报告。
浮点计算不是精确算术。实现使用归一化位置容差 $10^{-9}$、平行行列式阈值 $10^{-12}$ 和较严格的衰退方向角度容差。极端近乎平行、差异接近机器精度或接近退化的输入可能需要高精度几何谓词;结果接近临界阈值时应做容差敏感性检查,而不把最后若干小数位当作严格证明。正常题设坐标和测向精度下,本次案例远离这些数值极限。
交互图数据全部由同一Python求解器生成。已在最小DOM测试环境中检查32个“案例/测点数量/视图”组合,未发现缺失控件或NaN等非法图形坐标;并对360与736像素、明暗两种配色的SVG作静态图形检查。浏览器工具对本地文件URL的访问被策略拦截,因此没有声称完成真实浏览器的HTML/CSS布局验证。离线页面和对话内可视化均已生成;静态PNG可作为直接查看的补充。
几何算法参考:[CGAL最小包围圆定义与Welzl增量方法](https://doc.cgal.org/latest/Bounding_volumes/classCGAL_1_1Min__circle__2.html);[McGill旋转卡壳说明](https://www-cgrl.cs.mcgill.ca/~godfried/research/calipers.html)。本次代码仅用Python标准库,不依赖CGAL。