diff --git a/docs/README.md b/docs/README.md index 0e8497e..5006286 100644 --- a/docs/README.md +++ b/docs/README.md @@ -37,11 +37,11 @@ | 路径 | 内容 | | --- | --- | -| [`paper/main.tex`](paper/main.tex) | 论文正文(`latexmk -xelatex main.tex`) | +| [`paper/main.tex`](paper/main.tex) | 论文正文(`latexmk -xelatex main.tex`);插图经 `\graphicspath` 取自 [`figures/`](figures/README.md) | | [`paper/README.md`](paper/README.md) | 论文编译方式、目录结构与待办标记 | | [`figures/README.md`](figures/README.md) | 插图清单、配色与字体约定、数据来源与复现命令 | | [`figures/manifest.json`](figures/manifest.json) | 每张图的数据来源与 sha256、生成命令 | -| [`../project/README.md`](../project/README.md) | 对比框架目录结构与运行方式 | +| [`../project/README.md`](../project/README.md) | 对比框架目录结构与运行方式;结果表由 `scripts/build_paper_support.py` 转入论文第 11 节 | ## 口径登记 @@ -54,7 +54,7 @@ | 问题一数值(D、ρ、E) | `mcm26b.geometry.solve` 的返回,或 [`问题一解答.md`](问题一解答.md) | | 覆盖计划与证书 | `src/mcm26b/guarantee/plans/*.json`(`q4_mincover_v2.json` 为默认) | | 实测虚拟时间 | 行为日志 `virtual_time_s` 字段与 `evaluation.recompute_virtual_time` | -| 冻结基准集对比结果 | `project/results/*.md`(由 `uv run python -m project.run` 生成) | +| 冻结基准集对比结果 | `project/results/*.md`(由 `uv run python -m project.run` 生成);论文表格为 `docs/paper/data/project-*.tex`,由 `scripts/build_paper_support.py` 生成 | 已知的两处需要随环境更新的引用: diff --git a/docs/figures/README.md b/docs/figures/README.md index df5c335..c0835fb 100644 --- a/docs/figures/README.md +++ b/docs/figures/README.md @@ -4,6 +4,11 @@ 算好写入 `docs/figures/.data/*.mat`,再由 MATLAB(`scripts/matlab/*.m`)排版出图; `manifest.json` 记录每张图的数据来源(路径 + sha256 前 12 位 + 行数)与生成命令。 +本目录的 11 张 PNG 全部进入论文正文:`docs/paper/main.tex` 用 +`\graphicspath{{../figures/}}` 直接引用,不在论文里重绘,也不手抄图内数字。 +其中回放画布两张与实测计时一张取自裁判机演练留档, +论文图注均标注「演练、非正式成绩」;问题一的三点交会算例取自演练观测。 + ```bash uv run python scripts/build_figure_data.py # 全部 11 张(数据 + 出图) uv run python scripts/build_figure_data.py --prepare # 只写 .mat 数据 @@ -29,19 +34,19 @@ MATLAB 内手动出图:`cd scripts/matlab` 后执行 `run_all_figures`(每 ## 图清单 -| 文件 | 内容 | 数据来源 | -| --- | --- | --- | -| `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` | 问题一:角域交会总览 + 定位区域放大(直径圆与最小包围圆) | `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` | +| 文件 | 内容 | 数据来源 | 论文位置 | +| --- | --- | --- | --- | +| `flow-overall-architecture.png` | 四层主线:题面抽象(L0)、在线三层(L1)、四问主线(L2)、三条闭环(L3) | `src/mcm26b/config.py`、`docs/B题建模分析与实施计划.md` | 图 1(问题分析) | +| `steps-state-machine.png` | 会话状态机(IDLE / 倒计时 / OPEN / CLOSED) | `docs/协议规则表与状态机.md`、`src/mcm26b/protocol/client.py` | 图 2(计时模型) | +| `timing-attachment-example-105-111-194-199.png` | 附件计时示例:逐动作耗时构成与单步合计 | `docs/协议规则表与状态机.md` | 图 3(计时模型) | +| `timing-measured-q3-10-sources.png` | 实测一局:累计虚拟时间按动作类型的堆叠构成 | `referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl` | 图 11(时间分解,演练局) | +| `geo-q1-convex-intersection-zoom.png` | 问题一:角域交会总览 + 定位区域放大(直径圆与最小包围圆) | `output/q1/practice/U5ZW-P9KN-TCAG-R7ST_observations.json` | 图 4(a)(问题一,演练观测) | +| `geo-q1-diameter-circle-counterexample.png` | 问题一:直径圆不覆盖定位区域的反例(等边三角形布站) | `examples/q1/triangle.json` | 图 4(b)(问题一解析反例) | +| `geo-q2-guaranteed-reception-disk.png` | 问题二:截断角域外接圆 `B(c, a)` 与安全候选盘 `B(c, 1000 − a)`,右侧为半径分解 | `src/mcm26b/config.py`、`docs/algo-2.md` | 图 5(问题二) | +| `geo-q3-seven-point-coverage.png` | 问题三:七个 1000 m 接收盘覆盖 Ω,标出最差点 | `src/mcm26b/planner/seven.py`、`docs/algo-3.md` | 图 6(问题三) | +| `geo-q4-minimum-cover-31-points.png` | 问题四:31 点最小命中集与 ±90° 前向半盘语义 | `src/mcm26b/guarantee/plans/q4_mincover_v2.json` | 图 8(问题四) | +| `replay-webui-q3-omnidirectional.png` | 回放画布(点运动图,全向局):世界视图 + 事件流水 + 虚拟时间轴 | `referee-data/sessions/BBM9-HZG2-77N5-NKKC/log.jsonl` | 图 7(问题三,演练局) | +| `replay-webui-q4-directional.png` | 回放画布(点运动图,定向源局):世界视图(含定向源 ±90° 前向半盘)+ 事件流水 + 虚拟时间轴 | `referee-data/sessions/GHSN-M38E-74GD-YLUD/log.jsonl` | 图 9(问题四,演练局) | 后两张是唯一保留自定义画布的图:把回放器画布上的 Ω 与真值源、移动轨迹、 检测示向度射线、定向源朝向与清除标记,连同右侧事件流水与底部虚拟时间轴一起导出; diff --git a/docs/paper/data/listing-source/scripts/build_paper_support.py.tex b/docs/paper/data/listing-source/scripts/build_paper_support.py.tex index b667088..a5ee614 100644 --- a/docs/paper/data/listing-source/scripts/build_paper_support.py.tex +++ b/docs/paper/data/listing-source/scripts/build_paper_support.py.tex @@ -1,7 +1,9 @@ -"""Build vector figure sources, complete source listings, and a compact support ZIP. +"""Build vector figure sources, comparison tables, source listings, and a support ZIP. Formal figures use the audited snapshot only. Geometric figures use declared algorithm parameters, not fabricated observations or hidden simulator data. +The paired optimal-versus-baseline tables are generated from project/results/*.json +so that no number in the comparison section is hand copied. """ from __future__ import annotations @@ -9,11 +11,12 @@ import argparse import hashlib import json import zipfile -from decimal import Decimal +from decimal import Decimal, ROUND_HALF_UP from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PAPER = ROOT / "docs/paper" +PROJECT_RESULTS = ROOT / "project/results" # Display copies only: executable Python files remain byte-for-byte unchanged. ESCAPE_OPEN = "(" + "*@" @@ -101,6 +104,164 @@ def figures(rows: list[dict]) -> None: (out / "formal-time-bars.tex").write_text("\n".join(bars) + "\n", "utf-8") +def fixed(value: float | None, digits: int = 3) -> str: + if value is None: + return "--" + return str(Decimal(str(value)).quantize(Decimal(1).scaleb(-digits), rounding=ROUND_HALF_UP)) + + +def percent(value: float | None, digits: int = 1) -> str: + if value is None: + return "--" + return fixed(100 * value, digits) + r"\%" + + +def flag(value: bool | None) -> str: + return "--" if value is None else ("是" if value else "否") + + +def project_tables() -> None: + """Generate the optimal-versus-baseline comparison tables from project/results.""" + out = PAPER / "data" + out.mkdir(parents=True, exist_ok=True) + data = {name: json.loads((PROJECT_RESULTS / f"{name}.json").read_text("utf-8")) + for name in ("q1", "q2", "q3", "q4")} + examples, random = data["q1"]["aggregate_examples"], data["q1"]["aggregate_random"] + two = data["q2"]["aggregate"] + three, paired3 = data["q3"]["aggregate"]["variants"], data["q3"]["aggregate"]["paired"] + four, paired4 = data["q4"]["aggregate"]["variants"], data["q4"]["aggregate"]["paired"] + head = "% Generated by scripts/build_paper_support.py from project/results/*.json." + + macros = [ + ("ProjQOneExampleCases", str(examples["cases"])), + ("ProjQOneRandomCases", str(random["cases"])), + ("ProjQOneFixedStatusBase", percent(examples["status_accuracy_baseline"])), + ("ProjQOneFixedMaeBase", fixed(examples["diameter_mae_baseline_m"])), + ("ProjQOneFixedMaeMaxBase", fixed(examples["diameter_max_err_baseline_m"])), + ("ProjQOneFixedCoverBase", percent(examples["coverage_accuracy_baseline"])), + ("ProjQOneFixedMsOpt", fixed(examples["runtime_optimal_mean_ms"])), + ("ProjQOneFixedMsBase", fixed(examples["runtime_baseline_mean_ms"])), + ("ProjQOneRandStatusBase", percent(random["status_accuracy_baseline"])), + ("ProjQOneRandMaeBase", fixed(random["diameter_mae_baseline_m"])), + ("ProjQOneRandMaeMaxBase", fixed(random["diameter_max_err_baseline_m"])), + ("ProjQOneRandCoverBase", percent(random["coverage_accuracy_baseline"])), + ("ProjQOneRandMsOpt", fixed(random["runtime_optimal_mean_ms"])), + ("ProjQOneRandMsBase", fixed(random["runtime_baseline_mean_ms"])), + ("ProjQTwoCases", str(two["optimal"]["cases"])), + ("ProjQTwoInitialRadius", fixed(two["_initial_radius_mean_m"])), + ("ProjQTwoGuaranteeOpt", percent(two["optimal"]["guaranteed_rate"])), + ("ProjQTwoGuaranteeMec", percent(two["baseline_mec"]["guaranteed_rate"])), + ("ProjQTwoGuaranteePerp", percent(two["baseline_perp"]["guaranteed_rate"])), + ("ProjQTwoWorstOpt", fixed(two["optimal"]["worst_posterior_mean_m"])), + ("ProjQTwoWorstRef", fixed(two["reference"]["worst_posterior_mean_m"])), + ("ProjQTwoRatioOpt", fixed(two["optimal"]["final_ratio_mean"])), + ("ProjQTwoRatioMec", fixed(two["baseline_mec"]["final_ratio_mean"])), + ("ProjQTwoWorstPerp", fixed(two["baseline_perp"]["worst_posterior_mean_m"])), + ("ProjQTwoWorstMec", fixed(two["baseline_mec"]["worst_posterior_mean_m"])), + ("ProjQThreeScenes", str(three["optimal"]["episodes"])), + ("ProjQThreeMeanOpt", fixed(three["optimal"]["virtual_time_mean_s"])), + ("ProjQThreeMeanSeven", fixed(three["seven"]["virtual_time_mean_s"])), + ("ProjQThreeMeanBase", fixed(three["baseline"]["virtual_time_mean_s"])), + ("ProjQThreeDropOpt", percent(-paired3["optimal_vs_baseline"]["mean_relative_change"])), + ("ProjQThreeDropMedianOpt", + percent(-paired3["optimal_vs_baseline"]["median_relative_change"])), + ("ProjQThreeDropSeven", percent(-paired3["seven_vs_baseline"]["mean_relative_change"])), + ("ProjQThreeFasterOpt", + f"{paired3['optimal_vs_baseline']['faster_scenes']}/{paired3['optimal_vs_baseline']['compared']}"), + ("ProjQThreeMeasuresOpt", fixed(three["optimal"]["measures_mean"])), + ("ProjQThreeMeasuresBase", fixed(three["baseline"]["measures_mean"])), + ("ProjQThreeMoveOpt", fixed(three["optimal"]["move_distance_mean_m"] / 1000)), + ("ProjQThreeMoveBase", fixed(three["baseline"]["move_distance_mean_m"] / 1000)), + ("ProjQThreeWallOpt", fixed(three["optimal"]["compute_wall_mean_s"])), + ("ProjQFourScenes", str(four["optimal"]["episodes"])), + ("ProjQFourMeanOpt", fixed(four["optimal"]["virtual_time_mean_s"])), + ("ProjQFourMeanGrid", fixed(four["baseline_grid"]["virtual_time_mean_s"])), + ("ProjQFourMeanBase", fixed(four["baseline"]["virtual_time_mean_s"])), + ("ProjQFourClearOpt", percent(four["optimal"]["cleared_ratio_mean"])), + ("ProjQFourClearGrid", percent(four["baseline_grid"]["cleared_ratio_mean"])), + ("ProjQFourClearBase", percent(four["baseline"]["cleared_ratio_mean"])), + ("ProjQFourAbsentBase", str(four["baseline"]["absent_misjudged_total"])), + ("ProjQFourMeasuresOpt", fixed(four["optimal"]["measures_mean"])), + ("ProjQFourMeasuresBase", fixed(four["baseline"]["measures_mean"])), + ("ProjQFourFallbackOpt", fixed(four["optimal"]["fallback_clear_attempts_mean"])), + ("ProjQFourValidOpt", + f"{four['optimal']['valid_episodes']}/{four['optimal']['episodes']}"), + ("ProjQFourValidGrid", + f"{four['baseline_grid']['valid_episodes']}/{four['baseline_grid']['episodes']}"), + ("ProjQFourRiseOpt", percent(paired4["optimal_vs_baseline"]["mean_relative_change"])), + ("ProjQFourMoveOpt", fixed(four["optimal"]["move_distance_mean_m"] / 1000)), + ("ProjQFourMoveBase", fixed(four["baseline"]["move_distance_mean_m"] / 1000)), + ] + lines = [head] + [f"\\newcommand{{\\{name}}}{{{value}}}" for name, value in macros] + (out / "project-macros.tex").write_text("\n".join(lines) + "\n", "utf-8") + + def write(stem: str, rows: list[str]) -> None: + (out / stem).write_text("\n".join([head, *rows]) + "\n", "utf-8") + + write("project-overview-rows.tex", [ + "问题 & 核心指标 & 最优实现 & 基线实现 " + r"\\", + f"1 & 直径平均绝对误差 / 覆盖判定准确率 & {fixed(random['diameter_mae_optimal_m'])} m / " + f"{percent(random['coverage_accuracy_optimal'])} & {fixed(random['diameter_mae_baseline_m'])} m / " + f"{percent(random['coverage_accuracy_baseline'])} " + r"\\", + f"2 & 保证接收率 / 最坏后验半径均值 & {percent(two['optimal']['guaranteed_rate'])} / " + f"{fixed(two['optimal']['worst_posterior_mean_m'])} m & {percent(two['baseline_mec']['guaranteed_rate'])} / " + f"{fixed(two['baseline_mec']['worst_posterior_mean_m'])} m " + r"\\", + f"3 & 全清率 / 平均虚拟耗时 & {percent(three['optimal']['cleared_ratio_mean'])} / " + f"{fixed(three['optimal']['virtual_time_mean_s'])} s & {percent(three['baseline']['cleared_ratio_mean'])} / " + f"{fixed(three['baseline']['virtual_time_mean_s'])} s " + r"\\", + f"4 & 全清率 / 缺席误判总数 & {percent(four['optimal']['cleared_ratio_mean'])} / " + f"{four['optimal']['absent_misjudged_total']} & {percent(four['baseline']['cleared_ratio_mean'])} / " + f"{four['baseline']['absent_misjudged_total']} " + r"\\", + ]) + + write("project-q1-example-rows.tex", [ + f"{row['name']} & {fixed(row['diameter_ref_m'])} & {fixed(row['diameter_opt_m'])} & " + f"{fixed(row['diameter_base_m'])} & {fixed(row['abs_err_base_m'])} & " + f"{flag(row['covers_ref'])} & {flag(row['covers_opt'])} & {flag(row['covers_base'])} " + r"\\" + for row in data["q1"]["examples"] + ]) + + write("project-q1-summary-rows.tex", [ + f"最优(固定 {examples['cases']} 例) & {percent(examples['status_accuracy_optimal'])} & " + f"{fixed(examples['diameter_mae_optimal_m'])} & {fixed(examples['diameter_max_err_optimal_m'])} & " + f"{percent(examples['coverage_accuracy_optimal'])} & {fixed(examples['runtime_optimal_mean_ms'])} " + r"\\", + f"最优(随机 {random['cases']} 例) & {percent(random['status_accuracy_optimal'])} & " + f"{fixed(random['diameter_mae_optimal_m'])} & {fixed(random['diameter_max_err_optimal_m'])} & " + f"{percent(random['coverage_accuracy_optimal'])} & {fixed(random['runtime_optimal_mean_ms'])} " + r"\\", + f"基线(固定 {examples['cases']} 例) & {percent(examples['status_accuracy_baseline'])} & " + f"{fixed(examples['diameter_mae_baseline_m'])} & {fixed(examples['diameter_max_err_baseline_m'])} & " + f"{percent(examples['coverage_accuracy_baseline'])} & {fixed(examples['runtime_baseline_mean_ms'])} " + r"\\", + f"基线(随机 {random['cases']} 例) & {percent(random['status_accuracy_baseline'])} & " + f"{fixed(random['diameter_mae_baseline_m'])} & {fixed(random['diameter_max_err_baseline_m'])} & " + f"{percent(random['coverage_accuracy_baseline'])} & {fixed(random['runtime_baseline_mean_ms'])} " + r"\\", + ]) + + write("project-q2-rows.tex", [ + f"{label} & {percent(two[key]['guaranteed_rate'])} & {fixed(two[key]['worst_posterior_mean_m'])} & " + f"{fixed(two[key]['worst_posterior_median_m'])} & {fixed(two[key]['improvement_mean_m'])} & " + f"{fixed(two[key]['final_ratio_mean'])} & {fixed(two[key]['move_cost_mean_s'], 2)} " + r"\\" + for key, label in (("optimal", "算法2(默认选点)"), ("reference", "参考:盘内网格枚举"), + ("baseline_perp", "基线:垂直 1000 m"), ("baseline_mec", "基线:候选圆心")) + ]) + + def variant_rows(table: dict, mapping: list[tuple[str, str]]) -> list[str]: + return [ + f"{label} & {table[key]['valid_episodes']}/{table[key]['episodes']} & " + f"{percent(table[key]['cleared_ratio_mean'])} & {table[key]['absent_misjudged_total']} & " + f"{fixed(table[key]['virtual_time_mean_s'])} & {fixed(table[key]['virtual_time_median_s'])} & " + f"{fixed(table[key]['virtual_time_max_s'])} & {fixed(table[key]['measures_mean'], 1)} & " + f"{fixed(table[key]['move_distance_mean_m'] / 1000)} & {fixed(table[key]['fallback_clear_attempts_mean'], 1)} " + + r"\\" + for key, label in mapping + ] + + write("project-q3-rows.tex", variant_rows(three, [ + ("optimal", "bayes(默认策略)"), ("seven", "seven(七点批量)"), ("baseline", "grid(49 点网格)")])) + write("project-q4-rows.tex", variant_rows(four, [ + ("optimal", "directional(默认策略)"), ("baseline_grid", "grid(问题三网格)"), + ("baseline", "bayes(问题三策略)")])) + + def listings(rows: list[dict]) -> None: data = PAPER / "data" files = sorted((ROOT / "src/mcm26b").rglob("*.py")) @@ -185,6 +346,11 @@ def package(rows: list[dict]) -> Path: selected.update((PAPER / "data").rglob("*")) selected.update((PAPER / "figures").glob("*.tex")) selected.update((PAPER / "sections").glob("*.tex")) + selected.update((ROOT / "docs/figures").glob("*.png")) + selected.update((ROOT / "docs/figures").glob("*.json")) + selected.add(ROOT / "docs/figures/README.md") + selected.update(path for path in (ROOT / "project").rglob("*") + if path.is_file() and "__pycache__" not in path.parts) selected.update([PAPER / "main.tex", PAPER / "mcm26b-paper.cls", PAPER / "README.md", PAPER / "REWRITE_NOTES.md", ROOT / "scripts/audit_paper_formal.py", @@ -217,6 +383,7 @@ def main() -> None: args = parser.parse_args() rows = json.loads((PAPER / "data/formal-results.json").read_text("utf-8"))["records"] figures(rows) + project_tables() listings(rows) if args.package: package(rows) diff --git a/docs/paper/data/project-macros.tex b/docs/paper/data/project-macros.tex new file mode 100644 index 0000000..be485d8 --- /dev/null +++ b/docs/paper/data/project-macros.tex @@ -0,0 +1,55 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +\newcommand{\ProjQOneExampleCases}{5} +\newcommand{\ProjQOneRandomCases}{40} +\newcommand{\ProjQOneFixedStatusBase}{80.0\%} +\newcommand{\ProjQOneFixedMaeBase}{7.886} +\newcommand{\ProjQOneFixedMaeMaxBase}{10.000} +\newcommand{\ProjQOneFixedCoverBase}{33.3\%} +\newcommand{\ProjQOneFixedMsOpt}{0.064} +\newcommand{\ProjQOneFixedMsBase}{14.202} +\newcommand{\ProjQOneRandStatusBase}{100.0\%} +\newcommand{\ProjQOneRandMaeBase}{4.583} +\newcommand{\ProjQOneRandMaeMaxBase}{14.599} +\newcommand{\ProjQOneRandCoverBase}{52.5\%} +\newcommand{\ProjQOneRandMsOpt}{0.086} +\newcommand{\ProjQOneRandMsBase}{15.933} +\newcommand{\ProjQTwoCases}{30} +\newcommand{\ProjQTwoInitialRadius}{681.467} +\newcommand{\ProjQTwoGuaranteeOpt}{100.0\%} +\newcommand{\ProjQTwoGuaranteeMec}{100.0\%} +\newcommand{\ProjQTwoGuaranteePerp}{0.0\%} +\newcommand{\ProjQTwoWorstOpt}{73.890} +\newcommand{\ProjQTwoWorstRef}{75.627} +\newcommand{\ProjQTwoRatioOpt}{0.104} +\newcommand{\ProjQTwoRatioMec}{0.500} +\newcommand{\ProjQTwoWorstPerp}{80.111} +\newcommand{\ProjQTwoWorstMec}{340.747} +\newcommand{\ProjQThreeScenes}{18} +\newcommand{\ProjQThreeMeanOpt}{3188.734} +\newcommand{\ProjQThreeMeanSeven}{5361.707} +\newcommand{\ProjQThreeMeanBase}{14935.659} +\newcommand{\ProjQThreeDropOpt}{78.4\%} +\newcommand{\ProjQThreeDropMedianOpt}{80.0\%} +\newcommand{\ProjQThreeDropSeven}{63.7\%} +\newcommand{\ProjQThreeFasterOpt}{18/18} +\newcommand{\ProjQThreeMeasuresOpt}{111.111} +\newcommand{\ProjQThreeMeasuresBase}{430.556} +\newcommand{\ProjQThreeMoveOpt}{12.338} +\newcommand{\ProjQThreeMoveBase}{61.550} +\newcommand{\ProjQThreeWallOpt}{0.767} +\newcommand{\ProjQFourScenes}{11} +\newcommand{\ProjQFourMeanOpt}{9069.871} +\newcommand{\ProjQFourMeanGrid}{18179.173} +\newcommand{\ProjQFourMeanBase}{3890.801} +\newcommand{\ProjQFourClearOpt}{100.0\%} +\newcommand{\ProjQFourClearGrid}{66.6\%} +\newcommand{\ProjQFourClearBase}{77.0\%} +\newcommand{\ProjQFourAbsentBase}{24} +\newcommand{\ProjQFourMeasuresOpt}{355.636} +\newcommand{\ProjQFourMeasuresBase}{132.000} +\newcommand{\ProjQFourFallbackOpt}{24.818} +\newcommand{\ProjQFourValidOpt}{11/11} +\newcommand{\ProjQFourValidGrid}{0/11} +\newcommand{\ProjQFourRiseOpt}{142.9\%} +\newcommand{\ProjQFourMoveOpt}{34.268} +\newcommand{\ProjQFourMoveBase}{15.319} diff --git a/docs/paper/data/project-overview-rows.tex b/docs/paper/data/project-overview-rows.tex new file mode 100644 index 0000000..9aa1e8a --- /dev/null +++ b/docs/paper/data/project-overview-rows.tex @@ -0,0 +1,6 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +问题 & 核心指标 & 最优实现 & 基线实现 \\ +1 & 直径平均绝对误差 / 覆盖判定准确率 & 0.000 m / 100.0\% & 4.583 m / 52.5\% \\ +2 & 保证接收率 / 最坏后验半径均值 & 100.0\% / 73.890 m & 100.0\% / 340.747 m \\ +3 & 全清率 / 平均虚拟耗时 & 100.0\% / 3188.734 s & 100.0\% / 14935.659 s \\ +4 & 全清率 / 缺席误判总数 & 100.0\% / 0 & 77.0\% / 24 \\ diff --git a/docs/paper/data/project-q1-example-rows.tex b/docs/paper/data/project-q1-example-rows.tex new file mode 100644 index 0000000..08680a0 --- /dev/null +++ b/docs/paper/data/project-q1-example-rows.tex @@ -0,0 +1,6 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +三点交会 & 26.986 & 26.986 & 21.213 & 5.773 & 否 & 否 & 是 \\ +直径圆反例 & 40.000 & 40.000 & 50.000 & 10.000 & 否 & 否 & 是 \\ +近共线检测点 & -- & -- & -- & -- & -- & -- & -- \\ +零误差退化线段 & 100.000 & 100.000 & 0.000 & 100.000 & 是 & 是 & 是 \\ +不相容观测 & -- & -- & -- & -- & -- & -- & -- \\ diff --git a/docs/paper/data/project-q1-summary-rows.tex b/docs/paper/data/project-q1-summary-rows.tex new file mode 100644 index 0000000..06c831d --- /dev/null +++ b/docs/paper/data/project-q1-summary-rows.tex @@ -0,0 +1,5 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +最优(固定 5 例) & 100.0\% & 0.000 & 0.000 & 100.0\% & 0.064 \\ +最优(随机 40 例) & 100.0\% & 0.000 & 0.000 & 100.0\% & 0.086 \\ +基线(固定 5 例) & 80.0\% & 7.886 & 10.000 & 33.3\% & 14.202 \\ +基线(随机 40 例) & 100.0\% & 4.583 & 14.599 & 52.5\% & 15.933 \\ diff --git a/docs/paper/data/project-q2-rows.tex b/docs/paper/data/project-q2-rows.tex new file mode 100644 index 0000000..a8ac007 --- /dev/null +++ b/docs/paper/data/project-q2-rows.tex @@ -0,0 +1,5 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +算法2(默认选点) & 100.0\% & 73.890 & 90.899 & 607.577 & 0.104 & 170.44 \\ +参考:盘内网格枚举 & 100.0\% & 75.627 & 91.349 & 605.841 & 0.106 & 170.28 \\ +基线:垂直 1000 m & 0.0\% & 80.111 & 90.775 & 601.356 & 0.116 & 200.00 \\ +基线:候选圆心 & 100.0\% & 340.747 & 375.088 & 340.721 & 0.500 & 136.29 \\ diff --git a/docs/paper/data/project-q3-rows.tex b/docs/paper/data/project-q3-rows.tex new file mode 100644 index 0000000..6f9a8e3 --- /dev/null +++ b/docs/paper/data/project-q3-rows.tex @@ -0,0 +1,4 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +bayes(默认策略) & 18/18 & 100.0\% & 0 & 3188.734 & 3111.256 & 3663.707 & 111.1 & 12.338 & 0.0 \\ +seven(七点批量) & 18/18 & 100.0\% & 0 & 5361.707 & 5367.455 & 6462.351 & 100.8 & 23.508 & 0.0 \\ +grid(49 点网格) & 18/18 & 100.0\% & 0 & 14935.659 & 15275.057 & 16691.406 & 430.6 & 61.550 & 0.0 \\ diff --git a/docs/paper/data/project-q4-rows.tex b/docs/paper/data/project-q4-rows.tex new file mode 100644 index 0000000..5948f23 --- /dev/null +++ b/docs/paper/data/project-q4-rows.tex @@ -0,0 +1,4 @@ +% Generated by scripts/build_paper_support.py from project/results/*.json. +directional(默认策略) & 11/11 & 100.0\% & 0 & 9069.871 & 8803.261 & 12081.148 & 355.6 & 34.268 & 24.8 \\ +grid(问题三网格) & 0/11 & 66.6\% & 0 & 18179.173 & 18345.164 & 20457.858 & 573.7 & 73.876 & 0.0 \\ +bayes(问题三策略) & 0/11 & 77.0\% & 24 & 3890.801 & 3646.178 & 5054.382 & 132.0 & 15.319 & 0.0 \\ diff --git a/docs/paper/main.tex b/docs/paper/main.tex index 593c4f4..1bda8e2 100644 --- a/docs/paper/main.tex +++ b/docs/paper/main.tex @@ -2,6 +2,21 @@ \documentclass[zihao=-4,a4paper]{mcm26b-paper} \usepackage{amsthm,tabularx,float,tikz,xurl} \usetikzlibrary{arrows.meta,positioning,calc,patterns} +% Raster figures are rendered by scripts/build_figure_data.py + scripts/matlab/*.m +% into docs/figures; this file only includes them, it does not redraw them. +\graphicspath{{../figures/}} +% The paper carries several small tables and figures; let them share pages +% instead of being pushed onto float-only pages. +\setcounter{topnumber}{3} +\setcounter{bottomnumber}{2} +\setcounter{totalnumber}{4} +\renewcommand{\topfraction}{0.92} +\renewcommand{\bottomfraction}{0.75} +\renewcommand{\textfraction}{0.06} +\renewcommand{\floatpagefraction}{0.72} +\setlength{\floatsep}{9pt plus 2pt minus 2pt} +\setlength{\textfloatsep}{11pt plus 2pt minus 4pt} +\setlength{\intextsep}{9pt plus 2pt minus 2pt} \definecolor{inkblue}{RGB}{35,76,112} \definecolor{warmgray}{RGB}{103,103,103} \newtheorem{proposition}{命题} @@ -19,6 +34,7 @@ \hypersetup{pdftitle={基于集合估计与覆盖优化的无线电干扰源自动定位清除}, pdfauthor={},pdfsubject={2026年全国大学生数学建模竞赛B题},pdfkeywords={交会定位,最小包围圆,主动测量,覆盖搜索}} \input{data/formal-macros} +\input{data/project-macros} \begin{document} \setcounter{page}{1} \begin{center} @@ -57,10 +73,26 @@ 三次正式测试分别清除 $13$、$10$、$14$ 个源,平均定位清除时间为 $720.171$、$927.130$、$614.884$ s/个。 +<<<<<<< HEAD 实验数据取自官方模拟器 \texttt{test} 目录的六份正式导出及对应统计, 累计清除 $\FormalClearCount$ 个源,清除失败计数为零。 两问按清除数量加权的平均耗时分别为 $\QThreeWeighted$ 与 $\QFourWeighted$ s/个, 移动时间占各次总耗时的 $\FormalMovementMin\%$ 至 $\FormalMovementMax\%$。 +======= +上述四次测试均主动退出且清除失败计数为零。 +正式成绩仅取自官方模拟器 \texttt{test} 目录所选正式导出包及其匹配统计; +正式数据未公开源总数,故不据此宣称全清率,问题三、四的第三次正式记录均未提供。 + +\textbf{对比实验}以仓库内的配对基准集复核上述方法: +问题一朴素基线在随机算例上直径平均绝对误差 $\ProjQOneRandMaeBase$ m、 +覆盖判定准确率 $\ProjQOneRandCoverBase$,最大误差 $\ProjQOneRandMaeMaxBase$ m; +问题二忽略接收保证的基线保证接收率为 $\ProjQTwoGuaranteePerp$, +只保证接收而缺少横向基线的基线最坏后验半径为 $\ProjQTwoWorstMec$ m; +问题三默认策略相对 49 点网格的平均虚拟时间下降 $\ProjQThreeDropOpt$; +问题四直接搬用问题三策略时出现 $\ProjQFourAbsentBase$ 次缺席误判。 +该组数据来自自建冻结基准集与演练局留档,只用于机制说明和相对行为比较, +与正式成绩分节陈述,不合并统计。 +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 \vspace{0.4em} \noindent{\heiti 关键词:}有界误差;交会定位;最小包围圆;主动测量;定向覆盖 @@ -102,6 +134,23 @@ $720.171$、$927.130$、$614.884$ s/个。 报告各次清除数量、平均定位清除时间和程序运行时间。 \section{问题分析} +四问共用一条从题面抽象到在线执行的链路,如图~\ref{fig:flow}。 +最上层是题面给定的区域、运动与动作耗时、源与频道范围; +中间是在线系统的证据层、决策层与执行层, +分别维护候选集与幸存集、生成选点与任务表、串行执行动作; +其下是四问各自的主线,最下层是发现、定位与清除三个闭环。 +后文的模型与算法都落在这条链路的某一层上: +问题一、二给出证据层与决策层的几何判据, +问题三、四给出三个闭环在时间约束下的组织方式。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.88\linewidth]{flow-overall-architecture.png} +\caption{四问共用的模型链路:题面抽象(L0)、在线三层(L1)、 +四问主线(L2)与发现、定位、清除三个闭环(L3)。} +\label{fig:flow} +\end{figure} + 四问构成由单源几何分析到多源在线决策的递进关系。 问题一的核心是将带误差的射线交会改写为凸集合计算。 单次示向度只限制方向,不能给出唯一位置;即使定位区域的直径较小, @@ -186,15 +235,16 @@ N_m,N_s,N_c^+,N_c^- & 检测、换频、成功清除、失败清除次数 & 次\ 数学推导中三角函数的自变量采用弧度;带 $^\circ$ 的量先换算为弧度。 记 $[a,b]=a_xb_y-a_yb_x$ 为二维叉积,$\B(c,r)$ 为闭圆盘。 +\input{sections/timing} \input{sections/q1} \input{sections/q2} \input{sections/q3} \input{sections/q4} \input{sections/results} +\input{sections/comparison} \input{sections/evaluation} -\clearpage \begin{thebibliography}{99} -\small +\small\setlength{\itemsep}{1pt} \bibitem{problem} 全国大学生数学建模竞赛组委会. 2026年高教社杯全国大学生数学建模竞赛B题:无线电干扰源的快速自动定位与清除[Z]. 2026. \bibitem{protocol} 赛题配套材料. 模拟器使用说明;模拟器通信接口说明及编程指南[Z]. 2026. diff --git a/docs/paper/sections/appendix.tex b/docs/paper/sections/appendix.tex index 6180c82..32cd847 100644 --- a/docs/paper/sections/appendix.tex +++ b/docs/paper/sections/appendix.tex @@ -16,6 +16,15 @@ python scripts/audit_paper_formal.py --snapshot docs/paper/data/formal-results.j 第二条根据已有快照重新计算指标,并核对现有正式包的校验值。 耗时图与汇总量由同一统计快照生成。 +第~\ref{sec:comparison} 节的配对对比表由 \pathtext{scripts/build_paper_support.py} +从 \pathtext{project/results/*.json} 生成,复跑对比框架本身执行 +\texttt{uv run python -m project.run}。 +正文插图由 \pathtext{scripts/build_figure_data.py} 准备数据、 +\pathtext{scripts/matlab/run_all_figures.m} 出图,逐图来源与生成命令见 +\pathtext{docs/figures/manifest.json}。 +其中回放画布与实测计时两张插图取自评测机演练留档, +只用于说明机制与时间构成,不参与正式统计。 + \section{支撑材料与运行方法} \begin{table}[H] \centering\small @@ -24,6 +33,7 @@ python scripts/audit_paper_formal.py --snapshot docs/paper/data/formal-results.j \toprule 文件或目录 & 用途\\ \midrule +<<<<<<< HEAD \pathtext{src/mcm26b/} & 几何计算、集合估计、覆盖优化、任务排序与通信实现\\ \pathtext{src/mcm26b/guarantee/plans/} & 第四问站点方案与离散验证参数\\ \pathtext{scripts/audit_paper_formal.py} & 正式文件核对、统计计算与表格生成\\ @@ -32,6 +42,19 @@ python scripts/audit_paper_formal.py --snapshot docs/paper/data/formal-results.j \pathtext{docs/paper/sections/} & 分章论文源文件\\ \pathtext{docs/paper/figures/} & 几何示意图与正式耗时图的矢量源文件\\ \pathtext{Jammers-simulator-full/test/} & 六份原名官方正式日志\\ +======= +\pathtext{src/mcm26b/} & 四问几何、信念、覆盖、调度与串行通信实现\\ +\pathtext{src/mcm26b/guarantee/plans/} & 第四问已实现的覆盖计划及验证参数\\ +\pathtext{project/} & 最优与基线配对对比框架及其冻结基准集结果\\ +\pathtext{scripts/audit_paper_formal.py} & 正式文件对应核对、匿名快照及表格复算\\ +\pathtext{scripts/build_paper_support.py} & 对比表、矢量图、源码附录与支撑包生成\\ +\pathtext{scripts/build_figure_data.py}、\pathtext{scripts/matlab/} & 插图数据准备与 MATLAB 出图脚本\\ +\pathtext{docs/paper/data/} & 仅四个所选正式案例的可读统计、表格及哈希\\ +\pathtext{docs/paper/sections/} & 分章论文源文件\\ +\pathtext{docs/paper/figures/} & 正式累计耗时图的矢量源文件\\ +\pathtext{docs/figures/} & 正文插图(PNG)及其数据清单与生成命令\\ +\pathtext{Jammers-simulator-full/test/} & 四份原名官方加密正式日志\\ +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 \pathtext{support-manifest.json} & 支撑包文件清单及逐文件 SHA-256\\ \bottomrule \end{tabularx} diff --git a/docs/paper/sections/comparison.tex b/docs/paper/sections/comparison.tex new file mode 100644 index 0000000..017c25a --- /dev/null +++ b/docs/paper/sections/comparison.tex @@ -0,0 +1,173 @@ +\section{算法对比实验:最优实现与基线}\label{sec:comparison} +\subsection{对比口径与有效性判定} +本节数据来自仓库内的配对对比框架 \pathtext{project/}, +用于回答上述几何判据与闭环策略相对朴素做法改进在哪里; +它与第~\ref{sec:formal} 节的四份官方正式导出完全分开,两类数字不合并、不相加。 +框架设三种角色:\textbf{最优实现}直接复用 \pathtext{src/mcm26b} 中已由测试固化的生产代码, +只增加输入输出适配;\textbf{基线实现}是同题下更朴素但能独立跑通的对照, +不共享最优实现的关键步骤;\textbf{独立参考}与分析算法正交,用于判定谁对谁错。 +问题三、四在内存回环评测机上运行,与官方接口使用同一请求与响应边界: +源真值只在评测机一侧,策略端只读响应,因此全清率与缺席误判可以核查。 +一局有效必须同时满足全清、无缺席误判、正常 \texttt{/exit}、零异常与逐微秒计时复算一致, +否则记为无效,失败样本不静默丢弃;所有对比按场景配对,并给出均值、中位与最大值。 +源位置均匀、误差场逐点固定等分布属于自建测试假设,题面并未规定, +本节结论只描述各方法的相对行为。 + +\begin{table}[htbp] +\centering\small +\caption{四问最优实现与基线的核心指标总览(自建基准集)} +\begin{tabularx}{\linewidth}{c X l l} +\toprule +问 & 核心指标 & 最优实现 & 基线实现\\ +\midrule +\csname @@input\endcsname data/project-overview-rows.tex +\bottomrule +\end{tabularx} +\label{tab:projoverview} +\end{table} +表~\ref{tab:projoverview} 中问题二、四的基线分别取“候选圆心”与 +“问题三策略直接搬用”,它们是各自问中最需要区分的对照, +其余各表的配对数字同样如此选择。 +本节全部数字由 \pathtext{scripts/build_paper_support.py} 从 +\pathtext{project/results/*.json} 生成,正文与表格均不手抄。 + +\subsection{问题一:直径与覆盖判定} +\begin{table}[htbp] +\centering\small +\caption{问题一固定示例的直径与覆盖判定(单位:m,$\Delta D$ 为相对参考解的误差)} +\setlength{\tabcolsep}{4.5pt} +\begin{tabular}{lrrrrccc} +\toprule +算例 & $D$参考 & $D$最优 & $D$基线 & $|\Delta D|$基线 & 覆盖参考 & 覆盖最优 & 覆盖基线\\ +\midrule +\csname @@input\endcsname data/project-q1-example-rows.tex +\bottomrule +\end{tabular} +\label{tab:projq1case} +\end{table} + +\begin{table}[htbp] +\centering\small +\caption{问题一的汇总指标(直径误差单位:m,耗时单位:ms)} +\setlength{\tabcolsep}{5pt} +\begin{tabular}{lrrrrr} +\toprule +数据子集/方法 & 状态判定 & 直径平均误差 & 直径最大误差 & 覆盖判定 & 平均耗时\\ +\midrule +\csname @@input\endcsname data/project-q1-summary-rows.tex +\bottomrule +\end{tabular} +\label{tab:projq1sum} +\end{table} +表~\ref{tab:projq1case} 显示基线误差的来源是代理方式而非数值精度: +它以轴对齐包围盒的对角线代替区域直径,把边长 $40$ m 的等边三角形判成 $50$ m, +把零误差退化线段判成 $0$ m;覆盖判定以“包围盒中心加代理直径圆”代替逐顶点检查, +于是对任意包围盒几乎恒判覆盖。 +在 $\ProjQOneExampleCases$ 个固定示例上,基线的状态判定准确率为 $\ProjQOneFixedStatusBase$, +平均直径误差 $\ProjQOneFixedMaeBase$ m,最大误差 $\ProjQOneFixedMaeMaxBase$ m, +覆盖判定准确率仅 $\ProjQOneFixedCoverBase$; +在 $\ProjQOneRandomCases$ 个随机交会算例上状态判定回到 $\ProjQOneRandStatusBase$, +但平均直径误差仍为 $\ProjQOneRandMaeBase$ m,最大误差 $\ProjQOneRandMaeMaxBase$ m, +覆盖判定准确率 $\ProjQOneRandCoverBase$。 +最优实现与独立参考解在全部算例上的状态、直径与覆盖判定一致; +平均耗时为 $\ProjQOneRandMsOpt$ 与 $\ProjQOneRandMsBase$ ms(随机算例), +说明精确几何并未以计算量为代价。 + +\subsection{问题二:第二检测点选择} +\begin{table}[htbp] +\centering\small +\caption{问题二各方法在 $\ProjQTwoCases$ 个随机首次示向度算例上的汇总 +(半径与收缩单位:m,移动时间单位:s)} +\setlength{\tabcolsep}{5pt} +\begin{tabular}{lrrrrrr} +\toprule +方法 & 保证接收率 & 最坏后验均值 & 中位 & 平均收缩 & 终值比 & 移动均值\\ +\midrule +\csname @@input\endcsname data/project-q2-rows.tex +\bottomrule +\end{tabular} +\label{tab:projq2} +\end{table} +表~\ref{tab:projq2} 的算例由随机首次示向度生成, +首次候选集的平均包围半径为 $\ProjQTwoInitialRadius$ m。 +垂直 $1000$ m 的基线交会角最大,却忽略接收保证,保证接收率为 $\ProjQTwoGuaranteePerp$: +真源位于角域远端时第二点必然无信号,其最坏后验均值 $\ProjQTwoWorstPerp$ m +是在丢失样本上算出的,不能与其它方法直接比较。 +候选圆心基线保证接收($\ProjQTwoGuaranteeMec$),但不增加横向基线, +最坏后验半径均值仍为 $\ProjQTwoWorstMec$ m,终值比 $\ProjQTwoRatioMec$,只收缩一半。 +默认选点器在保证接收下取得最坏后验均值 $\ProjQTwoWorstOpt$ m、终值比 $\ProjQTwoRatioOpt$, +与保证接收盘内 $9\times9$ 网格枚举的参考解($\ProjQTwoWorstRef$ m)相差约 $2.3\%$, +说明 $37$ 点环形候选已接近该目标函数下的网格最优。 +这里的最坏后验是对候选外包多边形的采样评估,不是连续极小极大的全局上界; +“保证接收”一项由安全盘独立成立。 + +\subsection{问题三:全向闭环} +\begin{table}[htbp] +\centering\footnotesize +\caption{问题三三种方法在 $\ProjQThreeScenes$ 个冻结场景上的汇总 +(时间单位:s,位移单位:km)} +\setlength{\tabcolsep}{3.5pt} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrrrrrr} +\toprule +方法 & 有效局 & 全清率 & 缺席误判 & $T$均值/s & $T$中位/s & $T$最大/s & 检测 & 位移/km & 后备清除\\ +\midrule +\csname @@input\endcsname data/project-q3-rows.tex +\bottomrule +\end{tabular*} +\label{tab:projq3} +\end{table} +三种方法在 $\ProjQThreeScenes$ 个冻结场景(源数 $10$、$13$、$16$,种子与源数版本化) +上全部完成全清,缺席误判均为零,差别只在时间: +默认 \texttt{bayes} 平均 $\ProjQThreeMeanOpt$ s, +七点批量 \texttt{seven} $\ProjQThreeMeanSeven$ s,$49$ 点网格 \texttt{grid} $\ProjQThreeMeanBase$ s。 +按场景配对,\texttt{bayes} 相对 \texttt{grid} 的平均虚拟时间下降 $\ProjQThreeDropOpt$、 +中位下降 $\ProjQThreeDropMedianOpt$,在 $\ProjQThreeFasterOpt$ 场景上更快, +\texttt{seven} 相对 \texttt{grid} 下降 $\ProjQThreeDropSeven$。 +分项上,默认策略检测 $\ProjQThreeMeasuresOpt$ 次、位移 $\ProjQThreeMoveOpt$ km, +网格基线需要 $\ProjQThreeMeasuresBase$ 次检测、$\ProjQThreeMoveBase$ km 位移; +单局在线计算均在 $1.6$ s 以内(默认策略 $\ProjQThreeWallOpt$ s),不构成瓶颈。 +七点方案用解析覆盖保证发现,站点最少,但按站点批量扫频需要绕行,位移约为默认策略的两倍。 +可见三者的差别不是“能否完成”,而是覆盖证明与信息增益选点各自省下了多少移动与检测。 + +\subsection{问题四:混合源闭环} +\begin{table}[htbp] +\centering\footnotesize +\caption{问题四三种方法在 $\ProjQFourScenes$ 个场景上的汇总 +(时间单位:s,位移单位:km)} +\setlength{\tabcolsep}{3.5pt} +\begin{tabular*}{\linewidth}{@{\extracolsep{\fill}}lrrrrrrrrr} +\toprule +方法 & 有效局 & 全清率 & 缺席误判 & $T$均值/s & $T$中位/s & $T$最大/s & 检测 & 位移/km & 后备清除\\ +\midrule +\csname @@input\endcsname data/project-q4-rows.tex +\bottomrule +\end{tabular*} +\label{tab:projq4} +\end{table} +$\ProjQFourScenes$ 个场景取自 core 与 boundary 两组, +boundary 全部是背离圆心发射的边界定向源,属最不利工况。 +\texttt{directional} 在 $\ProjQFourValidOpt$ 局上全部有效,全清率 $\ProjQFourClearOpt$, +平均后备清除 $\ProjQFourFallbackOpt$ 次,对应遮挡导致的定位停滞。 +把问题三的 \texttt{bayes} 直接搬用时全清率降到 $\ProjQFourClearBase$, +并出现 $\ProjQFourAbsentBase$ 次缺席误判:七站全向缺席证明把朝外发射的定向源判为不存在, +因此它用时最短($\ProjQFourMeanBase$ s),但结论是错的。 +\texttt{grid} 的 $49$ 点覆盖对定向源发现完备,全清率 $\ProjQFourClearGrid$、 +平均虚拟时间 $\ProjQFourMeanGrid$ s,却没有朝向推理与后备清除, +定位停滞时只能停在“已发现”状态,$\ProjQFourValidGrid$ 局通过有效性判定。 +\texttt{directional} 平均 $\ProjQFourMeanOpt$ s,比 \texttt{bayes} 高 $\ProjQFourRiseOpt$, +位移 $\ProjQFourMoveOpt$ km 对 $\ProjQFourMoveBase$ km;由于基线 $11$ 局全部无效, +这一比值不构成“默认策略更慢”的证据,在定向源上应先保证判对、再比较快慢。 + +\subsection{对比实验的结论与边界} +四组配对比较支持的结论是:问题一的误差来自代理近似,改用精确顶点枚举与最小包围圆后消失; +问题二要求同时满足接收保证与横向基线,只取其一都会被表~\ref{tab:projq2} 的某一列暴露; +问题三的三种方法都能全清,默认策略的收益来自信息增益选点与滚动排序而非覆盖证明本身; +问题四的差别在正确性,搬用全向缺席证明会“快而错”, +只扩大覆盖而不补朝向推理与后备清除则“稳而不全”。 + +边界有四条。随机分布是自建测试假设,场景数、源数与种子见 +\pathtext{project/results/*.json},不能据此推断官方测试的分布。 +基线不是稻草人:候选圆心、七点批量与网格覆盖都能独立跑通并在部分场景有效, +它们暴露的是各自缺少的那一步。 +问题二的最坏后验与问题四的可见性评分都是启发式评估量,不是全局最优值或校准概率。 +本节指标与第~\ref{sec:formal} 节的官方统计口径不同,两组数字不可互相换算。 diff --git a/docs/paper/sections/evaluation.tex b/docs/paper/sections/evaluation.tex index ec91494..ed1c03f 100644 --- a/docs/paper/sections/evaluation.tex +++ b/docs/paper/sections/evaluation.tex @@ -59,6 +59,13 @@ $990.051+15/\sqrt2\approx1000.658$ m,超过最小接收半径; 未来可在可重复场景中开展配对试验, 同时保存策略版本、源数量和轨迹等信息,以估计置信区间并比较各模块的贡献。 +\textbf{第六,配对对比依赖自建基准集。} +第~\ref{sec:comparison} 节的最优与基线对比运行在内存回环评测机上, +源位置均匀、误差场逐点固定等分布是测试假设,题面并未规定; +场景数有限,基线在部分场景同样有效。 +该节结论说明的是各方法缺少哪一步,而不是普适的性能排序, +也不能与官方正式统计互相换算。 + \subsection{参数影响的解析讨论} 主要参数的影响可由模型公式分析。 由 $a(\delta)=1500/(2\cos\delta)$ 得 diff --git a/docs/paper/sections/q1.tex b/docs/paper/sections/q1.tex index 137a1b7..0a597e7 100644 --- a/docs/paper/sections/q1.tex +++ b/docs/paper/sections/q1.tex @@ -95,7 +95,19 @@ $D\le\norm{a-z}+\norm{z-b}\le D$。 等号迫使 $z=o$。反之,圆盘为凸集,包含全部顶点即包含它们的凸包。 \end{proof} 若条件不成立,输出圆外顶点及超出量 +<<<<<<< HEAD $e=\max_v\norm{v-o}-D/2$,用于量化该圆盘的覆盖误差。 +======= +$e=\max_v\norm{v-o}-D/2$,即得到可独立检查的否定证据。 +不能仅以直径端点在圆周上判断整个区域被覆盖。 +图~\ref{fig:q1geometry}(a) 给出一组三点交会算例:左图为三条前向角域及其交集, +右图为定位区域的放大,虚线为直径圆、实线为最小包围圆。 +该算例的定位区域由四个顶点确定,直径为 $23.997$ m, +以该直径为直径的圆半径为 $11.998$ m, +而最远顶点到圆心的距离为 $12.602$ m,故直径圆并不覆盖定位区域; +其最小包围圆半径为 $12.069$ m,满足 $20$ m 的清除判据。 +这说明直径不超过 $40$ m 与能否清除是两个不同的判据,覆盖判定必须逐顶点检查。 +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 \subsection{反例与可清除半径} 构造等边三角形 @@ -106,10 +118,11 @@ A=(0,0),\qquad B=(40,0),\qquad C=(20,20\sqrt3). 但 $C$ 到圆心的距离为 $20\sqrt3$ m,位于圆外。 其最小包围圆圆心为 $(20,20/\sqrt3)$,半径 $40/\sqrt3\approx23.094$ m,同样大于清除半径。 -图~\ref{fig:triangle} 展示两类圆的差别。 +图~\ref{fig:q1geometry}(b) 展示两类圆的差别。 \begin{figure}[htbp] \centering +<<<<<<< HEAD \begin{tikzpicture}[x=0.095cm,y=0.095cm,font=\small] \fill[inkblue!10] (0,0)--(40,0)--(20,34.641)--cycle; \draw[thick] (0,0)--(40,0)--(20,34.641)--cycle; @@ -128,6 +141,20 @@ $40/\sqrt3\approx23.094$ m,同样大于清除半径。 \end{tikzpicture} \caption{等边三角形的直径圆与最小包围圆。} \label{fig:triangle} +======= +\begin{minipage}[t]{0.49\linewidth}\centering +\includegraphics[width=\linewidth]{geo-q1-convex-intersection-zoom.png} +\end{minipage}\hfill +\begin{minipage}[t]{0.49\linewidth}\centering +\includegraphics[width=\linewidth]{geo-q1-diameter-circle-counterexample.png} +\end{minipage} +\caption{问题一的两组几何。(a) 三点交会算例:定位区域及其直径圆与最小包围圆, +观测取自官方演练案例 \texttt{U5ZW-P9KN-TCAG-R7ST} 的频道 $9$, +几何量由生产求解器 \texttt{mcm26b.geometry.solve} 现算; +(b) 等边三角形解析反例,虚线直径圆 $r=20$ m 不覆盖顶点 $C$, +实线最小包围圆 $\rho=40/\sqrt3\approx23.094$ m 覆盖全部顶点,不是模拟器观测。} +\label{fig:q1geometry} +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 \end{figure} 该形状可由题设的 $\pm1^\circ$ 角域构造出来。例如检测点 diff --git a/docs/paper/sections/q2.tex b/docs/paper/sections/q2.tex index d057f79..520c897 100644 --- a/docs/paper/sections/q2.tex +++ b/docs/paper/sections/q2.tex @@ -104,22 +104,11 @@ $1.005^\circ$ & 750.115391 & 249.884609\\ \begin{figure}[htbp] \centering -\begin{tikzpicture}[x=0.006cm,y=0.006cm,font=\small,>=Stealth] - \fill[inkblue!8] (0,0)--(1499.77,-26.18) arc[start angle=-1,end angle=1,radius=1500]--cycle; - \draw[inkblue!45] (750.114,0) circle (750.114); - \filldraw[fill=inkblue!12,draw=inkblue,thick] (750.114,0) circle (249.885); - \draw[thick] (0,0)--(1499.77,26.18); - \draw[thick] (0,0)--(1499.77,-26.18); - \draw[->,dashed] (0,0)--(1650,0) node[right] {$u(\theta)$}; - \fill (0,0) circle (8) node[below left] {$s$}; - \fill[inkblue] (750.114,0) circle (8) node[below] {$c$}; - \draw[->,inkblue] (1010,345)--(890,155); - \node[anchor=west,inkblue] at (990,390) {保证接收候选盘}; - \node at (750,-850) {圆域先验 $\Omega$ 可进一步收紧候选集}; - \node[anchor=east] at (1570,-125) {首次截断角域 $S$}; - \node[inkblue!70] at (750,830) {截断角域的最小包围圆}; -\end{tikzpicture} -\caption{第二点解析候选区域的几何关系,采用 $1^\circ$ 半角、同一坐标比例绘制。} +\includegraphics[width=0.70\linewidth]{geo-q2-guaranteed-reception-disk.png} +\caption{第二点解析候选区域。左图为首次截断角域 $S$、 +其最小包围圆 $\B(c,a)$ 与保证接收的候选盘 $\B(c,1000-a)$; +右图为半径分解,$a=1500/(2\cos\delta)$。取 $\delta=1^\circ$ 时 +$a\approx750.114$ m、$1000-a\approx249.886$ m,与表~\ref{tab:safeanalytic} 一致。} \label{fig:q2disk} \end{figure} diff --git a/docs/paper/sections/q3.tex b/docs/paper/sections/q3.tex index 8cdb4f3..e48a6de 100644 --- a/docs/paper/sections/q3.tex +++ b/docs/paper/sections/q3.tex @@ -1,5 +1,6 @@ \section{问题三:全向多源的覆盖搜索与滚动调度} \subsection{统一时间目标与任务状态} +<<<<<<< HEAD 设整个会话移动距离为 $L$,检测、换频、成功和失败清除次数分别为 $N_m,N_s,N_c^+,N_c^-$。忽略每段移动时间的微秒量化时,总虚拟耗时为 \begin{equation} @@ -10,6 +11,13 @@ T=\frac{L}{5}+5N_m+N_s+5N_c^++3N_c^-. 题目统计的平均定位清除时间为 $\bar T=T/N_c^+$,包含搜索、定位与清除的全部耗时。 清除请求中的频道指定操作对象,测向机保持原频道; 进入与退出的虚拟耗时为零,因此采用终点自由的开放路径模型。 +======= +按式~\eqref{eq:time} 的计时记账,优化目标是在全部源清除的约束下最小化虚拟耗时 $T$。 +题目统计的平均定位清除时间是 $\bar T=T/N_c^+$; +不能用每次清除动作的固定 $5$ s 代替。 +清除请求中的频道是目标频道,不改变测向机频道; +进入与退出均不增加虚拟时间,因此路径终点自由,无需返回原点。 +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 各频道的状态按“未知、已发现、可清除、已清除、已证无源”管理。 首次获得 $\direction$ 或 $\near$ 即确立该频道存在源。 @@ -49,6 +57,7 @@ $r^2+a^2-\sqrt3ar$。 \begin{figure}[htbp] \centering +<<<<<<< HEAD \begin{tikzpicture}[x=0.0019cm,y=0.0019cm,font=\small] \begin{scope} \clip (0,0) circle (1800); @@ -73,6 +82,11 @@ $r^2+a^2-\sqrt3ar$。 {$a=1159.7$ m\\$R=1800$ m\\最坏距离 $\le984.539$ m}; \end{tikzpicture} \caption{七点全向覆盖的站点构造与接收圆盘。} +======= +\includegraphics[width=0.42\linewidth]{geo-q3-seven-point-coverage.png} +\caption{七点全向覆盖构造。站点取自配置参数 $a=1159.7$ m, +圆圈为 $1000$ m 接收盘,最差点位于 $(0,-1825)$ 附近,最坏距离 $\le984.539$ m。} +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 \label{fig:seven} \end{figure} @@ -195,4 +209,23 @@ C(\pi)=\frac{\norm{p-q_{\pi_1}}}{5} 当前每轮追加有效定位测量上限为 $8$; 停滞、协议异常或预算耗尽时,记录各频道状态并终止。 程序以接口返回的现实预算为准,并预留退出时间, +<<<<<<< HEAD 虚拟动作耗时以整数微秒累计,程序运行时间由官方统计单独记录。 +======= +实际计时在整数微秒状态上累计,避免用实机运行秒数代替虚拟动作耗时。 + +\subsection{运行过程示例} +图~\ref{fig:replay3} 回放一局全向演练(案例 \texttt{BBM9-HZG2-77N5-NKKC},$10$ 个源): +左图为移动轨迹、检测示向度与已清除频道,右上方为事件流水, +下方为虚拟时间轴上的检测与清除时刻。 +该局说明覆盖扫描、定位测量与清除三类任务的交替关系, +以及为接收向外发射的源而短时越出 $\Omega$ 边界的路径形态;它属于演练留档,不计入正式成绩。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.85\linewidth]{replay-webui-q3-omnidirectional.png} +\caption{全向源演练局(\texttt{BBM9-HZG2-77N5-NKKC})的运行回放。 +该局为演练案例,非正式测试记录。} +\label{fig:replay3} +\end{figure} +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 diff --git a/docs/paper/sections/q4.tex b/docs/paper/sections/q4.tex index d0d0e68..9596f10 100644 --- a/docs/paper/sections/q4.tex +++ b/docs/paper/sections/q4.tex @@ -62,7 +62,14 @@ A_{ij}=\mathbf1\{ 验证结果覆盖全部 $3\,256\,200$ 个离散状态, 最坏覆盖距离约为 $990.051$ m。 -\input{figures/q4-plan} +\begin{figure}[htbp] +\centering +\includegraphics[width=0.70\linewidth]{geo-q4-minimum-cover-31-points.png} +\caption{31 点最小命中集的几何分布(左)与定向源的前向半盘语义(右)。 +站点取自计划文件,部分站点位于 $\Omega$ 之外,用于接收向外发射的边界源; +离散校验不等同于连续覆盖证明。} +\label{fig:q4plan} +\end{figure} 从离散覆盖推广到连续覆盖时,需同时计入位置与朝向的离散化误差。 设任意实际位置距某采样位置不超过 $\varepsilon$, @@ -175,6 +182,30 @@ $N_{\mathrm{cell}}\,t_{\mathrm{RTT},95}+t_{\mathrm{reserve}}$。 \item 覆盖任务结束后处理未解决频道;主动退出时报告清除数及停止依据。 \end{enumerate} \end{algorithmblock} +<<<<<<< HEAD 问题四采用站内批量检测与最近邻任务排序。 算法结合位置集合、朝向约束和剩余预算确定测点及清除顺序, 并以各频道的处理状态作为终止时的输出。 +======= +问题四使用“本站批量扫频、最近候选圆心服务”的调度骨架, +并未直接复用问题三的统一信息增益任务表。 +若要求严格的混合源全清证据,除采用连续覆盖计划外, +还须补全类型联合表示或限制联合集排空的适用范围,并确保全部定位清除任务完成。 +本文区分当前实现、可证明条件与尚需完善之处,不把后者追溯为正式测试时已执行的功能。 + +\subsection{运行过程示例} +图~\ref{fig:replay4} 回放一局含定向源的演练(案例 \texttt{GHSN-M38E-74GD-YLUD}): +左图在轨迹、检测示向度与已清除频道之外,画出每个定向源的前向半盘 +($\pm90^\circ$,半径取该源的 $r_{\mathrm{eff}}$),箭头方向即朝向。 +图中存在落在接收半径之内、却因位于背向半平面而无信号的检测点, +对应式~\eqref{eq:visibility} 的可见性条件,保留这一解释正是问题四与问题三 +在候选更新上的区别;该局同样不计入正式成绩。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.85\linewidth]{replay-webui-q4-directional.png} +\caption{含定向源演练局(\texttt{GHSN-M38E-74GD-YLUD})的运行回放, +紫色半盘为定向源的前向可见区域。该局为演练案例,非正式测试记录。} +\label{fig:replay4} +\end{figure} +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 diff --git a/docs/paper/sections/results.tex b/docs/paper/sections/results.tex index adfbb2a..8f0dc84 100644 --- a/docs/paper/sections/results.tex +++ b/docs/paper/sections/results.tex @@ -1,9 +1,21 @@ +<<<<<<< HEAD \section{官方正式测试结果与时间分解} \subsection{实验数据与评价指标} 实验采用官方模拟器 \pathtext{Jammers-simulator-full/test} 目录中的六份正式导出, 包括问题三、四各三次测试。各文件经 SHA-256 校验后,与官方原始导出、 结果侧文件及正式统计记录建立对应关系\cite{formal}。 案例编码与文件校验值列于附录~\ref{app:data}。 +======= +\section{官方正式测试结果与时间分解}\label{sec:formal} +\subsection{数据选择及对应关系核对} +本节实验样本严格限定为官方模拟器 +\pathtext{Jammers-simulator-full/test} 下四个正式导出包。 +问题三、四各有正式序号 $1$、$2$,文件名及案例编码见结果表和附录。 +本节表格不采用 \texttt{project/results} 中的自建评测数据, +也不将历史演练案例当作正式测试补入; +自建配对对比与演练局示例分别在第~\ref{sec:comparison} 节和各问的运行过程示例中单独陈述, +两类数据不合并统计。 +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 评价指标包括清除数量 $N_c^+$、平均定位清除时间 $\bar T$、程序运行时间 $T_{\mathrm{real}}$,以及检测、换频和清除的动作次数。 @@ -99,11 +111,34 @@ T_{\mathrm{move}} \end{table} \input{figures/formal-time-bars} +<<<<<<< HEAD 六次测试中,移动时间占总虚拟耗时的 $\FormalMovementMin\%$ 至 $\FormalMovementMax\%$, 是主要时间成本。问题三的检测次数为 $105$、$117$、$117$, 问题四为 $349$、$361$、$264$; 混合源案例中,朝向约束增加了观测与位置选择的复杂度。 因此,后续优化可同时考虑减少移动距离与提高单次观测的信息增益。 +======= +图~\ref{fig:timingmeasured} 按同一记账口径给出一局全向演练的累计时间构成: +横轴为动作序号,堆叠高度为累计虚拟时间, +移动项在整局中占主要部分,清除阶段表现为阶梯状跃升。 +该图用于说明式~\eqref{eq:time} 的分项在同一局内如何随时间累积, +其数据来自演练留档,不进入正式统计。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.58\linewidth]{timing-measured-q3-10-sources.png} +\caption{一局全向演练(\texttt{BBM9-HZG2-77N5-NKKC},$10$ 个源)的累计虚拟时间构成。 +该局为演练案例,不计入正式测试结果。} +\label{fig:timingmeasured} +\end{figure} + +四局移动时间占总虚拟时间的 $76.59\%$ 至 $78.02\%$。 +从这一组正式样本看,减少移动是影响总耗时的主要方向, +检测与换频仍应同时计入路径选择。 +由 $L_{\mathrm{eq}}=5T_{\mathrm{move}}$ 得四局等效距离分别约为 +$12.045$、$13.627$、$36.386$、$35.507$ km。 +它们由已量化的移动时间反推,不是独立轨迹测得的长度。 +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 由 $L_{\mathrm{eq}}=5T_{\mathrm{move}}$ 得问题三的等效移动距离依次为 $12.045$、$13.627$、$10.824$ km,问题四依次为 diff --git a/docs/paper/sections/timing.tex b/docs/paper/sections/timing.tex new file mode 100644 index 0000000..b680399 --- /dev/null +++ b/docs/paper/sections/timing.tex @@ -0,0 +1,70 @@ +\section{计时模型与会话状态机} +\subsection{会话状态与合法动作} +题目接口只有 \texttt{/enter}、\texttt{/measure}、\texttt{/clear} 与 \texttt{/exit} +四条指令。会话状态依次为未进入、进入倒计时、接口开放与断连, +进入倒计时为 $5$ s,如图~\ref{fig:statemachine}。 +只有 \texttt{accepted=true} 的响应推进虚拟时钟;被拒绝的请求返回的虚拟时刻为零, +不能当作当前时刻使用,也不改变位置与测向机频道。 +一次合法 \texttt{/measure} 结束后,测向机频道更新为本次检测频道; +\texttt{/clear} 给出的是目标频道,既不改变测向机频道,也不产生换频耗时。 +因此清除之后原地再测的耗时取决于上一次 \texttt{/measure} 的频道,而不是清除的频道。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.92\linewidth]{steps-state-machine.png} +\caption{会话状态机。进入后经 $5$ s 倒计时接口开放,主动退出或断连后进入关闭状态。} +\label{fig:statemachine} +\end{figure} + +\subsection{动作耗时与累计虚拟时间} +题面将全部动作折算为虚拟耗时,见表~\ref{tab:actioncost}。 +检测无论返回无信号、近距离还是示向度,均计 $5$ s; +清除成功计 $5$ s,其中光学定位 $3$ s、激光清除 $2$ s, +未命中目标时只支付光学检查的 $3$ s。 +移动与换频没有独立指令,由相邻动作的位置与频道变化推断。 + +\begin{table}[htbp] +\centering\small +\caption{动作耗时规则。移动耗时等于直线距离除以 $5$ m/s。} +\begin{tabularx}{\linewidth}{l l l X} +\toprule +动作 & 指令 & 虚拟耗时/s & 说明\\ +\midrule +进入 & \texttt{/enter} & 0 & 初始位置 $(0,0)$、初始频道 $1$\\ +检测 & \texttt{/measure} & $5+$移动$+$换频 & 三种检测结果耗时相同\\ +清除 & \texttt{/clear} & $5+$移动/$3+$移动 & 成功/未命中目标,均不切换测向频道\\ +退出 & \texttt{/exit} & 0 & 路径终点自由,无需返回原点\\ +换频 & 由检测推断 & 1 & 相邻 \texttt{/measure} 的频道变化\\ +移动 & 由动作推断 & 距离$/5$ & 相邻动作的位置变化\\ +\bottomrule +\end{tabularx} +\label{tab:actioncost} +\end{table} + +设整个会话的移动距离为 $L$,检测、换频、成功清除与失败清除的次数分别为 +$N_m,N_s,N_c^+,N_c^-$。忽略每段移动时间的微秒量化时,总虚拟耗时为 +\begin{equation} +T=\frac{L}{5}+5N_m+N_s+5N_c^++3N_c^-. +\label{eq:time} +\end{equation} +式~\eqref{eq:time} 是后文任务排序的服务时间来源, +也是由官方累计量反推移动时间(式~\eqref{eq:residual})的依据。 +图~\ref{fig:timingexample} 用附件的五步示例说明记账方式: +首次检测含移动 $100$ s 与检测 $5$ s,单步 $105$ s; +原地换到频道 $2$ 检测单步 $6$ s,累计 $111$ s; +清除目标频道 $3$ 未命中,移动 $80$ s 加光学检查 $3$ s,单步 $83$ s,累计 $194$ s; +清除不改变测向机频道,故随后原地以频道 $2$ 复测只需 $5$ s,累计 $199$ s,与附件一致。 + +\begin{figure}[htbp] +\centering +\includegraphics[width=0.55\linewidth]{timing-attachment-example-105-111-194-199.png} +\caption{附件计时示例的逐动作构成。移动项为距离除以 $5$ m/s,标题数字为该步合计。} +\label{fig:timingexample} +\end{figure} + +\subsection{虚拟时间、现实预算与终止} +虚拟时间度量任务完成速度,现实预算约束程序能否继续发出请求,两者不能混用。 +程序以 \texttt{/enter} 返回的 \texttt{remaining\_real\_duration\_s} 为准, +在每个动作前预留退出时间;虚拟时间以整数微秒累计,在线预测只用于规划与一致性核对。 +因此后文的“耗时”一律指式~\eqref{eq:time} 的虚拟时间,程序运行时间在结果表中单列。 +策略在预算不足或协议异常时主动退出并报告未解决频道,不以程序正常结束代替任务完成。 diff --git a/scripts/build_paper_support.py b/scripts/build_paper_support.py index b667088..545c5f5 100644 --- a/scripts/build_paper_support.py +++ b/scripts/build_paper_support.py @@ -1,7 +1,9 @@ -"""Build vector figure sources, complete source listings, and a compact support ZIP. +"""Build vector figure sources, comparison tables, source listings, and a support ZIP. Formal figures use the audited snapshot only. Geometric figures use declared algorithm parameters, not fabricated observations or hidden simulator data. +The paired optimal-versus-baseline tables are generated from project/results/*.json +so that no number in the comparison section is hand copied. """ from __future__ import annotations @@ -9,11 +11,12 @@ import argparse import hashlib import json import zipfile -from decimal import Decimal +from decimal import Decimal, ROUND_HALF_UP from pathlib import Path ROOT = Path(__file__).resolve().parents[1] PAPER = ROOT / "docs/paper" +PROJECT_RESULTS = ROOT / "project/results" # Display copies only: executable Python files remain byte-for-byte unchanged. ESCAPE_OPEN = "(" + "*@" @@ -46,6 +49,7 @@ def digest(data: bytes) -> str: def figures(rows: list[dict]) -> None: out = PAPER / "figures" out.mkdir(parents=True, exist_ok=True) +<<<<<<< HEAD plan = json.loads((ROOT / "src/mcm26b/guarantee/plans/q4_mincover_v2.json").read_text("utf-8")) lines = [ r"\begin{figure}[htbp]\centering", @@ -68,6 +72,8 @@ def figures(rows: list[dict]) -> None: r"\label{fig:q4plan}\end{figure}", ] (out / "q4-plan.tex").write_text("\n".join(lines) + "\n", "utf-8") +======= +>>>>>>> 78e4420a3e496c43db8e06556e539fd7d2aaf231 colors = ["inkblue", "inkblue!50", "warmgray!70", "black!18"] bars = [ r"\begin{figure}[htbp]\centering", @@ -101,6 +107,164 @@ def figures(rows: list[dict]) -> None: (out / "formal-time-bars.tex").write_text("\n".join(bars) + "\n", "utf-8") +def fixed(value: float | None, digits: int = 3) -> str: + if value is None: + return "--" + return str(Decimal(str(value)).quantize(Decimal(1).scaleb(-digits), rounding=ROUND_HALF_UP)) + + +def percent(value: float | None, digits: int = 1) -> str: + if value is None: + return "--" + return fixed(100 * value, digits) + r"\%" + + +def flag(value: bool | None) -> str: + return "--" if value is None else ("是" if value else "否") + + +def project_tables() -> None: + """Generate the optimal-versus-baseline comparison tables from project/results.""" + out = PAPER / "data" + out.mkdir(parents=True, exist_ok=True) + data = {name: json.loads((PROJECT_RESULTS / f"{name}.json").read_text("utf-8")) + for name in ("q1", "q2", "q3", "q4")} + examples, random = data["q1"]["aggregate_examples"], data["q1"]["aggregate_random"] + two = data["q2"]["aggregate"] + three, paired3 = data["q3"]["aggregate"]["variants"], data["q3"]["aggregate"]["paired"] + four, paired4 = data["q4"]["aggregate"]["variants"], data["q4"]["aggregate"]["paired"] + head = "% Generated by scripts/build_paper_support.py from project/results/*.json." + + macros = [ + ("ProjQOneExampleCases", str(examples["cases"])), + ("ProjQOneRandomCases", str(random["cases"])), + ("ProjQOneFixedStatusBase", percent(examples["status_accuracy_baseline"])), + ("ProjQOneFixedMaeBase", fixed(examples["diameter_mae_baseline_m"])), + ("ProjQOneFixedMaeMaxBase", fixed(examples["diameter_max_err_baseline_m"])), + ("ProjQOneFixedCoverBase", percent(examples["coverage_accuracy_baseline"])), + ("ProjQOneFixedMsOpt", fixed(examples["runtime_optimal_mean_ms"])), + ("ProjQOneFixedMsBase", fixed(examples["runtime_baseline_mean_ms"])), + ("ProjQOneRandStatusBase", percent(random["status_accuracy_baseline"])), + ("ProjQOneRandMaeBase", fixed(random["diameter_mae_baseline_m"])), + ("ProjQOneRandMaeMaxBase", fixed(random["diameter_max_err_baseline_m"])), + ("ProjQOneRandCoverBase", percent(random["coverage_accuracy_baseline"])), + ("ProjQOneRandMsOpt", fixed(random["runtime_optimal_mean_ms"])), + ("ProjQOneRandMsBase", fixed(random["runtime_baseline_mean_ms"])), + ("ProjQTwoCases", str(two["optimal"]["cases"])), + ("ProjQTwoInitialRadius", fixed(two["_initial_radius_mean_m"])), + ("ProjQTwoGuaranteeOpt", percent(two["optimal"]["guaranteed_rate"])), + ("ProjQTwoGuaranteeMec", percent(two["baseline_mec"]["guaranteed_rate"])), + ("ProjQTwoGuaranteePerp", percent(two["baseline_perp"]["guaranteed_rate"])), + ("ProjQTwoWorstOpt", fixed(two["optimal"]["worst_posterior_mean_m"])), + ("ProjQTwoWorstRef", fixed(two["reference"]["worst_posterior_mean_m"])), + ("ProjQTwoRatioOpt", fixed(two["optimal"]["final_ratio_mean"])), + ("ProjQTwoRatioMec", fixed(two["baseline_mec"]["final_ratio_mean"])), + ("ProjQTwoWorstPerp", fixed(two["baseline_perp"]["worst_posterior_mean_m"])), + ("ProjQTwoWorstMec", fixed(two["baseline_mec"]["worst_posterior_mean_m"])), + ("ProjQThreeScenes", str(three["optimal"]["episodes"])), + ("ProjQThreeMeanOpt", fixed(three["optimal"]["virtual_time_mean_s"])), + ("ProjQThreeMeanSeven", fixed(three["seven"]["virtual_time_mean_s"])), + ("ProjQThreeMeanBase", fixed(three["baseline"]["virtual_time_mean_s"])), + ("ProjQThreeDropOpt", percent(-paired3["optimal_vs_baseline"]["mean_relative_change"])), + ("ProjQThreeDropMedianOpt", + percent(-paired3["optimal_vs_baseline"]["median_relative_change"])), + ("ProjQThreeDropSeven", percent(-paired3["seven_vs_baseline"]["mean_relative_change"])), + ("ProjQThreeFasterOpt", + f"{paired3['optimal_vs_baseline']['faster_scenes']}/{paired3['optimal_vs_baseline']['compared']}"), + ("ProjQThreeMeasuresOpt", fixed(three["optimal"]["measures_mean"])), + ("ProjQThreeMeasuresBase", fixed(three["baseline"]["measures_mean"])), + ("ProjQThreeMoveOpt", fixed(three["optimal"]["move_distance_mean_m"] / 1000)), + ("ProjQThreeMoveBase", fixed(three["baseline"]["move_distance_mean_m"] / 1000)), + ("ProjQThreeWallOpt", fixed(three["optimal"]["compute_wall_mean_s"])), + ("ProjQFourScenes", str(four["optimal"]["episodes"])), + ("ProjQFourMeanOpt", fixed(four["optimal"]["virtual_time_mean_s"])), + ("ProjQFourMeanGrid", fixed(four["baseline_grid"]["virtual_time_mean_s"])), + ("ProjQFourMeanBase", fixed(four["baseline"]["virtual_time_mean_s"])), + ("ProjQFourClearOpt", percent(four["optimal"]["cleared_ratio_mean"])), + ("ProjQFourClearGrid", percent(four["baseline_grid"]["cleared_ratio_mean"])), + ("ProjQFourClearBase", percent(four["baseline"]["cleared_ratio_mean"])), + ("ProjQFourAbsentBase", str(four["baseline"]["absent_misjudged_total"])), + ("ProjQFourMeasuresOpt", fixed(four["optimal"]["measures_mean"])), + ("ProjQFourMeasuresBase", fixed(four["baseline"]["measures_mean"])), + ("ProjQFourFallbackOpt", fixed(four["optimal"]["fallback_clear_attempts_mean"])), + ("ProjQFourValidOpt", + f"{four['optimal']['valid_episodes']}/{four['optimal']['episodes']}"), + ("ProjQFourValidGrid", + f"{four['baseline_grid']['valid_episodes']}/{four['baseline_grid']['episodes']}"), + ("ProjQFourRiseOpt", percent(paired4["optimal_vs_baseline"]["mean_relative_change"])), + ("ProjQFourMoveOpt", fixed(four["optimal"]["move_distance_mean_m"] / 1000)), + ("ProjQFourMoveBase", fixed(four["baseline"]["move_distance_mean_m"] / 1000)), + ] + lines = [head] + [f"\\newcommand{{\\{name}}}{{{value}}}" for name, value in macros] + (out / "project-macros.tex").write_text("\n".join(lines) + "\n", "utf-8") + + def write(stem: str, rows: list[str]) -> None: + (out / stem).write_text("\n".join([head, *rows]) + "\n", "utf-8") + + write("project-overview-rows.tex", [ + "问题 & 核心指标 & 最优实现 & 基线实现 " + r"\\", + f"1 & 直径平均绝对误差 / 覆盖判定准确率 & {fixed(random['diameter_mae_optimal_m'])} m / " + f"{percent(random['coverage_accuracy_optimal'])} & {fixed(random['diameter_mae_baseline_m'])} m / " + f"{percent(random['coverage_accuracy_baseline'])} " + r"\\", + f"2 & 保证接收率 / 最坏后验半径均值 & {percent(two['optimal']['guaranteed_rate'])} / " + f"{fixed(two['optimal']['worst_posterior_mean_m'])} m & {percent(two['baseline_mec']['guaranteed_rate'])} / " + f"{fixed(two['baseline_mec']['worst_posterior_mean_m'])} m " + r"\\", + f"3 & 全清率 / 平均虚拟耗时 & {percent(three['optimal']['cleared_ratio_mean'])} / " + f"{fixed(three['optimal']['virtual_time_mean_s'])} s & {percent(three['baseline']['cleared_ratio_mean'])} / " + f"{fixed(three['baseline']['virtual_time_mean_s'])} s " + r"\\", + f"4 & 全清率 / 缺席误判总数 & {percent(four['optimal']['cleared_ratio_mean'])} / " + f"{four['optimal']['absent_misjudged_total']} & {percent(four['baseline']['cleared_ratio_mean'])} / " + f"{four['baseline']['absent_misjudged_total']} " + r"\\", + ]) + + write("project-q1-example-rows.tex", [ + f"{row['name']} & {fixed(row['diameter_ref_m'])} & {fixed(row['diameter_opt_m'])} & " + f"{fixed(row['diameter_base_m'])} & {fixed(row['abs_err_base_m'])} & " + f"{flag(row['covers_ref'])} & {flag(row['covers_opt'])} & {flag(row['covers_base'])} " + r"\\" + for row in data["q1"]["examples"] + ]) + + write("project-q1-summary-rows.tex", [ + f"最优(固定 {examples['cases']} 例) & {percent(examples['status_accuracy_optimal'])} & " + f"{fixed(examples['diameter_mae_optimal_m'])} & {fixed(examples['diameter_max_err_optimal_m'])} & " + f"{percent(examples['coverage_accuracy_optimal'])} & {fixed(examples['runtime_optimal_mean_ms'])} " + r"\\", + f"最优(随机 {random['cases']} 例) & {percent(random['status_accuracy_optimal'])} & " + f"{fixed(random['diameter_mae_optimal_m'])} & {fixed(random['diameter_max_err_optimal_m'])} & " + f"{percent(random['coverage_accuracy_optimal'])} & {fixed(random['runtime_optimal_mean_ms'])} " + r"\\", + f"基线(固定 {examples['cases']} 例) & {percent(examples['status_accuracy_baseline'])} & " + f"{fixed(examples['diameter_mae_baseline_m'])} & {fixed(examples['diameter_max_err_baseline_m'])} & " + f"{percent(examples['coverage_accuracy_baseline'])} & {fixed(examples['runtime_baseline_mean_ms'])} " + r"\\", + f"基线(随机 {random['cases']} 例) & {percent(random['status_accuracy_baseline'])} & " + f"{fixed(random['diameter_mae_baseline_m'])} & {fixed(random['diameter_max_err_baseline_m'])} & " + f"{percent(random['coverage_accuracy_baseline'])} & {fixed(random['runtime_baseline_mean_ms'])} " + r"\\", + ]) + + write("project-q2-rows.tex", [ + f"{label} & {percent(two[key]['guaranteed_rate'])} & {fixed(two[key]['worst_posterior_mean_m'])} & " + f"{fixed(two[key]['worst_posterior_median_m'])} & {fixed(two[key]['improvement_mean_m'])} & " + f"{fixed(two[key]['final_ratio_mean'])} & {fixed(two[key]['move_cost_mean_s'], 2)} " + r"\\" + for key, label in (("optimal", "算法2(默认选点)"), ("reference", "参考:盘内网格枚举"), + ("baseline_perp", "基线:垂直 1000 m"), ("baseline_mec", "基线:候选圆心")) + ]) + + def variant_rows(table: dict, mapping: list[tuple[str, str]]) -> list[str]: + return [ + f"{label} & {table[key]['valid_episodes']}/{table[key]['episodes']} & " + f"{percent(table[key]['cleared_ratio_mean'])} & {table[key]['absent_misjudged_total']} & " + f"{fixed(table[key]['virtual_time_mean_s'])} & {fixed(table[key]['virtual_time_median_s'])} & " + f"{fixed(table[key]['virtual_time_max_s'])} & {fixed(table[key]['measures_mean'], 1)} & " + f"{fixed(table[key]['move_distance_mean_m'] / 1000)} & {fixed(table[key]['fallback_clear_attempts_mean'], 1)} " + + r"\\" + for key, label in mapping + ] + + write("project-q3-rows.tex", variant_rows(three, [ + ("optimal", "bayes(默认策略)"), ("seven", "seven(七点批量)"), ("baseline", "grid(49 点网格)")])) + write("project-q4-rows.tex", variant_rows(four, [ + ("optimal", "directional(默认策略)"), ("baseline_grid", "grid(问题三网格)"), + ("baseline", "bayes(问题三策略)")])) + + def listings(rows: list[dict]) -> None: data = PAPER / "data" files = sorted((ROOT / "src/mcm26b").rglob("*.py")) @@ -185,6 +349,11 @@ def package(rows: list[dict]) -> Path: selected.update((PAPER / "data").rglob("*")) selected.update((PAPER / "figures").glob("*.tex")) selected.update((PAPER / "sections").glob("*.tex")) + selected.update((ROOT / "docs/figures").glob("*.png")) + selected.update((ROOT / "docs/figures").glob("*.json")) + selected.add(ROOT / "docs/figures/README.md") + selected.update(path for path in (ROOT / "project").rglob("*") + if path.is_file() and "__pycache__" not in path.parts) selected.update([PAPER / "main.tex", PAPER / "mcm26b-paper.cls", PAPER / "README.md", PAPER / "REWRITE_NOTES.md", ROOT / "scripts/audit_paper_formal.py", @@ -217,6 +386,7 @@ def main() -> None: args = parser.parse_args() rows = json.loads((PAPER / "data/formal-results.json").read_text("utf-8"))["records"] figures(rows) + project_tables() listings(rows) if args.package: package(rows)