#!/usr/bin/env python3 """braess_model.py の試験(標準ライブラリの unittest)。本番の種は使わない(種 1〜5)。""" from __future__ import annotations import json import tempfile import unittest from pathlib import Path import braess_model as bm class TimesTest(unittest.TestCase): def test_textbook_values(self): # Easley & Kleinberg (2010) 図8.1・8.2: 2,000台ずつで 65 分/全員が近道で 80 分/全員が片方で 85 分 self.assertEqual(bm.route_times(2000, 2000, 0)[:2], (6500, 6500)) self.assertEqual(bm.route_times(0, 0, 4000)[2], 8000) self.assertEqual(bm.route_times(4000, 0, 0)[0], 8500) def test_move_counts_the_mover(self): # 2,000台ずつから1台が近道へ: その車は 2,000+2,001 センチ分=40.01 分 self.assertEqual(bm.time_after_move(bm.UPPER, bm.SHORT, [2000, 2000, 0]), 4001) def test_equilibria(self): self.assertTrue(bm.is_equilibrium([2000, 2000, 0], road=False)) self.assertFalse(bm.is_equilibrium([2002, 1998, 0], road=False)) self.assertTrue(bm.is_equilibrium([0, 0, 4000], road=True)) self.assertFalse(bm.is_equilibrium([2000, 2000, 0], road=True)) # 同じ時間なら乗り換えない(奇数台の 1 台差は均衡) self.assertTrue(bm.is_equilibrium([2001, 2000, 0], road=False)) class RunTest(unittest.TestCase): def test_worlds_reach_textbook_equilibria(self): for seed in (1, 2, 3): b = bm.run_world(seed, False) r = bm.run_world(seed, True) self.assertTrue(b["equilibrium"] and r["equilibrium"]) self.assertEqual(b["total"], 4000 * 6500) self.assertEqual(r["final"], [0, 0, 4000]) self.assertEqual(r["moves"], 4000) self.assertEqual(r["back_to_upper"], 8500) self.assertEqual(b["init"], r["init"]) # 最初の分かれ方は同じ def test_deterministic(self): self.assertEqual(bm.run_world(5, True, snaps="road"), bm.run_world(5, True, snaps="road")) def test_snapshots_end_at_final(self): r = bm.run_world(4, True, snaps="road") last = r["snaps"][-1] self.assertEqual([last["u"], last["l"], last["m"]], r["final"]) self.assertEqual(r["snaps"][0]["moves"], 0) def test_theory(self): self.assertEqual(bm.theory_minutes(4000, False), 65) self.assertEqual(bm.theory_minutes(4000, True), 80) self.assertEqual(bm.theory_minutes(2000, True), 40) self.assertEqual(bm.theory_minutes(6000, True), 90) self.assertEqual(bm.theory_minutes(10000, True), 95) opt = bm.social_optimum(4000) self.assertEqual((opt["u"], opt["l"], opt["m"]), (1750, 1750, 500)) self.assertAlmostEqual(opt["avg_minutes"], 64.6875) class SummaryTest(unittest.TestCase): def test_small_summary_and_predictions(self): s = bm.build_summary([1, 2, 3], sweep_n=2, sweep_cars=[2000, 4000]) self.assertEqual(s["pair"]["worse"], 3) self.assertEqual(s["pair"]["road_minutes"], 80.0) self.assertEqual(s["sweep"]["better_cars"], [2000]) self.assertEqual(s["sweep"]["worse_cars"], [4000]) preds = {"predictions": [ {"id": "X1", "metric": "pair.road_minutes", "kind": "eq", "value": 80.0, "text": "x"}, {"id": "X2", "metric": "pair.worse", "kind": "range", "lo": 0, "hi": 2, "text": "x"}, ]} pc = bm.check_predictions(s, preds) self.assertEqual((pc["n"], pc["hits"]), (2, 1)) # 陽性対照: X2 は外れになること with tempfile.TemporaryDirectory() as d: bm.dump_json(Path(d) / "summary.json", s) self.assertEqual(json.loads((Path(d) / "summary.json").read_text(encoding="utf-8"))["pair"]["n"], 3) if __name__ == "__main__": unittest.main()