baseline
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
"""问题二同案配对对比:最优 / 两个基线 / 网格参考,用同一评估器打分。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
from mcm26b.config import ProblemConfig
|
||||
from mcm26b.planner import _worst_posterior_radius
|
||||
|
||||
from project.common import (Method, RESULTS_DIR, markdown_table, mean, ratio,
|
||||
write_json, write_text)
|
||||
from project.q2 import baseline, cases, optimal, reference
|
||||
from project.q2.region import first_bearing_region
|
||||
|
||||
METHODS: tuple[Method, ...] = (optimal.METHOD, baseline.PERP, baseline.MEC,
|
||||
reference.METHOD)
|
||||
EVAL_WITNESS_SPACING_M = 60.0
|
||||
|
||||
|
||||
def _evaluate(case: dict, config: ProblemConfig) -> list[dict]:
|
||||
region = first_bearing_region(case, config)
|
||||
station = (case["station"][0], case["station"][1])
|
||||
initial_radius = region.mec()[1]
|
||||
half_angle = (config.field.bearing_error_half_deg
|
||||
+ config.search.bearing_rounding_margin_deg)
|
||||
witnesses = region.witnesses(EVAL_WITNESS_SPACING_M)
|
||||
|
||||
plans = [optimal.candidate(case, config),
|
||||
baseline.candidate_perp(case, config),
|
||||
baseline.candidate_mec(case, config),
|
||||
reference.candidate(case, config)]
|
||||
rows = []
|
||||
for plan in plans:
|
||||
q = (plan["position"][0], plan["position"][1])
|
||||
guarantee_distance = max(math.dist(q, vertex) for vertex in region.polygon)
|
||||
worst = _worst_posterior_radius(
|
||||
region, q, witnesses, half_angle,
|
||||
config.field.near_radius_m, config.search.polygon_sides)
|
||||
move_cost = math.dist(station, q) / config.cost.speed_mps
|
||||
rows.append({
|
||||
"case_id": case["id"],
|
||||
"method": plan["method"],
|
||||
"initial_radius_m": round(initial_radius, 4),
|
||||
"q": [round(q[0], 2), round(q[1], 2)],
|
||||
"guaranteed_reception": bool(guarantee_distance
|
||||
<= config.field.r_eff_min_m + 1e-6),
|
||||
"guarantee_distance_m": round(guarantee_distance, 3),
|
||||
"worst_posterior_radius_m": round(worst, 4),
|
||||
"improvement_m": round(initial_radius - worst, 4),
|
||||
"final_ratio": round(worst / initial_radius, 4) if initial_radius > 0 else None,
|
||||
"move_cost_s": round(move_cost, 4),
|
||||
"reason": plan["reason"],
|
||||
})
|
||||
return rows
|
||||
|
||||
|
||||
def _aggregate(rows: list[dict], labels: list[str]) -> dict:
|
||||
summary: dict[str, dict] = {}
|
||||
initial = mean(r["initial_radius_m"] for r in rows) if rows else 0.0
|
||||
for label in labels:
|
||||
part = [r for r in rows if r["method"] == label]
|
||||
if not part:
|
||||
continue
|
||||
summary[label] = {
|
||||
"cases": len(part),
|
||||
"guaranteed_rate": ratio(sum(r["guaranteed_reception"] for r in part),
|
||||
len(part)),
|
||||
"worst_posterior_mean_m": mean(r["worst_posterior_radius_m"] for r in part),
|
||||
"worst_posterior_median_m": sorted(
|
||||
r["worst_posterior_radius_m"] for r in part)[len(part) // 2],
|
||||
"improvement_mean_m": mean(r["improvement_m"] for r in part),
|
||||
"final_ratio_mean": mean(r["final_ratio"] for r in part
|
||||
if r["final_ratio"] is not None),
|
||||
"move_cost_mean_s": mean(r["move_cost_s"] for r in part),
|
||||
}
|
||||
summary["_initial_radius_mean_m"] = initial
|
||||
return summary
|
||||
|
||||
|
||||
def _render(rows: list[dict], aggregate: dict, labels: list[str]) -> str:
|
||||
headers = ["算例", "方法", "初始 ρ(m)", "第二点 q", "保证接收",
|
||||
"q 到 K₁ 最远(m)", "最坏后验 ρ'(m)", "收缩(m)", "移动(s)"]
|
||||
body = []
|
||||
order = {label: index for index, label in enumerate(labels)}
|
||||
for row in sorted(rows, key=lambda r: (r["case_id"], order.get(r["method"], 99))):
|
||||
body.append([
|
||||
row["case_id"], row["method"], _f(row["initial_radius_m"]),
|
||||
f"({row['q'][0]:.0f},{row['q'][1]:.0f})",
|
||||
"是" if row["guaranteed_reception"] else "否",
|
||||
_f(row["guarantee_distance_m"]), _f(row["worst_posterior_radius_m"]),
|
||||
_f(row["improvement_m"]), _f(row["move_cost_s"]),
|
||||
])
|
||||
text = ["# 问题二:第二检测点选择对比", "",
|
||||
"初始 ρ 为首次示向度候选集 K₁ 的最小包围半径;最坏后验按"
|
||||
"目标边界采样 × 读数区间端点统一评估。", "",
|
||||
markdown_table(headers, body), "", "## 逐方法汇总", ""]
|
||||
agg_headers = ["方法", "保证接收率", "最坏后验均值(m)", "中位(m)",
|
||||
"平均收缩(m)", "终值比 ρ'/ρ", "移动均值(s)"]
|
||||
agg_rows = []
|
||||
for label in labels:
|
||||
s = aggregate.get(label)
|
||||
if not s:
|
||||
continue
|
||||
agg_rows.append([label, f"{s['guaranteed_rate']*100:.1f}%",
|
||||
_f(s["worst_posterior_mean_m"]),
|
||||
_f(s["worst_posterior_median_m"]),
|
||||
_f(s["improvement_mean_m"]),
|
||||
_f(s["final_ratio_mean"], 3),
|
||||
_f(s["move_cost_mean_s"])])
|
||||
text.append(markdown_table(agg_headers, agg_rows))
|
||||
text.append("")
|
||||
text.append(f"初始 ρ 均值:{aggregate['_initial_radius_mean_m']:.2f} m")
|
||||
return "\n".join(text) + "\n"
|
||||
|
||||
|
||||
def _f(value, digits: int = 2) -> str:
|
||||
if value is None:
|
||||
return "—"
|
||||
if isinstance(value, bool):
|
||||
return "是" if value else "否"
|
||||
if isinstance(value, float):
|
||||
return f"{value:.{digits}f}"
|
||||
return str(value)
|
||||
|
||||
|
||||
def run(quick: bool = False) -> dict:
|
||||
config = ProblemConfig()
|
||||
case_list = cases.random_cases(8 if quick else 30, config=config)
|
||||
rows = [row for case in case_list for row in _evaluate(case, config)]
|
||||
labels = [m.key for m in METHODS]
|
||||
aggregate = _aggregate(rows, labels)
|
||||
payload = {"question": 2, "methods": [m.__dict__ for m in METHODS],
|
||||
"cases": case_list, "rows": rows, "aggregate": aggregate}
|
||||
write_json(RESULTS_DIR / "q2.json", payload)
|
||||
write_text(RESULTS_DIR / "q2.md", _render(rows, aggregate, labels))
|
||||
return payload
|
||||
Reference in New Issue
Block a user