2
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"""问题四批量仿真基准:固定误差场下统计后备清除成本与总请求量。
|
||||
|
||||
逐局在进程内驱动 DirectionalExtension(本地模拟器;示向度误差场逐点
|
||||
固定,与官方"同一地点误差不变"口径一致),记录 completed、measures、
|
||||
clears、total_requests、fallback_clear_attempts、virtual_time_s 等指标,
|
||||
落盘 JSON 并打印汇总表。用于:
|
||||
- 验证后备清除成本(对跖探点修正后梯子命中率的现实校核);
|
||||
- 为论文问题四结果表提供可复现数字(种子、配置、逐局指标全部留档)。
|
||||
|
||||
用法示例:
|
||||
uv run python scripts/benchmark_q4.py --seeds 0-15 --sources 10 --directional 3
|
||||
uv run python scripts/benchmark_q4.py --seeds 20260910 --sources 16 --directional 6
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from mcm26b.config import ProblemConfig
|
||||
from mcm26b.evaluation import recompute_virtual_time
|
||||
from mcm26b.planner import DirectionalExtension
|
||||
from mcm26b.protocol.client import SimulatorClient
|
||||
from mcm26b.sim import LocalSimTransport, LocalSimulator
|
||||
from mcm26b.sim.scenes import mixed_scene
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def parse_seeds(spec: str) -> list[int]:
|
||||
"""种子列表:"0-15" 区间、"0,5,9" 枚举或单个值。"""
|
||||
seeds: list[int] = []
|
||||
for part in spec.split(","):
|
||||
part = part.strip()
|
||||
if "-" in part:
|
||||
lo, hi = part.split("-", 1)
|
||||
seeds.extend(range(int(lo), int(hi) + 1))
|
||||
else:
|
||||
seeds.append(int(part))
|
||||
return seeds
|
||||
|
||||
|
||||
def run_one(seed: int, count: int, directional: int, margin: float) -> dict:
|
||||
config = replace(ProblemConfig(),
|
||||
search=replace(ProblemConfig().search,
|
||||
bearing_rounding_margin_deg=margin))
|
||||
sources = mixed_scene(seed=seed, count=count, directional=directional)
|
||||
simulator = LocalSimulator(sources, robot_id="BENCH", config=config)
|
||||
client = SimulatorClient("BENCH", config=config,
|
||||
transport=LocalSimTransport(simulator))
|
||||
loop = DirectionalExtension(client, config)
|
||||
summary = loop.run()
|
||||
report = recompute_virtual_time(client.log.action_events())
|
||||
truth = sorted(s.channel for s in sources)
|
||||
return {
|
||||
"seed": seed,
|
||||
"sources": count,
|
||||
"directional": directional,
|
||||
"completed": summary["completed"],
|
||||
"stop_reason": summary["stop_reason"],
|
||||
"all_cleared": sorted(summary["cleared_channels"]) == truth,
|
||||
"measures": summary["measures"],
|
||||
"clears": summary["clears"],
|
||||
"total_requests": summary["total_requests"],
|
||||
"fallback_cells": [entry["cells"] for entry in summary["fallback_clears"]],
|
||||
"fallback_attempts": summary["fallback_clear_attempts"],
|
||||
"virtual_time_s": summary["virtual_time_s"],
|
||||
"virtual_recompute_mismatches": len(report["mismatches"]),
|
||||
"anomaly_count": len(summary["anomalies"]),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||
parser.add_argument("--seeds", default="0-15",
|
||||
help="种子,如 0-15 / 0,5,9 / 20260910")
|
||||
parser.add_argument("--sources", type=int, default=10)
|
||||
parser.add_argument("--directional", type=int, default=3)
|
||||
parser.add_argument("--bearing-margin-deg", type=float, default=0.005)
|
||||
parser.add_argument("--out", default="output/q4/benchmark.json")
|
||||
args = parser.parse_args()
|
||||
|
||||
rows = [run_one(seed, args.sources, args.directional, args.bearing_margin_deg)
|
||||
for seed in parse_seeds(args.seeds)]
|
||||
completed = sum(1 for row in rows if row["completed"])
|
||||
cleared = sum(1 for row in rows if row["all_cleared"])
|
||||
summary = {
|
||||
"config": {"sources": args.sources, "directional": args.directional,
|
||||
"bearing_margin_deg": args.bearing_margin_deg},
|
||||
"games": len(rows),
|
||||
"completed": completed,
|
||||
"all_cleared": cleared,
|
||||
"measures_total": sum(row["measures"] for row in rows),
|
||||
"clears_total": sum(row["clears"] for row in rows),
|
||||
"total_requests_total": sum(row["total_requests"] for row in rows),
|
||||
"fallback_attempts_total": sum(row["fallback_attempts"] for row in rows),
|
||||
"fallback_attempts_max": max((row["fallback_attempts"] for row in rows),
|
||||
default=0),
|
||||
"virtual_time_s_max": max((row["virtual_time_s"] for row in rows),
|
||||
default=0.0),
|
||||
"mismatch_total": sum(row["virtual_recompute_mismatches"] for row in rows),
|
||||
"anomaly_total": sum(row["anomaly_count"] for row in rows),
|
||||
}
|
||||
payload = {"summary": summary, "games": rows}
|
||||
out_path = ROOT / args.out
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8")
|
||||
print(json.dumps(summary, ensure_ascii=False, indent=2))
|
||||
header = ("seed", "done", "clear", "measures", "clears", "requests",
|
||||
"fb_attempts", "virtual_s")
|
||||
print("\t".join(header))
|
||||
for row in rows:
|
||||
print("\t".join(str(row[key]) for key in
|
||||
("seed", "completed", "all_cleared", "measures", "clears",
|
||||
"total_requests", "fallback_attempts", "virtual_time_s")))
|
||||
print(f"\n结果已写入 {out_path}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user