"""图论 / DP 工具箱验收(docs/第三四问图论与动态规划优化方案.md §3.2、§3.4)。 要点: - Held-Karp 精确开放路径必须**真的**最优(与全排列暴力对拍); - 启发式(NN + 2-opt / Or-opt)必须是可接受的上界,且不劣于 NN; - MST 权重必须是最优开放路径的合法下界(在线后悔值审计依赖它); - 路由度量口径可复算:actual == offline 时 gap 为 0,且 gap 不越界。 """ import itertools import math import random import unittest from mcm26b.routing import (EXACT_LIMIT, held_karp_open, improve_open_path, mst_weight, nearest_neighbor_order, optimal_open_path, or_opt_open, path_length, route_metrics, two_opt_open) def _random_points(count: int, seed: int, radius: float = 1500.0): rng = random.Random(seed) return [(rng.uniform(-radius, radius), rng.uniform(-radius, radius)) for _ in range(count)] class HeldKarpTests(unittest.TestCase): def test_matches_brute_force_on_small_instances(self): for seed in range(6): points = _random_points(6, seed) order = held_karp_open(points) best = min(path_length(list(perm)) for perm in itertools.permutations(points)) self.assertAlmostEqual(path_length(order), best, places=6, msg=f"seed={seed}") def test_returns_all_points_once(self): points = _random_points(9, 42) order = held_karp_open(points) self.assertEqual(sorted(order), sorted(points)) def test_single_and_empty(self): self.assertEqual(held_karp_open([]), []) point = [(3.0, 4.0)] self.assertEqual(held_karp_open(point), point) class HeuristicTests(unittest.TestCase): def test_two_opt_and_or_opt_never_worse_than_nn(self): for seed in range(5): points = _random_points(30, seed) base = path_length(nearest_neighbor_order(points)) self.assertLessEqual(path_length(two_opt_open( nearest_neighbor_order(points))), base + 1e-9) self.assertLessEqual(path_length(improve_open_path(points)), base + 1e-9) def test_optimal_open_path_beats_heuristics_small(self): points = _random_points(10, 7) optimal = path_length(optimal_open_path(points)) self.assertLessEqual(optimal, path_length(improve_open_path(points)) + 1e-9) def test_or_opt_accepts_only_improvements(self): points = _random_points(20, 3) order = nearest_neighbor_order(points) improved = or_opt_open(order) self.assertLessEqual(path_length(improved), path_length(order) + 1e-9) class LowerBoundTests(unittest.TestCase): def test_mst_is_a_lower_bound(self): for seed in range(8): points = _random_points(24, seed) optimum = path_length(optimal_open_path(points)) self.assertLessEqual(mst_weight(points), optimum + 1e-6, msg=f"seed={seed}") def test_mst_without_start(self): points = _random_points(12, 1) self.assertLessEqual(mst_weight(points, start=None), path_length(optimal_open_path(points)) + 1e-6) class RouteMetricsTests(unittest.TestCase): def test_gap_zero_when_actual_matches_offline(self): points = _random_points(8, 5) offline = path_length(optimal_open_path(points)) metrics = route_metrics(points, offline) self.assertAlmostEqual(metrics["route_gap_vs_offline"], 0.0, places=6) self.assertEqual(metrics["distinct_service_points"], 8) def test_dedupes_points(self): points = [(1.0, 1.0), (1.0, 1.0), (2.0, 2.0)] metrics = route_metrics(points, 10.0) self.assertEqual(metrics["distinct_service_points"], 2) def test_empty_is_safe(self): metrics = route_metrics([], 0.0) self.assertEqual(metrics["distinct_service_points"], 0) self.assertEqual(metrics["route_gap_vs_offline"], 0.0) if __name__ == "__main__": unittest.main()