Merge branch 'q3-bayes-no-ml' of git.simba-robotics.com:HelixCopex/mcm-26-b into q3-bayes-no-ml

This commit is contained in:
2026-09-12 17:21:45 +08:00
36 changed files with 8767 additions and 0 deletions
+4
View File
@@ -9,6 +9,8 @@
- [问题三搜索与总耗时改进方案(含七点覆盖证明和自建消融)](docs/algo-3.md) - [问题三搜索与总耗时改进方案(含七点覆盖证明和自建消融)](docs/algo-3.md)
- [第三、四问系统性架构优化与运行提速方案(运行历史复核、最小命中集、联合信念)](docs/第三四问架构优化与运行提速方案.md) - [第三、四问系统性架构优化与运行提速方案(运行历史复核、最小命中集、联合信念)](docs/第三四问架构优化与运行提速方案.md)
- [第三、四问图论与动态规划优化方案(定点实测、路由 gap、定向定位 DP、证书硬化)](docs/第三四问图论与动态规划优化方案.md) - [第三、四问图论与动态规划优化方案(定点实测、路由 gap、定向定位 DP、证书硬化)](docs/第三四问图论与动态规划优化方案.md)
- [1-4 问最优与基线实现对比框架](project/README.md)(`uv run python -m project.run`,结果在 `project/results/`)
- [最优与基线实现对比(论文口径说明)](docs/最优与基线实现对比.md)
- [论文](docs/paper/main.tex) - [论文](docs/paper/main.tex)
## 项目结构 ## 项目结构
@@ -27,6 +29,8 @@ src/mcm26b/
├── evaluation/ # 日志复算与统计(已完工) ├── evaluation/ # 日志复算与统计(已完工)
└── harness/ # 测试侧适配:评测机<->Transport 内存回环、故障注入、回放、场景 └── harness/ # 测试侧适配:评测机<->Transport 内存回环、故障注入、回放、场景
tests/ # pytest(含解耦守卫:运行时零导入 referee + 规则表不漂移) tests/ # pytest(含解耦守卫:运行时零导入 referee + 规则表不漂移)
project/ # 1-4 问"最优实现 / 基线实现"对比框架:逐问 optimal+baseline
# +独立参考,冻结基准集配对出数;见 project/README.md
scripts/ # 采集、基准、可视化与校核脚本(含问题四批量仿真 benchmark_q4.py、 scripts/ # 采集、基准、可视化与校核脚本(含问题四批量仿真 benchmark_q4.py、
# 运行历史计时分解 analyze_run_timing.py、第四问覆盖证书 verify_q4_cover.py) # 运行历史计时分解 analyze_run_timing.py、第四问覆盖证书 verify_q4_cover.py)
output/ examples/ # 演练证据、算例与生成产物 output/ examples/ # 演练证据、算例与生成产物
+131
View File
@@ -0,0 +1,131 @@
# 问题一至四:最优实现与基线实现对比
本文件是四问"最优实现 / 基线实现"对比的**论文口径说明**。代码与可复现
产物在仓库根目录 `project/`,运行 `uv run python -m project.run` 一次生成
`project/results/summary.md` 与逐问 `q1.md … q4.md`。本文件只摘录设计与结论,
数字以后者为唯一来源。
## 1. 交付物与目录
```text
project/
├── README.md 设计原则、各问对比设计、口径与边界
├── common.py 方法描述 / 指标聚合 / 落盘 / 闭环装配
├── run.py 统一入口(--question/--quick)
├── results/ 自动生成:q1.md/json … q4.md/json + summary.md
├── q1/ optimal.py baseline.py reference.py cases.py compare.py
├── q2/ optimal.py baseline.py reference.py region.py cases.py compare.py
├── q3/ optimal.py baseline.py compare.py
└── q4/ optimal.py baseline.py compare.py
```
角色边界:
- **最优实现**:直接适配 `src/mcm26b` 中已由 pytest 固化的生产实现,
不重写算法;
- **基线实现**:同题下更朴素、但能独立跑通的对照,不共享最优实现的关键步骤;
- **独立参考**:与分析算法正交的数值/枚举标准,用于判定谁对谁错。
第三、四问统一走 `LocalSimulator` 内存回环(与官方 HTTP 协议同一边界),
真值只在评测机侧;有效性判定沿用项目口径:
**全清 ∧ 无缺席误判 ∧ 正常 `/exit` ∧ 零异常 ∧ 逐微秒计时复算一致**。
## 2. 四问方法设计对照
| 问 | 最优实现 | 基线实现 | 独立参考 | 主要指标 |
| --- | --- | --- | --- | --- |
| 1 | 半平面交 + 顶点对精确直径 + Welzl MEC + 覆盖反例 | 网格采样 + **包围盒对角线**代理直径/覆盖 | 大框半平面裁剪 + 暴力直径/MEC | 状态、直径误差、覆盖准确率、耗时 |
| 2 | 保证接收盘内最小最坏后验半径(生产选点器) | ① 垂直 1000 m 最大交会角;② 候选集 MEC 圆心 | 保证接收盘内 9×9 网格枚举 | 保证接收率、最坏后验半径、移动时间 |
| 3 | `bayes`:粒子滤波 + 信息增益 + 滚动 2-opt | `grid`:49 点网格 + 就近服务;`seven`:七点批量 | 冻结基准集 + 真值全清校验 | 全清率、虚拟时间及其分布 |
| 4 | `directional`:31 点定向覆盖 + 联合信念 + 后备清除 | 问题三策略直接搬用(`bayes` / `grid`) | 冻结基准集(core+boundary) | 全清率、**缺席误判**、虚拟时间 |
## 3. 对比结果
### 3.1 问题一(40 个随机交会算例)
| 指标 | 最优 | 基线 |
| --- | --- | --- |
| 状态判定准确率 | 100% | 100% |
| 直径平均绝对误差 | **0.000 m** | 4.583 m(最大 14.60 m) |
| 直径圆覆盖判定准确率 | **100%** | 52.5% |
| 平均耗时 | **0.11 ms** | 22.35 ms |
固定示例进一步给出结构性反例(`examples/q1`,解析期望由
`tests/test_geometry*.py` 固化):
- `normal`:直径 26.99 m,**直径圆不覆盖**定位区域(最优判否、基线判是);
- `triangle`:直径 40.00 m 的等边三角形反例,最小包围圆半径 23.09 m > 20 m,
直径圆不覆盖;基线因"包围盒中心 + 对角线"恒判覆盖;
- `poor`:近共线检测点 → 无界,最优/基线/参考一致判无界;
- `segment`:零误差退化线段,最优判线段 D=100 m 且覆盖成立,基线退化为点;
- `empty`:观测不相容 → 空集。
> 基线的两处结构性缺陷:直径用包围盒对角线在斜/细长区域系统性高估;
> 覆盖用包围盒中心圆则恒判"能覆盖",恰好丢掉问题一要考的反例。
### 3.2 问题二(30 个随机首次示向度,初始 ρ 均值 681.5 m)
| 方法 | 保证接收率 | 最坏后验均值 | 终值比 ρ'/ρ | 移动均值 |
| --- | --- | --- | --- | --- |
| 最优(保证盘内最小最坏后验) | 100% | **73.89 m** | 0.104 | 170.4 s |
| 基线① 垂直 1000 m 最大交会角 | **0%** | 80.11 m | 0.116 | 200.0 s |
| 基线② MEC 圆心 | 100% | 340.75 m | 0.500 | 136.3 s |
| 参考(保证盘内网格枚举) | 100% | 75.63 m | 0.106 | 170.3 s |
> 基线①说明"只追交会角"会在本题失败:首次角域可延伸到 1500 m,
> 真源在远端时第二点距离超过 1000 m,测量直接 `no_signal`。
> 基线②保证接收但后验几乎不收缩(≈0.5ρ)。最优在同等保证下把后验压到 0.10ρ,
> 且与网格参考几乎重合(生产采样质量足够)。
### 3.3 问题三(18 个冻结 core 场景,全向 10/13/16 源)
| 方法 | 有效局 | 全清率 | 虚拟时间均值 | 中位 | 检测均值 | 请求均值 | 位移均值 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 最优 `bayes` | 18/18 | 100% | **3188.7 s** | 3111.3 s | 111.1 | 124.1 | 12338 m |
| 中间 `seven` | 18/18 | 100% | 5361.7 s | 5367.5 s | 100.8 | 113.8 | 23508 m |
| 基线 `grid` | 18/18 | 100% | 14935.7 s | 15275.1 s | 430.6 | 443.6 | 61550 m |
配对变化:`bayes` 相对 `grid` 虚拟时间 **−78.4%**(18/18 场景更快),
`seven` 相对 `grid` −63.7%。全部 54 局零异常、计时复算一致。
### 3.4 问题四(11 个冻结 core+boundary 场景,含朝外发射的边界定向源)
| 方法 | 有效局 | 全清率 | 缺席误判 | 虚拟时间均值 | 后备清除均值 |
| --- | --- | --- | --- | --- | --- |
| 最优 `directional` | 11/11 | **100%** | **0** | 9069.9 s | 24.8 |
| 基线 `grid`(49 点全向) | 0/11 | 67% | 0 | 18179.2 s | 0 |
| 基线 `bayes`(七站全向缺席证明) | 0/11 | 77% | **24** | 3890.8 s | 0 |
> 两条搬用基线的失效机理不同:
> `bayes` 用七站环的**全向**缺席证明,遇到背离圆心发射的边界定向源时
> 会把真源直接判成 ABSENT(累计 24 次 **缺席误判**,属"快而错",其虚拟时间
> 不可与最优直接比较);`grid` 的 49 点覆盖对定向源发现是完备的,但缺少
> 定向观测推理与 ≤20 m 后备清除,定位停滞只能停在 DETECTED("稳而不全")。
> 最优 `directional` 两种工况都是 11/11 全清、零误判,代价是多花时间与
> 平均 24.8 次后备清除。
## 4. 结论
1. 问题一:生产几何内核对状态、直径、覆盖三项与独立参考**逐案一致**;
朴素包围盒基线在覆盖判定上只有约 52% 准确率,且对固定反例全错。
2. 问题二:只有在"保证接收"约束内优化后验才既安全又有效;最优相对保守
基线把最坏后验半径从 0.5ρ 降到 0.10ρ,且与网格参考几乎重合。
3. 问题三:`bayes` 在相同全清率下把虚拟时间降到 `grid` 的约 1/5,
且逐场景配对 18/18 更快,说明"负观测感知 + 信息增益选点 + 全局排序"
三步都产生净收益。
4. 问题四:定向源的难点不是"发现"而是"证明缺席"与"失去信号后的清除";
直接搬用第三问策略会得到 24 次缺席误判,不可接受。`directional` 的
联合信念 + 后备清除是 11/11 全清的关键。
写论文时建议按"基线为什么错 → 最优补了哪一步 → 独立参考如何判定"的三段式
展开,并声明:随机分布是测试假设;基线时间快于最优的场景(如问题四 `bayes`)
不构成有效性优势,因为该局基线未完成全清任务。
## 5. 复现
```bash
uv run python -m project.run # 四问全部(约 3–4 分钟)
uv run python -m project.run --question 1 # 只跑问题一
uv run python -m project.run --quick # 缩小算例/基准集规模
uv run pytest tests/test_geometry.py tests/test_geometry_certificates.py -q # 几何回归
```
+124
View File
@@ -0,0 +1,124 @@
# 1-4 问「最优实现 / 基线实现」对比框架
本目录**不是**比赛运行时代码,而是把四问的生产实现与独立基线放在同一口径下
配对对比、生成可写进论文的"算法对比"证据。运行时代码仍只有 `src/mcm26b`,
`project/` 只依赖它和测试侧内存回环,不反向污染求解入口。
## 1. 设计原则
| 角色 | 含义 | 约束 |
| --- | --- | --- |
| **最优实现** | 复用 `src/mcm26b` 中已由测试固化的生产实现 | 不复制算法,只做 I/O 适配 |
| **基线实现** | 同题下更朴素、但能独立跑通的对照 | 不得共享最优实现的关键步骤 |
| **独立参考** | 与分析算法正交的数值/枚举标准 | 用于判定谁对谁错,而非自证 |
- 第三、四问走 `LocalSimulator` 内存回环,与官方 HTTP 协议同一边界;
真值只在评测机侧,策略端只看响应,因此"全清率/缺席误判"是可信的。
- 闭环有效性判定沿用项目口径:**全清 ∧ 无缺席误判 ∧ 正常 `/exit` ∧ 零异常
∧ 逐微秒计时复算一致**,任何一项不满足该局记为无效,失败不静默丢样本。
- 所有对比都按 `scene_id` 配对,汇总同时给均值、中位、最大与相对基线的
配对变化,避免"均值好看但逐局更差"。
## 2. 目录结构与职责
```text
project/
├── README.md 本文件
├── common.py 公共层:方法描述、指标聚合、结果落盘、闭环装配
├── run.py 统一入口:一次跑完四问并生成 results/
├── results/ 自动生成:q1.md/json … q4.md/json + summary.md
├── q1/ 问题一:交会定位区域直径与直径圆覆盖
│ ├── optimal.py 最优:半平面交 + 顶点对精确直径 + Welzl MEC + 覆盖反例
│ ├── baseline.py 基线:网格采样 + 轴对齐包围盒对角线代理
│ ├── reference.py 参考:大框 Sutherland–Hodgman 半平面裁剪 + 精确直径/MEC
│ ├── cases.py 固定示例(examples/q1)+ 随机交会算例
│ └── compare.py 同案配对:状态/直径/覆盖/耗时
├── q2/ 问题二:第二检测点选择
│ ├── optimal.py 最优:保证接收盘内最小最坏后验半径(生产选点器)
│ ├── baseline.py 基线:垂直 1000 m 最大交会角 / 候选集 MEC 圆心
│ ├── reference.py 参考:保证接收盘内稠密网格枚举
│ ├── region.py K1 = Ω ∩ 角域 ∩ B(s,1500) 统一构造
│ ├── cases.py 随机首次示向度算例
│ └── compare.py 统一评估器:保证接收、最坏后验、移动代价
├── q3/ 问题三:全向源闭环
│ ├── optimal.py 最优:bayes(粒子滤波 + 信息增益 + 滚动 2-opt)
│ ├── baseline.py 基线:grid(49 点网格)/ seven(七点批量)
│ └── compare.py 冻结基准集 core 配对对比
└── q4/ 问题四:含定向源闭环
├── optimal.py 最优:directional(联合信念 + 后备清除 + 定向覆盖计划)
├── baseline.py 基线:把问题三策略直接搬用(bayes / grid)
└── compare.py 冻结基准集 core+boundary 配对对比
```
## 3. 各问对比设计
### 问题一(`q1/`)
- **最优**:`mcm26b.geometry.localization.solve`,半平面交求凸多边形,顶点对
枚举直径,Welzl 最小包围圆,并输出直径圆覆盖判断、圆外顶点与最大超出距离。
- **基线**:固定大框网格采样,直径取**包围盒对角线**、覆盖取"包围盒中心 +
代理直径圆"。它在斜/细长区域上系统性高估直径,并对任意包围盒都几乎恒判覆盖。
- **参考**:从 ±1e7 m 大框出发逐半平面 Sutherland–Hodgman 裁剪,独立实现
直径暴力枚举与 1/2/3 点支撑圆暴力最小包围圆。与最优实现共享 0 个内部步骤。
- 指标:状态判定、直径绝对误差、直径圆覆盖准确率、运行耗时。
### 问题二(`q2/`)
- 候选集 `K1 = Ω ∩ W(s,θ±δ_eff) ∩ B(s,1500)`,三个方法共用同一次构造。
- **最优**:保证接收盘 `B(MEC 圆心, 1000−ρ)` 内环形采样,最小化"目标边界点 ×
读数区间端点"的最坏后验半径,移动时间同分决胜;保证接收严格,采样是启发式。
- **基线**:① 垂直首次示向度 1000 m 的"90° 交会点"——忽略接收保证,真源在
角域远端时必然 `no_signal`;② 候选集 MEC 圆心——保证接收但交会角可能最差。
- **参考**:保证接收盘内 9×9 网格枚举同一目标函数,检验生产采样接近网格最优。
- 指标:保证接收率、最坏后验包围半径、收缩量、移动时间。
### 问题三(`q3/`)
- **最优** `bayes`:网格粒子滤波幸存集 + 期望信息增益选点 + 滚动 2-opt 任务排序。
- **基线** `grid`:49 点(700 m 步长)网格发现 + 就近服务;`seven`:七点解析
覆盖 + 同站批量扫频,作为"覆盖证明 + 批量调度"的中间对照。
- 冻结基准集 `harness.suites`(种子/源数版本化),逐局校验全清与计时复算。
- 指标:全清率、均值/中位/最大虚拟时间、检测数、请求数、位移、墙钟。
### 问题四(`q4/`)
- **最优** `directional`:31 点定向覆盖计划 + 朝向遮挡推理 + (位置×朝向) 联合
信念 + ≤20 m 有限圆盘后备清除。
- **基线**:把问题三策略原样搬用——`bayes` 的七站全向缺席证明会把朝外定向源
误判为 ABSENT("快而错");`grid` 的 49 点覆盖对定向源发现完备,但无后备清除,
定位停滞只能停在 DETECTED("稳而不全")。
- 冻结基准集 `core+boundary`,其中 `boundary` 场景全部为背离圆心发射的边界定向
源,是最不利工况。
- 指标:全清率、缺席误判数、虚拟时间、后备清除次数、墙钟。
## 4. 运行方式
```bash
uv run python -m project.run # 四问全部,写入 project/results/
uv run python -m project.run --question 1 # 只跑问题一
uv run python -m project.run --quick # 缩小随机算例/基准集规模
```
> `project/` 用到 `numpy`(网格参考解)。它随 dev 依赖组(optuna)安装;
> 只跑正式求解入口 `mcm26b` 不需要它。
## 5. 结果文件
| 文件 | 内容 |
| --- | --- |
| `results/q1.md` / `q1.json` | 固定示例 + 随机算例的逐案明细与汇总 |
| `results/q2.md` / `q2.json` | 逐案第二点位置、保证接收、最坏后验与汇总 |
| `results/q3.md` / `q3.json` | 冻结基准集逐场景明细、逐方法汇总、配对变化 |
| `results/q4.md` / `q4.json` | core+boundary 逐场景明细与汇总 |
| `results/summary.md` | 四问核心指标总览 + 方法映射 + 目录结构 |
## 6. 口径与边界(写论文时必须一并声明)
- 问题一参考解与最优实现都是精确算法;固定示例的解析期望另有
`tests/test_geometry*.py` 固化,本框架在其上补齐"朴素基线错在哪"。
- 问题二"最坏后验半径"是**对候选集外包多边形的启发式评估**,不是连续问题
的全局最优证明;生产实现与参考网格共享该目标函数,只比较搜索质量。
- 问题三、四的随机分布(源位置均匀、误差场逐点固定等)是**测试假设**,
题面未规定;正式成绩以官方模拟器演练/正式测试为准。
- 基线不是稻草人:`q2` 的 MEC 圆心、`q3` 的 `seven`、`q4` 的 `grid` 都是
能独立跑通、在部分场景有效的真实方法;它们暴露的是"缺了哪一步"。
+18
View File
@@ -0,0 +1,18 @@
"""1-4 问"最优实现 / 基线实现"统一对比框架(非运行时依赖,仅用于分析与论文)。
本包不改动正式求解入口 ``mcm26b``,只做三层组织:
- ``project.q1`` … ``project.q4``:四个问题各自的
``optimal.py``(生产最优实现,直接复用 ``mcm26b`` 内核)、
``baseline.py``(朴素基线,参照组)、``compare.py``(同题配对对比);
- ``project.common``:方法描述、指标聚合、结果落盘、离线闭环装配;
- ``project.run``:统一入口,一次跑完四问并生成 ``project/results/``。
运行方式(仓库根目录)::
uv run python -m project.run # 四问全部
uv run python -m project.run --question 3 # 只跑第三问
uv run python -m project.run --quick # 缩小随机场景数
"""
__all__ = ["common"]
+308
View File
@@ -0,0 +1,308 @@
"""对比框架公共层:方法描述、指标聚合、结果落盘、离线闭环装配。
设计口径
--------
- **最优实现** = 复用 ``src/mcm26b`` 中已由测试固化的生产实现;
- **基线实现** = 同题下更朴素但可独立运行的参照实现(不得共享最优实现的
关键步骤),用于量化"生产实现好在哪里、差多少";
- **参考解** = 与分析算法相互独立的数值/枚举标准,用来判定谁对谁错。
第三、四问是闭环问题,统一走 ``LocalSimulator`` 内存回环(与官方 HTTP
协议同一边界),真值只在评测机侧,策略端只看响应。
"""
from __future__ import annotations
import json
import statistics
from dataclasses import dataclass, replace
from pathlib import Path
from time import perf_counter
from mcm26b.config import ProblemConfig
from mcm26b.evaluation import recompute_virtual_time
from mcm26b.harness import LocalSimTransport, LocalSimulator
from mcm26b.protocol.client import SimulatorClient
ROOT = Path(__file__).resolve().parents[1]
RESULTS_DIR = Path(__file__).resolve().parent / "results"
@dataclass(frozen=True)
class Method:
"""一个可对比方法:key 用作图表列名,kind 用于区分最优/基线/参考。"""
key: str
name: str
kind: str # optimal | baseline | reference
note: str = ""
def problem_config(*, persistent_search: bool = False,
margin_deg: float | None = None) -> ProblemConfig:
"""对比口径配置:默认关闭"扫到限时"的持续复扫(对齐首次找全证明)。"""
base = ProblemConfig()
kwargs: dict = {"persistent_search": persistent_search}
if margin_deg is not None:
kwargs["bearing_rounding_margin_deg"] = margin_deg
return replace(base, search=replace(base.search, **kwargs))
# ---------------------------------------------------------------- 结果落盘
def write_json(path: Path, payload) -> Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(payload, ensure_ascii=False, indent=2,
allow_nan=False) + "\n", encoding="utf-8")
return path
def write_text(path: Path, text: str) -> Path:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(text, encoding="utf-8")
return path
# ---------------------------------------------------------------- 指标工具
def mean(values) -> float:
values = list(values)
return round(statistics.fmean(values), 4) if values else 0.0
def median(values) -> float:
values = list(values)
return round(statistics.median(values), 4) if values else 0.0
def ratio(hit: int, total: int) -> float:
return round(hit / total, 4) if total else 0.0
def relative_change(value: float, reference: float) -> float | None:
if reference == 0:
return None
return round(value / reference - 1.0, 4)
def markdown_table(headers: list[str], rows: list[list[str]]) -> str:
lines = ["| " + " | ".join(headers) + " |",
"| " + " | ".join("---" for _ in headers) + " |"]
for row in rows:
lines.append("| " + " | ".join(str(cell) for cell in row) + " |")
return "\n".join(lines)
def _num(value, digits: int = 2) -> str:
if value is None:
return "—"
if isinstance(value, bool):
return "✔" if value else "✘"
if isinstance(value, float):
return f"{value:.{digits}f}"
return str(value)
# ---------------------------------------------------------------- 第三/四问闭环
def run_loop_episode(loop_cls, scene_spec, config: ProblemConfig) -> dict:
"""在冻结场景上跑一局闭环,返回逐局指标行(与 ``scripts/run_suite.py`` 同口径)。"""
sources = scene_spec.sources(config) # 真值:仅测试端可见
simulator = LocalSimulator(sources, robot_id="COMPARE", config=config,
error_seed=scene_spec.error_seed)
client = SimulatorClient("COMPARE", config=config,
transport=LocalSimTransport(simulator))
start = perf_counter()
loop = loop_cls(client, config)
summary = loop.run()
compute_wall_s = perf_counter() - start
report = recompute_virtual_time(client.log.action_events())
truth = sorted(source.channel for source in sources)
cleared = sorted(summary["cleared_channels"])
absent = sorted(summary["absent_channels"])
misjudged = sorted(set(absent) & set(truth))
valid = (cleared == truth and not misjudged
and summary["exit_reason"] == "user_exit"
and not summary["anomalies"]
and not report["mismatches"])
return {
"scene_id": scene_spec.scene_id,
"problem": scene_spec.problem,
"kind": scene_spec.kind,
"sources": len(truth),
"directional": scene_spec.directional,
"cleared": len(cleared),
"cleared_ratio": ratio(len(cleared), len(truth)),
"cleared_matches_truth": cleared == truth,
"absent_misjudged": len(misjudged),
"virtual_time_s": round(summary["virtual_time_s"], 3),
"accounted_virtual_time_s": (
round(summary["accounted_virtual_time_s"], 3)
if summary.get("accounted_virtual_time_s") is not None else None),
"measures": summary["measures"],
"clears": summary["clears"],
"clear_success": summary["clear_success"],
"total_requests": summary["total_requests"],
"move_distance_m": round(summary["move_distance_m"], 2),
"fallback_clear_attempts": summary.get("fallback_clear_attempts", 0),
"coverage_points": summary.get("coverage_points", 0),
"stop_reason": summary["stop_reason"],
"exit_reason": summary["exit_reason"],
"anomaly_count": len(summary["anomalies"]),
"virtual_recompute_mismatches": len(report["mismatches"]),
"compute_wall_s": round(compute_wall_s, 3),
"valid": valid,
}
# 第三/四问统一汇报列(顺序即 Markdown 列序)
EPISODE_COLUMNS = (
"cleared_ratio", "cleared_matches_truth", "absent_misjudged",
"virtual_time_s", "measures", "total_requests", "move_distance_m",
"fallback_clear_attempts", "compute_wall_s", "valid",
)
def aggregate_episodes(rows: list[dict], labels: list[str],
base_label: str | None = None) -> dict:
"""逐方法汇总 + 相对基线的配对变化(按 scene_id 对齐)。"""
summary: dict[str, dict] = {}
for label in labels:
part = [row for row in rows if row["variant"] == label]
if not part:
continue
summary[label] = {
"episodes": len(part),
"valid_episodes": sum(1 for row in part if row["valid"]),
"cleared_ratio_mean": mean(row["cleared_ratio"] for row in part),
"cleared_matches_truth": sum(1 for row in part
if row["cleared_matches_truth"]),
"absent_misjudged_total": sum(row["absent_misjudged"] for row in part),
"virtual_time_mean_s": mean(row["virtual_time_s"] for row in part),
"virtual_time_median_s": median(row["virtual_time_s"] for row in part),
"virtual_time_max_s": round(max(row["virtual_time_s"] for row in part), 3),
"measures_mean": mean(row["measures"] for row in part),
"total_requests_mean": mean(row["total_requests"] for row in part),
"move_distance_mean_m": mean(row["move_distance_m"] for row in part),
"fallback_clear_attempts_mean": mean(
row["fallback_clear_attempts"] for row in part),
"compute_wall_mean_s": mean(row["compute_wall_s"] for row in part),
}
paired: dict[str, dict] = {}
if base_label and len(labels) > 1:
base_rows = {row["scene_id"]: row for row in rows
if row["variant"] == base_label}
for label in labels:
if label == base_label:
continue
deltas, faster, valid_pairs = [], 0, 0
for row in rows:
if row["variant"] != label or row["scene_id"] not in base_rows:
continue
base_row = base_rows[row["scene_id"]]
reference = base_row["virtual_time_s"]
deltas.append(relative_change(row["virtual_time_s"], reference))
if not (row["valid"] and base_row["valid"]):
continue
valid_pairs += 1
if row["virtual_time_s"] < reference:
faster += 1
paired[f"{label}_vs_{base_label}"] = {
"mean_relative_change": mean(d for d in deltas if d is not None),
"median_relative_change": median(d for d in deltas if d is not None),
"faster_scenes": faster,
"compared": len(deltas),
"both_valid_scenes": valid_pairs,
}
return {"variants": summary, "paired": paired}
def episodes_markdown(title: str, rows: list[dict], labels: list[str],
base_label: str | None = None) -> str:
"""逐场景明细 + 汇总表。"""
headers = ["scene", "sources", "variant", "cleared", "T(s)", "检测",
"请求", "位移(m)", "后备清除", "墙钟(s)", "有效"]
body = []
for row in sorted(rows, key=lambda r: (r["scene_id"], labels.index(r["variant"]))):
body.append([
row["scene_id"], row["sources"], row["variant"],
f"{row['cleared']}/{row['sources']}",
_num(row["virtual_time_s"], 1), row["measures"],
row["total_requests"], _num(row["move_distance_m"], 1),
row["fallback_clear_attempts"], _num(row["compute_wall_s"], 2),
_num(row["valid"]),
])
text = [f"# {title}", "", "## 逐场景明细", "",
markdown_table(headers, body), "", "## 逐方法汇总", ""]
agg = aggregate_episodes(rows, labels, base_label)
agg_headers = ["方法", "有效局", "全清率", "全清对数", "缺席误判",
"T 均值(s)", "T 中位(s)", "T 最大(s)", "检测均值",
"请求均值", "位移均值(m)", "后备清除均值", "墙钟均值(s)"]
agg_rows = []
for label in labels:
s = agg["variants"].get(label)
if not s:
continue
agg_rows.append([
label, f"{s['valid_episodes']}/{s['episodes']}",
_num(s["cleared_ratio_mean"]), s["cleared_matches_truth"],
s["absent_misjudged_total"], _num(s["virtual_time_mean_s"], 1),
_num(s["virtual_time_median_s"], 1), _num(s["virtual_time_max_s"], 1),
_num(s["measures_mean"], 1), _num(s["total_requests_mean"], 1),
_num(s["move_distance_mean_m"], 1),
_num(s["fallback_clear_attempts_mean"], 2),
_num(s["compute_wall_mean_s"], 2),
])
text.append(markdown_table(agg_headers, agg_rows))
if agg["paired"]:
text += ["", "## 相对基线的配对变化(虚拟时间)", ""]
pair_rows = [[name, f"{v['mean_relative_change']*100:+.1f}%",
f"{v['median_relative_change']*100:+.1f}%",
f"{v['faster_scenes']}/{v['compared']}",
v["both_valid_scenes"]]
for name, v in agg["paired"].items()]
text.append(markdown_table(
["配对", "均值变化", "中位变化", "更快场景", "双方均有效"], pair_rows))
invalid = [name for name, value in agg["paired"].items()
if value["both_valid_scenes"] < value["compared"]]
if invalid:
text += ["",
"> 注:" + "、".join(invalid)
+ " 存在未通过有效性判定的配对;其虚拟时间仅作参考,"
"基线未完成全清任务时不构成「更快」证据"
"(以「双方均有效」列为准)。"]
return "\n".join(text) + "\n"
def json_safe(payload):
"""把 numpy 标量等转成 json 可写类型(防御式,非必需)。"""
return json.loads(json.dumps(payload, ensure_ascii=False, default=float))
# ---------------------------------------------------------------- 闭环批量执行
def execute_variants(scenes, variants, config: ProblemConfig) -> list[dict]:
"""对每个场景跑遍所有变体(串行;对比规模小,进程池收益有限且便于复现)。"""
rows = []
for scene in scenes:
for label, loop_cls in variants:
row = run_loop_episode(loop_cls, scene, config)
row["variant"] = label
rows.append(row)
return rows
def closed_loop_payload(question: int, suite_name: str, scenes, variants,
rows: list[dict], labels: list[str],
base_label: str, config: ProblemConfig) -> dict:
return {
"question": question,
"suite": suite_name,
"scenes": [scene.as_dict() for scene in scenes],
"variants": [{"label": label, "class": loop_cls.__name__}
for label, loop_cls in variants],
"config": {
"persistent_search": config.search.persistent_search,
"bearing_rounding_margin_deg": config.search.bearing_rounding_margin_deg,
"q4_coverage_plan": config.search.q4_coverage_plan,
},
"rows": rows,
"aggregate": aggregate_episodes(rows, labels, base_label),
}
+13
View File
@@ -0,0 +1,13 @@
"""问题一:交会定位多边形直径与直径圆覆盖。
- ``optimal.py`` :生产实现 ``mcm26b.geometry.localization.solve``(半平面交 +
顶点对精确直径 + Welzl 最小包围圆 + 覆盖反例见证);
- ``baseline.py`` :基线实现(网格采样 + 轴对齐包围盒代理直径/覆盖);
- ``reference.py``:独立精确参考解(大框半平面裁剪 + 暴力直径/最小包围圆);
- ``cases.py`` :固定示例(``examples/q1``)与随机算例生成;
- ``compare.py`` :同案配对对比并落盘。
"""
from . import baseline, cases, optimal, reference
__all__ = ["optimal", "baseline", "reference", "cases"]
+98
View File
@@ -0,0 +1,98 @@
"""问题一基线实现:网格采样 + 轴对齐包围盒(bbox)代理。
这是"先上手"的朴素做法,常见于直接对可行点打网格再读包围盒:
1. 在固定大框上按步长采样,保留满足全部角域约束的点;
2. 直径用包围盒对角线代理 —— 只要区域是斜的/细长的就会系统性高估;
3. 覆盖判断用"包围盒中心 + 代理直径"的圆 —— 因为包围盒自身对其中心对称,
该判据几乎恒为"能覆盖",从而丢掉问题一真正要考的反例。
本实现刻意保留这些结构性缺陷,作为``optimal.py``的对照;它不读取最优实现的
任何中间量,两条代码路径完全独立。
"""
from __future__ import annotations
import math
from time import perf_counter
import numpy as np
from mcm26b.geometry.localization import Measurement, halfplanes
from project.common import Method
METHOD = Method(
key="baseline",
name="网格采样 + bbox 对角线代理",
kind="baseline",
note="轴对齐包围盒近似直径与覆盖,O(盒面积/步长²),与参考解同分辨率",
)
BOX_M = 2600.0
STEP_M = 5.0
def solve_case(case: dict) -> dict:
start = perf_counter()
measurements = [Measurement(row["x"], row["y"], row["bearing_deg"],
row.get("half_angle_deg", 1.0))
for row in case["measurements"]]
planes = [plane for m in measurements for plane in halfplanes(m)]
xs = np.arange(-BOX_M, BOX_M + STEP_M, STEP_M)
grid_x, grid_y = np.meshgrid(xs, xs) # 行=y,列=x
mask = np.ones(grid_x.shape, dtype=bool)
for a, b, c in planes:
mask &= (a * grid_x + b * grid_y <= c)
result = _evaluate(mask, xs)
result["method"] = METHOD.key
result["runtime_ms"] = round((perf_counter() - start) * 1000.0, 4)
result["algorithm"] = METHOD.name
return result
def _evaluate(mask: np.ndarray, axis: np.ndarray) -> dict:
if not mask.any():
return _empty("empty")
# 可行点触到采样框边界 => 区域至少延伸到框外,判无界
if (mask[0, :].any() or mask[-1, :].any()
or mask[:, 0].any() or mask[:, -1].any()):
return _empty("unbounded")
rows, cols = np.nonzero(mask)
x_min, x_max = axis[cols].min(), axis[cols].max()
y_min, y_max = axis[rows].min(), axis[rows].max()
width, height = x_max - x_min, y_max - y_min
diameter = math.hypot(width, height) # 包围盒对角线代理
center = ((x_min + x_max) / 2.0, (y_min + y_max) / 2.0)
px = axis[cols]
py = axis[rows]
max_distance = float(np.max(np.hypot(px - center[0], py - center[1])))
return {
"status": "polygon" if mask.sum() >= 2 else "point",
"diameter_m": diameter,
"diameter_infinite": False,
"covers": bool(max_distance <= diameter / 2.0 + 1e-9),
"mec_radius_m": None,
"max_excess_m": round(max_distance - diameter / 2.0, 6),
"outside_vertex_count": None,
"vertex_count": int(mask.sum()),
"bbox_width_m": round(width, 3),
"bbox_height_m": round(height, 3),
"runtime_ms": None,
}
def _empty(status: str) -> dict:
return {
"status": status,
"diameter_m": None,
"diameter_infinite": status == "unbounded",
"covers": None,
"mec_radius_m": None,
"max_excess_m": None,
"outside_vertex_count": None,
"vertex_count": 0,
}
+67
View File
@@ -0,0 +1,67 @@
"""问题一算例:官方示例目录 ``examples/q1`` + 随机交会算例。"""
from __future__ import annotations
import json
import math
import random
from project.common import ROOT
EXAMPLE_DIR = ROOT / "examples" / "q1"
EXAMPLE_IDS = ("normal", "triangle", "poor", "segment", "empty")
def load_examples() -> list[dict]:
"""读取固定算例;缺文件时跳过,保证离线可跑。"""
cases = []
for key in EXAMPLE_IDS:
path = EXAMPLE_DIR / f"{key}.json"
if not path.exists():
continue
raw = json.loads(path.read_text(encoding="utf-8-sig"))
cases.append({
"id": raw.get("id", key),
"name": raw.get("name", key),
"source": "examples/q1",
"measurements": [
{"x": float(row["x"]), "y": float(row["y"]),
"bearing_deg": float(row["bearing_deg"]),
"half_angle_deg": float(row.get("half_angle_deg", 1.0))}
for row in raw["measurements"]
],
})
return cases
def random_case(seed: int) -> dict:
"""随机三点/四点交会:目标在 Ω 内,测站分布在不同象限。
仅生成"区域有界且非退化"的良态算例,避免参考网格因细长区域失效;
生成后由 ``compare`` 再用最优实现复核状态。
"""
rng = random.Random(seed)
target = (rng.uniform(-900, 900), rng.uniform(-900, 900))
count = rng.choice((3, 3, 4, 5))
stations = []
base_angle = rng.uniform(0, 360)
for index in range(count):
angle = base_angle + 360 * index / count + rng.uniform(-25, 25)
radius = rng.uniform(500, 1100)
stations.append((target[0] + radius * math.cos(math.radians(angle)),
target[1] + radius * math.sin(math.radians(angle))))
measurements = []
for (sx, sy) in stations:
true_bearing = math.degrees(math.atan2(target[1] - sy, target[0] - sx)) % 360.0
# 观测误差固定在同一地点、落在 ±1° 内;读数保留两位小数
error = rng.uniform(-0.9, 0.9)
measurements.append({"x": round(sx, 2), "y": round(sy, 2),
"bearing_deg": round((true_bearing + error) % 360.0, 2),
"half_angle_deg": 1.0})
return {"id": f"rand-{seed}", "name": "随机交会", "source": "synthetic",
"target": [round(target[0], 2), round(target[1], 2)],
"measurements": measurements}
def random_cases(count: int, base_seed: int = 7000) -> list[dict]:
return [random_case(base_seed + index) for index in range(count)]
+150
View File
@@ -0,0 +1,150 @@
"""问题一同案配对对比:最优 vs 基线 vs 独立参考解。"""
from __future__ import annotations
from project.common import (Method, RESULTS_DIR, markdown_table, mean,
ratio, write_json, write_text)
from project.q1 import baseline, cases, optimal, reference
METHODS: tuple[Method, ...] = (optimal.METHOD, baseline.METHOD, reference.METHOD)
FINITE = ("point", "segment", "polygon")
def _run(case: dict) -> dict:
opt = optimal.solve_case(case)
base = baseline.solve_case(case)
ref = reference.solve_case(case)
finite = ref["status"] in FINITE and ref["diameter_m"] is not None
return {
"id": case["id"],
"name": case.get("name", case["id"]),
"source": case.get("source", "synthetic"),
"measurements": len(case["measurements"]),
"status_ref": ref["status"],
"status_opt": opt["status"],
"status_base": base["status"],
"status_opt_ok": opt["status"] == ref["status"],
"status_base_ok": base["status"] == ref["status"],
"diameter_ref_m": ref["diameter_m"],
"diameter_opt_m": opt["diameter_m"],
"diameter_base_m": base["diameter_m"],
"abs_err_opt_m": (round(abs(opt["diameter_m"] - ref["diameter_m"]), 4)
if finite and opt["diameter_m"] is not None else None),
"abs_err_base_m": (round(abs(base["diameter_m"] - ref["diameter_m"]), 4)
if finite and base["diameter_m"] is not None else None),
"covers_ref": ref["covers"],
"covers_opt": opt["covers"],
"covers_base": base["covers"],
"covers_opt_ok": (opt["covers"] == ref["covers"]
if ref["covers"] is not None and opt["covers"] is not None
else None),
"covers_base_ok": (base["covers"] == ref["covers"]
if ref["covers"] is not None and base["covers"] is not None
else None),
"mec_radius_opt_m": opt["mec_radius_m"],
"mec_radius_ref_m": ref["mec_radius_m"],
"outside_vertices_opt": opt["outside_vertex_count"],
"runtime_opt_ms": opt["runtime_ms"],
"runtime_base_ms": base["runtime_ms"],
"runtime_ref_ms": ref["runtime_ms"],
"reference_resolution_m": ref["resolution_m"],
}
def _aggregate(rows: list[dict]) -> dict:
polygon_rows = [r for r in rows if r["status_ref"] == "polygon"]
opt_errs = [r["abs_err_opt_m"] for r in polygon_rows
if r["abs_err_opt_m"] is not None]
base_errs = [r["abs_err_base_m"] for r in polygon_rows
if r["abs_err_base_m"] is not None]
cov_opt = [r["covers_opt_ok"] for r in rows if r["covers_opt_ok"] is not None]
cov_base = [r["covers_base_ok"] for r in rows if r["covers_base_ok"] is not None]
return {
"cases": len(rows),
"diameter_compared": len(opt_errs),
"status_accuracy_optimal": ratio(sum(r["status_opt_ok"] for r in rows), len(rows)),
"status_accuracy_baseline": ratio(sum(r["status_base_ok"] for r in rows), len(rows)),
"diameter_mae_optimal_m": mean(opt_errs),
"diameter_mae_baseline_m": mean(base_errs),
"diameter_max_err_optimal_m": round(max(opt_errs), 4) if opt_errs else 0.0,
"diameter_max_err_baseline_m": round(max(base_errs), 4) if base_errs else 0.0,
"coverage_compared": len(cov_opt),
"coverage_accuracy_optimal": ratio(sum(cov_opt), len(cov_opt)),
"coverage_accuracy_baseline": ratio(sum(cov_base), len(cov_base)),
"runtime_optimal_mean_ms": mean(r["runtime_opt_ms"] for r in rows),
"runtime_baseline_mean_ms": mean(r["runtime_base_ms"] for r in rows),
"runtime_reference_mean_ms": mean(r["runtime_ref_ms"] for r in rows),
}
def _render(rows: list[dict], aggregate: dict, title: str) -> str:
headers = ["算例", "来源", "参考状态", "最优状态", "基线状态",
"D参考(m)", "D最优(m)", "D基线(m)", "|ΔD|最优", "|ΔD|基线",
"覆盖参考", "覆盖最优", "覆盖基线"]
body = []
for r in rows:
body.append([
r["name"], r["source"], r["status_ref"], r["status_opt"], r["status_base"],
_fmt(r["diameter_ref_m"]), _fmt(r["diameter_opt_m"]),
_fmt(r["diameter_base_m"]),
_fmt(r["abs_err_opt_m"]), _fmt(r["abs_err_base_m"]),
_bool(r["covers_ref"]), _bool(r["covers_opt"]), _bool(r["covers_base"]),
])
text = [f"# {title}", "",
"参考解为独立的大框半平面裁剪 + 精确直径/MEC;",
"固定示例见 `examples/q1`,解析期望由 `tests/test_geometry*.py` 固化。",
"", markdown_table(headers, body), "",
"## 汇总", ""]
summary_rows = [
["状态判定准确率", _pct(aggregate["status_accuracy_optimal"]),
_pct(aggregate["status_accuracy_baseline"])],
["直径平均绝对误差 (m)", _fmt(aggregate["diameter_mae_optimal_m"], 3),
_fmt(aggregate["diameter_mae_baseline_m"], 3)],
["直径最大误差 (m)", _fmt(aggregate["diameter_max_err_optimal_m"], 3),
_fmt(aggregate["diameter_max_err_baseline_m"], 3)],
["覆盖判定准确率", _pct(aggregate["coverage_accuracy_optimal"]),
_pct(aggregate["coverage_accuracy_baseline"])],
["平均耗时 (ms)", _fmt(aggregate["runtime_optimal_mean_ms"], 3),
_fmt(aggregate["runtime_baseline_mean_ms"], 3)],
]
text.append(markdown_table(["指标", "最优", "基线"], summary_rows))
return "\n".join(text) + "\n"
def _fmt(value, digits: int = 2) -> str:
if value is None:
return "—"
if isinstance(value, bool):
return "是" if value else "否"
if isinstance(value, float):
return f"{value:.{digits}f}"
return str(value)
def _bool(value) -> str:
return "—" if value is None else ("是" if value else "否")
def _pct(value: float) -> str:
return f"{value*100:.1f}%"
def run(quick: bool = False) -> dict:
example_rows = [_run(case) for case in cases.load_examples()]
random_rows = [_run(case) for case in
cases.random_cases(12 if quick else 40)]
payload = {
"question": 1,
"methods": [m.__dict__ for m in METHODS],
"examples": example_rows,
"random": random_rows,
"aggregate_examples": _aggregate(example_rows),
"aggregate_random": _aggregate(random_rows),
}
write_json(RESULTS_DIR / "q1.json", payload)
text = _render(example_rows, payload["aggregate_examples"],
"问题一:固定示例对比")
text += "\n" + _render(random_rows, payload["aggregate_random"],
"问题一:随机交会算例对比")
write_text(RESULTS_DIR / "q1.md", text)
return payload
+59
View File
@@ -0,0 +1,59 @@
"""问题一最优实现:复用生产几何内核 ``mcm26b.geometry.localization.solve``。
算法(已由 ``tests/test_geometry.py`` / ``tests/test_geometry_certificates.py``
固化,本模块只做输入输出适配,不重写):
1. 每条带 ±δ 误差的示向度写成前向角域的三条半平面约束(含 0°/360° 跨界);
2. 枚举全部边界直线交点,保留满足全部约束者,单调链求凸包;
3. 检查是否存在非零衰退方向判定无界;否则枚举凸包顶点对取精确直径;
4. 用直径两端点中点圆检验覆盖,并给出圆外顶点、最大超出距离等反例见证;
5. Welzl 增量法求最小包围圆,给出 20 m 清除余量证书。
"""
from __future__ import annotations
from time import perf_counter
from mcm26b.geometry.localization import Measurement, solve
from project.common import Method
METHOD = Method(
key="optimal",
name="精确半平面交 + 顶点对直径 + MEC",
kind="optimal",
note="生产内核 mcm26b.geometry.localization.solve",
)
def solve_case(case: dict) -> dict:
"""输入 case["measurements"],输出归一化结果。"""
measurements = [Measurement(row["x"], row["y"], row["bearing_deg"],
row.get("half_angle_deg", 1.0))
for row in case["measurements"]]
start = perf_counter()
result = solve(measurements)
elapsed_ms = (perf_counter() - start) * 1000.0
return normalize(result, elapsed_ms)
def normalize(result: dict, elapsed_ms: float) -> dict:
status = result["status"]
diameter = result.get("diameter_m")
circle = result.get("diameter_circle") or {}
mec = result.get("minimum_enclosing_circle") or {}
covers = circle.get("covers") if status in ("point", "segment", "polygon") else None
return {
"method": METHOD.key,
"status": status,
"diameter_m": diameter,
"diameter_infinite": result.get("diameter_infinite"),
"covers": covers,
"mec_radius_m": mec.get("radius_m"),
"max_excess_m": circle.get("max_excess_m"),
"outside_vertex_count": len(circle.get("outside_vertex_indices", []))
if circle else None,
"vertex_count": len(result.get("vertices", [])),
"runtime_ms": round(elapsed_ms, 4),
"algorithm": METHOD.name,
}
+178
View File
@@ -0,0 +1,178 @@
"""问题一独立参考解:大框 Sutherland–Hodgman 半平面裁剪 + 精确直径/MEC。
与生产实现 ``mcm26b.geometry.localization.solve`` 的独立性:
- 生产实现枚举"半平面边界直线两两交点"再筛可行点;
- 本参考解从一个大方框出发,逐条半平面做 Sutherland–Hodgman 裁剪,
是另一条构造路径,且这里的半平面系数、直径、最小包围圆都在本文件内
独立编码(不复用生产实现的 ``halfplanes`` / ``diameter`` / ``mec``)。
只要区域有界,大框(±1e7 m)裁剪得到的顶点即精确多边形;顶点触到大框
边界则判无界。最小包围圆用"枚举所有 1/2/3 点支撑圆"的暴力算法,直径用
顶点对暴力枚举,二者都是 O(m²)/O(m³) 的朴素精确算法,m 为多边形顶点数。
"""
from __future__ import annotations
import math
from time import perf_counter
from project.common import Method
METHOD = Method(
key="reference",
name="大框半平面裁剪 + 精确直径/MEC",
kind="reference",
note="独立于生产顶点枚举的精确参考解",
)
BOX_M = 1.0e7
_PARALLEL_EPS = 1e-12
def _planes(case: dict) -> list[tuple[float, float, float]]:
"""独立重写 a*x+b*y<=c 的半平面构造(与题面角域定义一致)。"""
planes = []
for row in case["measurements"]:
half = math.radians(row.get("half_angle_deg", 1.0))
theta = math.radians(row["bearing_deg"] % 360.0)
sx, sy = row["x"], row["y"]
for angle, sign in ((theta - half, 1.0), (theta + half, -1.0)):
a = sign * math.sin(angle)
b = -sign * math.cos(angle)
planes.append((a, b, a * sx + b * sy))
# 前向约束:不允许把角域取成反向无限双楔
a, b = -math.cos(theta), -math.sin(theta)
planes.append((a, b, a * sx + b * sy))
return planes
def _clip(polygon, a, b, c, tol=1e-7):
"""Sutherland–Hodgman:保留 a*x+b*y<=c 的一侧。"""
if not polygon:
return []
output = []
count = len(polygon)
for index in range(count):
p, q = polygon[index], polygon[(index + 1) % count]
fp = a * p[0] + b * p[1] - c
fq = a * q[0] + b * q[1] - c
p_in, q_in = fp <= tol, fq <= tol
if p_in:
output.append(p)
if p_in != q_in:
ratio = fp / (fp - fq)
output.append((p[0] + ratio * (q[0] - p[0]),
p[1] + ratio * (q[1] - p[1])))
deduped = []
for point in output:
if not deduped or math.dist(point, deduped[-1]) > 1e-9:
deduped.append(point)
if len(deduped) > 1 and math.dist(deduped[0], deduped[-1]) <= 1e-9:
deduped.pop()
return deduped
def _brute_force_diameter(points):
best = 0.0
pair = (points[0], points[0])
for i, pi in enumerate(points):
for pj in points[i + 1:]:
value = math.dist(pi, pj)
if value > best:
best, pair = value, (pi, pj)
return best, pair
def _circumcircle(a, b, c):
ux, uy = b[0] - a[0], b[1] - a[1]
vx, vy = c[0] - a[0], c[1] - a[1]
det = 2.0 * (ux * vy - uy * vx)
if abs(det) <= _PARALLEL_EPS * max(
math.hypot(ux, uy) * math.hypot(vx, vy), 1e-300):
return None
uu, vv = ux * ux + uy * uy, vx * vx + vy * vy
center = (a[0] + (uu * vy - vv * uy) / det,
a[1] + (ux * vv - vx * uu) / det)
return center, math.dist(center, a)
def _brute_force_mec(points):
"""枚举 1/2/3 点支撑圆的精确最小包围圆(朴素但无迭代)。"""
xs = [p[0] for p in points]
ys = [p[1] for p in points]
best_c = ((min(xs) + max(xs)) / 2.0, (min(ys) + max(ys)) / 2.0)
best_r = max(math.dist(best_c, p) for p in points)
candidates: list[tuple[tuple[float, float], float]] = []
for i, p in enumerate(points):
for q in points[i + 1:]:
candidates.append((((p[0] + q[0]) / 2.0, (p[1] + q[1]) / 2.0),
math.dist(p, q) / 2.0))
for i, p in enumerate(points):
for j in range(i + 1, len(points)):
for k in range(j + 1, len(points)):
circle = _circumcircle(p, points[j], points[k])
if circle is not None:
candidates.append(circle)
for center, radius in candidates:
if radius >= best_r:
continue
if all(math.dist(center, p) <= radius + 1e-7 for p in points):
best_c, best_r = center, radius
return best_c, best_r
def solve_case(case: dict) -> dict:
start = perf_counter()
planes = _planes(case)
polygon = [(-BOX_M, -BOX_M), (BOX_M, -BOX_M), (BOX_M, BOX_M), (-BOX_M, BOX_M)]
for a, b, c in planes:
polygon = _clip(polygon, a, b, c)
if not polygon:
return _finish(_degenerate("empty"), start)
boundary_tol = 1e-6 * BOX_M
if any(abs(point[0]) >= BOX_M - boundary_tol
or abs(point[1]) >= BOX_M - boundary_tol for point in polygon):
return _finish(_degenerate("unbounded"), start)
if len(polygon) == 1:
summary = {"status": "point", "diameter_m": 0.0, "covers": True,
"mec_radius_m": 0.0, "max_excess_m": 0.0,
"vertex_count": 1}
else:
value, (a, b) = _brute_force_diameter(polygon)
if len(polygon) == 2:
summary = {"status": "segment", "diameter_m": value, "covers": True,
"mec_radius_m": value / 2.0, "max_excess_m": 0.0,
"vertex_count": 2}
else:
_mec_center, radius = _brute_force_mec(polygon)
diameter_center = ((a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0)
max_distance = max(math.dist(diameter_center, p) for p in polygon)
summary = {
"status": "polygon",
"diameter_m": value,
"covers": bool(radius <= value / 2.0 + 1e-7),
"mec_radius_m": radius,
"max_excess_m": round(max_distance - value / 2.0, 9),
"vertex_count": len(polygon),
}
return _finish(summary, start)
def _degenerate(status: str) -> dict:
return {"status": status, "diameter_m": None, "covers": None,
"mec_radius_m": None, "max_excess_m": None, "vertex_count": 0}
def _finish(summary: dict, start: float) -> dict:
result = {
"method": METHOD.key,
"algorithm": METHOD.name,
"resolution_m": 0.0,
"diameter_infinite": summary["status"] == "unbounded",
"outside_vertex_count": None,
"runtime_ms": round((perf_counter() - start) * 1000.0, 4),
}
result.update(summary)
return result
+14
View File
@@ -0,0 +1,14 @@
"""问题二:第二检测点选择策略。
- ``optimal.py`` :生产选点器 ``mcm26b.planner.select_measurement_point``
(保证接收盘内最坏后验半径最小化,采样启发式);
- ``baseline.py`` :"垂直 1000 m 最大交会角"朴素启发式(忽略接收保证),
以及"退到候选集 MEC 圆心"的保守基线;
- ``reference.py``:在保证接收盘内稠密网格枚举的近似最优参考;
- ``cases.py`` :随机首次示向度算例;
- ``compare.py`` :同案配对对比并落盘。
"""
from . import baseline, cases, optimal, reference
__all__ = ["optimal", "baseline", "reference", "cases"]
+71
View File
@@ -0,0 +1,71 @@
"""问题二基线实现:两个朴素但常见的对照。
- ``candidate_perp``:沿首次示向度垂直方向、距检测点约 1000 m 取点。
这是"尽量把交会角做成 90°"的教科书启发式,**但它忽略接收距离保证**:
真源可能落在首次角域的远端(≤1500 m),此时第二点距离可超过 1000 m,
第二次测量直接 ``no_signal``,几何收益落空。
- ``candidate_mec``:退到候选集最小包围圆圆心。它确实落在保证接收盘内
(ρ≤1000 时),但交会角可能接近 0°、后验几乎不收缩,代表"最保守"一端。
"""
from __future__ import annotations
import math
from mcm26b.config import ProblemConfig
from mcm26b.planner import safe_disk_radius
from project.common import Method, problem_config
from project.q2.region import first_bearing_region
PERP = Method(
key="baseline_perp",
name="垂直 1000 m 最大交会角",
kind="baseline",
note="忽略接收保证的几何启发式",
)
MEC = Method(
key="baseline_mec",
name="候选集 MEC 圆心",
kind="baseline",
note="保证接收但交会角最差的保守基线",
)
METHODS = (PERP, MEC)
def _region(case: dict, config: ProblemConfig):
return first_bearing_region(case, config)
def candidate_perp(case: dict, config: ProblemConfig | None = None) -> dict:
config = config or problem_config()
region = _region(case, config)
station = (case["station"][0], case["station"][1])
theta = math.radians(case["bearing_deg"] + 90.0)
distance = config.field.r_eff_min_m
position = (station[0] + distance * math.cos(theta),
station[1] + distance * math.sin(theta))
return {
"method": PERP.key,
"position": [round(position[0], 4), round(position[1], 4)],
"worst_posterior_radius_m": None, # 由 compare 统一评估
"move_cost_s": None,
"safe_disk_radius_m": safe_disk_radius(region, config),
"initial_radius_m": region.mec()[1],
"reason": "沿首次示向度法向 1000 m 取 90° 交会点",
}
def candidate_mec(case: dict, config: ProblemConfig | None = None) -> dict:
config = config or problem_config()
region = _region(case, config)
center, radius = region.mec()
return {
"method": MEC.key,
"position": [round(center[0], 4), round(center[1], 4)],
"worst_posterior_radius_m": None,
"move_cost_s": None,
"safe_disk_radius_m": safe_disk_radius(region, config),
"initial_radius_m": radius,
"reason": "取候选集最小包围圆圆心,保证接收但交会角可能最差",
}
+37
View File
@@ -0,0 +1,37 @@
"""问题二算例:随机首次示向度(全向源)。"""
from __future__ import annotations
import math
import random
from mcm26b.config import ProblemConfig
def random_case(seed: int, config: ProblemConfig | None = None) -> dict:
"""随机检测点 s 与示向度 θ:默认让真源与读数一致(误差 ±1° 内)。"""
config = config or ProblemConfig()
rng = random.Random(seed)
radius = config.field.omega_radius_m
# 检测点可在目标域内或域外;这里取域内,符合题目常规场景
angle_s = rng.uniform(0, 2 * math.pi)
distance_s = radius * math.sqrt(rng.random()) * 0.9
station = (distance_s * math.cos(angle_s), distance_s * math.sin(angle_s))
# 真源与检测点距离落在 [60, 1500) 且仍在 Ω 内
while True:
bearing_true = rng.uniform(0, 2 * math.pi)
source_distance = rng.uniform(60, config.field.r_eff_max_m - 1)
target = (station[0] + source_distance * math.cos(bearing_true),
station[1] + source_distance * math.sin(bearing_true))
if math.hypot(*target) <= radius:
break
error = math.radians(rng.uniform(-0.95, 0.95))
bearing_deg = math.degrees(bearing_true + error) % 360.0
return {"id": f"q2-{seed}", "station": [round(station[0], 2), round(station[1], 2)],
"bearing_deg": round(bearing_deg, 2), "half_angle_deg": 1.0,
"true_source": [round(target[0], 2), round(target[1], 2)]}
def random_cases(count: int, base_seed: int = 4200,
config: ProblemConfig | None = None) -> list[dict]:
return [random_case(base_seed + index, config) for index in range(count)]
+136
View File
@@ -0,0 +1,136 @@
"""问题二同案配对对比:最优 / 两个基线 / 网格参考,用同一评估器打分。"""
from __future__ import annotations
import math
from mcm26b.config import ProblemConfig
from mcm26b.planner import _worst_posterior_radius
from project.common import (Method, RESULTS_DIR, markdown_table, mean, ratio,
write_json, write_text)
from project.q2 import baseline, cases, optimal, reference
from project.q2.region import first_bearing_region
METHODS: tuple[Method, ...] = (optimal.METHOD, baseline.PERP, baseline.MEC,
reference.METHOD)
EVAL_WITNESS_SPACING_M = 60.0
def _evaluate(case: dict, config: ProblemConfig) -> list[dict]:
region = first_bearing_region(case, config)
station = (case["station"][0], case["station"][1])
initial_radius = region.mec()[1]
half_angle = (config.field.bearing_error_half_deg
+ config.search.bearing_rounding_margin_deg)
witnesses = region.witnesses(EVAL_WITNESS_SPACING_M)
plans = [optimal.candidate(case, config),
baseline.candidate_perp(case, config),
baseline.candidate_mec(case, config),
reference.candidate(case, config)]
rows = []
for plan in plans:
q = (plan["position"][0], plan["position"][1])
guarantee_distance = max(math.dist(q, vertex) for vertex in region.polygon)
worst = _worst_posterior_radius(
region, q, witnesses, half_angle,
config.field.near_radius_m, config.search.polygon_sides)
move_cost = math.dist(station, q) / config.cost.speed_mps
rows.append({
"case_id": case["id"],
"method": plan["method"],
"initial_radius_m": round(initial_radius, 4),
"q": [round(q[0], 2), round(q[1], 2)],
"guaranteed_reception": bool(guarantee_distance
<= config.field.r_eff_min_m + 1e-6),
"guarantee_distance_m": round(guarantee_distance, 3),
"worst_posterior_radius_m": round(worst, 4),
"improvement_m": round(initial_radius - worst, 4),
"final_ratio": round(worst / initial_radius, 4) if initial_radius > 0 else None,
"move_cost_s": round(move_cost, 4),
"reason": plan["reason"],
})
return rows
def _aggregate(rows: list[dict], labels: list[str]) -> dict:
summary: dict[str, dict] = {}
initial = mean(r["initial_radius_m"] for r in rows) if rows else 0.0
for label in labels:
part = [r for r in rows if r["method"] == label]
if not part:
continue
summary[label] = {
"cases": len(part),
"guaranteed_rate": ratio(sum(r["guaranteed_reception"] for r in part),
len(part)),
"worst_posterior_mean_m": mean(r["worst_posterior_radius_m"] for r in part),
"worst_posterior_median_m": sorted(
r["worst_posterior_radius_m"] for r in part)[len(part) // 2],
"improvement_mean_m": mean(r["improvement_m"] for r in part),
"final_ratio_mean": mean(r["final_ratio"] for r in part
if r["final_ratio"] is not None),
"move_cost_mean_s": mean(r["move_cost_s"] for r in part),
}
summary["_initial_radius_mean_m"] = initial
return summary
def _render(rows: list[dict], aggregate: dict, labels: list[str]) -> str:
headers = ["算例", "方法", "初始 ρ(m)", "第二点 q", "保证接收",
"q 到 K₁ 最远(m)", "最坏后验 ρ'(m)", "收缩(m)", "移动(s)"]
body = []
order = {label: index for index, label in enumerate(labels)}
for row in sorted(rows, key=lambda r: (r["case_id"], order.get(r["method"], 99))):
body.append([
row["case_id"], row["method"], _f(row["initial_radius_m"]),
f"({row['q'][0]:.0f},{row['q'][1]:.0f})",
"是" if row["guaranteed_reception"] else "否",
_f(row["guarantee_distance_m"]), _f(row["worst_posterior_radius_m"]),
_f(row["improvement_m"]), _f(row["move_cost_s"]),
])
text = ["# 问题二:第二检测点选择对比", "",
"初始 ρ 为首次示向度候选集 K₁ 的最小包围半径;最坏后验按"
"目标边界采样 × 读数区间端点统一评估。", "",
markdown_table(headers, body), "", "## 逐方法汇总", ""]
agg_headers = ["方法", "保证接收率", "最坏后验均值(m)", "中位(m)",
"平均收缩(m)", "终值比 ρ'/ρ", "移动均值(s)"]
agg_rows = []
for label in labels:
s = aggregate.get(label)
if not s:
continue
agg_rows.append([label, f"{s['guaranteed_rate']*100:.1f}%",
_f(s["worst_posterior_mean_m"]),
_f(s["worst_posterior_median_m"]),
_f(s["improvement_mean_m"]),
_f(s["final_ratio_mean"], 3),
_f(s["move_cost_mean_s"])])
text.append(markdown_table(agg_headers, agg_rows))
text.append("")
text.append(f"初始 ρ 均值:{aggregate['_initial_radius_mean_m']:.2f} m")
return "\n".join(text) + "\n"
def _f(value, digits: int = 2) -> str:
if value is None:
return "—"
if isinstance(value, bool):
return "是" if value else "否"
if isinstance(value, float):
return f"{value:.{digits}f}"
return str(value)
def run(quick: bool = False) -> dict:
config = ProblemConfig()
case_list = cases.random_cases(8 if quick else 30, config=config)
rows = [row for case in case_list for row in _evaluate(case, config)]
labels = [m.key for m in METHODS]
aggregate = _aggregate(rows, labels)
payload = {"question": 2, "methods": [m.__dict__ for m in METHODS],
"cases": case_list, "rows": rows, "aggregate": aggregate}
write_json(RESULTS_DIR / "q2.json", payload)
write_text(RESULTS_DIR / "q2.md", _render(rows, aggregate, labels))
return payload
+42
View File
@@ -0,0 +1,42 @@
"""问题二最优实现:保证接收盘内的最坏后验半径最小化选点(生产实现)。
复用 ``mcm26b.planner.select_measurement_point``:
1. 在"保证接收盘" B(mec_center, 1000-ρ) 内环形采样候选点;
2. 对每个候选,用"目标边界点 × 读数区间端点(+near 分支)"估计
最坏后验最小包围半径,并以移动时间同分决胜;
3. 选定后按 1/4 间距加密复核,返回位置、后验半径与理由。
采样是启发式(不是连续问题全局最优证明),但保证接收是严格的。
"""
from __future__ import annotations
from mcm26b.config import ProblemConfig
from mcm26b.planner import safe_disk_radius, select_measurement_point
from project.common import Method, problem_config
from project.q2.region import first_bearing_region
METHOD = Method(
key="optimal",
name="保证接收盘内最小最坏后验半径",
kind="optimal",
note="生产实现 mcm26b.planner.select_measurement_point",
)
def candidate(case: dict, config: ProblemConfig | None = None) -> dict:
config = config or problem_config()
region = first_bearing_region(case, config)
station = (case["station"][0], case["station"][1])
plan = select_measurement_point(region, station, config)
return {
"method": METHOD.key,
"position": [round(plan.position[0], 4), round(plan.position[1], 4)],
"worst_posterior_radius_m": plan.worst_posterior_radius_m,
"move_cost_s": plan.move_cost_s,
"safe_disk_radius_m": safe_disk_radius(region, config),
"initial_radius_m": region.mec()[1],
"reason": plan.reason,
}
+67
View File
@@ -0,0 +1,67 @@
"""问题二近似最优参考:在保证接收盘内做稠密网格枚举。
目标函数与生产实现一致(最坏后验包围半径 + 移动时间同分决胜),但候选点
取自**均匀网格**而非环形采样族,因此可用来检验生产采样是否接近该目标下的
网格最优。注意这是"同一目标下的网格参考",不是连续问题的严格全局最优。
"""
from __future__ import annotations
import math
from mcm26b.config import ProblemConfig
from mcm26b.planner import (_worst_posterior_radius, safe_disk_radius)
from project.common import Method, problem_config
from project.q2.region import first_bearing_region
METHOD = Method(
key="reference",
name="保证接收盘内网格枚举",
kind="reference",
note="同一目标下的稠密网格参考,非连续全局最优",
)
GRID_SIDES = 9
WITNESS_SPACING_M = 150.0
def candidate(case: dict, config: ProblemConfig | None = None) -> dict:
config = config or problem_config()
region = first_bearing_region(case, config)
station = (case["station"][0], case["station"][1])
center = region.mec()[0]
safe = safe_disk_radius(region, config)
if safe < 0:
return _payload(center, region, config, station, "无保证接收盘:退回 MEC 圆心")
field, search = config.field, config.search
half_angle = field.bearing_error_half_deg + search.bearing_rounding_margin_deg
witnesses = region.witnesses(WITNESS_SPACING_M)
step = 2.0 * safe / (GRID_SIDES - 1)
best_key, best_point, best_worst = None, center, None
for i in range(GRID_SIDES):
for j in range(GRID_SIDES):
point = (center[0] - safe + i * step, center[1] - safe + j * step)
if math.dist(point, center) > safe + 1e-9:
continue
worst = _worst_posterior_radius(region, point, witnesses, half_angle,
field.near_radius_m, search.polygon_sides)
move = math.dist(station, point) / config.cost.speed_mps
key = (round(worst, 6), round(move, 6))
if best_key is None or key < best_key:
best_key, best_point, best_worst = key, point, worst
return _payload(best_point, region, config, station,
f"安全盘网格 {GRID_SIDES}×{GRID_SIDES} 取最坏后验最小",
worst_value=best_worst)
def _payload(point, region, config, station, reason, worst_value=None) -> dict:
return {
"method": METHOD.key,
"position": [round(point[0], 4), round(point[1], 4)],
"worst_posterior_radius_m": worst_value,
"move_cost_s": math.dist(station, point) / config.cost.speed_mps,
"safe_disk_radius_m": safe_disk_radius(region, config),
"initial_radius_m": region.mec()[1],
"reason": reason,
}
+24
View File
@@ -0,0 +1,24 @@
"""问题二候选集 K1 的统一构造(三个方法共用同一输入,保证配对公平)。
K1 = Ω ∩ W(s, θ ± δ_eff) ∩ B(s, 1500),δ_eff = 题设 1° + 示向度舍入裕量。
生产实现用"圆盘外切多边形 ∩ 半平面"的保守外包(``CandidateRegion``),
圆盘用外切多边形严格外包,保证真源不丢。
"""
from __future__ import annotations
from mcm26b.belief import CandidateRegion
from mcm26b.config import ProblemConfig, Vec2
from project.common import problem_config
def first_bearing_region(case: dict,
config: ProblemConfig | None = None) -> CandidateRegion:
config = config or problem_config()
field, search = config.field, config.search
half_angle = field.bearing_error_half_deg + search.bearing_rounding_margin_deg
station: Vec2 = (case["station"][0], case["station"][1])
region = CandidateRegion.initial(station, config)
region.clip_wedge(station, case["bearing_deg"], half_angle)
return region
+11
View File
@@ -0,0 +1,11 @@
"""问题三:全向源搜索—定位—清除闭环。
- ``optimal.py`` :贝叶斯网格粒子滤波 + 信息增益选点 + 滚动 2-opt(``bayes``);
- ``baseline.py``:49 点网格发现 + 就近服务(``grid``,历史基线),
以及七点解析覆盖批量扫频(``seven``,中间对照);
- ``compare.py`` :冻结基准集配对对比并落盘。
"""
from . import baseline, optimal
__all__ = ["optimal", "baseline"]
+28
View File
@@ -0,0 +1,28 @@
"""问题三基线实现:两条不共享贝叶斯决策的对照策略。
- ``OmnidirectionalLoop``(grid):49 点(700 m 步长)网格保证发现,
发现后就近进入定位子循环;策略简单、无信息增益选点、无任务排序优化。
- ``SevenBatch``(seven):七点解析覆盖 + 同站批量扫频 + 最近候选服务队列,
是"覆盖证明 + 批量调度"的中间对照(官方演练 12/12 全清)。
"""
from __future__ import annotations
from mcm26b.planner import OmnidirectionalLoop
from mcm26b.planner.seven import SevenBatch
from project.common import Method
GRID = Method(key="baseline", name="49 点网格 + 就近服务 (grid)",
kind="baseline", note="历史基线,无信息增益与滚动排序")
SEVEN = Method(key="seven", name="七点解析覆盖 + 批量扫频 (seven)",
kind="baseline", note="覆盖证明与批量调度的中间对照")
def build_grid(client, config):
"""工厂:49 点网格基线闭环。"""
return OmnidirectionalLoop(client, config)
def build_seven(client, config):
"""工厂:七点批量扫频中间对照。"""
return SevenBatch(client, config)
+32
View File
@@ -0,0 +1,32 @@
"""问题三冻结基准集配对对比:bayes(最优) vs grid(基线) vs seven(中间)。"""
from __future__ import annotations
from project.common import (RESULTS_DIR, closed_loop_payload, episodes_markdown,
execute_variants, problem_config, write_json,
write_text)
from project.q3 import baseline, optimal
VARIANTS = (
("optimal", optimal.build_loop),
("seven", baseline.build_seven),
("baseline", baseline.build_grid),
)
LABELS = [label for label, _ in VARIANTS]
BASE_LABEL = "baseline"
def run(quick: bool = False) -> dict:
from mcm26b.harness.suites import suite
scenes = suite("core", 3)
if quick:
scenes = scenes[:6]
config = problem_config()
rows = execute_variants(scenes, VARIANTS, config)
payload = closed_loop_payload(3, "core", scenes, VARIANTS, rows,
LABELS, BASE_LABEL, config)
write_json(RESULTS_DIR / "q3.json", payload)
write_text(RESULTS_DIR / "q3.md",
episodes_markdown("问题三:全向闭环配对对比(core 基准集)",
rows, LABELS, BASE_LABEL))
return payload
+19
View File
@@ -0,0 +1,19 @@
"""问题三最优实现:``mcm26b.planner.bayes_loop.BayesLoop``(bayes 策略)。
要点:逐频道网格粒子滤波维护幸存集;以期望信息增益选探点、
滚动时域 2-opt 排序任务;证书(凸证书或 PF 幸存集 MEC)达到 20 m 即清除;
全频道"已清除或证明不存在"后立即 /exit(虚拟时间即成绩口径)。
自建消融相对 SevenBatch 再降约 30%(docs/algo-3-bayes.md)。
"""
from __future__ import annotations
from mcm26b.planner.bayes_loop import BayesLoop
from project.common import Method
METHOD = Method(key="optimal", name="贝叶斯粒子滤波闭环 (bayes)",
kind="optimal", note="生产默认策略,虚拟时间口径")
def build_loop(client, config):
"""工厂:返回生产 bayes 闭环实例。"""
return BayesLoop(client, config)
+12
View File
@@ -0,0 +1,12 @@
"""问题四:含定向干扰源的检测与清除。
- ``optimal.py`` :``DirectionalExtension``(保证覆盖 + 定向观测推理 +
(位置×朝向) 联合信念 + ≤20 m 有限圆盘后备清除);
- ``baseline.py``:把问题三最优策略**直接搬用**的两条基线——
``bayes``(七站全向缺席证明)与 ``grid``(49 点全向覆盖,无后备清除);
- ``compare.py`` :core + boundary 基准集配对对比并落盘。
"""
from . import baseline, optimal
__all__ = ["optimal", "baseline"]
+29
View File
@@ -0,0 +1,29 @@
"""问题四基线实现:把问题三策略原样搬用的两条对照。
- ``bayes``:问题三最优策略直接用于混合源。它用七站环的**全向**缺席证明,
对"定向源朝外发射"场景会错误地把真源判为 ABSENT,属于"快而错"的典型;
- ``grid``:49 点全向网格(对定向源发现是完备的),但缺少定向观测推理与
≤20 m 后备清除,定位停滞时只能停在 DETECTED,属于"稳而不全"。
两条基线都不共享最优实现的联合信念与后备清除逻辑。
"""
from __future__ import annotations
from mcm26b.planner import OmnidirectionalLoop
from mcm26b.planner.bayes_loop import BayesLoop
from project.common import Method
BAYES = Method(key="baseline", name="问题三最优策略直接搬用 (bayes)",
kind="baseline", note="七站全向缺席证明在定向源上失效")
GRID = Method(key="baseline_grid", name="49 点网格全向策略 (grid)",
kind="baseline", note="覆盖完备但无定向推理与后备清除")
def build_bayes(client, config):
"""工厂:问题三 bayes 策略直接搬用。"""
return BayesLoop(client, config)
def build_grid(client, config):
"""工厂:49 点网格全向策略直接搬用。"""
return OmnidirectionalLoop(client, config)
+32
View File
@@ -0,0 +1,32 @@
"""问题四 core+boundary 基准集配对对比:directional(最优) vs 两条搬用基线。"""
from __future__ import annotations
from project.common import (RESULTS_DIR, closed_loop_payload, episodes_markdown,
execute_variants, problem_config, write_json,
write_text)
from project.q4 import baseline, optimal
VARIANTS = (
("optimal", optimal.build_loop),
("baseline_grid", baseline.build_grid),
("baseline", baseline.build_bayes),
)
LABELS = [label for label, _ in VARIANTS]
BASE_LABEL = "baseline"
def run(quick: bool = False) -> dict:
from mcm26b.harness.suites import suite
scenes = suite("core", 4) + suite("boundary", 4)
if quick:
scenes = scenes[:4]
config = problem_config()
rows = execute_variants(scenes, VARIANTS, config)
payload = closed_loop_payload(4, "core+boundary", scenes, VARIANTS, rows,
LABELS, BASE_LABEL, config)
write_json(RESULTS_DIR / "q4.json", payload)
write_text(RESULTS_DIR / "q4.md",
episodes_markdown("问题四:混合源闭环配对对比(core+boundary 基准集)",
rows, LABELS, BASE_LABEL))
return payload
+22
View File
@@ -0,0 +1,22 @@
"""问题四最优实现:``mcm26b.planner.DirectionalExtension``(directional 策略)。
三点相对全向闭环的加装:
1. 发现覆盖改用经全向/定向双口径证伪的 31 点最小命中集
(``q4_mincover_v2``,15 m/72 档验证零缺口);
2. 保证接收盘内 ``no_signal`` 只可能由朝向遮挡解释:记为"观测到定向",
对 MEC 圆心取对跖点复核,并用 (位置×朝向) 联合可行集选可见性探点;
3. 定位停滞时对有界候选集构造 ≤20 m 网格圆盘覆盖,预算核对后逐格 /clear
(清除与朝向无关),是定向源不依赖可见性的完备后备。
"""
from __future__ import annotations
from mcm26b.planner import DirectionalExtension
from project.common import Method
METHOD = Method(key="optimal", name="定向扩展闭环 (directional)",
kind="optimal", note="联合信念 + 后备清除 + 定向覆盖计划")
def build_loop(client, config):
"""工厂:返回定向扩展闭环实例。"""
return DirectionalExtension(client, config)
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
# 问题一:固定示例对比
参考解为独立的大框半平面裁剪 + 精确直径/MEC;
固定示例见 `examples/q1`,解析期望由 `tests/test_geometry*.py` 固化。
| 算例 | 来源 | 参考状态 | 最优状态 | 基线状态 | D参考(m) | D最优(m) | D基线(m) | |ΔD|最优 | |ΔD|基线 | 覆盖参考 | 覆盖最优 | 覆盖基线 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 三点交会 | examples/q1 | polygon | polygon | polygon | 26.99 | 26.99 | 21.21 | 0.00 | 5.77 | 否 | 否 | 是 |
| 直径圆反例 | examples/q1 | polygon | polygon | polygon | 40.00 | 40.00 | 50.00 | 0.00 | 10.00 | 否 | 否 | 是 |
| 近共线检测点 | examples/q1 | unbounded | unbounded | unbounded | — | — | — | — | — | — | — | — |
| 零误差退化线段 | examples/q1 | segment | segment | point | 100.00 | 100.00 | 0.00 | 0.00 | 100.00 | 是 | 是 | 是 |
| 不相容观测 | examples/q1 | empty | empty | empty | — | — | — | — | — | — | — | — |
## 汇总
| 指标 | 最优 | 基线 |
| --- | --- | --- |
| 状态判定准确率 | 100.0% | 80.0% |
| 直径平均绝对误差 (m) | 0.000 | 7.886 |
| 直径最大误差 (m) | 0.000 | 10.000 |
| 覆盖判定准确率 | 100.0% | 33.3% |
| 平均耗时 (ms) | 0.064 | 14.202 |
# 问题一:随机交会算例对比
参考解为独立的大框半平面裁剪 + 精确直径/MEC;
固定示例见 `examples/q1`,解析期望由 `tests/test_geometry*.py` 固化。
| 算例 | 来源 | 参考状态 | 最优状态 | 基线状态 | D参考(m) | D最优(m) | D基线(m) | |ΔD|最优 | |ΔD|基线 | 覆盖参考 | 覆盖最优 | 覆盖基线 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 随机交会 | synthetic | polygon | polygon | polygon | 40.32 | 40.32 | 29.15 | 0.00 | 11.16 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 33.36 | 33.36 | 35.36 | 0.00 | 2.00 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 39.78 | 39.78 | 40.31 | 0.00 | 0.53 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 37.50 | 37.50 | 36.06 | 0.00 | 1.44 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 48.24 | 48.24 | 47.43 | 0.00 | 0.81 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 27.97 | 27.97 | 25.00 | 0.00 | 2.97 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 31.84 | 31.84 | 32.02 | 0.00 | 0.17 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 39.13 | 39.13 | 31.62 | 0.00 | 7.51 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 31.60 | 31.60 | 32.02 | 0.00 | 0.42 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 34.49 | 34.49 | 32.02 | 0.00 | 2.48 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 32.61 | 32.61 | 26.93 | 0.00 | 5.68 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 37.41 | 37.41 | 35.36 | 0.00 | 2.05 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 35.15 | 35.15 | 36.06 | 0.00 | 0.90 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 20.56 | 20.56 | 18.03 | 0.00 | 2.53 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 25.23 | 25.23 | 18.03 | 0.00 | 7.20 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 31.61 | 31.61 | 32.02 | 0.00 | 0.41 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 22.19 | 22.19 | 14.14 | 0.00 | 8.05 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 26.31 | 26.31 | 18.03 | 0.00 | 8.28 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 37.85 | 37.85 | 28.28 | 0.00 | 9.56 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 25.78 | 25.78 | 11.18 | 0.00 | 14.60 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 22.53 | 22.53 | 18.03 | 0.00 | 4.51 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 36.84 | 36.84 | 26.93 | 0.00 | 9.91 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 51.21 | 51.21 | 47.17 | 0.00 | 4.04 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 32.88 | 32.88 | 29.15 | 0.00 | 3.73 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 29.99 | 29.99 | 32.02 | 0.00 | 2.02 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 36.50 | 36.50 | 28.28 | 0.00 | 8.21 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 26.38 | 26.38 | 18.03 | 0.00 | 8.35 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 50.11 | 50.11 | 49.24 | 0.00 | 0.86 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 23.29 | 23.29 | 22.36 | 0.00 | 0.93 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 40.73 | 40.73 | 42.43 | 0.00 | 1.70 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 34.09 | 34.09 | 25.00 | 0.00 | 9.09 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 23.35 | 23.35 | 18.03 | 0.00 | 5.32 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 14.89 | 14.89 | 11.18 | 0.00 | 3.71 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 38.12 | 38.12 | 33.54 | 0.00 | 4.58 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 13.68 | 13.68 | 5.00 | 0.00 | 8.68 | 是 | 是 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 41.44 | 41.44 | 43.01 | 0.00 | 1.57 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 22.99 | 22.99 | 18.03 | 0.00 | 4.97 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 34.00 | 34.00 | 35.36 | 0.00 | 1.35 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 16.20 | 16.20 | 7.07 | 0.00 | 9.13 | 否 | 否 | 是 |
| 随机交会 | synthetic | polygon | polygon | polygon | 26.90 | 26.90 | 25.00 | 0.00 | 1.90 | 否 | 否 | 是 |
## 汇总
| 指标 | 最优 | 基线 |
| --- | --- | --- |
| 状态判定准确率 | 100.0% | 100.0% |
| 直径平均绝对误差 (m) | 0.000 | 4.583 |
| 直径最大误差 (m) | 0.000 | 14.599 |
| 覆盖判定准确率 | 100.0% | 52.5% |
| 平均耗时 (ms) | 0.086 | 15.932 |
File diff suppressed because it is too large Load Diff
+137
View File
@@ -0,0 +1,137 @@
# 问题二:第二检测点选择对比
初始 ρ 为首次示向度候选集 K₁ 的最小包围半径;最坏后验按目标边界采样 × 读数区间端点统一评估。
| 算例 | 方法 | 初始 ρ(m) | 第二点 q | 保证接收 | q 到 K₁ 最远(m) | 最坏后验 ρ'(m) | 收缩(m) | 移动(s) |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| q2-4200 | optimal | 674.59 | (1257,89) | 是 | 907.09 | 54.45 | 620.13 | 181.42 |
| q2-4200 | baseline_perp | 674.59 | (194,462) | 否 | 1679.16 | 76.22 | 598.36 | 200.00 |
| q2-4200 | baseline_mec | 674.59 | (1173,-226) | 是 | 674.59 | 337.29 | 337.29 | 134.92 |
| q2-4200 | reference | 674.59 | (1173,100) | 是 | 850.11 | 57.70 | 616.89 | 170.02 |
| q2-4201 | optimal | 261.33 | (-1501,-348) | 是 | 461.91 | 13.55 | 247.78 | 92.38 |
| q2-4201 | baseline_perp | 261.33 | (-782,-715) | 否 | 1131.68 | 27.22 | 234.11 | 200.00 |
| q2-4201 | baseline_mec | 261.33 | (-1578,-63) | 是 | 261.33 | 130.67 | 130.67 | 52.27 |
| q2-4201 | reference | 261.33 | (-1393,-432) | 是 | 527.03 | 14.98 | 246.36 | 105.41 |
| q2-4202 | optimal | 591.09 | (-1126,-861) | 是 | 869.01 | 37.92 | 553.17 | 173.80 |
| q2-4202 | baseline_perp | 591.09 | (408,-1235) | 否 | 1561.73 | 64.57 | 526.52 | 200.00 |
| q2-4202 | baseline_mec | 591.09 | (-731,-967) | 是 | 591.09 | 295.54 | 295.54 | 118.22 |
| q2-4202 | reference | 591.09 | (-1038,-865) | 是 | 788.83 | 46.22 | 544.86 | 157.77 |
| q2-4203 | optimal | 326.92 | (1145,-1745) | 是 | 683.58 | 17.13 | 309.79 | 136.72 |
| q2-4203 | baseline_perp | 326.92 | (1837,-1523) | 否 | 1204.35 | 32.99 | 293.93 | 200.00 |
| q2-4203 | baseline_mec | 326.92 | (788,-1388) | 是 | 326.92 | 163.46 | 163.46 | 65.38 |
| q2-4203 | reference | 326.92 | (1125,-1725) | 是 | 657.98 | 16.90 | 310.02 | 131.60 |
| q2-4204 | optimal | 750.20 | (-401,208) | 是 | 876.74 | 91.60 | 658.60 | 175.35 |
| q2-4204 | baseline_perp | 750.20 | (-1541,302) | 否 | 1817.48 | 90.79 | 659.41 | 200.00 |
| q2-4204 | baseline_mec | 750.20 | (-336,-34) | 是 | 750.20 | 375.10 | 375.10 | 150.04 |
| q2-4204 | reference | 750.20 | (-336,216) | 是 | 923.80 | 92.25 | 657.95 | 184.76 |
| q2-4205 | optimal | 750.14 | (-503,21) | 是 | 881.59 | 91.10 | 659.04 | 176.32 |
| q2-4205 | baseline_perp | 750.14 | (-612,-1470) | 否 | 1817.36 | 90.78 | 659.36 | 200.00 |
| q2-4205 | baseline_mec | 750.14 | (-568,-220) | 是 | 750.14 | 375.08 | 375.06 | 150.03 |
| q2-4205 | reference | 750.14 | (-568,30) | 是 | 927.74 | 92.25 | 657.89 | 185.55 |
| q2-4206 | optimal | 750.12 | (216,677) | 是 | 892.00 | 90.96 | 659.16 | 178.40 |
| q2-4206 | baseline_perp | 750.12 | (1592,1266) | 否 | 1817.31 | 90.77 | 659.34 | 200.00 |
| q2-4206 | baseline_mec | 750.12 | (457,742) | 是 | 750.12 | 375.10 | 375.02 | 150.02 |
| q2-4206 | reference | 750.12 | (207,742) | 是 | 838.72 | 94.21 | 655.91 | 167.74 |
| q2-4207 | optimal | 750.12 | (1205,476) | 是 | 899.09 | 90.85 | 659.27 | 179.82 |
| q2-4207 | baseline_perp | 750.12 | (578,1465) | 否 | 1817.31 | 90.78 | 659.34 | 200.00 |
| q2-4207 | baseline_mec | 750.12 | (1028,299) | 是 | 750.12 | 375.09 | 375.03 | 150.02 |
| q2-4207 | reference | 750.12 | (1028,49) | 是 | 852.52 | 92.97 | 657.15 | 170.50 |
| q2-4208 | optimal | 750.19 | (-1409,-294) | 是 | 901.20 | 90.75 | 659.44 | 180.24 |
| q2-4208 | baseline_perp | 750.19 | (-8,239) | 否 | 1817.46 | 90.79 | 659.40 | 200.00 |
| q2-4208 | baseline_mec | 750.19 | (-1167,-229) | 是 | 750.19 | 375.10 | 375.09 | 150.04 |
| q2-4208 | reference | 750.19 | (-1417,-229) | 是 | 849.54 | 93.05 | 657.14 | 169.91 |
| q2-4209 | optimal | 750.20 | (463,849) | 是 | 849.70 | 93.03 | 657.17 | 169.94 |
| q2-4209 | baseline_perp | 750.20 | (-621,1100) | 否 | 1817.43 | 90.79 | 659.41 | 200.00 |
| q2-4209 | baseline_mec | 750.20 | (527,607) | 是 | 750.20 | 375.10 | 375.10 | 150.04 |
| q2-4209 | reference | 750.20 | (527,857) | 是 | 901.34 | 90.74 | 659.46 | 180.27 |
| q2-4210 | optimal | 706.03 | (-429,1151) | 是 | 915.53 | 63.69 | 642.34 | 183.11 |
| q2-4210 | baseline_perp | 706.03 | (-206,15) | 否 | 1722.00 | 80.64 | 625.39 | 200.00 |
| q2-4210 | baseline_mec | 706.03 | (-145,1227) | 是 | 706.03 | 353.02 | 353.02 | 141.21 |
| q2-4210 | reference | 706.03 | (-366,1080) | 是 | 830.65 | 72.40 | 633.63 | 166.13 |
| q2-4211 | optimal | 750.20 | (-619,-443) | 是 | 901.34 | 90.74 | 659.46 | 180.27 |
| q2-4211 | baseline_perp | 750.20 | (-379,1037) | 否 | 1817.47 | 90.79 | 659.41 | 200.00 |
| q2-4211 | baseline_mec | 750.20 | (-554,-202) | 是 | 750.20 | 375.10 | 375.10 | 150.04 |
| q2-4211 | reference | 750.20 | (-554,-451) | 是 | 943.43 | 94.71 | 655.49 | 188.69 |
| q2-4212 | optimal | 750.13 | (343,-10) | 是 | 905.38 | 90.90 | 659.23 | 181.08 |
| q2-4212 | baseline_perp | 750.13 | (1486,-980) | 否 | 1817.34 | 90.78 | 659.35 | 200.00 |
| q2-4212 | baseline_mec | 750.13 | (520,-187) | 是 | 750.13 | 375.09 | 375.04 | 150.03 |
| q2-4212 | reference | 750.13 | (520,-437) | 是 | 845.04 | 93.65 | 656.48 | 169.01 |
| q2-4213 | optimal | 750.12 | (-347,-795) | 是 | 859.45 | 92.44 | 657.68 | 171.89 |
| q2-4213 | baseline_perp | 750.12 | (-1650,-75) | 否 | 1817.31 | 90.77 | 659.34 | 200.00 |
| q2-4213 | baseline_mec | 750.12 | (-524,-618) | 是 | 750.12 | 375.09 | 375.03 | 150.02 |
| q2-4213 | reference | 750.12 | (-524,-368) | 是 | 893.01 | 90.88 | 659.24 | 178.60 |
| q2-4214 | optimal | 750.16 | (623,-1142) | 是 | 906.31 | 90.87 | 659.29 | 181.26 |
| q2-4214 | baseline_perp | 750.16 | (-445,-639) | 否 | 1817.36 | 90.78 | 659.38 | 200.00 |
| q2-4214 | baseline_mec | 750.16 | (559,-1383) | 是 | 750.16 | 375.09 | 375.07 | 150.03 |
| q2-4214 | reference | 750.16 | (559,-1134) | 是 | 855.60 | 92.58 | 657.58 | 171.12 |
| q2-4215 | optimal | 559.27 | (-1338,120) | 是 | 820.56 | 35.56 | 523.71 | 164.11 |
| q2-4215 | baseline_perp | 559.27 | (-731,-1350) | 否 | 1513.40 | 59.96 | 499.32 | 200.00 |
| q2-4215 | baseline_mec | 559.27 | (-1224,-306) | 是 | 559.27 | 279.64 | 279.64 | 111.85 |
| q2-4215 | reference | 559.27 | (-1444,-636) | 是 | 828.17 | 35.86 | 523.42 | 165.63 |
| q2-4216 | optimal | 725.32 | (-758,-1062) | 是 | 869.88 | 65.59 | 659.73 | 173.97 |
| q2-4216 | baseline_perp | 725.32 | (-669,40) | 否 | 1747.41 | 79.80 | 645.52 | 200.00 |
| q2-4216 | baseline_mec | 725.32 | (-1023,-1133) | 是 | 725.32 | 362.66 | 362.66 | 145.06 |
| q2-4216 | reference | 725.32 | (-749,-1133) | 是 | 919.99 | 63.67 | 661.65 | 184.00 |
| q2-4217 | optimal | 750.16 | (-1472,727) | 是 | 848.85 | 93.13 | 657.03 | 169.77 |
| q2-4217 | baseline_perp | 750.16 | (10,819) | 否 | 1817.41 | 90.78 | 659.38 | 200.00 |
| q2-4217 | baseline_mec | 750.16 | (-1231,663) | 是 | 750.16 | 375.09 | 375.07 | 150.03 |
| q2-4217 | reference | 750.16 | (-1481,663) | 是 | 900.62 | 90.77 | 659.39 | 180.12 |
| q2-4218 | optimal | 750.18 | (-332,-300) | 是 | 864.09 | 92.27 | 657.91 | 172.82 |
| q2-4218 | baseline_perp | 750.18 | (-1821,-299) | 否 | 1817.39 | 90.78 | 659.39 | 200.00 |
| q2-4218 | baseline_mec | 750.18 | (-573,-235) | 是 | 750.18 | 375.09 | 375.08 | 150.04 |
| q2-4218 | reference | 750.18 | (-323,-235) | 是 | 913.40 | 91.48 | 658.69 | 182.68 |
| q2-4219 | optimal | 750.13 | (-75,786) | 是 | 868.14 | 91.76 | 658.37 | 173.63 |
| q2-4219 | baseline_perp | 750.13 | (-100,-705) | 否 | 1817.34 | 90.78 | 659.35 | 200.00 |
| q2-4219 | baseline_mec | 750.13 | (-140,544) | 是 | 750.13 | 375.09 | 375.04 | 150.03 |
| q2-4219 | reference | 750.13 | (-140,794) | 是 | 916.75 | 91.35 | 658.78 | 183.35 |
| q2-4220 | optimal | 750.13 | (-142,237) | 是 | 865.60 | 92.06 | 658.07 | 173.12 |
| q2-4220 | baseline_perp | 750.13 | (-1191,-184) | 否 | 1817.34 | 90.78 | 659.35 | 200.00 |
| q2-4220 | baseline_mec | 750.13 | (35,60) | 是 | 750.13 | 375.09 | 375.04 | 150.03 |
| q2-4220 | reference | 750.13 | (285,60) | 是 | 887.39 | 90.95 | 659.18 | 177.48 |
| q2-4221 | optimal | 750.12 | (537,-636) | 是 | 859.18 | 92.47 | 657.65 | 171.84 |
| q2-4221 | baseline_perp | 750.12 | (-232,638) | 否 | 1817.32 | 90.78 | 659.35 | 200.00 |
| q2-4221 | baseline_mec | 750.12 | (472,-395) | 是 | 750.12 | 375.09 | 375.03 | 150.02 |
| q2-4221 | reference | 750.12 | (472,-644) | 是 | 801.14 | 98.78 | 651.34 | 160.23 |
| q2-4222 | optimal | 672.85 | (1340,149) | 是 | 896.12 | 46.69 | 626.15 | 179.22 |
| q2-4222 | baseline_perp | 672.85 | (461,-526) | 否 | 1662.43 | 70.95 | 601.90 | 200.00 |
| q2-4222 | baseline_mec | 672.85 | (1571,-82) | 是 | 672.85 | 336.42 | 336.42 | 134.57 |
| q2-4222 | reference | 672.85 | (1326,82) | 是 | 830.57 | 53.42 | 619.43 | 166.11 |
| q2-4223 | optimal | 395.70 | (-1736,-787) | 是 | 696.84 | 21.22 | 374.48 | 139.37 |
| q2-4223 | baseline_perp | 395.70 | (-1461,-1548) | 否 | 1286.11 | 39.72 | 355.98 | 200.00 |
| q2-4223 | baseline_mec | 395.70 | (-1416,-467) | 是 | 395.70 | 197.85 | 197.85 | 79.14 |
| q2-4223 | reference | 395.70 | (-1416,-14) | 是 | 716.03 | 20.83 | 374.87 | 143.21 |
| q2-4224 | optimal | 750.16 | (-899,669) | 是 | 888.47 | 90.87 | 659.29 | 177.69 |
| q2-4224 | baseline_perp | 750.16 | (467,1281) | 否 | 1817.40 | 90.78 | 659.38 | 200.00 |
| q2-4224 | baseline_mec | 750.16 | (-657,734) | 是 | 750.16 | 375.09 | 375.07 | 150.03 |
| q2-4224 | reference | 750.16 | (-907,734) | 是 | 834.61 | 94.50 | 655.66 | 166.92 |
| q2-4225 | optimal | 750.12 | (-305,-294) | 是 | 866.51 | 91.95 | 658.17 | 173.30 |
| q2-4225 | baseline_perp | 750.12 | (-1064,-1577) | 否 | 1817.31 | 90.77 | 659.34 | 200.00 |
| q2-4225 | baseline_mec | 750.12 | (-482,-470) | 是 | 750.12 | 375.10 | 375.02 | 150.02 |
| q2-4225 | reference | 750.12 | (-732,-470) | 是 | 886.54 | 91.02 | 659.10 | 177.31 |
| q2-4226 | optimal | 750.12 | (587,389) | 是 | 898.38 | 90.90 | 659.22 | 179.68 |
| q2-4226 | baseline_perp | 750.12 | (1757,433) | 否 | 1817.31 | 90.77 | 659.34 | 200.00 |
| q2-4226 | baseline_mec | 750.12 | (523,630) | 是 | 750.12 | 375.11 | 375.01 | 150.02 |
| q2-4226 | reference | 750.12 | (523,380) | 是 | 941.12 | 94.47 | 655.65 | 188.22 |
| q2-4227 | optimal | 527.91 | (711,-1627) | 是 | 843.99 | 29.98 | 497.92 | 168.80 |
| q2-4227 | baseline_perp | 527.91 | (1503,-1122) | 否 | 1466.90 | 55.63 | 472.28 | 200.00 |
| q2-4227 | baseline_mec | 527.91 | (377,-1293) | 是 | 527.91 | 263.95 | 263.95 | 105.58 |
| q2-4227 | reference | 527.91 | (-95,-1293) | 是 | 819.70 | 30.74 | 497.17 | 163.94 |
| q2-4228 | optimal | 750.12 | (-160,534) | 是 | 892.39 | 90.93 | 659.19 | 178.48 |
| q2-4228 | baseline_perp | 750.12 | (20,2020) | 否 | 1817.31 | 90.77 | 659.34 | 200.00 |
| q2-4228 | baseline_mec | 750.12 | (-96,775) | 是 | 750.12 | 375.11 | 375.01 | 150.02 |
| q2-4228 | reference | 750.12 | (-96,525) | 是 | 936.39 | 93.58 | 656.54 | 187.28 |
| q2-4229 | optimal | 750.20 | (455,-883) | 是 | 876.70 | 91.34 | 658.86 | 175.34 |
| q2-4229 | baseline_perp | 750.20 | (-684,-787) | 否 | 1817.44 | 90.79 | 659.41 | 200.00 |
| q2-4229 | baseline_mec | 750.20 | (519,-1124) | 是 | 750.20 | 375.10 | 375.10 | 150.04 |
| q2-4229 | reference | 750.20 | (519,-874) | 是 | 923.76 | 91.91 | 658.30 | 184.75 |
## 逐方法汇总
| 方法 | 保证接收率 | 最坏后验均值(m) | 中位(m) | 平均收缩(m) | 终值比 ρ'/ρ | 移动均值(s) |
| --- | --- | --- | --- | --- | --- | --- |
| optimal | 100.0% | 73.89 | 90.90 | 607.58 | 0.104 | 170.44 |
| baseline_perp | 0.0% | 80.11 | 90.78 | 601.36 | 0.116 | 200.00 |
| baseline_mec | 100.0% | 340.75 | 375.09 | 340.72 | 0.500 | 136.29 |
| reference | 100.0% | 75.63 | 91.35 | 605.84 | 0.106 | 170.28 |
初始 ρ 均值:681.47 m
File diff suppressed because it is too large Load Diff
+75
View File
@@ -0,0 +1,75 @@
# 问题三:全向闭环配对对比(core 基准集)
## 逐场景明细
| scene | sources | variant | cleared | T(s) | 检测 | 请求 | 位移(m) | 后备清除 | 墙钟(s) | 有效 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| q3-core-s100-n10 | 10 | optimal | 10/10 | 2563.0 | 109 | 119 | 9330.1 | 0 | 0.70 | ✔ |
| q3-core-s100-n10 | 10 | seven | 10/10 | 4465.5 | 102 | 112 | 19057.6 | 0 | 1.18 | ✔ |
| q3-core-s100-n10 | 10 | baseline | 10/10 | 15309.6 | 564 | 574 | 59457.9 | 0 | 1.16 | ✔ |
| q3-core-s100-n13 | 13 | optimal | 13/13 | 3120.8 | 114 | 127 | 11914.1 | 0 | 0.73 | ✔ |
| q3-core-s100-n13 | 13 | seven | 13/13 | 5216.0 | 96 | 109 | 22919.8 | 0 | 1.40 | ✔ |
| q3-core-s100-n13 | 13 | baseline | 13/13 | 16205.3 | 450 | 463 | 67311.6 | 0 | 1.46 | ✔ |
| q3-core-s100-n16 | 16 | optimal | 16/16 | 3618.1 | 113 | 129 | 14355.4 | 0 | 0.89 | ✔ |
| q3-core-s100-n16 | 16 | seven | 16/16 | 5978.8 | 96 | 112 | 26653.8 | 0 | 1.66 | ✔ |
| q3-core-s100-n16 | 16 | baseline | 16/16 | 13180.4 | 254 | 270 | 58011.9 | 0 | 1.85 | ✔ |
| q3-core-s101-n10 | 10 | optimal | 10/10 | 3101.7 | 119 | 129 | 11723.5 | 0 | 0.69 | ✔ |
| q3-core-s101-n10 | 10 | seven | 10/10 | 4498.1 | 107 | 117 | 19065.6 | 0 | 1.11 | ✔ |
| q3-core-s101-n10 | 10 | baseline | 10/10 | 15552.8 | 545 | 555 | 61248.9 | 0 | 1.15 | ✔ |
| q3-core-s101-n13 | 13 | optimal | 13/13 | 2943.3 | 112 | 125 | 11106.4 | 0 | 0.85 | ✔ |
| q3-core-s101-n13 | 13 | seven | 13/13 | 5390.3 | 101 | 114 | 23656.4 | 0 | 1.43 | ✔ |
| q3-core-s101-n13 | 13 | baseline | 13/13 | 16620.6 | 452 | 465 | 69338.1 | 0 | 1.60 | ✔ |
| q3-core-s101-n16 | 16 | optimal | 16/16 | 3560.0 | 99 | 115 | 14469.9 | 0 | 0.81 | ✔ |
| q3-core-s101-n16 | 16 | seven | 16/16 | 6462.4 | 92 | 108 | 29211.8 | 0 | 1.76 | ✔ |
| q3-core-s101-n16 | 16 | baseline | 16/16 | 13434.4 | 260 | 276 | 59121.8 | 0 | 1.96 | ✔ |
| q3-core-s102-n10 | 10 | optimal | 10/10 | 2916.0 | 125 | 135 | 10634.8 | 0 | 0.58 | ✔ |
| q3-core-s102-n10 | 10 | seven | 10/10 | 5411.3 | 118 | 128 | 23326.6 | 0 | 1.28 | ✔ |
| q3-core-s102-n10 | 10 | baseline | 10/10 | 15015.5 | 613 | 623 | 56517.5 | 0 | 1.28 | ✔ |
| q3-core-s102-n13 | 13 | optimal | 13/13 | 3039.5 | 122 | 135 | 11242.3 | 0 | 0.84 | ✔ |
| q3-core-s102-n13 | 13 | seven | 13/13 | 5209.5 | 112 | 125 | 22407.3 | 0 | 1.52 | ✔ |
| q3-core-s102-n13 | 13 | baseline | 13/13 | 16062.8 | 450 | 463 | 66603.8 | 0 | 1.54 | ✔ |
| q3-core-s102-n16 | 16 | optimal | 16/16 | 3266.2 | 119 | 135 | 12441.1 | 0 | 0.74 | ✔ |
| q3-core-s102-n16 | 16 | seven | 16/16 | 5549.9 | 97 | 113 | 24489.6 | 0 | 1.65 | ✔ |
| q3-core-s102-n16 | 16 | baseline | 16/16 | 13146.4 | 282 | 298 | 57012.1 | 0 | 1.71 | ✔ |
| q3-core-s103-n10 | 10 | optimal | 10/10 | 2847.8 | 112 | 122 | 10684.1 | 0 | 0.68 | ✔ |
| q3-core-s103-n10 | 10 | seven | 10/10 | 4877.3 | 110 | 120 | 20886.4 | 0 | 1.22 | ✔ |
| q3-core-s103-n10 | 10 | baseline | 10/10 | 14895.2 | 550 | 560 | 57815.9 | 0 | 1.27 | ✔ |
| q3-core-s103-n13 | 13 | optimal | 13/13 | 3339.9 | 114 | 127 | 13024.4 | 0 | 0.71 | ✔ |
| q3-core-s103-n13 | 13 | seven | 13/13 | 5344.6 | 102 | 115 | 23373.1 | 0 | 1.26 | ✔ |
| q3-core-s103-n13 | 13 | baseline | 13/13 | 16691.4 | 501 | 514 | 68212.0 | 0 | 1.56 | ✔ |
| q3-core-s103-n16 | 16 | optimal | 16/16 | 3378.8 | 99 | 115 | 13559.2 | 0 | 0.89 | ✔ |
| q3-core-s103-n16 | 16 | seven | 16/16 | 5526.4 | 87 | 103 | 24681.8 | 0 | 1.77 | ✔ |
| q3-core-s103-n16 | 16 | baseline | 16/16 | 12627.8 | 256 | 272 | 55189.0 | 0 | 1.83 | ✔ |
| q3-core-s104-n10 | 10 | optimal | 10/10 | 3317.0 | 115 | 125 | 12950.2 | 0 | 0.66 | ✔ |
| q3-core-s104-n10 | 10 | seven | 10/10 | 4915.9 | 105 | 115 | 21219.3 | 0 | 1.06 | ✔ |
| q3-core-s104-n10 | 10 | baseline | 10/10 | 15302.4 | 549 | 559 | 59887.2 | 0 | 1.19 | ✔ |
| q3-core-s104-n13 | 13 | optimal | 13/13 | 3085.2 | 99 | 112 | 12176.2 | 0 | 0.75 | ✔ |
| q3-core-s104-n13 | 13 | seven | 13/13 | 5437.5 | 97 | 110 | 24012.7 | 0 | 1.48 | ✔ |
| q3-core-s104-n13 | 13 | baseline | 13/13 | 16278.7 | 444 | 457 | 67868.5 | 0 | 1.56 | ✔ |
| q3-core-s104-n16 | 16 | optimal | 16/16 | 3057.8 | 85 | 101 | 12379.1 | 0 | 0.77 | ✔ |
| q3-core-s104-n16 | 16 | seven | 16/16 | 5340.9 | 85 | 101 | 23799.8 | 0 | 1.68 | ✔ |
| q3-core-s104-n16 | 16 | baseline | 16/16 | 12872.6 | 224 | 240 | 57378.0 | 0 | 2.00 | ✔ |
| q3-core-s105-n10 | 10 | optimal | 10/10 | 2933.1 | 108 | 118 | 11220.4 | 0 | 0.72 | ✔ |
| q3-core-s105-n10 | 10 | seven | 10/10 | 4370.8 | 107 | 117 | 18433.9 | 0 | 1.08 | ✔ |
| q3-core-s105-n10 | 10 | baseline | 10/10 | 15247.7 | 579 | 589 | 58688.4 | 0 | 1.17 | ✔ |
| q3-core-s105-n13 | 13 | optimal | 13/13 | 3645.3 | 122 | 135 | 14331.4 | 0 | 0.96 | ✔ |
| q3-core-s105-n13 | 13 | seven | 13/13 | 6311.3 | 104 | 117 | 28171.5 | 0 | 1.66 | ✔ |
| q3-core-s105-n13 | 13 | baseline | 13/13 | 16509.4 | 485 | 498 | 67791.8 | 0 | 1.52 | ✔ |
| q3-core-s105-n16 | 16 | optimal | 16/16 | 3663.7 | 114 | 130 | 14543.5 | 0 | 0.81 | ✔ |
| q3-core-s105-n16 | 16 | seven | 16/16 | 6204.3 | 97 | 113 | 27776.7 | 0 | 1.55 | ✔ |
| q3-core-s105-n16 | 16 | baseline | 16/16 | 13889.0 | 292 | 308 | 60450.1 | 0 | 1.94 | ✔ |
## 逐方法汇总
| 方法 | 有效局 | 全清率 | 全清对数 | 缺席误判 | T 均值(s) | T 中位(s) | T 最大(s) | 检测均值 | 请求均值 | 位移均值(m) | 后备清除均值 | 墙钟均值(s) |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| optimal | 18/18 | 1.00 | 18 | 0 | 3188.7 | 3111.3 | 3663.7 | 111.1 | 124.1 | 12338.1 | 0.00 | 0.77 |
| seven | 18/18 | 1.00 | 18 | 0 | 5361.7 | 5367.5 | 6462.4 | 100.8 | 113.8 | 23508.0 | 0.00 | 1.43 |
| baseline | 18/18 | 1.00 | 18 | 0 | 14935.7 | 15275.1 | 16691.4 | 430.6 | 443.6 | 61550.2 | 0.00 | 1.54 |
## 相对基线的配对变化(虚拟时间)
| 配对 | 均值变化 | 中位变化 | 更快场景 | 双方均有效 |
| --- | --- | --- | --- | --- |
| optimal_vs_baseline | -78.4% | -80.0% | 18/18 | 18 |
| seven_vs_baseline | -63.7% | -66.9% | 18/18 | 18 |
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
# 问题四:混合源闭环配对对比(core+boundary 基准集)
## 逐场景明细
| scene | sources | variant | cleared | T(s) | 检测 | 请求 | 位移(m) | 后备清除 | 墙钟(s) | 有效 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| q4-boundary-n10-d5 | 10 | optimal | 10/10 | 10269.5 | 488 | 540 | 36107.7 | 44 | 2.63 | ✔ |
| q4-boundary-n10-d5 | 10 | baseline_grid | 6/10 | 17632.7 | 735 | 741 | 66308.6 | 0 | 5.08 | ✘ |
| q4-boundary-n10-d5 | 10 | baseline | 5/10 | 3128.1 | 135 | 140 | 11530.4 | 0 | 0.53 | ✘ |
| q4-boundary-n16-d8 | 16 | optimal | 16/16 | 12081.1 | 421 | 626 | 44820.7 | 190 | 3.58 | ✔ |
| q4-boundary-n16-d8 | 16 | baseline_grid | 9/16 | 20457.9 | 623 | 632 | 83969.3 | 0 | 8.86 | ✘ |
| q4-boundary-n16-d8 | 16 | baseline | 8/16 | 3558.7 | 149 | 157 | 13173.5 | 0 | 0.52 | ✘ |
| q4-core-s200-n10-d3 | 10 | optimal | 10/10 | 8718.7 | 402 | 412 | 31508.6 | 0 | 8.30 | ✔ |
| q4-core-s200-n10-d3 | 10 | baseline_grid | 9/10 | 16546.8 | 576 | 585 | 65379.2 | 0 | 2.81 | ✘ |
| q4-core-s200-n10-d3 | 10 | baseline | 10/10 | 3354.1 | 116 | 126 | 13100.5 | 0 | 0.67 | ✘ |
| q4-core-s200-n13-d6 | 13 | optimal | 13/13 | 8808.4 | 332 | 347 | 33967.1 | 3 | 2.82 | ✔ |
| q4-core-s200-n13-d6 | 13 | baseline_grid | 7/13 | 18438.3 | 529 | 536 | 76651.4 | 0 | 8.21 | ✘ |
| q4-core-s200-n13-d6 | 13 | baseline | 11/13 | 3264.4 | 111 | 122 | 12761.9 | 0 | 0.80 | ✘ |
| q4-core-s200-n16-d8 | 16 | optimal | 16/16 | 7629.2 | 267 | 283 | 29970.8 | 0 | 3.27 | ✔ |
| q4-core-s200-n16-d8 | 16 | baseline_grid | 10/16 | 18871.3 | 526 | 536 | 78861.6 | 0 | 9.06 | ✘ |
| q4-core-s200-n16-d8 | 16 | baseline | 12/16 | 4026.1 | 143 | 155 | 15645.6 | 0 | 1.86 | ✘ |
| q4-core-s201-n10-d3 | 10 | optimal | 10/10 | 8514.0 | 380 | 391 | 31129.9 | 2 | 2.07 | ✔ |
| q4-core-s201-n10-d3 | 10 | baseline_grid | 7/10 | 16779.7 | 624 | 631 | 65293.4 | 0 | 5.71 | ✘ |
| q4-core-s201-n10-d3 | 10 | baseline | 7/10 | 2823.3 | 120 | 127 | 10391.4 | 0 | 0.71 | ✘ |
| q4-core-s201-n13-d6 | 13 | optimal | 13/13 | 9183.0 | 325 | 338 | 36095.1 | 0 | 2.50 | ✔ |
| q4-core-s201-n13-d6 | 13 | baseline_grid | 8/13 | 19390.8 | 584 | 592 | 79678.9 | 0 | 4.40 | ✘ |
| q4-core-s201-n13-d6 | 13 | baseline | 11/13 | 4965.2 | 143 | 165 | 20260.8 | 0 | 1.11 | ✘ |
| q4-core-s201-n16-d8 | 16 | optimal | 16/16 | 8129.4 | 263 | 279 | 32667.0 | 0 | 3.50 | ✔ |
| q4-core-s201-n16-d8 | 16 | baseline_grid | 9/16 | 19845.0 | 495 | 504 | 84765.2 | 0 | 10.22 | ✘ |
| q4-core-s201-n16-d8 | 16 | baseline | 13/16 | 4759.1 | 125 | 138 | 19835.5 | 0 | 2.12 | ✘ |
| q4-core-s202-n10-d3 | 10 | optimal | 10/10 | 8803.3 | 420 | 430 | 31426.3 | 0 | 2.36 | ✔ |
| q4-core-s202-n10-d3 | 10 | baseline_grid | 7/10 | 17220.0 | 669 | 676 | 66144.9 | 0 | 5.13 | ✘ |
| q4-core-s202-n10-d3 | 10 | baseline | 7/10 | 3646.2 | 135 | 142 | 14105.9 | 0 | 0.68 | ✘ |
| q4-core-s202-n13-d6 | 13 | optimal | 13/13 | 9630.4 | 347 | 367 | 37652.0 | 10 | 3.11 | ✔ |
| q4-core-s202-n13-d6 | 13 | baseline_grid | 10/13 | 16443.2 | 498 | 508 | 67316.3 | 0 | 1.84 | ✘ |
| q4-core-s202-n13-d6 | 13 | baseline | 13/13 | 4219.3 | 123 | 137 | 17136.7 | 0 | 0.81 | ✘ |
| q4-core-s202-n16-d8 | 16 | optimal | 16/16 | 8001.5 | 267 | 305 | 31602.7 | 24 | 3.95 | ✔ |
| q4-core-s202-n16-d8 | 16 | baseline_grid | 12/16 | 18345.2 | 452 | 464 | 78265.8 | 0 | 8.90 | ✘ |
| q4-core-s202-n16-d8 | 16 | baseline | 13/16 | 5054.4 | 152 | 165 | 20561.9 | 0 | 2.05 | ✘ |
## 逐方法汇总
| 方法 | 有效局 | 全清率 | 全清对数 | 缺席误判 | T 均值(s) | T 中位(s) | T 最大(s) | 检测均值 | 请求均值 | 位移均值(m) | 后备清除均值 | 墙钟均值(s) |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| optimal | 11/11 | 1.00 | 11 | 0 | 9069.9 | 8803.3 | 12081.1 | 355.6 | 392.5 | 34268.0 | 24.82 | 3.46 |
| baseline_grid | 0/11 | 0.67 | 0 | 0 | 18179.2 | 18345.2 | 20457.9 | 573.7 | 582.3 | 73875.9 | 0.00 | 6.38 |
| baseline | 0/11 | 0.77 | 2 | 24 | 3890.8 | 3646.2 | 5054.4 | 132.0 | 143.1 | 15318.6 | 0.00 | 1.08 |
## 相对基线的配对变化(虚拟时间)
| 配对 | 均值变化 | 中位变化 | 更快场景 | 双方均有效 |
| --- | --- | --- | --- | --- |
| optimal_vs_baseline | +142.9% | +141.4% | 0/11 | 0 |
| baseline_grid_vs_baseline | +381.1% | +372.3% | 0/11 | 0 |
> 注:optimal_vs_baseline、baseline_grid_vs_baseline 存在未通过有效性判定的配对;其虚拟时间仅作参考,基线未完成全清任务时不构成「更快」证据(以「双方均有效」列为准)。
+50
View File
@@ -0,0 +1,50 @@
# 1-4 问最优 vs 基线:对比总览
本节所有数字由 `uv run python -m project.run` 在同机同口径下生成,
逐问明细见 `q1.md` … `q4.md`。
| 问 | 核心指标 | 最优实现 | 基线实现 |
| --- | --- | --- | --- |
| 1 | 直径平均误差 / 覆盖准确率 | 0.00 m / 100% | 4.58 m / 52% |
| 2 | 保证接收率 / 最坏后验均值 | 100% / 73.9 m | 100% / 340.7 m |
| 3 | 全清率 / T 均值(相对基线) | 100% / 3189 s (-78%) | 100% / 14936 s |
| 4 | 全清率 / 缺席误判(问题三策略搬用) | 100% / 0 | 77% / 24 |
## 方法映射
| 问 | 最优实现 | 基线实现 | 独立参考 |
| --- | --- | --- | --- |
| 1 | 半平面交 + 顶点对直径 + MEC | 网格采样 + bbox 对角线代理 | 大框半平面裁剪 + 精确直径/MEC |
| 2 | 保证接收盘内最小最坏后验半径 | 垂直 1000 m / MEC 圆心 | 保证接收盘内网格枚举 |
| 3 | bayes(粒子滤波 + 信息增益 + 滚动调度) | grid(49 点网格)/ seven(七点批量) | 冻结基准集 + 逐微秒计时复算 |
| 4 | directional(联合信念 + 后备清除) | bayes / grid 直接搬用 | 冻结基准集 + 真值全清校验 |
## 目录结构
```text
project/
├── common.py 公共层:方法描述/指标聚合/结果落盘/闭环装配
├── q1/ 问题一:直径与直径圆覆盖
│ ├── optimal.py 生产实现:半平面交 + 顶点对直径 + MEC
│ ├── baseline.py 基线:网格采样 + bbox 对角线代理
│ ├── reference.py 参考:大框半平面裁剪 + 精确直径/MEC
│ ├── cases.py 固定示例(examples/q1)+ 随机交会算例
│ └── compare.py 同案配对对比
├── q2/ 问题二:第二检测点选择
│ ├── optimal.py 生产实现:保证接收盘内最小最坏后验半径
│ ├── baseline.py 基线:垂直 1000 m / MEC 圆心
│ ├── reference.py 参考:保证接收盘内网格枚举
│ ├── region.py K1 统一构造
│ ├── cases.py 随机首次示向度算例
│ └── compare.py
├── q3/ 问题三:全向闭环
│ ├── optimal.py bayes(生产默认策略)
│ ├── baseline.py grid(49 点网格)/ seven(七点批量)
│ └── compare.py
├── q4/ 问题四:混合源闭环
│ ├── optimal.py directional(定向扩展)
│ ├── baseline.py bayes / grid 直接搬用
│ └── compare.py
├── run.py 统一入口(本文件)
└── results/ 自动生成:q1.md/json … q4.md/json + summary.md
```
+148
View File
@@ -0,0 +1,148 @@
"""统一入口:一次跑完 1-4 问的"最优 vs 基线"对比并生成 results/。
用法(仓库根目录)::
uv run python -m project.run # 四问全部
uv run python -m project.run --question 3 # 只跑第三问
uv run python -m project.run --quick # 缩小算例/基准集规模
"""
from __future__ import annotations
import argparse
import time
from project.common import RESULTS_DIR, markdown_table, write_text
from project.q1 import compare as q1_compare
from project.q2 import compare as q2_compare
from project.q3 import compare as q3_compare
from project.q4 import compare as q4_compare
RUNNERS = {
1: q1_compare.run,
2: q2_compare.run,
3: q3_compare.run,
4: q4_compare.run,
}
STRUCTURE = """\
project/
├── common.py 公共层:方法描述/指标聚合/结果落盘/闭环装配
├── q1/ 问题一:直径与直径圆覆盖
│ ├── optimal.py 生产实现:半平面交 + 顶点对直径 + MEC
│ ├── baseline.py 基线:网格采样 + bbox 对角线代理
│ ├── reference.py 参考:大框半平面裁剪 + 精确直径/MEC
│ ├── cases.py 固定示例(examples/q1)+ 随机交会算例
│ └── compare.py 同案配对对比
├── q2/ 问题二:第二检测点选择
│ ├── optimal.py 生产实现:保证接收盘内最小最坏后验半径
│ ├── baseline.py 基线:垂直 1000 m / MEC 圆心
│ ├── reference.py 参考:保证接收盘内网格枚举
│ ├── region.py K1 统一构造
│ ├── cases.py 随机首次示向度算例
│ └── compare.py
├── q3/ 问题三:全向闭环
│ ├── optimal.py bayes(生产默认策略)
│ ├── baseline.py grid(49 点网格)/ seven(七点批量)
│ └── compare.py
├── q4/ 问题四:混合源闭环
│ ├── optimal.py directional(定向扩展)
│ ├── baseline.py bayes / grid 直接搬用
│ └── compare.py
├── run.py 统一入口(本文件)
└── results/ 自动生成:q1.md/json … q4.md/json + summary.md
"""
def _q1_headline(payload: dict) -> list[str]:
agg = payload["aggregate_random"]
return ["1", "直径平均误差 / 覆盖准确率",
f"{agg['diameter_mae_optimal_m']:.2f} m / "
f"{agg['coverage_accuracy_optimal']*100:.0f}%",
f"{agg['diameter_mae_baseline_m']:.2f} m / "
f"{agg['coverage_accuracy_baseline']*100:.0f}%"]
def _q2_headline(payload: dict) -> list[str]:
agg = payload["aggregate"]
opt = agg["optimal"]
base = agg["baseline_mec"]
return ["2", "保证接收率 / 最坏后验均值",
f"{opt['guaranteed_rate']*100:.0f}% / "
f"{opt['worst_posterior_mean_m']:.1f} m",
f"{base['guaranteed_rate']*100:.0f}% / "
f"{base['worst_posterior_mean_m']:.1f} m"]
def _q3_headline(payload: dict) -> list[str]:
agg = payload["aggregate"]["variants"]
opt, base = agg["optimal"], agg["baseline"]
paired = payload["aggregate"]["paired"].get("optimal_vs_baseline", {})
return ["3", "全清率 / T 均值(相对基线)",
f"{opt['cleared_ratio_mean']*100:.0f}% / "
f"{opt['virtual_time_mean_s']:.0f} s "
f"({paired.get('mean_relative_change', 0)*100:+.0f}%)",
f"{base['cleared_ratio_mean']*100:.0f}% / "
f"{base['virtual_time_mean_s']:.0f} s"]
def _q4_headline(payload: dict) -> list[str]:
agg = payload["aggregate"]["variants"]
opt, base = agg["optimal"], agg["baseline"]
return ["4", "全清率 / 缺席误判(问题三策略搬用)",
f"{opt['cleared_ratio_mean']*100:.0f}% / "
f"{opt['absent_misjudged_total']}",
f"{base['cleared_ratio_mean']*100:.0f}% / "
f"{base['absent_misjudged_total']}"]
def _write_summary(payloads: dict[int, dict]) -> None:
lines = ["# 1-4 问最优 vs 基线:对比总览", "",
"本节所有数字由 `uv run python -m project.run` 在同机同口径下生成,",
"逐问明细见 `q1.md` … `q4.md`。", ""]
rows = []
if 1 in payloads:
rows.append(_q1_headline(payloads[1]))
if 2 in payloads:
rows.append(_q2_headline(payloads[2]))
if 3 in payloads:
rows.append(_q3_headline(payloads[3]))
if 4 in payloads:
rows.append(_q4_headline(payloads[4]))
lines.append(markdown_table(["问", "核心指标", "最优实现", "基线实现"], rows))
lines += ["", "## 方法映射", "",
"| 问 | 最优实现 | 基线实现 | 独立参考 |",
"| --- | --- | --- | --- |",
"| 1 | 半平面交 + 顶点对直径 + MEC | 网格采样 + bbox 对角线代理 | 大框半平面裁剪 + 精确直径/MEC |",
"| 2 | 保证接收盘内最小最坏后验半径 | 垂直 1000 m / MEC 圆心 | 保证接收盘内网格枚举 |",
"| 3 | bayes(粒子滤波 + 信息增益 + 滚动调度) | grid(49 点网格)/ seven(七点批量) | 冻结基准集 + 逐微秒计时复算 |",
"| 4 | directional(联合信念 + 后备清除) | bayes / grid 直接搬用 | 冻结基准集 + 真值全清校验 |",
"", "## 目录结构", "", "```text", STRUCTURE.rstrip(), "```", ""]
write_text(RESULTS_DIR / "summary.md", "\n".join(lines))
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--question", choices=("1", "2", "3", "4", "all"),
default="all")
parser.add_argument("--quick", action="store_true",
help="缩小随机算例数/冻结基准集规模")
args = parser.parse_args(argv)
selected = [1, 2, 3, 4] if args.question == "all" else [int(args.question)]
payloads: dict[int, dict] = {}
for question in selected:
start = time.perf_counter()
print(f"[Q{question}] 运行对比 …", flush=True)
payloads[question] = RUNNERS[question](quick=args.quick)
print(f"[Q{question}] 完成,用时 {time.perf_counter() - start:.1f}s",
flush=True)
if selected == [1, 2, 3, 4]:
_write_summary(payloads)
print(f"总览已写入 {RESULTS_DIR / 'summary.md'}")
print(f"结果目录:{RESULTS_DIR}")
return 0
if __name__ == "__main__":
raise SystemExit(main())