c094eb88fa
Adds src/ (scripts, raw measurement logs, derived data, report source), tests/, LICENSE (MIT, code) and LICENSE-DATA.md (CC BY 4.0 for the survey's data; third-party terms). The report is updated to the current site; the v1 and v2 data hand-off files are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
94 lines
3.8 KiB
Python
94 lines
3.8 KiB
Python
"""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"))
|