Files
mcm-26-b/tests/test_coverage_plan.py
T
2026-09-12 14:48:43 +08:00

100 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""覆盖计划(CoveragePlan)与运行期校核验收(方案 §P0-2)。
要点:
- 计划是"可离线搜索、可在运行期校验"的一等对象,运行期必须重新校核;
- 校核必须能**拒绝**非法点集(删点即被检出),否则校核是装饰;
- 两类覆盖语义不得混用:第三问 full_disk、第四问 forward_half_disk;
- 第四问最小命中集必须在同一采样空间上与原 49 点表等价覆盖。
"""
import unittest
from mcm26b.config import ProblemConfig
from mcm26b.coverage import coverage_points
from mcm26b.guarantee import (plan_instance, plan_names, seven_plan,
square_49_plan, verify_full_disk,
verify_forward_half_disk, verify_plan)
class CoveragePlanTests(unittest.TestCase):
def test_q4_mincover_plan_is_certified(self):
self.assertIn("q4_mincover_v1", plan_names())
plan = plan_instance("q4_mincover_v1")
self.assertEqual(plan.problem, 4)
self.assertEqual(plan.coverage, "forward_half_disk")
certificate = verify_plan(plan)
self.assertEqual(certificate.uncovered, 0, certificate.as_dict())
self.assertTrue(certificate.valid)
# 最小命中集必须显著小于旧的充分条件表
self.assertLess(len(plan.points), len(square_49_plan().points))
self.assertLess(certificate.nn_tour_m, 25_000.0)
def test_hardened_plan_passes_fine_grid(self):
"""S2:现役计划必须在 15 m/72 档(325.6 万个 (p,h))零缺口。
75 m/24 档的"零缺口"曾是采样过拟合(v1 在 25 m/48 档有 125 个缺口)。
"""
self.assertIn("q4_mincover_v2", plan_names())
plan = plan_instance("q4_mincover_v2")
certificate = plan.certificate
self.assertIsNotNone(certificate)
self.assertEqual(certificate.target_step_m, 15.0)
self.assertEqual(certificate.headings, 72)
verified = verify_plan(plan)
self.assertEqual(verified.uncovered, 0, verified.as_dict())
self.assertLess(verified.worst_hit_distance_m, 1000.0)
self.assertTrue(verified.valid)
def test_legacy_overfit_plan_fails_fine_grid(self):
"""负例:旧的 31 点表在细网格上有缺口,硬化不是装饰。"""
legacy = plan_instance("q4_mincover_v1", verify=False)
certificate = verify_forward_half_disk(list(legacy.points), 25, 48)
self.assertGreater(certificate.uncovered, 0)
def test_square49_and_mincover_cover_the_same_space(self):
for name in ("square49", "q4_mincover_v1"):
certificate = verify_forward_half_disk(plan_instance(name).points)
self.assertEqual(certificate.uncovered, 0, name)
def test_verifier_rejects_a_broken_plan(self):
"""删掉关键点必须被检出——否则运行期校核没有意义。"""
points = list(plan_instance("q4_mincover_v1").points)
broken = points[:-1]
certificate = verify_forward_half_disk(broken)
self.assertGreater(certificate.uncovered, 0)
self.assertFalse(certificate.valid)
def test_hardened_plan_rejects_a_removed_point(self):
"""硬化计划同样必须能拒绝删点(在证书分辨率上复核)。"""
plan = plan_instance("q4_mincover_v2")
points = list(plan.points)
certificate = verify_forward_half_disk(
points[:-1], plan.certificate.target_step_m,
plan.certificate.headings)
self.assertGreater(certificate.uncovered, 0)
def test_seven_plan_full_disk_semantics(self):
plan = seven_plan(1200.0)
certificate = verify_full_disk(plan.points)
self.assertEqual(certificate.kind, "full_disk")
self.assertEqual(certificate.uncovered, 0)
def test_seven_plan_too_small_ring_is_rejected(self):
"""环半径过小则 Ω 边缘覆盖不全,full_disk 校核必须报缺口。"""
certificate = verify_full_disk(seven_plan(800.0).points)
self.assertGreater(certificate.uncovered, 0)
def test_square49_order_matches_legacy_ledger(self):
"""历史基线 grid49_immediate 必须逐位可复现(点集与顺序都一致)。"""
self.assertEqual(list(square_49_plan().points),
list(coverage_points(ProblemConfig())))
def test_coverage_semantics_are_not_interchangeable(self):
"""第三问七站点集不满足第四问"前向半盘命中"语义(朝向遮挡)。"""
certificate = verify_forward_half_disk(seven_plan(1200.0).points)
self.assertGreater(certificate.uncovered, 0)
if __name__ == "__main__":
unittest.main()