mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 11:43:20 +00:00
74 lines
3.1 KiB
Python
74 lines
3.1 KiB
Python
"""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()
|