mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 03:33:33 +00:00
61 lines
2.6 KiB
Python
61 lines
2.6 KiB
Python
"""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()
|