2026-07-30 18:57:10 +02:00

61 lines
2.6 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.

"""fig3 — uncle recovery under the Blend cascade: hops × per-hop delay × U (4-panel grid).
Committed generator for report fig3. Previously fig3 was produced ad hoc (via make_figures on the
blend-hops-delay run, then hand-copied into report-figures/) and had NO reproducible source in the
repo; this script closes that gap. Per uncle cap U it plots mean D̂/D vs the per-hop budget δ_max,
one curve per hop count, at N=1000 from the canonical blend-hops-delay sweep (per-trajectory 50%
burn-in via figures_pernode.equilibrium). Accuracy is bounded by 1 (slot-counting cannot over-count
occupied slots), so the y-axis is capped at the exact-recovery bound — no above-1 headroom.
Run: python scripts/hops_delay_grid.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
from tsi_sim.plotting.figures_pernode import equilibrium # noqa: E402
HERE = Path(__file__).resolve().parent.parent
RUNS = HERE / "runs"
FIGS = HERE / "report-figures"
def main() -> None:
import matplotlib.pyplot as plt
src = sorted(RUNS.glob("*_blend-hops-delay/results.parquet"))[-1]
eq = equilibrium(pd.read_parquet(src))
eq = eq[(eq.n_nodes == 1000) & (eq.topology == "blend") & (eq.stake_dist == "pareto")]
us = sorted(eq.max_uncles.unique())
hops = sorted(eq.blend_hops.unique())
style.apply_style()
fig, axes = plt.subplots(1, len(us), figsize=(3.2 * len(us), 3.6), sharey=True)
for ax, U in zip(axes, us, strict=True):
s = eq[eq.max_uncles == U]
for i, h in enumerate(hops):
g = s[s.blend_hops == h].groupby("blend_delay_max").mean_ratio.agg(["mean", "sem"])
ax.errorbar(g.index, g["mean"], yerr=g["sem"], fmt="-o", ms=4, capsize=2,
color=style.OKABE_ITO[i], label=f"{int(h)} hops")
ax.axhline(1.0, color="0.4", lw=1.0, ls="--", zorder=0)
ax.axhline(0.98, color="0.75", lw=0.8, ls=":", zorder=0)
ax.set_title(f"U = {int(U)}")
ax.set_xlabel(r"per-hop budget $\delta_{max}$ (s)")
axes[0].set_ylabel(r"mean $\hat D / D$")
axes[0].set_ylim(top=1.01) # bounded by 1: cap at the exact-recovery bound
axes[0].legend(fontsize=8, loc="lower left")
fig.suptitle(r"Uncle recovery under Blend cascade: hops × per-hop delay × U "
r"(pareto, N=1000, f=1/30)", y=1.02)
style.save(fig, FIGS / "fig3_hops_delay", provenance="scripts/hops_delay_grid.py")
plt.close(fig)
print("wrote fig3_hops_delay")
if __name__ == "__main__":
main()