Files
cable-latency-survey/src/models.py
T
Toby c094eb88fa Open-source release: pipeline code, raw logs, derived data, licences, tests
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>
2026-10-27 20:09:54 -04:00

85 lines
3.0 KiB
Python

"""Distance models for the latency test.
Every model returns the shortest possible path length (km) between two
lat/lon points *within that model*. On a flat map that is the straight line
across the plane: a cable can bend, detour or loop, but it can never be
shorter than the straight line, so these are hard lower bounds.
Scale conventions (the ones flat-earth maps themselves use):
globe sphere, mean radius 6371.0 km, great-circle distance
ae azimuthal equidistant centred on the North Pole (the "Gleason"
map). Pole-to-anywhere distances are true, so 1 degree of
latitude = 111.2 km everywhere, equator 10,008 km from the pole.
dualpole azimuthal equidistant centred on 0N 0E (both poles are points,
10,008 km above/below the centre). Distances from 0,0 are true.
mercator flat Mercator rectangle, true scale on the equator, cut along
the antimeridian (180E/W) like every wall map.
"""
import math
R = 6371.0
C_VACUUM = 299_792.458 # km/s
N_FIBER = 1.4682 # group index, standard single-mode fibre @1550nm
C_FIBER = C_VACUUM / N_FIBER # ~204,190 km/s
MODELS = ["globe", "ae", "dualpole", "mercator"]
def globe(lat1, lon1, lat2, lon2):
p1, p2 = math.radians(lat1), math.radians(lat2)
dp, dl = p2 - p1, math.radians(lon2 - lon1)
a = math.sin(dp / 2) ** 2 + math.cos(p1) * math.cos(p2) * math.sin(dl / 2) ** 2
return 2 * R * math.asin(min(1.0, math.sqrt(a)))
def ae_xy(lat, lon):
r = R * math.radians(90 - lat)
t = math.radians(lon)
return r * math.sin(t), -r * math.cos(t)
def dualpole_xy(lat, lon, lon0=0.0):
p, l = math.radians(lat), math.radians(lon - lon0)
cosc = max(-1.0, min(1.0, math.cos(p) * math.cos(l)))
c = math.acos(cosc)
k = 1.0 if c < 1e-9 else c / math.sin(c)
return R * k * math.cos(p) * math.sin(l), R * k * math.sin(p)
def mercator_xy(lat, lon, cut=180.0):
lat = max(-85.0, min(85.0, lat))
x = R * math.radians((lon - cut) % 360.0)
y = R * math.log(math.tan(math.pi / 4 + math.radians(lat) / 2))
return x, y
def _plane(f, a, b, **kw):
x1, y1 = f(*a, **kw)
x2, y2 = f(*b, **kw)
return math.hypot(x2 - x1, y2 - y1)
def distance(model, a, b):
"""a, b = (lat, lon). Returns km."""
if model == "globe":
return globe(*a, *b)
if model == "ae":
return _plane(ae_xy, a, b)
if model == "dualpole":
return _plane(dualpole_xy, a, b)
if model == "mercator":
return _plane(mercator_xy, a, b)
raise ValueError(model)
def floor_ms(km, speed=C_FIBER):
"""Minimum possible round-trip time in ms for a path of `km` each way."""
return 2 * km / speed * 1000
if __name__ == "__main__":
syd, akl, la, sel = (-33.87, 151.21), (-36.85, 174.76), (34.05, -118.24), (37.57, 126.98)
for name, a, b in [("SYD-AKL", syd, akl), ("SYD-LAX", syd, la), ("SEL-SYD", sel, syd)]:
row = [f"{m}={distance(m, a, b):8.0f}km/{floor_ms(distance(m, a, b)):6.1f}ms" for m in MODELS]
print(name, *row)