align / code /scripts /p2 /eval_report.py
viai957's picture
Add files using upload-large-folder tool
63f41a6 verified
Raw History Blame Contribute Delete
56.1 kB
#!/usr/bin/env python
"""P2 evaluation report: pass@k, Wilson / bootstrap CIs, paired McNemar, correction lift.
`--config sota` runs the separately registered SOTA analysis instead (docs/PREREG_SOTA.md): co-primary class A and
class B on miniF2F-test, unbiased pass@32 from n = 64, paired sign-flip test, Holm over the two, secondaries, exploratory
outputs, protocol-tuple tags, and the rewritten-statement count. Every report that touches a test split is appended to
an append-only, hash-chained look log. The P2 primary below is untouched.
.venv/bin/python scripts/p2/eval_report.py \\
--problems minif2f.jsonl mobench.jsonl \\
--run base:gens_base.jsonl:ver_base.jsonl --run sft:gens_sft.jsonl:ver_sft.jsonl \\
--baseline base --candidate sft --out-dir REPORT_DIR
`--run NAME:GEN[,GEN..]:VER[,VER..]`. Writes `report.json` and `report.md`. Every table row names
its problem set, its token cap and the verifier budget; nothing is pooled across caps.
Pre-registered endpoints (docs/P2.md, written before any data): PRIMARY = miniF2F solved@8 at cap
16384, baseline vs post-SFT, paired exact McNemar, alpha = 0.05. Everything else in the report is
labelled SECONDARY and is NOT multiplicity-corrected.
What this script refuses to do (each is a number that would be about a different experiment):
* score a run with a missing verification, a verification with no generation, a stale verdict
(`code_sha256` mismatch), or a problem with a different number of round-0 samples than the
rest (unless --allow-ragged-n): a missing sample is not a failure of the model;
* report pass@k for k > n;
* compare two runs whose verifier pins / config / budget differ (--allow-verifier-mismatch
to override; the mismatch is then printed in the report header);
* compare two runs that do not cover exactly the same problems;
* compare two runs generated with different sampling settings or prompted differently (--allow-design-mismatch);
* label a comparison PRIMARY unless it is the registered test: miniF2F at cap 16384, k = 8, ALL 244 problems, baseline = the
registered model revision, temperature 1.0 / top_p 0.95 / no top_k (anything else is printed as secondary, with the reason).
"""
from __future__ import annotations
import argparse
import getpass
import hashlib
import importlib.util
import json
import os
import re
import socket
import sys
from collections import Counter, defaultdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
REPO = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(REPO))
def _load_common():
spec = importlib.util.spec_from_file_location("p2_common", Path(__file__).with_name("p2_common.py"))
mod = importlib.util.module_from_spec(spec)
sys.modules["p2_common"] = mod
spec.loader.exec_module(mod)
return mod
C = sys.modules.get("p2_common") or _load_common()
from align.eval import paired as PD # noqa: E402
from align.eval import proof_stats as S # noqa: E402
#: What docs/P2.md section 1 registers. `n_problems`, `baseline_model_rev` and the sampling settings are part of the
#: registration: a comparison that differs in any of them is a DIFFERENT experiment and must not carry the PRIMARY label
#: (a 64-problem subset run labelled PRIMARY would print a confirmatory-looking p-value for a test nobody registered).
PRIMARY = {"source": "minif2f", "cap": 16384, "k": 8, "n_problems": 244,
"baseline_model_rev": "dfd02e6271a58375dfbf3ece0175277cf6b6a89a",
"sampling": {"temperature": 1.0, "top_p": 0.95, "top_k": [0, -1]}}
DEFAULT_KS = (1, 2, 4, 8, 16, 32)
DOC = REPO / "docs" / "P2.md"
_PREREG_BLOCK = re.compile(r"<!-- PREREG:BEGIN -->\n(.*?)<!-- PREREG:END -->", re.S)
_PREREG_RECORDED = re.compile(r"PREREG_SHA256:\s*`([0-9a-f]{64})`")
def prereg_status(doc: Path = DOC) -> dict:
"""Recompute the hash of the pre-registered block of docs/P2.md and compare it with the
value recorded next to it. A report whose analysis plan was edited after registration says so
in its header instead of silently presenting the new plan as the registered one."""
if not Path(doc).exists():
return {"status": "missing", "doc": str(doc)}
text = Path(doc).read_text()
m = _PREREG_BLOCK.search(text)
rec = _PREREG_RECORDED.search(text)
if not m:
return {"status": "no_block", "doc": str(doc)}
h = hashlib.sha256(m.group(1).encode()).hexdigest()
if not rec:
return {"status": "unrecorded", "computed": h}
return {"status": "verified" if rec.group(1) == h else "MODIFIED_AFTER_REGISTRATION", "recorded": rec.group(1), "computed": h}
class Sample:
__slots__ = ("accepted", "reasons", "finish", "n_tok", "parent", "budget", "verify_s", "eff_max", "sampling", "model_rev", "prompt_sha",
"fidelity")
def __init__(self, accepted, reasons, finish, n_tok, parent, budget, verify_s, eff_max, sampling=None, model_rev=None, prompt_sha=None,
fidelity=None):
self.accepted, self.reasons, self.finish = accepted, reasons, finish
self.n_tok, self.parent, self.budget, self.verify_s = n_tok, parent, budget, verify_s
self.eff_max = eff_max # tokens the sampler was really allowed (== cap unless the context window clipped it)
self.sampling = sampling # (temperature, top_p, top_k) as recorded; None = not recorded
self.model_rev, self.prompt_sha = model_rev, prompt_sha
#: SOTA configuration only: for an ACCEPTED sample, "faithful" | "rewritten" | "unreadable" (the displayed theorem
#: compared with the reference statement); None = not checked (rejected sample, or no statements were supplied).
self.fidelity = fidelity
def _sampling_of(rec: dict) -> tuple:
"""(temperature, top_p, top_k) exactly as recorded; a missing key stays None (unknown is not 'default')."""
sp = rec.get("sampling") or {}
return (sp.get("temperature"), sp.get("top_p"), sp.get("top_k"))
def verifier_signature(v: dict) -> str:
cfg = dict(v.get("config", {}))
return hashlib.sha256(json.dumps({"pins": v.get("pins"), "config": cfg}, sort_keys=True).encode()).hexdigest()[:16]
# =========================================================================== loading
def _fidelity(statements: Optional[dict], pid: str, completion_text: str) -> Optional[str]:
if statements is None or pid not in statements:
return None
shown = C.displayed_signature(completion_text)
if shown is None:
return "unreadable"
return "faithful" if shown == C.statement_key(statements[pid])[2] else "rewritten"
def load_run(gen_paths, ver_paths, statements: Optional[dict] = None) -> tuple[dict[tuple, Sample], dict]:
"""`statements` ({problem_id: formal_statement}) switches on the statement-fidelity check of accepted samples (SOTA configuration)."""
ver: dict[tuple, dict] = {}
sigs: Counter = Counter()
for p in ver_paths:
for rec in C.iter_jsonl(p):
key = C.gen_key(rec)
if key in ver:
raise C.ContractError(f"{p}: duplicate verification key {key}")
ver[key] = rec
sigs[verifier_signature(rec["verifier"])] += 1
samples: dict[tuple, Sample] = {}
for p in gen_paths:
with open(p, encoding="utf-8") as f:
for lineno, line in enumerate(f, 1):
if not line.strip():
continue
rec = json.loads(line)
C.validate_generation(rec)
key = C.gen_key(rec)
if key in samples:
raise C.ContractError(f"{p}:{lineno}: duplicate generation key {key}")
v = ver.get(key)
if v is None:
raise C.ContractError(f"{p}:{lineno}: generation {key} has no verification record")
if v["code_sha256"] != C.code_sha(rec["lean_code"]):
raise C.ContractError(f"{p}:{lineno}: stale verification for {key} (code_sha256 mismatch)")
par = rec["parent"]
samples[key] = Sample(bool(v["accepted"]), list(v["reasons"]), rec["finish_reason"], len(rec["completion_ids"]),
None if par is None else (par["problem_id"], int(par["sample_idx"]), int(par["round"]), key[3]),
v["budget_s"], v["verify_s"], C.effective_max_tokens(rec),
_sampling_of(rec), rec["model_rev"], hashlib.sha256(json.dumps(rec["prompt_ids"]).encode()).hexdigest()[:16],
_fidelity(statements, rec["problem_id"], rec["completion_text"]) if v["accepted"] else None)
orphan = set(ver) - set(samples)
if orphan:
raise C.ContractError(f"{len(orphan)} verification records have no generation (first: {sorted(orphan)[0]})")
meta = {"verifier_signatures": dict(sigs), "n_samples": len(samples),
"budgets": sorted({s.budget for s in samples.values()}),
"verifier": next(iter(ver.values()))["verifier"] if ver else None}
return samples, meta
# =========================================================================== metrics
def round0_by_problem(samples, problem_ids, cap: int, allow_ragged: bool) -> dict[str, list[bool]]:
"""problem_id -> accepted flags of round-0 samples ordered by sample_idx (contiguous 0..n-1)."""
per: dict[str, dict[int, bool]] = defaultdict(dict)
for (pid, si, rd, c), s in samples.items():
if rd == 0 and c == cap and pid in problem_ids:
per[pid][si] = s.accepted
missing = sorted(set(problem_ids) - set(per))
if missing:
raise C.ContractError(f"incomplete run at cap {cap}: {len(missing)} problems have no round-0 sample (first: {missing[0]})")
out = {}
for pid, d in per.items():
if sorted(d) != list(range(len(d))):
raise C.ContractError(f"{pid}: round-0 sample_idx not contiguous from 0 ({sorted(d)[:12]}...)")
out[pid] = [d[i] for i in range(len(d))]
ns = {len(v) for v in out.values()}
if len(ns) > 1 and not allow_ragged:
raise C.ContractError(f"problems have different numbers of round-0 samples at cap {cap}: {sorted(ns)} (--allow-ragged-n to override)")
return out
def run_metrics(samples, problem_ids, cap: int, ks, seed: int, n_boot: int, allow_ragged: bool = False) -> dict:
flags = round0_by_problem(samples, set(problem_ids), cap, allow_ragged)
pids = sorted(flags)
n_min = min(len(v) for v in flags.values())
counts = [(len(flags[p]), sum(flags[p])) for p in pids]
out: dict = {"cap_tokens": cap, "n_problems": len(pids), "n_per_problem_min": n_min,
"n_per_problem_max": max(len(v) for v in flags.values()), "n_samples_round0": sum(n for n, _ in counts)}
tot_s, tot_c = out["n_samples_round0"], sum(c for _, c in counts)
per_problem_p1 = [c / n for n, c in counts]
lo, hi = S.bootstrap_mean_ci(per_problem_p1, seed, n_boot)
nlo, nhi = S.wilson_interval(tot_c, tot_s)
out["pass@1"] = {"value": tot_c / tot_s, "ci95_cluster_bootstrap": [lo, hi],
"ci95_wilson_pooled_samples_ANTICONSERVATIVE": [nlo, nhi]}
out["pass@k"] = {}
out["solved@k"] = {}
for k in ks:
if k > n_min:
out["pass@k"][str(k)] = None # explicit: not computable, not zero
out["solved@k"][str(k)] = None
continue
vals = [S.pass_at_k(n, c, k) for n, c in counts]
blo, bhi = S.bootstrap_mean_ci(vals, seed + k, n_boot)
solved = sum(1 for p in pids if S.solved_at_k(flags[p], k))
wlo, whi = S.wilson_interval(solved, len(pids))
out["pass@k"][str(k)] = {"value": sum(vals) / len(vals), "ci95_cluster_bootstrap": [blo, bhi]}
out["solved@k"][str(k)] = {"solved": solved, "n": len(pids), "fraction": solved / len(pids), "ci95_wilson": [wlo, whi]}
r0 = [s for (pid, si, rd, c), s in samples.items() if rd == 0 and c == cap and pid in set(pids)]
out["completion"] = {
"length_capped_fraction": sum(1 for s in r0 if s.finish == "length") / len(r0),
"mean_completion_tokens": sum(s.n_tok for s in r0) / len(r0),
"no_code_fraction": sum(1 for s in r0 if s.reasons == ["no_code"]) / len(r0),
"reason_histogram": dict(Counter(r for s in r0 if not s.accepted for r in s.reasons[:1]).most_common(12)),
"verify_s_mean": sum(s.verify_s for s in r0) / len(r0),
"context_clipped_fraction": sum(1 for s in r0 if s.eff_max < cap) / len(r0),
"mean_effective_max_tokens": sum(s.eff_max for s in r0) / len(r0),
}
out["design"] = {"model_revs": sorted({s.model_rev for s in r0}), "sampling": sorted({tuple(s.sampling or ()) for s in r0}, key=str)}
out["_flags"] = flags # stripped before JSON
return out
def primary_blockers(src: str, cap: int, k: int, base_metrics: dict) -> list[str]:
"""Why a comparison of this shape may NOT carry the PRIMARY label ([] = it is the registered test)."""
if (src, cap, k) != (PRIMARY["source"], PRIMARY["cap"], PRIMARY["k"]):
return ["not the registered (set, cap, k)"]
why = []
if base_metrics["n_problems"] != PRIMARY["n_problems"]:
why.append(f"{base_metrics['n_problems']} problems scored, the registered set has {PRIMARY['n_problems']}")
revs = base_metrics["design"]["model_revs"]
if revs != [PRIMARY["baseline_model_rev"]]:
why.append(f"baseline model_rev {revs} is not the registered {PRIMARY['baseline_model_rev']}")
want = PRIMARY["sampling"]
for t, p, kk in base_metrics["design"]["sampling"] or [()]:
if (t, p) != (want["temperature"], want["top_p"]) or kk not in want["top_k"]:
why.append(f"baseline sampling (temperature, top_p, top_k) = {(t, p, kk)} is not the registered {(want['temperature'], want['top_p'], want['top_k'])}")
return why
PRIMARY_ALPHA = 0.05
def registered_decision(m: dict) -> str:
"""The pre-registered decision rule, applied mechanically so the wording is not chosen after the data:
success = p < alpha AND the candidate solves more problems; p < alpha with the candidate lower is a
significant degradation; anything else is "no detectable improvement" (never "no effect")."""
sig = m["p_value_exact_two_sided"] < PRIMARY_ALPHA
if sig and m["solved_b"] > m["solved_a"]:
return "SUCCESS: significant improvement"
if sig and m["solved_b"] < m["solved_a"]:
return "significant DEGRADATION"
return "no detectable improvement"
def design_mismatches(base: dict[tuple, Sample], cand: dict[tuple, Sample]) -> list[str]:
"""Differences between the two systems' GENERATION that no verifier signature can show: sampling
settings and the round-0 prompts. A paired test of two runs that were not asked the same question
with the same sampler measures the harness, not the model."""
out = []
sb = {tuple(s.sampling or ()) for s in base.values()}
sc = {tuple(s.sampling or ()) for s in cand.values()}
if len(sb) != 1 or len(sc) != 1 or sb != sc:
out.append(f"sampling settings differ or are mixed: baseline {sorted(sb, key=str)} vs candidate {sorted(sc, key=str)}")
common = sorted(k for k in set(base) & set(cand) if k[2] == 0)
bad = sorted({k[0] for k in common if base[k].prompt_sha != cand[k].prompt_sha})
if bad:
out.append(f"{len(bad)} problems were prompted differently in the two runs at the same sample keys (first: {bad[0]})")
return out
def compare(base: dict, cand: dict, k: int, seed: int, n_boot: int) -> dict:
fb, fc = base["_flags"], cand["_flags"]
if set(fb) != set(fc):
raise C.ContractError("the two runs cover different problem sets")
if min(base["n_per_problem_min"], cand["n_per_problem_min"]) < k:
return {"k": k, "available": False, "why": f"needs >= {k} samples per problem in both runs"}
sa = {p: S.solved_at_k(fb[p], k) for p in fb}
sb = {p: S.solved_at_k(fc[p], k) for p in fb}
m = S.mcnemar_exact(sa, sb)
pids = sorted(fb)
va = [S.pass_at_k(len(fb[p]), sum(fb[p]), k) for p in pids]
vb = [S.pass_at_k(len(fc[p]), sum(fc[p]), k) for p in pids]
dlo, dhi = S.paired_bootstrap_diff_ci(va, vb, seed + 7 * k, n_boot)
return {"k": k, "available": True, "mcnemar": m.to_json(), "delta_solved": m.solved_b - m.solved_a,
"delta_solved_fraction": (m.solved_b - m.solved_a) / m.n_problems,
"delta_pass@k": sum(vb) / len(vb) - sum(va) / len(va), "delta_pass@k_ci95_paired_bootstrap": [dlo, dhi]}
def correction_lift(samples, problem_ids, cap: int) -> Optional[dict]:
"""Per-round table: how many unsolved problems did correction rounds convert, at what cost.
A problem is solved through round r when ANY sample of rounds <= r was accepted. The lift is
confounded with compute (corrections are extra samples): the table reports samples and
completion tokens spent per round so the comparison against `pass@(n0 + m)` can be made."""
pids = set(problem_ids)
rounds = sorted({rd for (pid, si, rd, c) in samples if c == cap and pid in pids})
if len(rounds) < 2:
return None
acc_by_round: dict[int, set[str]] = defaultdict(set)
gen_by_round: Counter = Counter()
tok_by_round: Counter = Counter()
acc_samples: Counter = Counter()
for (pid, si, rd, c), s in samples.items():
if c != cap or pid not in pids:
continue
gen_by_round[rd] += 1
tok_by_round[rd] += s.n_tok
if s.accepted:
acc_by_round[rd].add(pid)
acc_samples[rd] += 1
if rd > 0:
par = s.parent
if par is None or par not in samples:
raise C.ContractError(f"correction sample {(pid, si, rd, c)} has no parent among the generations")
if samples[par].accepted:
raise C.ContractError(f"correction sample {(pid, si, rd, c)} corrects an ACCEPTED parent {par}")
n = len(pids)
solved = set(acc_by_round[0])
rows = [{"round": 0, "samples": gen_by_round[0], "completion_tokens": tok_by_round[0], "accepted_samples": acc_samples[0],
"newly_solved_problems": len(solved), "solved_through_round": len(solved), "solved_fraction": len(solved) / n}]
unsolved_after0 = n - len(solved)
for r in rounds[1:]:
new = acc_by_round[r] - solved
solved |= acc_by_round[r]
lo, hi = S.wilson_interval(len(solved) - rows[0]["solved_through_round"], unsolved_after0) if unsolved_after0 else (0.0, 0.0)
rows.append({"round": r, "samples": gen_by_round[r], "completion_tokens": tok_by_round[r], "accepted_samples": acc_samples[r],
"sample_success_rate": acc_samples[r] / gen_by_round[r] if gen_by_round[r] else None,
"newly_solved_problems": len(new), "solved_through_round": len(solved), "solved_fraction": len(solved) / n,
"conversion_of_round0_unsolved": (len(solved) - rows[0]["solved_through_round"]) / unsolved_after0 if unsolved_after0 else None,
"conversion_ci95_wilson": [lo, hi]})
return {"cap_tokens": cap, "n_problems": n, "rows": rows,
"note": "lift is extra-sample compute, not free: compare solved_through_round with pass@(n0 + corrections per problem) of round 0 when n0 allows"}
# =========================================================================== report
def build_report(problems_by_source: dict[str, dict], runs: dict[str, tuple], baseline: Optional[str], candidate: Optional[str],
ks, seed: int, n_boot: int, allow_ragged: bool, allow_verifier_mismatch: bool, allow_design_mismatch: bool = False) -> dict:
rep: dict = {"preregistered_primary": dict(PRIMARY), "prereg": prereg_status(), "runs": {}, "comparisons": [], "correction_lift": {}}
metrics: dict[str, dict] = {}
for name, (samples, meta) in runs.items():
caps = sorted({c for (_, _, _, c) in samples})
rep["runs"][name] = {"meta": meta, "tables": {}}
metrics[name] = {}
for src, probs in problems_by_source.items():
for cap in caps:
covered = {pid for (pid, _, _, c) in samples if c == cap and pid in probs}
if not covered:
continue
m = run_metrics(samples, set(probs), cap, ks, seed, n_boot, allow_ragged)
metrics[name][(src, cap)] = m
rep["runs"][name]["tables"][f"{src}@{cap}"] = {k: v for k, v in m.items() if not k.startswith("_")}
lift = correction_lift(samples, set(probs), cap)
if lift:
rep["correction_lift"][f"{name}/{src}@{cap}"] = lift
if baseline and candidate:
sig_b = {verifier_signature(runs[baseline][1]["verifier"])} if runs[baseline][1]["verifier"] else set()
sig_c = {verifier_signature(runs[candidate][1]["verifier"])} if runs[candidate][1]["verifier"] else set()
if sig_b != sig_c or runs[baseline][1]["budgets"] != runs[candidate][1]["budgets"]:
msg = f"verifier differs: baseline {sig_b} budgets {runs[baseline][1]['budgets']} vs candidate {sig_c} budgets {runs[candidate][1]['budgets']}"
if not allow_verifier_mismatch:
raise C.ContractError(msg + " (--allow-verifier-mismatch to compare anyway)")
rep["verifier_mismatch"] = msg
design = design_mismatches(runs[baseline][0], runs[candidate][0])
if design:
if not allow_design_mismatch:
raise C.ContractError("; ".join(design) + " (--allow-design-mismatch to compare anyway)")
rep["design_mismatch"] = design
for (src, cap), mb in sorted(metrics[baseline].items()):
mc = metrics[candidate].get((src, cap))
if mc is None:
continue
for k in ks:
cmpd = compare(mb, mc, k, seed, n_boot)
blockers = primary_blockers(src, cap, k, mb)
cmpd.update({"source": src, "cap_tokens": cap, "baseline": baseline, "candidate": candidate,
"endpoint": "PRIMARY" if not blockers else "secondary"})
if not blockers and cmpd["available"]:
cmpd["decision"] = registered_decision(cmpd["mcnemar"])
if blockers and blockers != ["not the registered (set, cap, k)"]:
cmpd["not_primary_because"] = blockers # primary-shaped but not the registered test
rep["comparisons"].append(cmpd)
if not any(c["endpoint"] == "PRIMARY" and c["available"] for c in rep["comparisons"]):
rep["primary_missing"] = "the pre-registered primary comparison (miniF2F, cap 16384, k=8) could not be computed from these runs"
near = [c for c in rep["comparisons"] if c.get("not_primary_because")]
if near:
rep["primary_missing"] += "; the primary-shaped comparison below is NOT the registered test: " + "; ".join(near[0]["not_primary_because"])
return rep
def render_md(rep: dict) -> str:
L = ["# P2 evaluation report", "",
f"Pre-registered primary endpoint: {PRIMARY['source']} solved@{PRIMARY['k']} at cap {PRIMARY['cap']}, paired exact McNemar, alpha 0.05. All other rows are secondary and not multiplicity-corrected.", ""]
pr = rep.get("prereg", {})
L += [f"Pre-registration (docs/P2.md): {pr.get('status')}" + (f" (sha256 {pr.get('computed')})" if pr.get("computed") else ""), ""]
if pr.get("status") != "verified":
L += ["WARNING: the registered analysis plan could not be verified; treat every p-value below as exploratory.", ""]
if rep.get("verifier_mismatch"):
L += [f"WARNING: {rep['verifier_mismatch']}", ""]
if rep.get("design_mismatch"):
L += [f"WARNING: generation design differs between the runs: {'; '.join(rep['design_mismatch'])}", ""]
if rep.get("primary_missing"):
L += [f"WARNING: {rep['primary_missing']}", ""]
pct = lambda x: "n/a" if x is None else f"{100 * x:.1f}%"
for name, r in rep["runs"].items():
L += [f"## Run `{name}`", "", f"verifier budgets (s): {r['meta']['budgets']}; samples: {r['meta']['n_samples']}", "",
"| set @ cap | problems | n/prob | pass@1 [95% cluster-bootstrap] | " + " | ".join(f"pass@{k}" for k in DEFAULT_KS) + " | length-capped | mean tok | mean effective max_tokens |",
"|" + "---|" * (7 + len(DEFAULT_KS))]
for tab, t in r["tables"].items():
p1 = t["pass@1"]
cells = []
for k in DEFAULT_KS:
v = t["pass@k"].get(str(k))
cells.append("n/a" if v is None else f"{pct(v['value'])} ({t['solved@k'][str(k)]['solved']}/{t['n_problems']})")
L.append(f"| {tab} | {t['n_problems']} | {t['n_per_problem_min']} | {pct(p1['value'])} [{pct(p1['ci95_cluster_bootstrap'][0])}, {pct(p1['ci95_cluster_bootstrap'][1])}] | "
+ " | ".join(cells) + f" | {pct(t['completion']['length_capped_fraction'])} | {t['completion']['mean_completion_tokens']:.0f} | {t['completion']['mean_effective_max_tokens']:.0f} |")
L.append("")
if rep["comparisons"]:
L += ["## Paired comparison (McNemar exact on solved@k)", "",
"| endpoint | set @ cap | k | solved base | solved cand | only base | only cand | discordant | p (exact, 2-sided) | delta pass@k [95% paired bootstrap] |", "|" + "---|" * 10]
for c in rep["comparisons"]:
if not c["available"]:
L.append(f"| {c['endpoint']} | {c['source']} @ {c['cap_tokens']} | {c['k']} | n/a | n/a | | | | | {c['why']} |")
continue
m = c["mcnemar"]
L.append(f"| {c['endpoint']} | {c['source']} @ {c['cap_tokens']} | {c['k']} | {m['solved_a']} | {m['solved_b']} | {m['only_a']} | {m['only_b']} | "
f"{m['n_discordant']} | {m['p_value_exact_two_sided']:.4f} | {100 * c['delta_pass@k']:+.1f} pp [{100 * c['delta_pass@k_ci95_paired_bootstrap'][0]:+.1f}, {100 * c['delta_pass@k_ci95_paired_bootstrap'][1]:+.1f}] |")
L.append("")
for c in rep["comparisons"]:
if c.get("decision"):
L += [f"**Pre-registered decision (primary, alpha {PRIMARY_ALPHA}): {c['decision']}.** A null result means no gain of roughly 6 pp or more was detectable, not that there is no effect (docs/P2.md section 1).", ""]
if rep["correction_lift"]:
L += ["## Correction-round lift", "", "| run / set @ cap | round | samples | completion tokens | accepted samples | newly solved problems | solved through round | solved % | conversion of round-0 unsolved [95% Wilson] |", "|" + "---|" * 9]
for name, t in rep["correction_lift"].items():
for r in t["rows"]:
conv = r.get("conversion_of_round0_unsolved")
ci = r.get("conversion_ci95_wilson")
L.append(f"| {name} | {r['round']} | {r['samples']} | {r['completion_tokens']} | {r['accepted_samples']} | {r['newly_solved_problems']} | "
f"{r['solved_through_round']} | {pct(r['solved_fraction'])} | " + ("" if conv is None else f"{pct(conv)} [{pct(ci[0])}, {pct(ci[1])}]") + " |")
L += ["", "Lift costs extra samples: compare against round-0 pass@k at the same total samples per problem before calling it a gain.", ""]
return "\n".join(L)
# =========================================================================== SOTA configuration (docs/PREREG_SOTA.md)
#
# A SEPARATE registration from the P2 primary above (which is left exactly as it was). Co-primary endpoints, both on
# miniF2F-test: A = class A (single pass, cap 40k), B = class B (attempt + up to 2 Lean-feedback repairs, 40k total
# completion tokens). Estimand: mean over problems of the unbiased pass@32 from n = 64 samples. Test: paired sign-flip
# permutation over problems, two-sided alpha = 0.05, Holm over {A, B}. Secondaries: exact McNemar on solved@32 of the
# first 32 samples, mean pass@1, ProofNet-test class A. Everything else is exploratory.
SOTA = {
"name": "sota-v1",
"alpha": 0.05,
"coprimary": {"source": "minif2f", "split": "test", "n_problems": 244, "cap": 40960, "n": 64, "k": 32},
"class_b": {"max_repairs": 2, "ledger_tokens": 40960},
"secondary": {"mcnemar_first_n": 32, "proofnet": {"source": "proofnet", "split": "test", "k": 32}},
"verifier_budget_s": 300.0,
"n_flips": 100_000,
"exploratory_ks": (1, 8, 16, 32),
}
LOOK_LOG_DEFAULT = REPO / "docs" / "evidence" / "test_split_looks.jsonl"
def _prereg_hash_module():
spec = importlib.util.spec_from_file_location("p2_prereg_hash", Path(__file__).with_name("prereg_hash.py"))
mod = importlib.util.module_from_spec(spec)
sys.modules["p2_prereg_hash"] = mod
spec.loader.exec_module(mod)
return mod
def prereg_overall_hash() -> Optional[str]:
"""Current sha256 of (registration text + analysis code), as printed by scripts/p2/prereg_hash.py; None if an input is missing."""
try:
return _prereg_hash_module().compute()["sha256"]
except FileNotFoundError:
return None
def verifier_hash(v: Optional[dict]) -> Optional[str]:
"""Full sha256 of the verifier identity (pins + config, canonical JSON; `workers` is deliberately excluded, as in
`verifier_signature`). This is the value the frozen-verifier rule compares."""
if not v:
return None
return hashlib.sha256(json.dumps({"pins": v.get("pins"), "config": dict(v.get("config", {}))}, sort_keys=True).encode()).hexdigest()
def class_a_counts(samples, pids, cap: int) -> tuple[dict, dict]:
"""-> ({pid: (n, c)}, {pid: flags}) from round-0 samples at `cap` (ragged n is refused)."""
flags = round0_by_problem(samples, set(pids), cap, allow_ragged=False)
return {p: (len(f), sum(f)) for p, f in flags.items()}, flags
def class_b_counts(samples, pids, cap: int, max_repairs: int, ledger: int, allow_unrepaired: bool = False) -> Optional[dict]:
"""Class B: {pid: (n_trajectories, c_solved)}; None if the run has no repair rounds at all.
A trajectory is the chain round-0 sample -> its repair -> its repair (<= `max_repairs`). It is solved iff some element
is accepted AND the cumulative completion tokens up to and including that element are <= `ledger` (the 40k TOTAL).
A failed sample with ledger left but no repair record is a missing record, not a failure: refused unless `allow_unrepaired`."""
pset = set(pids)
if not any(rd > 0 and c == cap and pid in pset for (pid, _, rd, c) in samples):
return None
child: dict[tuple, tuple] = {}
for key, s in samples.items():
if key[2] > 0 and key[3] == cap and key[0] in pset:
if s.parent in child:
raise C.ContractError(f"two repairs claim the same parent {s.parent}")
child[s.parent] = key
out = {}
for pid in sorted(pset):
n = solved = 0
for key in sorted(k for k in samples if k[0] == pid and k[2] == 0 and k[3] == cap):
n += 1
cum, cur, ok = 0, key, False
for _ in range(max_repairs + 1):
s = samples[cur]
cum += s.n_tok
if s.accepted:
ok = cum <= ledger
break
if cum >= ledger or cur[2] >= max_repairs:
break
nxt = child.get(cur)
if nxt is None:
if not allow_unrepaired:
raise C.ContractError(f"{cur} failed with ledger left but has no repair record (--allow-unrepaired to count it as a failure)")
break
cur = nxt
solved += ok
out[pid] = (n, solved)
return out
def sota_contrast(cb: dict, cc: dict, k: int, seed: int, n_boot: int, n_flips: int) -> dict:
"""Paired contrast of the unbiased pass@k estimand; `cb`/`cc` are {pid: (n, c)}. Sign-flip p + problem bootstrap CI."""
if set(cb) != set(cc):
raise C.ContractError("the two runs cover different problem sets")
pids = sorted(cb)
if min(min(n for n, _ in cb.values()), min(n for n, _ in cc.values())) < k:
return {"available": False, "k": k, "why": f"needs >= {k} samples per problem in both runs"}
va = PD.pass_at_k_array([cb[p][0] for p in pids], [cb[p][1] for p in pids], k)
vb = PD.pass_at_k_array([cc[p][0] for p in pids], [cc[p][1] for p in pids], k)
d = vb - va
t = PD.sign_flip_test(d, n_flips=n_flips, seed=seed)
lo, hi = PD.paired_bootstrap_ci(d, seed + 11 * k, n_boot)
return {"available": True, "k": k, "n_problems": len(pids), "n_per_problem": sorted({cb[p][0] for p in pids} | {cc[p][0] for p in pids}),
"mean_baseline": float(va.mean()), "mean_candidate": float(vb.mean()), "delta": float(d.mean()),
"delta_ci95_paired_bootstrap": [lo, hi], "p_sign_flip": t["p"], "sign_flip_method": t["method"], "sign_flip_n": t["n_flips"],
"n_changed_problems": t["n_nonzero"], "null_quantiles_of_delta": t["null_quantiles"]}
def _fidelity_summary(samples, pids, cap: int) -> dict:
pset = set(pids)
acc = [(key, s) for key, s in samples.items() if key[3] == cap and key[0] in pset and s.accepted]
cnt = Counter(s.fidelity or "unchecked" for _, s in acc)
bad = [(k, s) for k, s in acc if s.fidelity in ("rewritten", "unreadable")]
return {"accepted_samples": len(acc), "faithful": cnt["faithful"], "rewritten": cnt["rewritten"], "unreadable": cnt["unreadable"],
"unchecked": cnt["unchecked"],
"rewritten_statement_count": cnt["rewritten"] + cnt["unreadable"],
"problems_with_rewritten_accept": len({k[0] for k, _ in bad}),
"in_repair_rounds": sum(1 for k, _ in bad if k[2] > 0)}
def _strict_class_a(samples, pids, cap: int) -> dict:
"""Class-A (n, c) with rewritten/unreadable accepts counted as failures (exploratory fidelity sensitivity)."""
per: dict[str, dict[int, bool]] = defaultdict(dict)
for (pid, si, rd, c), s in samples.items():
if rd == 0 and c == cap and pid in pids:
per[pid][si] = s.accepted and s.fidelity == "faithful"
return {p: (len(d), sum(d.values())) for p, d in per.items()}
def append_look_log(path, entry: dict) -> dict:
"""Append ONE hash-chained line to the test-split look log (append-only: O_APPEND, flock, fsync; each line carries the
sha256 of the previous line's content, so a deletion or edit is detectable by `verify_look_log`)."""
import fcntl
path = Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "a+b") as f:
fcntl.flock(f, fcntl.LOCK_EX)
f.seek(0)
lines = [l for l in f.read().splitlines() if l.strip()]
prev = json.loads(lines[-1])["entry_sha256"] if lines else "0" * 64
rec = dict(entry, prev_sha256=prev)
rec["entry_sha256"] = hashlib.sha256(json.dumps(rec, sort_keys=True).encode()).hexdigest()
f.write((json.dumps(rec, sort_keys=True) + "\n").encode())
f.flush()
os.fsync(f.fileno())
return rec
def verify_look_log(path) -> dict:
"""Recompute the chain. -> {ok, n_entries, error}."""
prev, n = "0" * 64, 0
for i, line in enumerate(Path(path).read_text().splitlines(), 1):
if not line.strip():
continue
rec = json.loads(line)
got = rec.pop("entry_sha256", None)
if rec.get("prev_sha256") != prev:
return {"ok": False, "n_entries": n, "error": f"line {i}: chain broken (prev_sha256 does not match the previous entry)"}
if hashlib.sha256(json.dumps(rec, sort_keys=True).encode()).hexdigest() != got:
return {"ok": False, "n_entries": n, "error": f"line {i}: entry content does not match its hash"}
prev, n = got, n + 1
return {"ok": True, "n_entries": n, "error": None}
def look_entry(config: str, splits: list, report_json: Path, report_md: Path, inputs: dict) -> dict:
try:
who = getpass.getuser()
except Exception: # noqa: BLE001 (no passwd entry in some containers)
who = os.environ.get("USER", "unknown")
return {"who": {"user": who, "host": socket.gethostname(), "pid": os.getpid(), "argv": list(sys.argv)},
"when_utc": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
"what": {"config": config, "splits": sorted(splits), "report_json": str(report_json), "report_md": str(report_md),
"report_json_sha256": C.sha256_file(report_json), "report_md_sha256": C.sha256_file(report_md)},
"inputs": inputs}
def build_sota_report(problems_by_source: dict, runs: dict, baseline: str, candidate: str, seed: int, n_boot: int,
frozen_verifier_hash: Optional[str] = None, registered_hash: Optional[str] = None,
allow_verifier_mismatch: bool = False, allow_design_mismatch: bool = False, allow_unrepaired: bool = False,
n_flips: Optional[int] = None) -> dict:
cfg, alpha = SOTA, SOTA["alpha"]
n_flips = n_flips or cfg["n_flips"]
cp, cb_cfg = cfg["coprimary"], cfg["class_b"]
sb, sc = runs[baseline][0], runs[candidate][0]
mb_, mc_ = runs[baseline][1], runs[candidate][1]
sig_b = {verifier_signature(mb_["verifier"])} if mb_["verifier"] else set()
sig_c = {verifier_signature(mc_["verifier"])} if mc_["verifier"] else set()
rep: dict = {"configuration": cfg["name"], "registered": {k: v for k, v in cfg.items()}, "baseline": baseline, "candidate": candidate,
"seed": seed}
if sig_b != sig_c or mb_["budgets"] != mc_["budgets"]:
msg = f"verifier differs: baseline {sig_b} budgets {mb_['budgets']} vs candidate {sig_c} budgets {mc_['budgets']}"
if not allow_verifier_mismatch:
raise C.ContractError(msg + " (--allow-verifier-mismatch to compare anyway)")
rep["verifier_mismatch"] = msg
design = design_mismatches(sb, sc)
if design:
if not allow_design_mismatch:
raise C.ContractError("; ".join(design) + " (--allow-design-mismatch to compare anyway)")
rep["design_mismatch"] = design
cur = prereg_overall_hash()
rep["prereg"] = {"current_overall_sha256": cur, "registered_overall_sha256": registered_hash,
"status": "unregistered" if not registered_hash else ("verified" if registered_hash == cur else "MODIFIED_AFTER_REGISTRATION")}
def subset(src, split):
return {pid: r for pid, r in problems_by_source.get(src, {}).items() if r.get("split") == split}
test = subset(cp["source"], cp["split"])
pids = set(test)
if not pids:
raise C.ContractError(f"no {cp['source']}/{cp['split']} problems supplied: the co-primary set is empty")
prob_set_hash = hashlib.sha256(json.dumps(sorted((p, test[p]["statement_sha"]) for p in test)).encode()).hexdigest()
# ---- blockers: what stops a number from carrying the CONFIRMATORY label (never an exception: the numbers are still shown)
blockers = []
if len(pids) != cp["n_problems"]:
blockers.append(f"{len(pids)} {cp['source']}/{cp['split']} problems scored, the registered set has {cp['n_problems']}")
for name, (_, meta) in runs.items():
if name not in (baseline, candidate):
continue
if len(meta["verifier_signatures"]) != 1:
blockers.append(f"run {name}: records were verified under {len(meta['verifier_signatures'])} different verifier configurations")
if meta["budgets"] != [cfg["verifier_budget_s"]]:
blockers.append(f"run {name}: verifier budgets {meta['budgets']} are not the registered [{cfg['verifier_budget_s']}]")
vh = verifier_hash(meta["verifier"])
if frozen_verifier_hash is None:
blockers.append("no frozen verifier hash supplied (--frozen-verifier-hash): the frozen-verifier rule cannot be checked")
elif vh != frozen_verifier_hash:
blockers.append(f"run {name}: verifier hash {vh} is not the frozen {frozen_verifier_hash}")
blockers = list(dict.fromkeys(blockers))
if rep["prereg"]["status"] != "verified":
blockers.append(f"pre-registration hash status: {rep['prereg']['status']}")
if rep.get("verifier_mismatch"):
blockers.append("verifier mismatch overridden")
if rep.get("design_mismatch"):
blockers.append("generation design mismatch overridden")
if allow_unrepaired:
blockers.append("--allow-unrepaired: failed samples without a repair record were counted as failures")
fid = {name: _fidelity_summary(runs[name][0], pids, cp["cap"]) for name in (baseline, candidate)}
if any(f["unchecked"] for f in fid.values()):
blockers.append("statement fidelity (rewritten-statement count) was not checked for every accepted sample")
tok = {name: sum(s.n_tok for (pid, _, _, c), s in runs[name][0].items() if pid in pids and c == cp["cap"]) for name in (baseline, candidate)}
def tag(klass: str, source: str, split: str, n_prob: int, n: int, k: int, ledger=None) -> dict:
return {"class": klass, "benchmark": f"{source}/{split}", "n_problems": n_prob, "problem_set_sha256": prob_set_hash if source == cp["source"] else None,
"n": n, "k": k, "cap_tokens": cp["cap"], "ledger_tokens": ledger,
"lean_mathlib_pins": (mb_["verifier"] or {}).get("pins"), "verifier_hash": verifier_hash(mb_["verifier"]),
"verifier_budget_s": mb_["budgets"], "verifier_config": (mb_["verifier"] or {}).get("config"),
"rewritten_statement_count": {"baseline": fid[baseline]["rewritten_statement_count"], "candidate": fid[candidate]["rewritten_statement_count"]},
"completion_tokens_total": {"baseline": tok[baseline], "candidate": tok[candidate]}, "statement_version": "as supplied in the problems file (see problem_set_sha256)"}
# ---- co-primary A
co: dict = {}
ca_b, fl_b = class_a_counts(sb, pids, cp["cap"])
ca_c, fl_c = class_a_counts(sc, pids, cp["cap"])
for nm, cn in (("baseline", ca_b), ("candidate", ca_c)):
ns = {n for n, _ in cn.values()}
if ns != {cp["n"]}:
blockers.append(f"{nm}: n per problem is {sorted(ns)}, the registered n is {cp['n']}")
A = sota_contrast(ca_b, ca_c, cp["k"], seed, n_boot, n_flips)
A["protocol"] = tag("A", cp["source"], cp["split"], len(pids), cp["n"], cp["k"])
co["A"] = A
# ---- co-primary B
bb = class_b_counts(sb, pids, cp["cap"], cb_cfg["max_repairs"], cb_cfg["ledger_tokens"], allow_unrepaired)
bc = class_b_counts(sc, pids, cp["cap"], cb_cfg["max_repairs"], cb_cfg["ledger_tokens"], allow_unrepaired)
if bb is None or bc is None:
B = {"available": False, "k": cp["k"], "why": "no Lean-feedback repair rounds in the " + ("baseline" if bb is None else "candidate") + " run"}
else:
B = sota_contrast(bb, bc, cp["k"], seed + 1, n_boot, n_flips)
if B["available"] and B["n_per_problem"] != [cp["n"]]:
blockers.append(f"class B: trajectories per problem {B['n_per_problem']}, registered {cp['n']}")
B["protocol"] = tag("B", cp["source"], cp["split"], len(pids), cp["n"], cp["k"], cb_cfg["ledger_tokens"])
co["B"] = B
pA = A["p_sign_flip"] if A["available"] else 1.0
pB = B["p_sign_flip"] if B["available"] else 1.0 # an uncomputed co-primary counts as p = 1: Holm is over the registered two
h = PD.holm([pA, pB], alpha)
for key, nm, adj, rej in zip(("A", "B"), ("A", "B"), h["adjusted"], h["reject"]):
e = co[key]
e["p_holm"], e["reject_holm"] = adj, bool(rej)
if e["available"]:
e["decision"] = ("SUCCESS: significant improvement" if rej and e["delta"] > 0 else
"significant DEGRADATION" if rej and e["delta"] < 0 else "no detectable improvement")
co["holm"] = {"alpha": alpha, "m": 2, "p_in": [pA, pB], **{k: v for k, v in h.items() if k != "alpha"}}
rep["coprimary"] = co
rep["confirmatory_blockers"] = blockers
rep["label"] = "CONFIRMATORY" if not blockers and A["available"] and B["available"] else "NOT CONFIRMATORY"
rep["fidelity"] = fid
# ---- secondary (not multiplicity-corrected, stated as such)
sec: dict = {}
n1 = cfg["secondary"]["mcnemar_first_n"]
if min(len(f) for f in fl_b.values()) >= n1 and min(len(f) for f in fl_c.values()) >= n1:
m = S.mcnemar_exact({p: S.solved_at_k(fl_b[p], n1) for p in fl_b}, {p: S.solved_at_k(fl_c[p], n1) for p in fl_c})
sec["mcnemar_first_32"] = dict(m.to_json(), definition=f"solved@{n1} = any of the FIRST {n1} samples (by sample_idx), class A")
else:
sec["mcnemar_first_32"] = {"available": False, "why": f"needs >= {n1} samples per problem"}
sec["mean_pass@1"] = sota_contrast(ca_b, ca_c, 1, seed + 2, n_boot, n_flips)
pn = cfg["secondary"]["proofnet"]
pset = set(subset(pn["source"], pn["split"]))
if pset and all(any(k[0] == p and k[2] == 0 and k[3] == cp["cap"] for k in sb) for p in pset) and all(any(k[0] == p and k[2] == 0 and k[3] == cp["cap"] for k in sc) for p in pset):
pb_, _ = class_a_counts(sb, pset, cp["cap"])
pc_, _ = class_a_counts(sc, pset, cp["cap"])
sec["proofnet_class_A"] = sota_contrast(pb_, pc_, pn["k"], seed + 3, n_boot, n_flips)
sec["proofnet_class_A"]["protocol"] = tag("A", pn["source"], pn["split"], len(pset), min(n for n, _ in pb_.values()), pn["k"])
else:
sec["proofnet_class_A"] = {"available": False, "why": "no complete proofnet/test class-A samples in both runs"}
rep["secondary"] = sec
# ---- exploratory (no multiplicity control, no decision attached)
exp: dict = {"fidelity_strict_class_A": None, "other_sets": {}, "class_A_other_k": {}}
sa_b, sa_c = _strict_class_a(sb, pids, cp["cap"]), _strict_class_a(sc, pids, cp["cap"])
exp["fidelity_strict_class_A"] = dict(sota_contrast(sa_b, sa_c, cp["k"], seed + 4, n_boot, n_flips),
definition="class A with rewritten/unreadable accepted samples counted as failures")
for k in cfg["exploratory_ks"]:
if k != cp["k"]:
exp["class_A_other_k"][str(k)] = sota_contrast(ca_b, ca_c, k, seed + 5 + k, n_boot, n_flips)
for src, probs in sorted(problems_by_source.items()):
for split in sorted({r.get("split") for r in probs.values()}):
if (src, split) in ((cp["source"], cp["split"]), (pn["source"], pn["split"])):
continue
ids = set(subset(src, split))
try:
xb, _ = class_a_counts(sb, ids, cp["cap"])
xc, _ = class_a_counts(sc, ids, cp["cap"])
except C.ContractError as e:
exp["other_sets"][f"{src}/{split}"] = {"available": False, "why": str(e)}
continue
exp["other_sets"][f"{src}/{split}"] = {str(k): sota_contrast(xb, xc, k, seed + 9 + k, n_boot, n_flips) for k in cfg["exploratory_ks"]}
rep["exploratory"] = exp
return rep
def render_sota_md(rep: dict) -> str:
pct = lambda x: "n/a" if x is None else f"{100 * x:.2f}"
L = [f"# SOTA evaluation report ({rep['configuration']})", "",
f"**{rep['label']}.** Baseline `{rep['baseline']}`, candidate `{rep['candidate']}`. Registered in docs/PREREG_SOTA.md: co-primary A and B on miniF2F-test, "
"unbiased pass@32 from n = 64, paired sign-flip test, two-sided alpha 0.05, Holm over {A, B}.", ""]
pr = rep["prereg"]
L += [f"Pre-registration hash: {pr['status']} (current {pr['current_overall_sha256']}, registered {pr['registered_overall_sha256']}).", ""]
if rep["confirmatory_blockers"]:
L += ["WARNING: not confirmatory because:"] + [f"* {b}" for b in rep["confirmatory_blockers"]] + [""]
for w in ("verifier_mismatch", "design_mismatch"):
if rep.get(w):
L += [f"WARNING: {w}: {rep[w]}", ""]
L += ["## Co-primary (Holm over A and B)", "",
"| class | k | n/prob | pass@k base % | pass@k cand % | delta pp [95% problem bootstrap] | p (sign-flip) | p (Holm) | decision |", "|" + "---|" * 9]
for key in ("A", "B"):
e = rep["coprimary"][key]
if not e["available"]:
L.append(f"| {key} | {e['k']} | | | | | | | UNAVAILABLE: {e['why']} |")
continue
L.append(f"| {key} | {e['k']} | {e['n_per_problem']} | {pct(e['mean_baseline'])} | {pct(e['mean_candidate'])} | {pct(e['delta']).rjust(1)} "
f"[{pct(e['delta_ci95_paired_bootstrap'][0])}, {pct(e['delta_ci95_paired_bootstrap'][1])}] | {e['p_sign_flip']:.5f} ({e['sign_flip_method']}) | "
f"{e['p_holm']:.5f} | {e['decision']} |")
L += ["", "Rewritten-statement count (accepted samples whose displayed theorem differs from the reference statement; unreadable included):", ""]
for name, f in rep["fidelity"].items():
L.append(f"* `{name}`: {f['rewritten_statement_count']} of {f['accepted_samples']} accepted samples "
f"(rewritten {f['rewritten']}, unreadable {f['unreadable']}, unchecked {f['unchecked']}; {f['problems_with_rewritten_accept']} problems; {f['in_repair_rounds']} in repair rounds)")
L += ["", "Protocol tuple (class A):", "", "```json", json.dumps(rep["coprimary"]["A"]["protocol"], indent=1, default=str), "```", "",
"## Secondary (not multiplicity-corrected)", ""]
m = rep["secondary"]["mcnemar_first_32"]
if m.get("n_problems"):
L.append(f"* McNemar exact, solved@32 of the first 32 samples, class A: solved {m['solved_a']} vs {m['solved_b']}, only-base {m['only_a']}, only-cand {m['only_b']}, p = {m['p_value_exact_two_sided']:.4f}")
else:
L.append(f"* McNemar first 32: unavailable ({m.get('why')})")
for key, nm in (("mean_pass@1", "mean pass@1 (class A)"), ("proofnet_class_A", "ProofNet-test class A pass@32")):
e = rep["secondary"][key]
L.append(f"* {nm}: " + (f"{pct(e['mean_baseline'])} -> {pct(e['mean_candidate'])}, delta {pct(e['delta'])} pp [{pct(e['delta_ci95_paired_bootstrap'][0])}, {pct(e['delta_ci95_paired_bootstrap'][1])}], p = {e['p_sign_flip']:.4f}"
if e.get("available") else f"unavailable ({e.get('why')})"))
L += ["", "## Exploratory (descriptive; no decision attached)", ""]
e = rep["exploratory"]["fidelity_strict_class_A"]
if e.get("available"):
L.append(f"* class A pass@32 with rewritten accepts counted as failures: {pct(e['mean_baseline'])} -> {pct(e['mean_candidate'])}, delta {pct(e['delta'])} pp, p = {e['p_sign_flip']:.4f}")
for k, e in rep["exploratory"]["class_A_other_k"].items():
if e.get("available"):
L.append(f"* miniF2F-test class A pass@{k}: delta {pct(e['delta'])} pp [{pct(e['delta_ci95_paired_bootstrap'][0])}, {pct(e['delta_ci95_paired_bootstrap'][1])}], p = {e['p_sign_flip']:.4f}")
for name, ks in rep["exploratory"]["other_sets"].items():
for k, e in (ks.items() if "available" not in ks else []):
if e.get("available"):
L.append(f"* {name} class A pass@{k}: delta {pct(e['delta'])} pp, p = {e['p_sign_flip']:.4f}")
if "available" in ks:
L.append(f"* {name}: unavailable ({ks['why']})")
return "\n".join(L) + "\n"
def main(argv=None) -> int:
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--config", choices=("p2", "sota"), default="p2",
help="p2 = the registered P2 primary (default, unchanged); sota = the separately registered SOTA analysis (docs/PREREG_SOTA.md)")
ap.add_argument("--verify-prereg", action="store_true", help="print the pre-registration hash status and exit")
ap.add_argument("--problems", nargs="+")
ap.add_argument("--run", action="append", help="NAME:GEN[,GEN..]:VER[,VER..]")
ap.add_argument("--baseline")
ap.add_argument("--candidate")
ap.add_argument("--out-dir")
ap.add_argument("--ks", type=int, nargs="+", default=list(DEFAULT_KS))
ap.add_argument("--seed", type=int, default=0)
ap.add_argument("--bootstrap", type=int, default=10_000)
ap.add_argument("--allow-ragged-n", action="store_true")
ap.add_argument("--allow-verifier-mismatch", action="store_true")
ap.add_argument("--allow-design-mismatch", action="store_true", help="compare runs whose sampling settings or round-0 prompts differ (recorded in the report)")
ap.add_argument("--frozen-verifier-hash", help="sota: sha256 of the frozen verifier (pins + config); a run verified under anything else is not confirmatory")
ap.add_argument("--registered-hash", help="sota: the overall hash from scripts/p2/prereg_hash.py that was time-stamped")
ap.add_argument("--n-flips", type=int, default=SOTA["n_flips"], help="sota: Monte Carlo sign flips when more than 20 problems differ")
ap.add_argument("--allow-unrepaired", action="store_true", help="sota: count failed samples with no repair record as class-B failures (recorded)")
ap.add_argument("--look-log", default=os.environ.get("P2_LOOK_LOG", str(LOOK_LOG_DEFAULT)),
help="append-only log of every test-split report (default docs/evidence/test_split_looks.jsonl, or $P2_LOOK_LOG)")
a = ap.parse_args(argv)
if a.verify_prereg:
st = prereg_status()
print(json.dumps(st))
return 0 if st["status"] == "verified" else 1
if not (a.problems and a.run and a.out_dir):
ap.error("--problems, --run and --out-dir are required")
problems_by_source: dict[str, dict] = defaultdict(dict)
for p in a.problems:
for pid, rec in C.load_problems(p).items():
problems_by_source[rec["source"]][pid] = rec
statements = {pid: rec["formal_statement"] for probs in problems_by_source.values() for pid, rec in probs.items()} if a.config == "sota" else None
runs = {}
for spec in a.run:
name, gens, vers = spec.split(":")
if name in runs:
raise SystemExit(f"duplicate run name {name}")
runs[name] = load_run(gens.split(","), vers.split(","), statements)
if bool(a.baseline) != bool(a.candidate):
raise SystemExit("--baseline and --candidate go together")
for n in (a.baseline, a.candidate):
if n and n not in runs:
raise SystemExit(f"unknown run {n}")
if a.config == "sota":
if not (a.baseline and a.candidate):
raise SystemExit("--config sota needs --baseline and --candidate")
rep = build_sota_report(dict(problems_by_source), runs, a.baseline, a.candidate, a.seed, a.bootstrap, a.frozen_verifier_hash, a.registered_hash,
a.allow_verifier_mismatch, a.allow_design_mismatch, a.allow_unrepaired, a.n_flips)
md = render_sota_md(rep)
else:
rep = build_report(dict(problems_by_source), runs, a.baseline, a.candidate, tuple(a.ks), a.seed, a.bootstrap, a.allow_ragged_n, a.allow_verifier_mismatch, a.allow_design_mismatch)
md = render_md(rep)
out = Path(a.out_dir)
out.mkdir(parents=True, exist_ok=True)
(out / "report.json").write_text(json.dumps(rep, indent=1, default=str))
(out / "report.md").write_text(md)
splits = sorted({f"{src}/{r.get('split')}" for src, probs in problems_by_source.items() for r in probs.values() if r.get("split") == "test"})
if splits: # a test-split look: recorded BEFORE the numbers are printed
inputs = {"problems": {p: C.sha256_file(p) for p in a.problems},
"runs": {n: {"gen": {g: C.sha256_file(g) for g in spec.split(":")[1].split(",")}, "ver": {v: C.sha256_file(v) for v in spec.split(":")[2].split(",")}}
for n, spec in ((sp.split(":")[0], sp) for sp in a.run)}}
append_look_log(a.look_log, look_entry(a.config, splits, out / "report.json", out / "report.md", inputs))
print(md)
return 0
if __name__ == "__main__":
raise SystemExit(main())