This commit is contained in:
Horldsence
2026-09-13 10:27:09 +08:00
parent b810ec7777
commit 82311a47cc
52 changed files with 2563 additions and 1278 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33 -27
View File
@@ -1,48 +1,54 @@
# docs/figures —— 论文插图
每张图只输出 PNG,由 `scripts/make_figures.py` 用 matplotlib 生成。
每张图只输出 PNG。数据由 `scripts/build_figure_data.py` 从行为日志、覆盖计划与生产求解器
算好写入 `docs/figures/.data/*.mat`,再由 MATLAB(`scripts/matlab/*.m`)排版出图;
`manifest.json` 记录每张图的数据来源(路径 + sha256 前 12 位 + 行数)与生成命令。
```bash
uv run python scripts/make_figures.py # 全部 11 张
uv run python scripts/make_figures.py --list # 图名
uv run python scripts/make_figures.py --only geo-q3-seven-point-coverage
uv run python scripts/make_figures.py --scale 1.5 # 整体放大(尺寸倍率)
uv run python scripts/build_figure_data.py # 全部 11 张(数据 + 出图)
uv run python scripts/build_figure_data.py --prepare # 只写 .mat 数据
uv run python scripts/build_figure_data.py --list # 图名
uv run python scripts/build_figure_data.py --only geo-q3-seven-point-coverage
```
依赖:`matplotlib`、`Pillow`、`docs/paper/fonts/` 下的字体。
`matplotlib` 缓存写入仓库内 `.mpl-cache/`,不污染用户目录。
MATLAB 内手动出图:`cd scripts/matlab` 后执行 `run_all_figures`(每张图单独一个会话,
本机图形子系统连续渲染多张会崩)。
依赖:`numpy`、`scipy`(写 `.mat`)与 MATLAB R2025a。
## 约定
- 图内不放标题、图注与解释性文字,只保留坐标轴、图例与数据标注;
- **除回放画布(点运动图 `replay-webui-*`)外,全部使用 MATLAB 默认图形样式**:
默认配色(`defaultAxesColorOrder`)、默认字体与字号、默认坐标轴与图例;
不引入自定义调色板,不手绘图例色块。
- 图内不放标题、图注与解释性文字,只保留坐标轴、图例与必要的元素标注;
图的含义由文件名与下表承担,解释写在论文正文。
- 配色(`scripts/make_figures.py::C`):深蓝 `#1f4e79` 主流程与协议,
砖红 `#b03a2e` 真值源与关键结论,墨绿 `#1e7a5f` 清除与通过项,
土黄 `#b8860b` 后备与警示,灰色只用于网格与次要文字。
- 字体:中文宋体 `SongtiSC-Regular/Bold`,标题黑体 `HeitiSC-Light/Medium`,
数字与西文 Times New Roman,均取自 `docs/paper/fonts/`,与 `main.tex` 一致。
- 输出为 PNG(`savefig.dpi = 200`,另有 `--scale` 倍率),按 160 mm 宽排版时
与正文同字号。
- 图例一律用 MATLAB `legend` 放在坐标区外(`southoutside`),不压数据;
散点标签按候选方位自动避让,保证互不重叠。
- 输出 PNG(`exportgraphics`,300 dpi、白底),导出前关闭坐标轴工具条。
## 图清单
| 文件 | 内容 | 数据来源 |
| --- | --- | --- |
| `flow-overall-architecture.png` | 总体思路:题面抽象、在线三层、四问主线、三条闭环 | `src/mcm26b/config.py`、`docs/B题建模分析与实施计划.md` |
| `flow-overall-architecture.png` | 四层主线:题面抽象(L0)、在线三层(L1)、四问主线(L2)、三条闭环(L3) | `src/mcm26b/config.py`、`docs/B题建模分析与实施计划.md` |
| `steps-state-machine.png` | 会话状态机(IDLE / 倒计时 / OPEN / CLOSED) | `docs/协议规则表与状态机.md`、`src/mcm26b/protocol/client.py` |
| `timing-attachment-example-105-111-194-199.png` | 附件计时示例逐步推演 | `docs/协议规则表与状态机.md` |
| `timing-measured-q3-10-sources.png` | 实测一局的虚拟时间分解(累计曲线 + 构成) | `referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl` |
| `geo-q1-convex-intersection-zoom.png` | 问题一:角域交会总览 + 定位区域放大(直径圆与 MEC) | `output/q1/practice/U5ZW-P9KN-TCAG-R7ST_observations.json` |
| `geo-q1-diameter-circle-counterexample.png` | 问题一:直径圆不覆盖的反例(等边三角形布站) | `examples/q1/triangle.json` |
| `geo-q2-guaranteed-reception-disk.png` | 问题二:候选盘、截断角域外接圆、安全盘 `B(c, 1000−a)` | `src/mcm26b/config.py`、`docs/algo-2.md` |
| `geo-q3-seven-point-coverage.png` | 问题三:七点覆盖(7 个 1000 m 接收盘的并集覆盖 Ω)与三套计划对比 | `src/mcm26b/planner/seven.py`、`docs/algo-3.md` |
| `geo-q4-minimum-cover-31-points.png` | 问题四:31 点最小命中集与 `forward_half_disk` 语义 | `src/mcm26b/guarantee/plans/q4_mincover_v2.json` |
| `replay-webui-q3-omnidirectional.png` | WebUI 回放画布复现(全向局,10 源全清) | `referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl` |
| `replay-webui-q4-directional.png` | WebUI 回放画布复现(定向源局) | `referee-data/sessions/GM56-DFXZ-XKDM-XYV7/log.jsonl` |
| `timing-attachment-example-105-111-194-199.png` | 附件计时示例:逐动作耗时构成与单步合计 | `docs/协议规则表与状态机.md` |
| `timing-measured-q3-10-sources.png` | 实测一局:累计虚拟时间按动作类型的堆叠构成 | `referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl` |
| `geo-q1-convex-intersection-zoom.png` | 问题一:角域交会总览 + 定位区域放大(直径圆与最小包围圆) | `output/q1/practice/U5ZW-P9KN-TCAG-R7ST_observations.json` |
| `geo-q1-diameter-circle-counterexample.png` | 问题一:直径圆不覆盖定位区域的反例(等边三角形布站) | `examples/q1/triangle.json` |
| `geo-q2-guaranteed-reception-disk.png` | 问题二:截断角域外接圆 `B(c, a)` 与安全候选盘 `B(c, 1000 − a)`,右侧为半径分解 | `src/mcm26b/config.py`、`docs/algo-2.md` |
| `geo-q3-seven-point-coverage.png` | 问题三:七个 1000 m 接收盘覆盖 Ω,标出最差点 | `src/mcm26b/planner/seven.py`、`docs/algo-3.md` |
| `geo-q4-minimum-cover-31-points.png` | 问题四:31 点最小命中集与 ±90° 前向半盘语义 | `src/mcm26b/guarantee/plans/q4_mincover_v2.json` |
| `replay-webui-q3-omnidirectional.png` | 回放画布(点运动图,全向局):世界视图 + 事件流水 + 虚拟时间轴 | `referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl` |
| `replay-webui-q4-directional.png` | 回放画布(点运动图,定向源局):世界视图(含定向源 ±90° 前向半盘)+ 事件流水 + 虚拟时间轴 | `referee-data/sessions/GHSN-M38E-74GD-YLUD/log.jsonl` |
后两张对应 WebUI 的预览功能:把回放器画布上的 Ω 与真值源、运动轨迹、
检测示向度射线、清除标记,以及右侧事件流水与底部虚拟时间轴一起导出。
后两张是唯一保留自定义画布的图:把回放器画布上的 Ω 与真值源、移动轨迹、
检测示向度射线、定向源朝向与清除标记,连同右侧事件流水与底部虚拟时间轴一起导出;
定向源局额外画出每个定向源的 **±90° 前向半盘**(半径取该源 `r_eff_m`,朝向箭头即半盘半径)。
q4 回放固定用演练局 `GHSN-M38E-74GD-YLUD`(`build_figure_data.py::Q4_REPLAY_CASE`);
本机没有该局留档时回退到自动挑选,因此 `examples/replay/` 里归档了这一局。
## 数据来源与复现
Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 116 KiB

+31 -55
View File
@@ -1,36 +1,12 @@
{
"generator": "scripts/make_figures.py",
"command": "uv run python scripts/make_figures.py",
"palette": {
"ink": "#1c2430",
"muted": "#5c6673",
"grid": "#e4e9ee",
"primary": "#1f4e79",
"primary_fill": "#dce6f1",
"accent": "#b03a2e",
"accent_fill": "#f6dcd8",
"success": "#1e7a5f",
"success_fill": "#d8ece5",
"warn": "#b8860b",
"warn_fill": "#f6ecd2",
"violet": "#5b4b8a"
},
"fonts": {
"dir": "docs/paper/fonts",
"map": {
"song": "SongtiSC-Regular.ttf",
"song_bold": "SongtiSC-Bold.ttf",
"hei": "HeitiSC-Light.ttf",
"hei_bold": "HeitiSC-Medium.ttf",
"times": "Times New Roman.ttf",
"times_bold": "Times New Roman Bold.ttf"
}
},
"generator": "scripts/build_figure_data.py(数据)+ scripts/matlab/*.m(MATLAB 出图)",
"command": "uv run python scripts/build_figure_data.py",
"manual": "MATLAB 中 cd scripts/matlab 后执行 run_all_figures",
"figures": [
{
"stem": "flow-overall-architecture",
"png": "docs/figures/flow-overall-architecture.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_flow_overall.m",
"sources": [
{
"path": "src/mcm26b/config.py",
@@ -47,7 +23,7 @@
{
"stem": "steps-state-machine",
"png": "docs/figures/steps-state-machine.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_steps_state_machine.m",
"sources": [
{
"path": "docs/协议规则表与状态机.md",
@@ -64,7 +40,7 @@
{
"stem": "timing-attachment-example-105-111-194-199",
"png": "docs/figures/timing-attachment-example-105-111-194-199.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_timing_attachment.m",
"sources": [
{
"path": "docs/协议规则表与状态机.md",
@@ -73,10 +49,24 @@
}
]
},
{
"stem": "timing-measured-q3-10-sources",
"png": "docs/figures/timing-measured-q3-10-sources.png",
"matlab": "scripts/matlab/fig_timing_measured.m",
"sources": [
{
"path": "referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl",
"sha256_12": "5a308e53ed09",
"lines": 133,
"case_code": "BBM9-HZG2-77N5-NKKC",
"module": "q3_practice"
}
]
},
{
"stem": "geo-q1-convex-intersection-zoom",
"png": "docs/figures/geo-q1-convex-intersection-zoom.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_geo_q1_meetup.m",
"sources": [
{
"path": "output/q1/practice/U5ZW-P9KN-TCAG-R7ST_observations.json",
@@ -88,7 +78,7 @@
{
"stem": "geo-q1-diameter-circle-counterexample",
"png": "docs/figures/geo-q1-diameter-circle-counterexample.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_geo_q1_counterexample.m",
"sources": [
{
"path": "examples/q1/triangle.json",
@@ -100,7 +90,7 @@
{
"stem": "geo-q2-guaranteed-reception-disk",
"png": "docs/figures/geo-q2-guaranteed-reception-disk.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_geo_q2_safe_disk.m",
"sources": [
{
"path": "src/mcm26b/config.py",
@@ -117,7 +107,7 @@
{
"stem": "geo-q3-seven-point-coverage",
"png": "docs/figures/geo-q3-seven-point-coverage.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_geo_q3_coverage.m",
"sources": [
{
"path": "src/mcm26b/planner/seven.py",
@@ -134,7 +124,7 @@
{
"stem": "geo-q4-minimum-cover-31-points",
"png": "docs/figures/geo-q4-minimum-cover-31-points.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_geo_q4_mincover.m",
"sources": [
{
"path": "src/mcm26b/guarantee/plans/q4_mincover_v2.json",
@@ -148,24 +138,10 @@
}
]
},
{
"stem": "timing-measured-q3-10-sources",
"png": "docs/figures/timing-measured-q3-10-sources.png",
"scale": 1.0,
"sources": [
{
"path": "referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl",
"sha256_12": "5a308e53ed09",
"lines": 133,
"case_code": "BBM9-HZG2-77N5-NKKC",
"module": "q3_practice"
}
]
},
{
"stem": "replay-webui-q3-omnidirectional",
"png": "docs/figures/replay-webui-q3-omnidirectional.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_replay.m",
"sources": [
{
"path": "referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl",
@@ -178,13 +154,13 @@
{
"stem": "replay-webui-q4-directional",
"png": "docs/figures/replay-webui-q4-directional.png",
"scale": 1.0,
"matlab": "scripts/matlab/fig_replay.m",
"sources": [
{
"path": "referee-data/sessions/GM56-DFXZ-XKDM-XYV7/log.jsonl",
"sha256_12": "f498b2073cdf",
"lines": 421,
"case_code": "GM56-DFXZ-XKDM-XYV7"
"path": "referee-data/sessions/GHSN-M38E-74GD-YLUD/log.jsonl",
"sha256_12": "280696d901b4",
"lines": 429,
"case_code": "GHSN-M38E-74GD-YLUD"
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 101 KiB

+7 -7
View File
@@ -3,7 +3,7 @@
> 本册逐图给出坐标口径、关键量与图中各元素的含义。插图位于 [`figures/`](figures/),复现命令:
>
> ```bash
> uv run python scripts/make_figures.py --only geo-q1-convex-intersection-zoom,\
> uv run python scripts/build_figure_data.py --only geo-q1-convex-intersection-zoom,\
> geo-q1-diameter-circle-counterexample,geo-q2-guaranteed-reception-disk,\
> geo-q3-seven-point-coverage,geo-q4-minimum-cover-31-points
> ```
@@ -30,14 +30,14 @@
(2° 宽,图中放大成可见宽度);三者交集就是定位区域 $P$。检测点相距 600~900 m,
而 $P$ 的直径只有 24 m,所以左图里 $P$ 缩成一个点——这是**必须做局部放大**的原因。
**右(放大,视野 ±31.4 m)**:以最小包围圆圆心为中心:
**右(放大,视野 ±20.6 m)**:以定位区域为中心:
- 浅蓝扇形 = 三个角域在放大框内的可见部分;三者**重叠成白色区域**= $P$;
- 红色实线多边形 = $P$ 的顶点边界(与左图同一个集合);
- 浅蓝扇形 = 三个角域在放大框内的可见部分;**橙色多边形** = 三者交集,即定位区域 $P$;
- 左图里 $P$ 的直径只有 24 m,缩成一个红色方块,放大后才看得到形状;
- 蓝色虚线圆 = **直径圆**,半径 $D/2=11.9984$ m,圆心是中点 $(-450.634,\ 508.944)$;
- 绿色实线圆 = **最小包围圆(MEC)**,$\rho=12.0686$ m,圆心 $(-449.977,\ 507.953)$。
**关键量(图 (c) 栏)**
**关键量**
| 量 | 值 | 判定 |
| --- | ---: | --- |
@@ -64,7 +64,7 @@ $P$:顶点 $(0,0)$、$(40,0)$、$(20,34.641)$。
- 直径 $D=40.0000$ m,最远点对是底边两端 ⇒ 直径圆圆心 $(20,0)$、半径 20 m;
- 到第三个顶点 $(20,34.641)$ 的距离是 $20\sqrt3\approx34.6410$ m > 20 m,
故**直径圆不覆盖**:$E=+14.6410$ m(图中箭头所指);
故**直径圆不覆盖**:$E=+14.6410$ m;
- 该反例的最小包围圆半径 $\rho=40/\sqrt3\approx23.0940$ m **> 20 m**,
属于"覆盖失败且无法保证清除"的更坏情形。
@@ -136,7 +136,7 @@ D(a)=984.5391\ \text{m}<1000\ \text{m}
\quad(\text{几何余量 }15.46\ \text{m}).
$$
**图上看到的就是这个证明**:7 个半径 1000 m 的接收盘(浅绿,裁剪在 Ω 内)的
**图上看到的就是这个证明**:7 个半径 1000 m 的接收盘(浅绿)的
并集**完全覆盖** Ω,最坏点出现在 $(0,-1800)$(Ω 正下方边界,图中圈出),
到最近站点 $1159.7$ m(正下方环站)的距离恰为 984.54 m。
+1 -1
View File
@@ -3,7 +3,7 @@
> 插图:[`figures/flow-overall-architecture.png`](figures/flow-overall-architecture.png)(总体思路与算法主线)、
> [`figures/steps-state-machine.png`](figures/steps-state-machine.png)(执行步骤)、
> [`figures/timing-attachment-example-105-111-194-199.png`](figures/timing-attachment-example-105-111-194-199.png)(附件计时示例)。
> 复现:`uv run python scripts/make_figures.py --only flow-overall-architecture,steps-state-machine,timing-attachment-example-105-111-194-199`
> 复现:`uv run python scripts/build_figure_data.py --only flow-overall-architecture,steps-state-machine,timing-attachment-example-105-111-194-199`
本文给出四问的思路、端到端流程图、执行步骤与执行时间。
算法细节指向 [`algo-1.md`](algo-1.md)~[`algo-4.md`](algo-4.md),
+3 -3
View File
@@ -3,7 +3,7 @@
> 配套插图:[`figures/steps-state-machine.png`](figures/steps-state-machine.png)(状态机与动作回合)、
> [`figures/timing-attachment-example-105-111-194-199.png`](figures/timing-attachment-example-105-111-194-199.png)(附件示例逐步推演)、
> [`figures/timing-measured-q3-10-sources.png`](figures/timing-measured-q3-10-sources.png)(实测一局的时序分解)。
> 复现:`uv run python scripts/make_figures.py --only steps-state-machine,timing-attachment-example-105-111-194-199,timing-measured-q3-10-sources`
> 复现:`uv run python scripts/build_figure_data.py --only steps-state-machine,timing-attachment-example-105-111-194-199,timing-measured-q3-10-sources`
本文说明每一步做什么、每步花多少虚拟时间、这些数字如何验证。
规则原文见 `issue/` 下附件1 表1、表2 与附件2 第 4、10 节;
@@ -108,7 +108,7 @@ uv run pytest tests/test_protocol_timing.py -q # 逐步断言移动、换频
## 4. 实测时序分解
对评测机留档的每一局,`scripts/make_figures.py` 按与 `referee.simulator`
对评测机留档的每一局,`scripts/build_figure_data.py` 按与 `referee.simulator`
同源的规则逐动作复算,并检查分项之和是否等于响应中的 `virtual_time_s`。
### 4.1 问题三演练局 `BBM9-HZG2-77N5-NKKC`(10 源全清)
@@ -145,7 +145,7 @@ uv run pytest tests/test_protocol_timing.py -q # 逐步断言移动、换频
```bash
# 用评测机留档重画(含真值)
uv run python scripts/make_figures.py --only timing-measured-q3-10-sources,\
uv run python scripts/build_figure_data.py --only timing-measured-q3-10-sources,\
replay-webui-q3-omnidirectional,replay-webui-q4-directional
# 归档新日志,评测机数据被清理后仍可复现插图