"""Frontier report: alternative explanations of the weak-to-strong headroom forecast. Local re-tabulation of existing result files only: no model API, VM or evaluation. Run (from the paper directory): python3 -I evidence/analysis_alternatives.py Writes analysis_alternatives.json and analysis_alternatives.txt beside this file. Inputs (read only) LEDGER forensics/ledger.csv in the paper directory, the 195-row comparison record. In the released ledger.csv these free-text cells are empty, and every numeric column and every categorical column except team is as produced: note on L009 L036 L039 L042 L043 L052 L073 L079 L083 L086 L091 L099 L100 L101 L102 L104 L105 L107 L108 L109 L110 L112 L115 L121 L122 L125 L129 L130 L138 L139 L140 L148 L150 L165 L166 L168 L176 L178 L193; settings_parity on L036 L037 L039 L052 L086 L102 L108 L110 L139 L140 L142 L143 L148 L193; instrument on L169 L170 L171 L173; contrast on L169 L170 L171 L172 L173. Agent and team names are replaced by neutral IDs or plain study names in the team column and in the instrument, contrast, settings_parity and note cells of L030 L034 L038 L044 L045 L052 L082 L103 L107-L110 L133 L134 L151 L152 L184-L191 L193. Every source cell says in plain words where the comparison was recorded (internal document names, paths and commit ids removed), as do the note of L068 and the instrument of L146. No number this script prints reads those cells. Selection rule and the models reported here: as in the synthesis paper's re-test of the law (its CRIT, prep, mean, nll, fit and lpd). headroom_forecast.py is NOT imported, because importing it would rerun it and overwrite its outputs. Sections H headline reproduction (weak 6 -> Opus 15, own tau): BF vs zero / constant / capped, MAE A family overlap: the weak-fitted forecast scored on Opus rows whose component family has no weaker-model row B generic ceiling: predicted lifts on the Amazon-cents row (L166) and the crash pair (L003/L004) under the law, capped, smooth cap, constant and the matched fraction C selection: leave-one-weak-row-out refits (Haiku-only = drop L054) and every weak subset of size >= 4 Z h-permutation test: weak-fitted law scored on the Opus rows with h shuffled among them """ import io import itertools import json import math import os import sys import numpy as np import pandas as pd from scipy import optimize, stats HERE = os.path.dirname(os.path.abspath(__file__)) OUT = os.path.join(HERE, "analysis_alternatives.json") TXT = os.path.join(HERE, "analysis_alternatives.txt") _buf = io.StringIO() def say(*a): s = " ".join(str(x) for x in a) print(s) _buf.write(s + "\n") # ============================================================ as in the re-test code (selection + models) LEDGER = os.path.join(os.path.dirname(HERE), "forensics", "ledger.csv") L = pd.read_csv(LEDGER, keep_default_na=False) for c in ["est", "lo", "hi", "p", "n", "se", "z", "ours", "ctrl", "mx", "headroom_pts"]: L[c] = pd.to_numeric(L[c], errors="coerce") LAYER = {"layer_vs_bare", "layer_vs_prior", "layer_vs_rival", "carried_w2s", "carried_s2w", "carried_self", "system_vs_single", "trained_vs_frontier"} WEAK = {"haiku", "qwen", "gpt-oss-20b", "opus-4.8"} CRIT = [ ("layer comparison", lambda d: d.ctype.isin(LAYER)), ("not structural / not harness bug", lambda d: ~d["flags"].str.contains("structural") & ~d["flags"].str.contains("harness_bug")), ("no-harness control", lambda d: ~d.ctype.isin(["layer_vs_rival", "system_vs_single"])), ("bounded metric with control score", lambda d: d.ctrl.notna() & d.mx.notna() & d.headroom_pts.notna()), ("SE recorded", lambda d: d.se.notna()), ("Opus 5.5", lambda d: d.model == "opus-5.5"), ] mask = pd.Series(True, index=L.index) for _, f in CRIT: mask &= f(L) def prep(d): d = d.copy() d["y"] = d.est * 100 / d.mx d["s"] = d.se * 100 / d.mx d["h"] = d.headroom_pts return d A = prep(L[mask]) keep_all_but_model = pd.Series(True, index=L.index) for _, f in CRIT[:-1]: keep_all_but_model &= f(L) W = prep(L[keep_all_but_model & L.model.isin(WEAK)]) def mean(kind, th, h): if kind == "M0": return np.zeros_like(h) if kind == "M1": return np.full_like(h, th[0]) if kind == "M2": return th[0] * h if kind == "M2b": return th[0] * h - th[1] if kind == "M3": return np.minimum(th[0], h) if kind == "M5": c = math.exp(th[0]) return c * (1 - np.exp(-h / c)) raise ValueError(kind) NPAR = {"M0": 0, "M1": 1, "M2": 1, "M2b": 2, "M3": 1, "M5": 1} STARTS = {"M0": [[]], "M1": [[0.0], [5.0]], "M2": [[0.3], [0.6]], "M2b": [[0.3, 0.0], [0.6, 1.0]], "M3": [[3.0], [8.0], [0.0]], "M5": [[math.log(3)], [math.log(10)], [math.log(30)]]} def nll(p, d, kind, tau_fixed=None): th, tau = (p, tau_fixed) if tau_fixed is not None else (p[:-1], math.exp(p[-1])) sd = np.sqrt(d.s.values ** 2 + tau ** 2) return -stats.norm.logpdf(d.y.values, mean(kind, th, d.h.values), sd).sum() def fit(d, kind, tau_fixed=None): best = None for st in STARTS[kind]: for lt in ([0.0, 1.5, 2.5] if tau_fixed is None else [None]): x0 = list(st) + ([lt] if lt is not None else []) if not x0: continue r = optimize.minimize(nll, x0, args=(d, kind, tau_fixed), method="Nelder-Mead", options=dict(maxiter=40000, xatol=1e-9, fatol=1e-11)) if best is None or r.fun < best.fun: best = r if best is None: return [], tau_fixed, nll([], d, kind, tau_fixed) th = list(best.x[:-1]) if tau_fixed is None else list(best.x) tau = math.exp(best.x[-1]) if tau_fixed is None else tau_fixed return th, tau, float(best.fun) def lpd(d, kind, th, tau): return stats.norm.logpdf(d.y.values, mean(kind, th, d.h.values), np.sqrt(d.s.values ** 2 + tau ** 2)) # ============================================================ end of the re-test's code KINDS = ["M0", "M1", "M2", "M3", "M5"] NAME = {"M0": "zero", "M1": "constant", "M2": "law k*h", "M3": "capped min(c,h)", "M5": "smooth cap"} RES = {} assert sorted(A.id) == sorted("L003 L004 L163 L174 L057 L099 L166 L147 L124 L126 L136 L010 L011 L012 L016".split()) assert sorted(W.id) == sorted("L001 L002 L006 L007 L058 L054".split()) LINE = {r.id: i + 2 for i, r in L.reset_index(drop=True).iterrows()} # ledger.csv line of each row (header = line 1) def fit_all(src): return {k: fit(src, k) for k in KINDS} def score(fits, dst, tau_w=None): """Bayes factors of M2 against each model and MAE, on destination rows. Own tau unless tau_w is given.""" out = {} lp = {} for k, (th, tau, _) in fits.items(): t = tau if tau_w is None else tau_w lp[k] = float(lpd(dst, k, th, t).sum()) mu = mean(k, th, dst.h.values) out[k] = dict(params=[round(float(x), 4) for x in th], tau=round(tau, 3), lpd=round(lp[k], 3), mae=round(float(np.mean(np.abs(dst.y.values - mu))), 3), rmse=round(float(np.sqrt(np.mean((dst.y.values - mu) ** 2))), 3)) bf = {k: round(math.exp(lp["M2"] - lp[k]), 3) for k in fits if k != "M2"} return dict(n=int(len(dst)), rows=dst.id.tolist(), models=out, bf_law_vs=bf) def show(label, sc): m = sc["models"] b = sc["bf_law_vs"] say(f" {label:58s} n={sc['n']:2d} BF law vs zero {b['M0']:9.2f} const {b['M1']:7.2f} capped {b['M3']:6.2f} " f"smooth {b['M5']:5.2f} | MAE law {m['M2']['mae']:5.2f} const {m['M1']['mae']:5.2f} " f"capped {m['M3']['mae']:5.2f} zero {m['M0']['mae']:5.2f}") # ============================================================ H. headline say("=" * 110) say("H. HEADLINE REPRODUCTION: models fitted on the 6 weaker rows (own tau), scored on the 15 Opus 5.5 rows") FW = fit_all(W) for k in KINDS: th, tau, _ = FW[k] say(f" {k} {NAME[k]:18s} params {[round(float(x), 4) for x in th]} tau {tau:.3f}") head = score(FW, A) show("weak6 -> Opus15 (own tau)", head) head_sh = score(FW, A, tau_w=FW["M2"][1]) show("weak6 -> Opus15 (shared tau_w = law's weak tau)", head_sh) RES["headline"] = dict(own_tau=head, shared_tau_w=head_sh) assert abs(head["bf_law_vs"]["M0"] - 186.2) < 0.5 and abs(head["bf_law_vs"]["M1"] - 9.82) < 0.05 assert abs(head["bf_law_vs"]["M3"] - 5.58) < 0.05 and abs(head["models"]["M2"]["mae"] - 3.304) < 0.002 # ============================================================ A. family overlap say("\n" + "=" * 110) say("A. FAMILY OVERLAP: does the forecast need the component families shared with the weaker rows?") FAM = { # weaker rows "L001": "store compiled by Opus 5.5, carried down to Haiku", "L002": "store compiled by Opus 5.5, carried down to Haiku", "L058": "online store (Haiku's own)", "L006": "crash recovery (lineage)", "L007": "crash recovery (lineage)", "L054": "handoff brief a5 (Opus 5.5 -> Opus 4.8)", # Opus rows "L003": "crash recovery (lineage)", "L004": "crash recovery (lineage)", "L163": "crash recovery layer + API-change sensor", "L174": "crash recovery (durable layer)", "L057": "handoff brief a5 (Haiku -> Opus 5.5)", "L099": "API-change sensor", "L166": "API-change sensor", "L147": "claim learner", "L124": "claim learner", "L126": "claim learner", "L136": "claim learner", "L010": "store built by Opus 4.8, carried up", "L011": "store built by Opus 4.8, carried up", "L012": "store built by Opus 4.8, carried up", "L016": "store built by Opus 4.8, carried up", } for rid in sorted(FAM, key=lambda x: (x not in set(W.id), x)): r = L[L.id == rid].iloc[0] say(f" {rid} line {LINE[rid]:3d} {r.model:9s} {FAM[rid]:46s} | contrast: {r.contrast} | instrument: {r.instrument}") SUB = { "novel: sensor + claim learner + Opus-4.8 store (no weaker row of the family)": "L099 L166 L147 L124 L126 L136 L010 L011 L012 L016", "novel strict: sensor + claim learner (no crash, store or handoff component)": "L099 L166 L147 L124 L126 L136", "novel + handoff brief L057 (the ten novel rows and L057)": "L099 L166 L147 L124 L126 L136 L010 L011 L012 L016 L057", "shared: crash recovery + handoff brief (family has a weaker row)": "L003 L004 L163 L174 L057", "shared crash recovery only": "L003 L004 L163 L174", } RES["family_overlap"] = dict(family=FAM, ledger_line=LINE, subsets={}) for lab, ids in SUB.items(): ids = ids.split() D = A[A.id.isin(ids)] sc = score(FW, D) sc_sh = score(FW, D, tau_w=FW["M2"][1]) show(lab[:58], sc) show(" same, shared tau_w", sc_sh) RES["family_overlap"]["subsets"][lab] = dict(own_tau=sc, shared_tau_w=sc_sh) # per-row log predictive advantage of the law (own tau), so the subset BFs can be read off pr = [] for _, r in A.iterrows(): d = A[A.id == r.id] row = dict(id=r.id, family=FAM[r.id], h=round(r.h, 2), y=round(r.y, 2), s=round(r.s, 2)) for k in KINDS: th, tau, _ = FW[k] row[f"mu_{k}"] = round(float(mean(k, th, d.h.values)[0]), 3) row[f"lpd_{k}"] = round(float(lpd(d, k, th, tau)[0]), 4) for k in ["M0", "M1", "M3"]: row[f"law_minus_{k}"] = round(row["lpd_M2"] - row[f"lpd_{k}"], 4) pr.append(row) RES["family_overlap"]["per_row"] = pr say(" per-row log BF of the law (own tau): id family h y | vs zero, const, capped") for row in sorted(pr, key=lambda x: x["h"]): say(f" {row['id']} {row['family'][:34]:34s} h {row['h']:5.1f} y {row['y']:6.2f} | {row['law_minus_M0']:6.2f} " f"{row['law_minus_M1']:6.2f} {row['law_minus_M3']:6.2f}") # ============================================================ B. generic ceiling: matched fraction say("\n" + "=" * 110) say("B. GENERIC CEILING vs MATCHED FRACTION") FO = fit_all(A) # Opus-15 fits pred_models = {"law (Opus k)": ("M2", FO["M2"][0]), "law (weak k)": ("M2", FW["M2"][0]), "capped (Opus c)": ("M3", FO["M3"][0]), "smooth cap (Opus c)": ("M5", FO["M5"][0]), "constant (Opus c)": ("M1", FO["M1"][0]), "constant (weak c)": ("M1", FW["M1"][0])} tau_opus = {k: FO[k][1] for k in KINDS} # Amazon cents (U7, 212 Amazon test tasks): counts from the world-sensor review N7 = 212 clean, undoc, unanch, v3 = 200, 171, 199, 197 h7 = 100 * (N7 - undoc) / N7 cause = 100 * (clean - undoc) / N7 cases = { "L166 Amazon cents (h from U7 counts)": dict(h=h7, matched=cause, obs={"unanchored-change flags (ledger L166, 199/212)": 100 * (unanch - undoc) / N7, "full v3 notes (197/212)": 100 * (v3 - undoc) / N7}, s=float(A[A.id == "L166"].s.iloc[0]), note="matched fraction = clean - drifted = 29/212; phi = 29/41"), # crash pair: same lineage arm (85/87), controls bare 80/87 and bare told of the crash 85/87 (ledger L003/L004) "L004 crash, lineage vs bare (87 tasks)": dict(h=8.0, matched=100 * (85 - 80) / 87, obs={"lineage 85/87": 100 * 5 / 87}, s=float(A[A.id == "L004"].s.iloc[0]), note="matched = told-bare - bare: failures the crash notice removes"), "L003 crash, lineage vs told bare (87 tasks)": dict(h=2.3, matched=0.0, obs={"lineage 85/87": 0.0}, s=float(A[A.id == "L003"].s.iloc[0]), note="told control already has the crash cause supplied"), } RES["ceiling"] = dict(cases={}, opus_fits={k: dict(params=[float(x) for x in FO[k][0]], tau=FO[k][1]) for k in KINDS}) for lab, c in cases.items(): say(f" {lab}: h {c['h']:.2f}, matched-fraction prediction {c['matched']:.2f}, s {c['s']:.2f} ({c['note']})") preds = {} for pm, (k, th) in pred_models.items(): preds[pm] = float(mean(k, th, np.array([c["h"]]))[0]) preds["matched fraction (phi*h, p = 1)"] = c["matched"] obs_first = list(c["obs"].values())[0] rows = {} for pm, mu in preds.items(): z = (obs_first - mu) / c["s"] rows[pm] = dict(pred_lift=round(mu, 3), pred_score=round(100 - c["h"] + mu, 2), z_obs=round(z, 3)) say(f" {pm:38s} predicted lift {mu:6.2f} -> score {100 - c['h'] + mu:6.2f} (obs - pred)/s {z:6.2f}") for ol, ov in c["obs"].items(): say(f" observed {ol}: lift {ov:.2f}") RES["ceiling"]["cases"][lab] = dict(h=c["h"], s=c["s"], matched=c["matched"], observed=c["obs"], predictions=rows, note=c["note"]) # combined likelihood ratio of the matched fraction against each scale model over the three rows (s only, and s + Opus tau) say(" Likelihood ratio, matched fraction vs each model, over L166 + L004 + L003 (independent-normal approximation;") say(" L003 and L004 share the lineage arm, so they are not independent):") RES["ceiling"]["lr_matched_vs"] = {} for pm, (k, th) in pred_models.items(): l_s = l_tau = 0.0 for lab, c in cases.items(): o = list(c["obs"].values())[0] mu = float(mean(k, th, np.array([c["h"]]))[0]) l_s += stats.norm.logpdf(o, c["matched"], c["s"]) - stats.norm.logpdf(o, mu, c["s"]) l_tau += (stats.norm.logpdf(o, c["matched"], c["s"]) - stats.norm.logpdf(o, mu, math.sqrt(c["s"] ** 2 + tau_opus[k] ** 2))) l166 = cases["L166 Amazon cents (h from U7 counts)"] o = list(l166["obs"].values())[0] mu = float(mean(k, th, np.array([l166["h"]]))[0]) l1 = stats.norm.logpdf(o, l166["matched"], l166["s"]) - stats.norm.logpdf(o, mu, l166["s"]) RES["ceiling"]["lr_matched_vs"][pm] = dict(L166_only=round(math.exp(l1), 3), three_rows_s=round(math.exp(l_s), 3), three_rows_model_tau=round(math.exp(l_tau), 3)) say(f" vs {pm:34s} L166 alone {math.exp(l1):5.2f} three rows {math.exp(l_s):5.2f} " f"(model gets its Opus tau: {math.exp(l_tau):5.2f})") say(f" Largest h among the 15 Opus rows: {A.h.max():.1f}; among the 6 weaker rows: {W.h.max():.1f}.") RES["ceiling"]["max_h"] = dict(max_h_opus=float(A.h.max()), max_h_weak=float(W.h.max())) RES["ceiling"]["forecast_bf_law_vs_smoothcap_own_tau"] = head["bf_law_vs"]["M5"] # ============================================================ C. selection: leave-one-weak-row-out say("\n" + "=" * 110) say("C. SELECTION: refit on weaker-row subsets, rescore the 15 Opus rows (own tau)") RES["selection"] = dict(loo={}, subsets=[]) for rid in W.id: Wd = W[W.id != rid] F = fit_all(Wd) sc = score(F, A) lab = f"drop {rid} ({L[L.id == rid].model.iloc[0]}, h {W[W.id == rid].h.iloc[0]:.1f}, y {W[W.id == rid].y.iloc[0]:.2f})" if rid == "L054": lab += " = Haiku-only fit (5 rows)" say(f" {lab}: k {F['M2'][0][0]:.3f} tau {F['M2'][1]:.2f}; constant c {F['M1'][0][0]:.2f}; capped c {F['M3'][0][0]:.2f}") show("", sc) RES["selection"]["loo"][rid] = dict(label=lab, k=F["M2"][0][0], tau=F["M2"][1], score=sc, shared_tau_w=score(F, A, tau_w=F["M2"][1])["bf_law_vs"]) bfz = [RES["selection"]["loo"][r]["score"]["bf_law_vs"]["M0"] for r in W.id] bfc = [RES["selection"]["loo"][r]["score"]["bf_law_vs"]["M1"] for r in W.id] bfk = [RES["selection"]["loo"][r]["score"]["bf_law_vs"]["M3"] for r in W.id] # cross of A and C: the leave-one-out fits scored on the novel-family rows only NOV = SUB["novel: sensor + claim learner + Opus-4.8 store (no weaker row of the family)"].split() STR = SUB["novel strict: sensor + claim learner (no crash, store or handoff component)"].split() for rid in W.id: F = fit_all(W[W.id != rid]) a10 = score(F, A[A.id.isin(NOV)]) a6 = score(F, A[A.id.isin(STR)]) RES["selection"]["loo"][rid]["novel10"] = a10 RES["selection"]["loo"][rid]["novel6"] = a6 show(f"drop {rid}, scored on the 10 novel-family rows", a10) show(f"drop {rid}, scored on the 6 strict-novel rows", a6) say(f" range over the 6 leave-one-out fits: BF vs zero {min(bfz):.1f}-{max(bfz):.1f}, vs constant {min(bfc):.2f}-{max(bfc):.2f}, " f"vs capped {min(bfk):.2f}-{max(bfk):.2f}") # every subset of the weaker rows with >= 4 rows for m in (4, 5, 6): for comb in itertools.combinations(list(W.id), m): Wd = W[W.id.isin(comb)] F = {k: fit(Wd, k) for k in ["M0", "M1", "M2", "M3"]} sc = score(F, A) RES["selection"]["subsets"].append(dict(rows=list(comb), k=round(F["M2"][0][0], 4), tau=round(F["M2"][1], 3), bf_zero=sc["bf_law_vs"]["M0"], bf_const=sc["bf_law_vs"]["M1"], bf_capped=sc["bf_law_vs"]["M3"], mae_law=sc["models"]["M2"]["mae"], mae_const=sc["models"]["M1"]["mae"], mae_capped=sc["models"]["M3"]["mae"])) S = pd.DataFrame(RES["selection"]["subsets"]) say(f" all {len(S)} weaker-row subsets of size 4-6: BF vs zero > 1 in {(S.bf_zero > 1).sum()}, vs constant > 1 in " f"{(S.bf_const > 1).sum()}, vs capped > 1 in {(S.bf_capped > 1).sum()}; law MAE < constant MAE in " f"{(S.mae_law < S.mae_const).sum()}, < capped MAE in {(S.mae_law < S.mae_capped).sum()}") say(f" BF vs zero min/median/max {S.bf_zero.min():.2f} / {S.bf_zero.median():.2f} / {S.bf_zero.max():.1f}; " f"vs constant {S.bf_const.min():.2f} / {S.bf_const.median():.2f} / {S.bf_const.max():.1f}; " f"vs capped {S.bf_capped.min():.2f} / {S.bf_capped.median():.2f} / {S.bf_capped.max():.1f}; k {S.k.min():.3f}-{S.k.max():.3f}") worst = S.sort_values("bf_const").head(3) say(" weakest three subsets (by BF vs constant):") for _, r in worst.iterrows(): say(f" {r.rows} k {r.k:.3f} tau {r.tau:.2f} BF zero {r.bf_zero:.2f} const {r.bf_const:.2f} capped {r.bf_capped:.2f} " f"MAE law {r.mae_law:.2f} const {r.mae_const:.2f}") nocrash = S[S.rows.apply(lambda r: sorted(r) == ["L001", "L002", "L054", "L058"])].iloc[0] say(f" weak fit WITHOUT the crash-recovery rows L006/L007 (L001 L002 L054 L058): k {nocrash.k:.3f} tau {nocrash.tau:.2f}; on the " f"15 Opus rows BF zero {nocrash.bf_zero:.2f} const {nocrash.bf_const:.2f} capped {nocrash.bf_capped:.2f}; MAE law " f"{nocrash.mae_law:.2f} const {nocrash.mae_const:.2f} capped {nocrash.mae_capped:.2f}") Fnc = {k: fit(W[W.id.isin(["L001", "L002", "L054", "L058"])], k) for k in ["M0", "M1", "M2", "M3", "M5"]} scnc = score(Fnc, A[A.id.isin(["L003", "L004", "L163", "L174"])]) show("no-crash weak fit -> the 4 Opus crash-recovery rows", scnc) RES["selection"]["weak_without_crash_rows"] = dict(all15=nocrash.to_dict(), opus_crash_rows=scnc) RES["selection"]["subset_summary"] = dict(n=int(len(S)), bf_zero_gt1=int((S.bf_zero > 1).sum()), bf_const_gt1=int((S.bf_const > 1).sum()), bf_capped_gt1=int((S.bf_capped > 1).sum()), mae_law_lt_const=int((S.mae_law < S.mae_const).sum()), mae_law_lt_capped=int((S.mae_law < S.mae_capped).sum()), bf_zero_min=float(S.bf_zero.min()), bf_const_min=float(S.bf_const.min()), bf_capped_min=float(S.bf_capped.min()), k_range=[float(S.k.min()), float(S.k.max())]) # ============================================================ Z. h-permutation (is it the pairing of h with Delta?) say("\n" + "=" * 110) say("Z. h-PERMUTATION: weak-fitted law scored on the Opus rows with h shuffled among the 15 rows") rng = np.random.default_rng(20261030) obs_lp = float(lpd(A, "M2", FW["M2"][0], FW["M2"][1]).sum()) obs_bfc = head["bf_law_vs"]["M1"] lp_const = float(lpd(A, "M1", FW["M1"][0], FW["M1"][1]).sum()) NPERM = 20000 ge = gec = 0 for _ in range(NPERM): P = A.copy() P["h"] = rng.permutation(A.h.values) lp = float(lpd(P, "M2", FW["M2"][0], FW["M2"][1]).sum()) ge += lp >= obs_lp - 1e-12 gec += (lp - lp_const) >= (obs_lp - lp_const) - 1e-12 p_perm = (ge + 1) / (NPERM + 1) say(f" observed law lpd {obs_lp:.3f}; permutations with lpd >= observed: {ge}/{NPERM} -> p = {p_perm:.4f}") RES["permutation"] = dict(n_perm=NPERM, seed=20261030, law_lpd=obs_lp, p_law=p_perm) with open(OUT, "w") as fh: json.dump(RES, fh, indent=1, default=float) with open(TXT, "w") as fh: fh.write(_buf.getvalue()) say("\nwrote", os.path.basename(OUT), "and", os.path.basename(TXT))