This commit is contained in:
Horldsence
2026-09-12 16:58:25 +08:00
parent 1fa2264a17
commit 892451dca9
36 changed files with 8767 additions and 0 deletions
+178
View File
@@ -0,0 +1,178 @@
"""问题一独立参考解:大框 Sutherland–Hodgman 半平面裁剪 + 精确直径/MEC。
与生产实现 ``mcm26b.geometry.localization.solve`` 的独立性:
- 生产实现枚举"半平面边界直线两两交点"再筛可行点;
- 本参考解从一个大方框出发,逐条半平面做 Sutherland–Hodgman 裁剪,
是另一条构造路径,且这里的半平面系数、直径、最小包围圆都在本文件内
独立编码(不复用生产实现的 ``halfplanes`` / ``diameter`` / ``mec``)。
只要区域有界,大框(±1e7 m)裁剪得到的顶点即精确多边形;顶点触到大框
边界则判无界。最小包围圆用"枚举所有 1/2/3 点支撑圆"的暴力算法,直径用
顶点对暴力枚举,二者都是 O(m²)/O(m³) 的朴素精确算法,m 为多边形顶点数。
"""
from __future__ import annotations
import math
from time import perf_counter
from project.common import Method
METHOD = Method(
key="reference",
name="大框半平面裁剪 + 精确直径/MEC",
kind="reference",
note="独立于生产顶点枚举的精确参考解",
)
BOX_M = 1.0e7
_PARALLEL_EPS = 1e-12
def _planes(case: dict) -> list[tuple[float, float, float]]:
"""独立重写 a*x+b*y<=c 的半平面构造(与题面角域定义一致)。"""
planes = []
for row in case["measurements"]:
half = math.radians(row.get("half_angle_deg", 1.0))
theta = math.radians(row["bearing_deg"] % 360.0)
sx, sy = row["x"], row["y"]
for angle, sign in ((theta - half, 1.0), (theta + half, -1.0)):
a = sign * math.sin(angle)
b = -sign * math.cos(angle)
planes.append((a, b, a * sx + b * sy))
# 前向约束:不允许把角域取成反向无限双楔
a, b = -math.cos(theta), -math.sin(theta)
planes.append((a, b, a * sx + b * sy))
return planes
def _clip(polygon, a, b, c, tol=1e-7):
"""Sutherland–Hodgman:保留 a*x+b*y<=c 的一侧。"""
if not polygon:
return []
output = []
count = len(polygon)
for index in range(count):
p, q = polygon[index], polygon[(index + 1) % count]
fp = a * p[0] + b * p[1] - c
fq = a * q[0] + b * q[1] - c
p_in, q_in = fp <= tol, fq <= tol
if p_in:
output.append(p)
if p_in != q_in:
ratio = fp / (fp - fq)
output.append((p[0] + ratio * (q[0] - p[0]),
p[1] + ratio * (q[1] - p[1])))
deduped = []
for point in output:
if not deduped or math.dist(point, deduped[-1]) > 1e-9:
deduped.append(point)
if len(deduped) > 1 and math.dist(deduped[0], deduped[-1]) <= 1e-9:
deduped.pop()
return deduped
def _brute_force_diameter(points):
best = 0.0
pair = (points[0], points[0])
for i, pi in enumerate(points):
for pj in points[i + 1:]:
value = math.dist(pi, pj)
if value > best:
best, pair = value, (pi, pj)
return best, pair
def _circumcircle(a, b, c):
ux, uy = b[0] - a[0], b[1] - a[1]
vx, vy = c[0] - a[0], c[1] - a[1]
det = 2.0 * (ux * vy - uy * vx)
if abs(det) <= _PARALLEL_EPS * max(
math.hypot(ux, uy) * math.hypot(vx, vy), 1e-300):
return None
uu, vv = ux * ux + uy * uy, vx * vx + vy * vy
center = (a[0] + (uu * vy - vv * uy) / det,
a[1] + (ux * vv - vx * uu) / det)
return center, math.dist(center, a)
def _brute_force_mec(points):
"""枚举 1/2/3 点支撑圆的精确最小包围圆(朴素但无迭代)。"""
xs = [p[0] for p in points]
ys = [p[1] for p in points]
best_c = ((min(xs) + max(xs)) / 2.0, (min(ys) + max(ys)) / 2.0)
best_r = max(math.dist(best_c, p) for p in points)
candidates: list[tuple[tuple[float, float], float]] = []
for i, p in enumerate(points):
for q in points[i + 1:]:
candidates.append((((p[0] + q[0]) / 2.0, (p[1] + q[1]) / 2.0),
math.dist(p, q) / 2.0))
for i, p in enumerate(points):
for j in range(i + 1, len(points)):
for k in range(j + 1, len(points)):
circle = _circumcircle(p, points[j], points[k])
if circle is not None:
candidates.append(circle)
for center, radius in candidates:
if radius >= best_r:
continue
if all(math.dist(center, p) <= radius + 1e-7 for p in points):
best_c, best_r = center, radius
return best_c, best_r
def solve_case(case: dict) -> dict:
start = perf_counter()
planes = _planes(case)
polygon = [(-BOX_M, -BOX_M), (BOX_M, -BOX_M), (BOX_M, BOX_M), (-BOX_M, BOX_M)]
for a, b, c in planes:
polygon = _clip(polygon, a, b, c)
if not polygon:
return _finish(_degenerate("empty"), start)
boundary_tol = 1e-6 * BOX_M
if any(abs(point[0]) >= BOX_M - boundary_tol
or abs(point[1]) >= BOX_M - boundary_tol for point in polygon):
return _finish(_degenerate("unbounded"), start)
if len(polygon) == 1:
summary = {"status": "point", "diameter_m": 0.0, "covers": True,
"mec_radius_m": 0.0, "max_excess_m": 0.0,
"vertex_count": 1}
else:
value, (a, b) = _brute_force_diameter(polygon)
if len(polygon) == 2:
summary = {"status": "segment", "diameter_m": value, "covers": True,
"mec_radius_m": value / 2.0, "max_excess_m": 0.0,
"vertex_count": 2}
else:
_mec_center, radius = _brute_force_mec(polygon)
diameter_center = ((a[0] + b[0]) / 2.0, (a[1] + b[1]) / 2.0)
max_distance = max(math.dist(diameter_center, p) for p in polygon)
summary = {
"status": "polygon",
"diameter_m": value,
"covers": bool(radius <= value / 2.0 + 1e-7),
"mec_radius_m": radius,
"max_excess_m": round(max_distance - value / 2.0, 9),
"vertex_count": len(polygon),
}
return _finish(summary, start)
def _degenerate(status: str) -> dict:
return {"status": status, "diameter_m": None, "covers": None,
"mec_radius_m": None, "max_excess_m": None, "vertex_count": 0}
def _finish(summary: dict, start: float) -> dict:
result = {
"method": METHOD.key,
"algorithm": METHOD.name,
"resolution_m": 0.0,
"diameter_infinite": summary["status"] == "unbounded",
"outside_vertex_count": None,
"runtime_ms": round((perf_counter() - start) * 1000.0, 4),
}
result.update(summary)
return result