mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 19:53:10 +00:00
96 lines
3.8 KiB
Python
96 lines
3.8 KiB
Python
|
|
"""Exact large-N topology probe: mean gossip path latency l_mean(N, degree) up to N = 10^6.
|
||
|
|
|
||
|
|
The only N-dependent term in the load law rho = f*D_vis (report §3.3/§4, eq 1) is the mean
|
||
|
|
shortest-path transport latency l_mean over the peering graph, which grows ~ log_(d-1) N.
|
||
|
|
Direct per-node simulation is memory-bound at N ~ 3*10^4 (the N x N matrix), but l_mean is
|
||
|
|
measurable EXACTLY at any N with sampled-source Dijkstra on the same graph generator the
|
||
|
|
simulator uses (circulant base + 10x Maslov-Sneppen swaps, geo per-link latencies).
|
||
|
|
|
||
|
|
Writes runs/topology_probe.parquet: one row per (n, degree, replicate) with l_mean, quantiles,
|
||
|
|
and the derived D_vis / rho for the study's mixing budgets. Used by report §3.8.
|
||
|
|
|
||
|
|
Run: python scripts/topology_probe.py (~2-3 h, parallel over cells)
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import sys
|
||
|
|
import time
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import pandas as pd
|
||
|
|
from joblib import Parallel, delayed
|
||
|
|
from scipy.sparse import csr_matrix
|
||
|
|
from scipy.sparse.csgraph import dijkstra
|
||
|
|
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
||
|
|
|
||
|
|
from tsi_sim.config import SimConfig # noqa: E402
|
||
|
|
from tsi_sim.topology import ( # noqa: E402
|
||
|
|
_circulant_edges,
|
||
|
|
_double_edge_swaps,
|
||
|
|
_sample_link_latencies,
|
||
|
|
)
|
||
|
|
|
||
|
|
HERE = Path(__file__).resolve().parent.parent
|
||
|
|
RUNS = HERE / "runs"
|
||
|
|
|
||
|
|
F = 1.0 / 30.0
|
||
|
|
HOPS = 3
|
||
|
|
DELAYS = (4.0, 8.0) # blend mixing budgets studied in the N-scaling ladder
|
||
|
|
LINK_MEAN = 0.5 # per-link geo mean (slots), matching the ladder configs
|
||
|
|
N_GRID = (1_000, 4_000, 16_000, 64_000, 250_000, 1_000_000)
|
||
|
|
DEGREES = (4, 6, 8)
|
||
|
|
N_SOURCES = 64 # sampled Dijkstra sources per graph
|
||
|
|
REPS = {n: (3 if n <= 64_000 else 1) for n in N_GRID}
|
||
|
|
|
||
|
|
|
||
|
|
def probe_cell(n: int, degree: int, rep: int) -> dict:
|
||
|
|
rng = np.random.default_rng(np.random.SeedSequence([n, degree, rep, 20260721]))
|
||
|
|
cfg = SimConfig(n_nodes=min(n, 10_000), degree=degree, link_latency_mean=LINK_MEAN,
|
||
|
|
link_latency_dist="geo") # only used for latency sampling params
|
||
|
|
t0 = time.time()
|
||
|
|
edges = _circulant_edges(n, degree)
|
||
|
|
edges = _double_edge_swaps(edges, n_swaps=10 * len(edges), rng=rng)
|
||
|
|
e = np.array(sorted(edges), dtype=np.int64)
|
||
|
|
w = _sample_link_latencies(e.shape[0], cfg, rng)
|
||
|
|
adj = csr_matrix(
|
||
|
|
(np.concatenate([w, w]),
|
||
|
|
(np.concatenate([e[:, 0], e[:, 1]]), np.concatenate([e[:, 1], e[:, 0]]))),
|
||
|
|
shape=(n, n))
|
||
|
|
sources = rng.choice(n, size=min(N_SOURCES, n), replace=False)
|
||
|
|
dist = dijkstra(adj, directed=False, indices=sources)
|
||
|
|
mask = np.isfinite(dist) & (dist > 0)
|
||
|
|
d = dist[mask]
|
||
|
|
row = dict(
|
||
|
|
n=n, degree=degree, replicate=rep, n_sources=len(sources), n_edges=e.shape[0],
|
||
|
|
l_mean=float(d.mean()), l_p50=float(np.percentile(d, 50)),
|
||
|
|
l_p90=float(np.percentile(d, 90)), l_p99=float(np.percentile(d, 99)),
|
||
|
|
l_max=float(d.max()), build_s=float(time.time() - t0),
|
||
|
|
)
|
||
|
|
for delay in DELAYS:
|
||
|
|
dvis = HOPS * delay / 2.0 + (HOPS + 1) * row["l_mean"]
|
||
|
|
row[f"d_vis_{delay:g}"] = dvis
|
||
|
|
row[f"rho_{delay:g}"] = F * dvis
|
||
|
|
print(f"n={n:>9,} deg={degree} rep={rep}: l_mean={row['l_mean']:.2f} "
|
||
|
|
f"p99={row['l_p99']:.2f} rho(8)={row['rho_8']:.3f} [{row['build_s']:.0f}s]",
|
||
|
|
flush=True)
|
||
|
|
return row
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> None:
|
||
|
|
cells = [(n, d, r) for n in N_GRID for d in DEGREES for r in range(REPS[n])]
|
||
|
|
# large-N cells first so the slowest work starts immediately
|
||
|
|
cells.sort(key=lambda c: -c[0])
|
||
|
|
rows = Parallel(n_jobs=4, prefer="processes")(
|
||
|
|
delayed(probe_cell)(n, d, r) for n, d, r in cells)
|
||
|
|
df = pd.DataFrame(rows)
|
||
|
|
out = RUNS / "topology_probe.parquet"
|
||
|
|
df.to_parquet(out)
|
||
|
|
print(f"wrote {len(df)} rows -> {out}")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|