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-13 10:55:45 +08:00
21 changed files with 905 additions and 40 deletions
@@ -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)
+55
View File
@@ -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}
@@ -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 \\
@@ -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 & 是 & 是 & 是 \\
不相容观测 & -- & -- & -- & -- & -- & -- & -- \\
@@ -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 \\
+5
View File
@@ -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 \\
+4
View File
@@ -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 \\
+4
View File
@@ -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 \\