@@ -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,28 +49,6 @@ def digest(data: bytes) -> str:
def figures(rows: list[dict]) -> None:
out = PAPER / "figures"
out.mkdir(parents=True, exist_ ok=True)
plan = json.loads((ROOT / "src/mcm26b/guarantee/plans/q4_ mincover_ v2.json").read_ text("utf-8"))
lines = [
r"\begin { figure} [htbp]\centering ",
r"\begin { tikzpicture} [x=0.00145cm,y=0.00145cm,font=\small ]",
r"\fill [inkblue!5] (0,0) circle (1800);",
r"\draw [thick] (0,0) circle (1800);",
r"\draw [->,warmgray] (-2900,0)--(2950,0) node[right] { $ x $ /m} ;",
r"\draw [->,warmgray] (0,-2800)--(0,2850) node[above] { $ y $ /m} ;",
]
for x in (-2000, -1000, 1000, 2000):
lines += [fr"\draw ({ x} ,-35)--({ x} ,35);",
fr"\node [below,font=\scriptsize] at ({ x} ,-50) { { { x} } } ;"]
for x, y in plan["points"]:
lines.append(fr"\fill [inkblue] ({ x} ,{ y} ) circle (33);")
lines += [
r"\node [anchor=west,align=left] at (3250,900) { 圆域:源位置先验\\ 蓝点:31个检测站} ;",
r"\node [anchor=west,align=left] at (3250,-700) { 部分站点在圆域外\\ 用于接收向外发射的源} ;",
r"\end { tikzpicture} ",
r"\caption { 第四问31点计划的几何分布。站点取自计划文件,不代表正式运行轨迹;离散验证不等同于连续覆盖证明。} ",
r"\label { fig:q4plan} \end { figure} ",
]
(out / "q4-plan.tex").write_ text("\n ".join(lines) + "\n ", "utf-8")
colors = ["inkblue", "inkblue!50", "warmgray!70", "black!18"]
bars = [
r"\begin { figure} [htbp]\centering ",
@@ -101,6 +82,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"))
@@ -190,6 +329,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 / "mcm 26 b - paper.cls",
PAPER / "README.md", PAPER / "REWRITE _ NOTES.md",
ROOT / "scripts / audit _ paper _ formal.py",
@@ -222,6 +366,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 )