Spaces:
Sleeping
Sleeping
File size: 3,486 Bytes
e855a27 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | """Cross-check the RAPTOR engine against an independent CSA implementation.
Runs many random (origin, departure-time) queries through both engines over the
same network and compares earliest-arrival labels at every stop. They should
match exactly; any difference is a bug.
Usage: python scripts/validate.py [n_samples] [budget_min]
"""
from __future__ import annotations
import pickle
import random
import sys
import time
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from config import NETWORK_FILE, WALK_GRAPH_FILE # noqa: E402
from engine.raptor import compute_isochrone # noqa: E402
from engine.reference_csa import build_connections, csa_isochrone # noqa: E402
def main() -> None:
n_samples = int(sys.argv[1]) if len(sys.argv) > 1 else 25
budget_min = int(sys.argv[2]) if len(sys.argv) > 2 else 60
budget = budget_min * 60
net = pickle.load(open(NETWORK_FILE, "rb"))
walk = pickle.load(open(WALK_GRAPH_FILE, "rb")) if WALK_GRAPH_FILE.exists() else None
print(f"Network: {net.n_stops} stops | walk graph: {'yes' if walk else 'no'} | "
f"{n_samples} samples @ {budget_min} min")
print("Building CSA connections...")
conns = build_connections(net)
print(f" {len(conns[0])} connections")
rng = random.Random(42)
total_stops = total_mismatch = raptor_worse = raptor_better = 0
max_abs = 0
examples = []
for i in range(n_samples):
s = rng.randrange(net.n_stops) # random origin at an existing stop
lat, lon = net.stop_lat[s], net.stop_lon[s]
dep = rng.randrange(6 * 3600, 22 * 3600) # random departure 06:00–22:00
rap = {r.stop_index: r.arrival for r in
compute_isochrone(net, lat, lon, dep, budget, walk_graph=walk).stops}
ref = csa_isochrone(net, conns, lat, lon, dep, budget, walk_graph=walk)
keys = set(rap) | set(ref)
for k in keys:
a = rap.get(k, INF)
b = ref.get(k, INF)
total_stops += 1
if a != b:
total_mismatch += 1
d = abs(a - b)
max_abs = max(max_abs, d)
if a > b:
raptor_worse += 1 # RAPTOR slower/missed -> potential gap
else:
raptor_better += 1 # RAPTOR faster than optimal -> would be a real bug
if len(examples) < 8:
examples.append((i, k, a, b))
print(f" sample {i+1}/{n_samples}: origin '{net.stop_name[s][:24]}' "
f"dep {dep//3600:02d}:{dep%3600//60:02d} | "
f"RAPTOR {len(rap)} vs CSA {len(ref)} reached")
print("\n=== results ===")
print(f"stop-labels compared: {total_stops}")
print(f"mismatches: {total_mismatch} "
f"({100*total_mismatch/max(total_stops,1):.4f}%)")
print(f" RAPTOR later than CSA (missed/slower): {raptor_worse}")
print(f" RAPTOR earlier than CSA (UNSOUND bug): {raptor_better}")
print(f"max abs arrival diff: {max_abs} s")
if examples:
print("examples (sample, stop, raptor_arr, csa_arr):")
for ex in examples:
print(f" {ex}")
ok = raptor_better == 0 and total_mismatch == 0
print("\nRESULT:", "PASS — engines agree exactly" if ok else
("PASS (sound; RAPTOR never beats optimal)" if raptor_better == 0 else "FAIL — RAPTOR unsound"))
INF = 10 ** 12
if __name__ == "__main__":
main()
|