"""Reduce WonderNetwork's public one-day ping dump (2020-07-19/20) to one robust RTT floor per city pair and test it against every model. RTT used = median of the per-run minimums (each run = 30 pings). Using the median instead of the single fastest ping discards glitch runs and errs in the flat models' favour (a higher RTT is easier for a long flat route to explain). """ import csv, gzip, json, statistics, sys from collections import defaultdict from models import MODELS, distance, floor_ms, C_FIBER, C_VACUUM RAW = "raw/wn" SLACK_KM = 200 # each endpoint may sit up to 100 km from its listed coordinates SLACK_MS = 1.0 # timer / queueing jitter allowance servers = {} with open(f"{RAW}/servers-2020-07-19.csv") as f: for r in csv.DictReader(f): servers[r["id"]] = dict(name=r["title"], country=r["country"], lat=float(r["latitude"]), lon=float(r["longitude"])) runs = defaultdict(list) avgs = defaultdict(list) with gzip.open(f"{RAW}/pings-2020-07-19-2020-07-20.csv.gz", "rt") as f: for r in csv.DictReader(f): s, d = r["source"], r["destination"] if s == d or s not in servers or d not in servers: continue try: mn, av = float(r["min"]), float(r["avg"]) except ValueError: continue if mn > 0: key = tuple(sorted((s, d), key=int)) runs[key].append(mn) avgs[key].append(av) def impossible(rtt, km, speed=C_FIBER): return rtt + SLACK_MS < floor_ms(max(0.0, km - SLACK_KM), speed) pairs = [] for (a, b), mins in runs.items(): if len(mins) < 5: continue A, B = servers[a], servers[b] pa, pb = (A["lat"], A["lon"]), (B["lat"], B["lon"]) row = dict(a=a, b=b, n=len(mins), rtt=round(statistics.median(mins), 2), best=round(min(mins), 2), avg=round(statistics.median(avgs[(a, b)]), 2)) for m in MODELS: row[m] = round(distance(m, pa, pb), 1) pairs.append(row) # A pair that is impossible on the globe is impossible on every flat map too # (all three flat maps stretch every distance relative to the globe), so one # of the two servers' advertised location must be wrong. Greedily drop the # server implicated most often; when only 1:1 conflicts remain we can't tell # which side is lying, so both go. mislocated = set() while True: bad = [r for r in pairs if r["a"] not in mislocated and r["b"] not in mislocated and impossible(r["rtt"], r["globe"])] if not bad: break count = defaultdict(int) for r in bad: count[r["a"]] += 1 count[r["b"]] += 1 worst = max(count.values()) mislocated |= {s for s, n in count.items() if n == worst} if worst == 1 else {max(count, key=count.get)} clean = [r for r in pairs if r["a"] not in mislocated and r["b"] not in mislocated] def report(rows, label): print(f"\n{label}: {len(rows)} pairs") for m in MODELS: fib = sum(impossible(r["rtt"], r[m]) for r in rows) vac = sum(impossible(r["rtt"], r[m], C_VACUUM) for r in rows) print(f" {m:9s} beats light-in-fibre: {fib:5d} ({fib/len(rows):6.2%}) beats light-in-vacuum: {vac:5d} ({vac/len(rows):6.2%})") print("mislocated servers dropped:", ", ".join(f"{servers[s]['name']} ({servers[s]['country']})" for s in sorted(mislocated, key=int))) report(pairs, "ALL pairs") report(clean, "CLEAN pairs") south = [r for r in clean if servers[r["a"]]["lat"] < 0 and servers[r["b"]]["lat"] < 0] report(south, "CLEAN, both ends in southern hemisphere") if len(sys.argv) > 1: used = {s for r in clean for s in (r["a"], r["b"])} json.dump(dict(servers={k: v for k, v in servers.items() if k in used}, mislocated=[servers[s] for s in sorted(mislocated, key=int)], pairs=clean), open(sys.argv[1], "w"))