mirror of
https://github.com/logos-blockchain/research.git
synced 2026-08-07 11:43:20 +00:00
215 lines
8.7 KiB
Python
215 lines
8.7 KiB
Python
"""One-time migration: split REPORT-tsi-parameter-selection.md into a thematic 4-part set + index.
|
||
|
||
The single report grew dense and heavily cross-referenced; this slices it into four cohesive parts
|
||
(kept in tsi-sim-pernode/ so all report-figures/ links stay valid) plus a short index that reuses the
|
||
canonical filename as the entry point. Section NUMBERS (§1-§9, A-C) are preserved as stable identifiers
|
||
across files; every §ref is rewritten into a clickable link to a portable `<a id="s6-5">` anchor,
|
||
same-file or cross-file as appropriate. Figure embeds and §9's config/script/run paths are untouched.
|
||
|
||
Run: python scripts/split_report.py (reads REPORT-...md, writes the 4 parts + overwrites the index)
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
# ruff: noqa: E501 (one-time migration; index/nav strings are intentionally long prose)
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
HERE = Path(__file__).resolve().parent.parent
|
||
SRC = HERE / "REPORT-tsi-parameter-selection.md"
|
||
INDEX = "REPORT-tsi-parameter-selection.md"
|
||
P1 = "tsi-report-1-overview-and-recommendations.md"
|
||
P2 = "tsi-report-2-accuracy-and-design.md"
|
||
P3 = "tsi-report-3-robustness-and-incentives.md"
|
||
P4 = "tsi-report-4-reproducibility-and-appendices.md"
|
||
|
||
# top-level section id -> part filename, and the section order within each part
|
||
PART_SECTIONS = {
|
||
P1: ["1", "7", "8"],
|
||
P2: ["2", "3", "4", "5"],
|
||
P3: ["6"],
|
||
P4: ["9", "A", "B", "C"],
|
||
}
|
||
PART_TITLE = {
|
||
P1: "Part 1 — Overview and recommendations",
|
||
P2: "Part 2 — Accuracy and design",
|
||
P3: "Part 3 — Robustness and incentives",
|
||
P4: "Part 4 — Reproducibility and appendices",
|
||
}
|
||
SEC_TO_FILE = {s: f for f, secs in PART_SECTIONS.items() for s in secs}
|
||
|
||
|
||
def top_id(line: str) -> str | None:
|
||
m = re.match(r"^##\s+Appendix\s+([A-C])\b", line)
|
||
if m:
|
||
return m.group(1)
|
||
m = re.match(r"^##\s+(\d+)\.", line)
|
||
return m.group(1) if m else None
|
||
|
||
|
||
def header_anchor(line: str) -> str | None:
|
||
"""Anchor id for a section/subsection header line, e.g. §6.5 -> s6-5, App B.2 -> sB-2."""
|
||
m = re.match(r"^##\s+Appendix\s+([A-C])\b", line)
|
||
if m:
|
||
return "s" + m.group(1)
|
||
m = re.match(r"^##\s+(\d+)\.", line)
|
||
if m:
|
||
return "s" + m.group(1)
|
||
m = re.match(r"^###\s+([0-9A-C]+)\.(\d+)", line)
|
||
if m:
|
||
return f"s{m.group(1)}-{m.group(2)}"
|
||
return None
|
||
|
||
|
||
def make_ref_rewriter(current_file: str):
|
||
"""Rewrite §N/§N.M and 'Appendix X' refs into links to their anchor (same- or cross-file)."""
|
||
def link(top: str, sub: str | None, label: str) -> str:
|
||
anchor = "s" + top + (f"-{sub}" if sub else "")
|
||
tgt = SEC_TO_FILE.get(top)
|
||
if tgt is None:
|
||
return label # unknown target: leave as text
|
||
dest = f"#{anchor}" if tgt == current_file else f"{tgt}#{anchor}"
|
||
return f"[{label}]({dest})"
|
||
|
||
def sec_sub(m: re.Match) -> str:
|
||
top, sub = m.group(1), m.group(2)
|
||
return link(top, sub, m.group(0))
|
||
|
||
def appendix(m: re.Match) -> str:
|
||
return link(m.group(1), None, m.group(0))
|
||
|
||
sec_re = re.compile(r"§\s?(\d+)(?:\.(\d+))?")
|
||
app_re = re.compile(r"\bAppendix\s+([A-C])\b")
|
||
|
||
def rewrite(text: str) -> str:
|
||
return app_re.sub(appendix, sec_re.sub(sec_sub, text))
|
||
|
||
return rewrite
|
||
|
||
|
||
def render_lines(lines: list[str], current_file: str) -> list[str]:
|
||
"""Inject anchors before headers and rewrite §refs, skipping fenced code blocks."""
|
||
rewrite = make_ref_rewriter(current_file)
|
||
out: list[str] = []
|
||
in_fence = False
|
||
for ln in lines:
|
||
if ln.lstrip().startswith("```"):
|
||
in_fence = not in_fence
|
||
out.append(ln)
|
||
continue
|
||
if in_fence:
|
||
out.append(ln) # never touch code (refs there stay plain text)
|
||
continue
|
||
aid = header_anchor(ln)
|
||
if aid is not None:
|
||
out.append(f'<a id="{aid}"></a>')
|
||
out.append(ln) # header title kept verbatim (no links inside headers)
|
||
continue
|
||
out.append(rewrite(ln))
|
||
return out
|
||
|
||
|
||
def part_header(fname: str, units_note: str) -> list[str]:
|
||
nav = (f"*[Part 1 — Overview & recommendations]({P1}) · [Part 2 — Accuracy & design]({P2}) · "
|
||
f"[Part 3 — Robustness & incentives]({P3}) · [Part 4 — Reproducibility & appendices]({P4}) · "
|
||
f"[Index]({INDEX})*")
|
||
where = ("*Sections live across the set: §1/§7/§8 in Part 1, §2–§5 in Part 2, §6 in Part 3, "
|
||
"§9 and Appendices A–C in Part 4.*")
|
||
return [
|
||
f"# Total-Stake-Inference parameter selection — {PART_TITLE[fname].split('— ')[1]}",
|
||
"",
|
||
units_note,
|
||
"",
|
||
nav,
|
||
"",
|
||
where,
|
||
"",
|
||
"---",
|
||
"",
|
||
]
|
||
|
||
|
||
def build_index(units_note: str) -> str:
|
||
lines = [
|
||
"# Total-Stake-Inference parameter selection",
|
||
"",
|
||
units_note,
|
||
"",
|
||
"This analysis selects and justifies the TSI parameters for Cryptarchia, from a per-node "
|
||
"network simulation (`tsi-sim-pernode`). It is split into four parts:",
|
||
"",
|
||
f"1. **[Overview and recommendations]({P1})** — the executive summary, the per-knob parameter "
|
||
"reference (§7), and the safest selection with residual risks and the recommendation-vs-spec "
|
||
"deltas (§8).",
|
||
f"2. **[Accuracy and design]({P2})** — the model and counting rule (§2), the seven findings and "
|
||
"their evidence (§3), and the design equations / selection algorithm (§4–§5).",
|
||
f"3. **[Robustness and incentives]({P3})** — jitter, grinding, withholding, selfish mining, the "
|
||
"reward design, fork/reorg depth, and organic churn (§6).",
|
||
f"4. **[Reproducibility and appendices]({P4})** — how to re-run every study (§9), the residual "
|
||
"f-rounding offset (App A), the per-epoch noise floor (App B), and consensus detail (App C).",
|
||
"",
|
||
f"**Headline recommendation** (Cryptarchia baseline f = 1/30): security `k = 2160`, uncle "
|
||
f"window `W = 300` slots, uncle cap `U = ⌈ρ⌉ + 1` (2 at the Blend target), learning rate "
|
||
f"`β = 1`, peering degree ≥ 6 at scale, soft uncle rewards with `w_u + w_n < 1`, and operate "
|
||
f"at load `ρ = f·D_vis < 1`. The full recommended-configuration table and rationale are in "
|
||
f"**[Part 1 →]({P1})**.",
|
||
"",
|
||
]
|
||
return "\n".join(lines) + "\n"
|
||
|
||
|
||
def main() -> None:
|
||
text = SRC.read_text()
|
||
if "## 6. Robustness" not in text:
|
||
sys.exit("Source has already been split (no '## 6. Robustness' found in "
|
||
f"{SRC.name}, which is now the index). This one-time migration is complete; "
|
||
"re-run against the pre-split backup only.")
|
||
raw = text.split("\n")
|
||
|
||
# frontmatter (title + units note) is everything before the first "## " header
|
||
first_h = next(i for i, l in enumerate(raw) if l.startswith("## "))
|
||
units_note = raw[2] # the italic "*Per-node network simulation ... 1 slot = 1 s.*" line
|
||
|
||
# slice into top-level sections
|
||
sections: dict[str, list[str]] = {}
|
||
cur: str | None = None
|
||
for l in raw[first_h:]:
|
||
tid = top_id(l)
|
||
if tid is not None:
|
||
cur = tid
|
||
sections[cur] = []
|
||
if cur is not None:
|
||
sections[cur].append(l)
|
||
|
||
# update the reading-order note (in §1) to describe the 4-part structure
|
||
ro_old_prefix = "The rest of the report, in reading order:"
|
||
ro_new = ("This report is split into four parts (see the [index](" + INDEX + ")): "
|
||
"**Part 1** — the recommended configuration, the per-knob parameter reference (§7) and "
|
||
"the safest selection with residual risks and spec deltas (§8); **Part 2** — the model "
|
||
"and counting rule (§2), the evidence behind each finding (§3), and the design equations "
|
||
"and selection algorithm (§4–§5); **Part 3** — robustness against noise, attacks and the "
|
||
"incentive design (§6); **Part 4** — reproducibility (§9) and the appendices (the residual "
|
||
"~1 % f-rounding offset, the ±0.9 % per-epoch noise floor, and consensus detail).")
|
||
sections["1"] = [
|
||
ro_new if l.startswith(ro_old_prefix) else l for l in sections["1"]
|
||
]
|
||
|
||
# assemble each part
|
||
for fname, sec_ids in PART_SECTIONS.items():
|
||
body: list[str] = list(part_header(fname, units_note))
|
||
for sid in sec_ids:
|
||
body.extend(sections[sid])
|
||
body.append("") # spacer between sections
|
||
rendered = render_lines(body, fname)
|
||
(HERE / fname).write_text("\n".join(rendered).rstrip() + "\n")
|
||
print(f"wrote {fname} ({len(rendered)} lines)")
|
||
|
||
# index last (overwrites the source-name file)
|
||
(HERE / INDEX).write_text(build_index(units_note))
|
||
print(f"wrote {INDEX} (index)")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|