research/tools/simulators/tsi/tsi-sim-pernode/scripts/window_scale_analysis.py
2026-07-30 18:57:10 +02:00

74 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Window sufficiency at scale + the W-as-buffer question (fig25, report §3.4).
From the window-scale sweep (N = 1 000 vs 10 000, W = 50..600, delta in {8, 16, 32} s, U in
{1, 2}, k = 256): the window floor's position is N-invariant, a wider window buys back the
near-boundary (rho ~ 1) undershoot at U = 1, and no window fixes sustained overload (rho > U).
Run: python scripts/window_scale_analysis.py
"""
from __future__ import annotations
import sys
from pathlib import Path
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from tsi_sim.plotting import style # noqa: E402
HERE = Path(__file__).resolve().parent.parent
RUNS = HERE / "runs"
FIGS = HERE / "report-figures"
BAR = 0.98
def main() -> None:
src = sorted(RUNS.glob("*_window-scale/results.parquet"))[-1]
df = pd.read_parquet(src)
# Early stop terminates each config once it converges (~epoch 16), well short of the
# nominal ``epochs`` (40). A fixed ``epoch >= 20`` tail would drop almost every config;
# take the second half of each config's *actually-run* epochs instead (matches the
# burn_frac=0.5 tail used elsewhere).
keys = ["n_nodes", "blend_delay_max", "uncle_window", "max_uncles", "replicate"]
tail_from = df.groupby(keys).epoch.transform("max") * 0.5
t = df[df.epoch >= tail_from]
eq = (t.groupby(["n_nodes", "blend_delay_max", "uncle_window", "max_uncles"],
as_index=False).mean_ratio.mean())
import matplotlib.pyplot as plt
style.apply_style()
fig, axes = plt.subplots(1, 3, figsize=(13.2, 4.0), sharey=True)
for ax, delay in zip(axes, (8.0, 16.0, 32.0), strict=True):
for n, u in ((1000, 1), (10000, 1), (1000, 2), (10000, 2)):
s = eq[(eq.blend_delay_max == delay) & (eq.n_nodes == n)
& (eq.max_uncles == u)].sort_values("uncle_window")
ax.plot(s.uncle_window, s.mean_ratio,
"-o" if u == 1 else "--s", ms=4, lw=1.3,
color=style.OKABE_ITO[0 if n == 1000 else 1],
label=f"N={n:,}, U={u}" if delay == 8.0 else None)
ax.axhline(BAR, color="0.7", lw=0.8, ls="--")
ax.set_xlabel("uncle window W (slots)")
ax.set_title(f"blending budget δ = {delay:g} s")
axes[0].set_ylabel(r"$\hat D / D$")
axes[0].text(60, BAR + 0.006, "0.98 recovery bar", fontsize=7, color="0.5")
axes[0].legend(fontsize=8, loc="lower right")
fig.suptitle("Window sufficiency at scale: a wider W buys back the ρ ≈ 1 boundary (middle) "
"but cannot fix sustained overload (right)", y=1.03)
style.save(fig, FIGS / "fig25_window_scale",
provenance=f"scripts/window_scale_analysis.py ({src.parent.name})")
plt.close(fig)
print("wrote fig25_window_scale")
for delay in (8.0, 16.0, 32.0):
s = eq[(eq.blend_delay_max == delay) & (eq.max_uncles == 1)]
piv = s.pivot(index="n_nodes", columns="uncle_window", values="mean_ratio")
print(f"\nU=1 δ={delay:g}:")
print(piv.round(3).to_string())
if __name__ == "__main__":
main()