2026-07-30 18:57:10 +02:00
|
|
|
|
"""Deficit-vs-load figure (fig26) from the rho-boundary sweep (configs/rho-boundary.yaml).
|
|
|
|
|
|
|
|
|
|
|
|
The "region below the block rate": the estimator equilibrium is bounded by 1 (it cannot over-count
|
|
|
|
|
|
occupied slots), so the signal of interest is the UNDER-COUNT DEFICIT 1 - D̂/D >= 0 as a function of
|
|
|
|
|
|
the load rho = f*D_vis, per uncle cap U. hops is fixed at 3 in the sweep so rho ∝ blend_delay_max.
|
|
|
|
|
|
|
|
|
|
|
|
Left panel: deficit 1 - D̂/D vs rho, per U (log-y), with the U=⌈ρ⌉ boundary visible.
|
|
|
|
|
|
Right panel: the same as accuracy D̂/D vs rho, y-axis capped at the 1.0 bound — no above-1 headroom;
|
|
|
|
|
|
residual above-1 shows only as ±σ error bars (sampling noise around ≤1).
|
|
|
|
|
|
|
|
|
|
|
|
Run: python scripts/rho_boundary_analysis.py
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
import pandas as pd
|
|
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
|
|
|
|
|
|
|
|
|
|
from tsi_sim.plotting import style # noqa: E402
|
Correctness pass: derive rho in code, and the absolute vs-1.0 test
Two findings from re-reviewing the fine-delay section.
1. The rho values I put in s3.2a were wrong. The report derives
rho = f*D_vis with D_vis = hops*delta_max/2 + (hops+1)*ell_mean from
a MEASURED ell_mean (1.211 slots at N=1000/degree=6), not from the
link_latency_mean parameter (0.5). Hand-substituting a guessed 1.5
inflated every value by ~0.04: the band is rho 0.21-0.41, not
0.25-0.45.
To stop that recurring, graph_ell_mean moves out of
rho_boundary_analysis.py into figures_pernode.py, joined by a new
rho_for() that both scripts and any future quotation go through;
plot_fine_delay.py now prints the derived rho per delay.
This also exposed an inconsistency in the existing s3.2 table, which
rounded delta_max=4 to "rho ~ 0.4" while s3.2a called the same cell
0.36 and prose elsewhere already used 0.56 for delta_max=8. The s3.2
column now carries the derived values (0.36/0.56/0.96/1.76).
2. Testing each cell against the exact target 1.0 -- the same question
the gap test asks, without reference to the other model --
corroborates the first-fork onset independently. Unrestricted: 1/15
cells below 1 (t=-2.09, chance). Countable: 4/15, and not scattered
-- delta_max=4 at U=1, and ALL THREE caps at delta_max=5 (-0.0012 to
-0.0019, t=-2.5..-3.7). A shortfall appearing at every cap at once,
only at the top of the band, only under the restricted model, is the
first-fork cost seen absolutely.
That makes "one uncle slot is sufficient -- not approximately,
exactly" too strong as I had written it. s3.2a now states the
residual (0.1-0.2% at the top of the band, zero below delta_max=3),
reconciles it with the s1 headline, and notes that since all three
caps show the same shortfall the residual is not a capacity limit.
The bound quoted in s1 moves from "below 0.15%" to "<= 0.2%".
Also adds the new run directories to s9's canonical list, which covered
every other study but not these.
Tests: 209 passed. ruff clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 10:34:33 +02:00
|
|
|
|
from tsi_sim.plotting.figures_pernode import graph_ell_mean # noqa: E402
|
2026-07-30 18:57:10 +02:00
|
|
|
|
|
|
|
|
|
|
HERE = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
RUNS = HERE / "runs"
|
|
|
|
|
|
FIGS = HERE / "report-figures"
|
Round-4 TSI report review: apply findings, editorial pass, code + figure fixes
Applied the reconstructed round-4 review to the TSI parameter-selection report
set (reports/tsi) and executed the follow-ups.
Report (reports/tsi):
- Applied the must+should findings across README + parts 1-4: cross-part numeric
corrections, figure-caption fixes, spec reconciliation, and cross-file companions
(hops-degradation and notch/reward numbers, tip-agreement ordering, density-window
timing, VRF -> ZK Proof-of-Leadership, w_u window/reward gloss).
- Editorial pass for timeless voice (no "now adopted / merged / coin" narration) and
a gentle spec-safety framing (recommendations are thresholds; the protocol's
MAX_UNCLES=4 sits safely above them).
- Added the fork-rate-vs-scale table (6.10), defined "grinding gain", promoted the
clock-skew study to its own paragraph, added the correlated-latency caveat, and
moved fig27/fig28 beside their discussion.
- Documented the Blend cascade in 2: hops propagate over the shared gossip graph
(not direct links), the final broadcast comes from the last relay, relays are
blind forwarders.
Simulator (tools/simulators/tsi/tsi-sim-pernode):
- Docstring/dead-code fixes: theory.block_count_ceiling (legacy framing), measure,
reorg (catch-up reading), metrics (removed two dead helpers), config (fixed_point
10^-6; clock_skew_max/lottery_chunks documented inert), stake_vs_delay.
- Generator correctness + regenerated figures: figures_pernode.CONFIG_COLS now
exhaustive (f no longer pooled); rho_boundary_analysis SEM across replicates +
hollow floored markers + de-hardcoded ell_mean (measured from the run's graph);
appendix_fluct per-N sigma + ~18x title (figB2); bootstrap_dynamics driving
estimate so fig1 epoch-0 matches genesis.
- pytest: 186 passed; report links 528/0 dangling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 13:13:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
2026-07-30 18:57:10 +02:00
|
|
|
|
def load() -> pd.DataFrame:
|
|
|
|
|
|
src = sorted(RUNS.glob("*_rho-boundary/results.parquet"))[-1]
|
|
|
|
|
|
df = pd.read_parquet(src)
|
|
|
|
|
|
keys = ["blend_delay_max", "max_uncles", "replicate"]
|
|
|
|
|
|
df["emax"] = df.groupby(keys).epoch.transform("max")
|
|
|
|
|
|
tail = df[df.epoch >= df.emax // 2]
|
Round-4 TSI report review: apply findings, editorial pass, code + figure fixes
Applied the reconstructed round-4 review to the TSI parameter-selection report
set (reports/tsi) and executed the follow-ups.
Report (reports/tsi):
- Applied the must+should findings across README + parts 1-4: cross-part numeric
corrections, figure-caption fixes, spec reconciliation, and cross-file companions
(hops-degradation and notch/reward numbers, tip-agreement ordering, density-window
timing, VRF -> ZK Proof-of-Leadership, w_u window/reward gloss).
- Editorial pass for timeless voice (no "now adopted / merged / coin" narration) and
a gentle spec-safety framing (recommendations are thresholds; the protocol's
MAX_UNCLES=4 sits safely above them).
- Added the fork-rate-vs-scale table (6.10), defined "grinding gain", promoted the
clock-skew study to its own paragraph, added the correlated-latency caveat, and
moved fig27/fig28 beside their discussion.
- Documented the Blend cascade in 2: hops propagate over the shared gossip graph
(not direct links), the final broadcast comes from the last relay, relays are
blind forwarders.
Simulator (tools/simulators/tsi/tsi-sim-pernode):
- Docstring/dead-code fixes: theory.block_count_ceiling (legacy framing), measure,
reorg (catch-up reading), metrics (removed two dead helpers), config (fixed_point
10^-6; clock_skew_max/lottery_chunks documented inert), stake_vs_delay.
- Generator correctness + regenerated figures: figures_pernode.CONFIG_COLS now
exhaustive (f no longer pooled); rho_boundary_analysis SEM across replicates +
hollow floored markers + de-hardcoded ell_mean (measured from the run's graph);
appendix_fluct per-N sigma + ~18x title (figB2); bootstrap_dynamics driving
estimate so fig1 epoch-0 matches genesis.
- pytest: 186 passed; report links 528/0 dangling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 13:13:03 +02:00
|
|
|
|
# Per-trajectory tail mean FIRST, then mean + SEM ACROSS replicates. Pooling every
|
|
|
|
|
|
# (replicate x tail-epoch) row instead would treat correlated within-trajectory epochs as
|
|
|
|
|
|
# independent samples and understate the true replicate spread (by ~1.5x, up to ~3x).
|
|
|
|
|
|
per_rep = (tail.groupby(keys, as_index=False).mean_ratio.mean())
|
|
|
|
|
|
g = (per_rep.groupby(["blend_delay_max", "max_uncles"])
|
2026-07-30 18:57:10 +02:00
|
|
|
|
.mean_ratio.agg(["mean", "sem"]).reset_index())
|
Round-4 TSI report review: apply findings, editorial pass, code + figure fixes
Applied the reconstructed round-4 review to the TSI parameter-selection report
set (reports/tsi) and executed the follow-ups.
Report (reports/tsi):
- Applied the must+should findings across README + parts 1-4: cross-part numeric
corrections, figure-caption fixes, spec reconciliation, and cross-file companions
(hops-degradation and notch/reward numbers, tip-agreement ordering, density-window
timing, VRF -> ZK Proof-of-Leadership, w_u window/reward gloss).
- Editorial pass for timeless voice (no "now adopted / merged / coin" narration) and
a gentle spec-safety framing (recommendations are thresholds; the protocol's
MAX_UNCLES=4 sits safely above them).
- Added the fork-rate-vs-scale table (6.10), defined "grinding gain", promoted the
clock-skew study to its own paragraph, added the correlated-latency caveat, and
moved fig27/fig28 beside their discussion.
- Documented the Blend cascade in 2: hops propagate over the shared gossip graph
(not direct links), the final broadcast comes from the last relay, relays are
blind forwarders.
Simulator (tools/simulators/tsi/tsi-sim-pernode):
- Docstring/dead-code fixes: theory.block_count_ceiling (legacy framing), measure,
reorg (catch-up reading), metrics (removed two dead helpers), config (fixed_point
10^-6; clock_skew_max/lottery_chunks documented inert), stake_vs_delay.
- Generator correctness + regenerated figures: figures_pernode.CONFIG_COLS now
exhaustive (f no longer pooled); rho_boundary_analysis SEM across replicates +
hollow floored markers + de-hardcoded ell_mean (measured from the run's graph);
appendix_fluct per-N sigma + ~18x title (figB2); bootstrap_dynamics driving
estimate so fig1 epoch-0 matches genesis.
- pytest: 186 passed; report links 528/0 dangling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 13:13:03 +02:00
|
|
|
|
# Derive the rho axis from the run itself — f, hops, and the *measured* ell_mean — not from
|
|
|
|
|
|
# hardcoded constants: rho = f*D_vis with D_vis = hops*delta_max/2 + (hops+1)*ell_mean. This
|
|
|
|
|
|
# only re-labels the x-axis from the existing simulation data; it never re-simulates.
|
|
|
|
|
|
f = float(df.f.iloc[0])
|
|
|
|
|
|
hops = int(df.blend_hops.iloc[0])
|
|
|
|
|
|
ell = graph_ell_mean(df)
|
|
|
|
|
|
g["rho"] = f * (hops * g.blend_delay_max / 2.0 + (hops + 1) * ell)
|
2026-07-30 18:57:10 +02:00
|
|
|
|
g["deficit"] = 1.0 - g["mean"]
|
|
|
|
|
|
return g
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def fig26(g: pd.DataFrame) -> None:
|
|
|
|
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
style.apply_style()
|
|
|
|
|
|
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(9.6, 4.2))
|
Round-4 TSI report review: apply findings, editorial pass, code + figure fixes
Applied the reconstructed round-4 review to the TSI parameter-selection report
set (reports/tsi) and executed the follow-ups.
Report (reports/tsi):
- Applied the must+should findings across README + parts 1-4: cross-part numeric
corrections, figure-caption fixes, spec reconciliation, and cross-file companions
(hops-degradation and notch/reward numbers, tip-agreement ordering, density-window
timing, VRF -> ZK Proof-of-Leadership, w_u window/reward gloss).
- Editorial pass for timeless voice (no "now adopted / merged / coin" narration) and
a gentle spec-safety framing (recommendations are thresholds; the protocol's
MAX_UNCLES=4 sits safely above them).
- Added the fork-rate-vs-scale table (6.10), defined "grinding gain", promoted the
clock-skew study to its own paragraph, added the correlated-latency caveat, and
moved fig27/fig28 beside their discussion.
- Documented the Blend cascade in 2: hops propagate over the shared gossip graph
(not direct links), the final broadcast comes from the last relay, relays are
blind forwarders.
Simulator (tools/simulators/tsi/tsi-sim-pernode):
- Docstring/dead-code fixes: theory.block_count_ceiling (legacy framing), measure,
reorg (catch-up reading), metrics (removed two dead helpers), config (fixed_point
10^-6; clock_skew_max/lottery_chunks documented inert), stake_vs_delay.
- Generator correctness + regenerated figures: figures_pernode.CONFIG_COLS now
exhaustive (f no longer pooled); rho_boundary_analysis SEM across replicates +
hollow floored markers + de-hardcoded ell_mean (measured from the run's graph);
appendix_fluct per-N sigma + ~18x title (figB2); bootstrap_dynamics driving
estimate so fig1 epoch-0 matches genesis.
- pytest: 186 passed; report links 528/0 dangling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 13:13:03 +02:00
|
|
|
|
floor = 3e-4
|
2026-07-30 18:57:10 +02:00
|
|
|
|
for i, U in enumerate((0, 1, 2, 3)):
|
|
|
|
|
|
s = g[g.max_uncles == U].sort_values("rho")
|
|
|
|
|
|
c = style.OKABE_ITO[i]
|
Round-4 TSI report review: apply findings, editorial pass, code + figure fixes
Applied the reconstructed round-4 review to the TSI parameter-selection report
set (reports/tsi) and executed the follow-ups.
Report (reports/tsi):
- Applied the must+should findings across README + parts 1-4: cross-part numeric
corrections, figure-caption fixes, spec reconciliation, and cross-file companions
(hops-degradation and notch/reward numbers, tip-agreement ordering, density-window
timing, VRF -> ZK Proof-of-Leadership, w_u window/reward gloss).
- Editorial pass for timeless voice (no "now adopted / merged / coin" narration) and
a gentle spec-safety framing (recommendations are thresholds; the protocol's
MAX_UNCLES=4 sits safely above them).
- Added the fork-rate-vs-scale table (6.10), defined "grinding gain", promoted the
clock-skew study to its own paragraph, added the correlated-latency caveat, and
moved fig27/fig28 beside their discussion.
- Documented the Blend cascade in 2: hops propagate over the shared gossip graph
(not direct links), the final broadcast comes from the last relay, relays are
blind forwarders.
Simulator (tools/simulators/tsi/tsi-sim-pernode):
- Docstring/dead-code fixes: theory.block_count_ceiling (legacy framing), measure,
reorg (catch-up reading), metrics (removed two dead helpers), config (fixed_point
10^-6; clock_skew_max/lottery_chunks documented inert), stake_vs_delay.
- Generator correctness + regenerated figures: figures_pernode.CONFIG_COLS now
exhaustive (f no longer pooled); rho_boundary_analysis SEM across replicates +
hollow floored markers + de-hardcoded ell_mean (measured from the run's graph);
appendix_fluct per-N sigma + ~18x title (figB2); bootstrap_dynamics driving
estimate so fig1 epoch-0 matches genesis.
- pytest: 186 passed; report links 528/0 dangling.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-31 13:13:03 +02:00
|
|
|
|
# left: deficit on a log axis. A cell counts as a RESOLVED positive deficit only if it
|
|
|
|
|
|
# is both positive and above its own 2*SEM noise level; unresolved cells (at/below noise,
|
|
|
|
|
|
# or slightly negative because D̂/D sits a hair above 1 from sampling noise) are clamped to
|
|
|
|
|
|
# the axis floor and drawn HOLLOW, so a point on the floor cannot be misread as a measured
|
|
|
|
|
|
# deficit. A faint line joins the series for legibility.
|
|
|
|
|
|
rho = s.rho.values
|
|
|
|
|
|
d = np.clip(s.deficit.values, floor, None)
|
|
|
|
|
|
resolved = (s.deficit.values > floor) & (s.deficit.values > 2.0 * s["sem"].values)
|
|
|
|
|
|
ax1.plot(rho, d, "-", lw=0.8, color=c, alpha=0.5, zorder=0)
|
|
|
|
|
|
ax1.plot(rho[resolved], d[resolved], "o", ms=4, color=c, label=f"U = {U}")
|
|
|
|
|
|
ax1.plot(rho[~resolved], d[~resolved], "o", ms=4, mfc="none", mec=c)
|
|
|
|
|
|
# right: accuracy, capped at 1.0, with the across-replicate SEM
|
2026-07-30 18:57:10 +02:00
|
|
|
|
ax2.errorbar(s.rho, s["mean"], yerr=s["sem"], fmt="-o", ms=4, capsize=2,
|
|
|
|
|
|
color=c, label=f"U = {U}")
|
|
|
|
|
|
ax1.set_yscale("log")
|
|
|
|
|
|
ax1.set_xlabel(r"load $\rho = f\,D_{vis}$")
|
|
|
|
|
|
ax1.set_ylabel(r"under-count deficit $1 - \hat D/D$")
|
|
|
|
|
|
ax1.set_title(r"deficit grows once $\rho$ exceeds the uncle cap")
|
|
|
|
|
|
ax1.axvline(1.0, color="0.6", lw=0.8, ls=":")
|
|
|
|
|
|
ax1.legend(fontsize=8, title="uncle cap")
|
|
|
|
|
|
ax2.axhline(1.0, color="0.4", lw=1.0, ls="--")
|
|
|
|
|
|
ax2.text(g.rho.min(), 1.001, r"$\hat D/D = 1$ bound (cannot over-count)",
|
|
|
|
|
|
fontsize=7, color="0.4", va="bottom")
|
|
|
|
|
|
ax2.set_ylim(0.0, 1.02) # cap at the bound: no above-1 headroom
|
|
|
|
|
|
ax2.set_xlabel(r"load $\rho = f\,D_{vis}$")
|
|
|
|
|
|
ax2.set_ylabel(r"accuracy $\hat D/D$ (bounded by 1)")
|
|
|
|
|
|
ax2.set_title("equilibrium sits at or below 1 at every load")
|
|
|
|
|
|
ax2.legend(fontsize=8, loc="lower left", title="uncle cap")
|
|
|
|
|
|
fig.suptitle(r"The region below the block rate: under-count deficit vs load "
|
|
|
|
|
|
r"(blend, N=1000, f=1/30, hops=3)", y=1.02)
|
|
|
|
|
|
style.save(fig, FIGS / "fig26_deficit_vs_rho", provenance="scripts/rho_boundary_analysis.py")
|
|
|
|
|
|
plt.close(fig)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
|
g = load()
|
|
|
|
|
|
fig26(g)
|
|
|
|
|
|
above = g[g["mean"] > 1 + 2 * g["sem"]]
|
|
|
|
|
|
print(f"bounded-by-1 check: {len(above)}/{len(g)} cells above 1 by >2 SEM; "
|
|
|
|
|
|
f"max D̂/D = {g['mean'].max():.4f}")
|
|
|
|
|
|
print("wrote fig26_deficit_vs_rho")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
main()
|