#!/usr/bin/env python3 """exp16 — arm protocol driver (research/20 §D5). $0. Phases: tune — ch1-15 (tuning half): threshold sensitivity (±50% on floor/over_rep/subsume/λ), pick frozen config. test — ch16-25 (test half): frozen config, PRIMARY reported result (anti-overfit §D1). full — all 57 chunks: catastrophe screen + trade-off curve + candidate dump for canon/alias/precision@30. Usage: python run_arms.py {tune|test|full} """ from __future__ import annotations import json import sys from pathlib import Path import exp16_common as X import arms as A OUT = Path("/home/ubuntu/books/gu-zhenren/exp16") OUT.mkdir(parents=True, exist_ok=True) def build(cfg, chunks, contrast, lam_for_arms): ar = A.Arms(chunks, contrast, cfg=cfg) return {"A1": ar.arm_A1(), "A2": ar.arm_A2(lam=lam_for_arms), "A3": ar.arm_A3(lam=0.0), "A3-abl": ar.arm_A3_abl()}, ar def recall_f3(ranked, gt, chunks): rp, _ = A.recall_by(ranked, gt, chunks, top_k=None) # f>=3 = union of f>=10 and f3-9 hits = tot = 0 for e in gt: f = X.gt_occurrences(e, chunks) if f >= 3: tot += 1 cset = A.candidate_set(ranked) hits += any(s in cset for s in e.norm_surfaces) return hits / tot if tot else None, rp def phase_tune(chunks, gt, contrast): tune_ch = X.TUNING_CH sub = [c for c in chunks if c.chapter in tune_ch] gt_t = A.gt_in_chapters(gt, chunks, tune_ch) print(f"== TUNING half (ch1-15): {len(sub)} chunks, {len(gt_t)} GT terms occurring here ==\n") base = dict(A.FROZEN) results = {} def run(cfg, tag): armset, ar = build(cfg, sub, contrast, lam_for_arms=0.0) rA1_f3, rpA1 = recall_f3(armset["A1"], gt_t, sub) rA3_f3, rpA3 = recall_f3(armset["A3"], gt_t, sub) ok3, _ = A.catastrophe_screen(armset["A3"], 50) results[tag] = dict(A1_recall_f3=round(rA1_f3, 3), A3_recall_f3=round(rA3_f3, 3), A3_recall_f_lt3=rpA3["f<3"][0] and round(rpA3["f<3"][0], 3), A3_recall_overall=round(rpA3["overall"][0], 3), catastrophe=ok3, n_A3=len(armset["A3"]), pseudo_prec_A3=round(A.pseudo_precision(armset["A3"], gt_t, cfg["top_k"]), 3)) r = results[tag] print(f" {tag:<26} A1@f>=3={r['A1_recall_f3']} A3@f>=3={r['A3_recall_f3']} " f"A3@f<3={r['A3_recall_f_lt3']} A3@all={r['A3_recall_overall']} " f"cat={'ok' if r['catastrophe'] else 'FAIL'} nA3={r['n_A3']} pp={r['pseudo_prec_A3']}") print("baseline (frozen candidate defaults):") run(base, "floor3_over15_sub0.80") print("\nsensitivity — freq_floor (±50%: 2,3,4):") for fl in (2, 3, 4): c = dict(base); c["freq_floor"] = fl run(c, f"floor{fl}") print("\nsensitivity — formant_min_over_rep (±50%: 7.5,15,22.5,30):") for orp in (7.5, 15.0, 22.5, 30.0): c = dict(base); c["formant_min_over_rep"] = orp run(c, f"over_rep{orp}") print("\nsensitivity — subsume_alpha (0.5,0.8,1.0[off]):") for sa in (0.5, 0.8, 1.0): c = dict(base); c["subsume_alpha"] = sa run(c, f"subsume{sa}") print("\nsensitivity — formant_min_partners (2,3,4):") for mp in (2, 3, 4): c = dict(base); c["formant_min_partners"] = mp run(c, f"partners{mp}") json.dump(results, open(OUT / "tuning_sensitivity.json", "w"), ensure_ascii=False, indent=1) print(f"\n[written] {OUT/'tuning_sensitivity.json'}") print("\nFROZEN choice: freq_floor=3, formant_min_over_rep=15, subsume_alpha=0.80, min_partners=3, lam=0 " "(injected drafts) — stable across the sweep; see report §1.") def phase_test(chunks, gt, contrast): test_ch = X.TEST_CH sub = [c for c in chunks if c.chapter in test_ch] gt_t = A.gt_in_chapters(gt, chunks, test_ch) print(f"== TEST half (ch16-25): {len(sub)} chunks, {len(gt_t)} GT terms occurring here — FROZEN config ==\n") armset, ar = build(dict(A.FROZEN), sub, contrast, lam_for_arms=A.FROZEN["lam"]) K = A.FROZEN["top_k"] out = {} for name, ranked in armset.items(): out[name] = A.summarize(name, ranked, gt_t, sub, K) s = out[name]; rp = s["recall_proposed"] print(f"{name}: catastrophe={'PASS' if s['catastrophe_ok'] else 'FAIL'}") print(f" recall@PROPOSED overall={rp['overall'][0]} f>=10={rp['f>=10'][0]} f3-9={rp['f3-9'][0]} f<3={rp['f<3'][0]}") print(f" by type: " + " ".join(f"{t}={rp[t][0]}({rp[t][1]})" for t in ("name","title","place","term","nickname") if rp[t][0] is not None)) print(f" misses@proposed: {s['misses_proposed']}") json.dump(out, open(OUT / "test_half_result.json", "w"), ensure_ascii=False, indent=1) print(f"\n[written] {OUT/'test_half_result.json'}") def phase_full(chunks, gt, contrast): print(f"== FULL slice (57 chunks) — FROZEN config; catastrophe + trade-off + candidate dump ==\n") armset, ar = build(dict(A.FROZEN), chunks, contrast, lam_for_arms=A.FROZEN["lam"]) K = A.FROZEN["top_k"] out = {"formants": {c: ar.formants[c] for c in ar.formants}} for name, ranked in armset.items(): out[name] = A.summarize(name, ranked, gt, chunks, K) # trade-off curve for the winner A3 (recall@K vs pseudo-precision@K) A3 = armset["A3"] curve = [] for k in (30, 50, 90, 150, 250, 400, 700, len(A3)): rk, _ = A.recall_by(A3, gt, chunks, top_k=k) curve.append(dict(k=k, recall_overall=round(rk["overall"][0], 3), recall_f3=round((sum(1 for e in gt if X.gt_occurrences(e,chunks)>=3 and any(({c.src:i for i,c in enumerate(A3)}.get(s,10**9))=3)), 3), pseudo_prec=round(A.pseudo_precision(A3, gt, k), 3))) out["A3_tradeoff_curve"] = curve # dump A3 candidates (for canon/alias/precision@30 stages) + spread/dst for freq core dump = [] for c in armset["A3"]: dump.append(dict(src=c.src, score=round(c.score, 2), freq=c.freq, types=c.types, evidence=c.evidence[:4], dst_variants=c.dst_variants[:4], spread=round(c.spread, 3), from_pattern=c.from_pattern)) json.dump(dump, open(OUT / "A3_candidates_full.json", "w"), ensure_ascii=False, indent=1) json.dump(out, open(OUT / "full_slice_result.json", "w"), ensure_ascii=False, indent=1) print("A3 trade-off (recall vs pseudo-precision):") for row in curve: print(f" top-{row['k']:<5} recall_all={row['recall_overall']} recall_f>=3={row['recall_f3']} pseudo-prec={row['pseudo_prec']}") print(f"\n[written] {OUT/'full_slice_result.json'}, {OUT/'A3_candidates_full.json'} ({len(dump)} candidates)") if __name__ == "__main__": phase = sys.argv[1] if len(sys.argv) > 1 else "tune" chunks = X.load_chunks(); gt = X.load_gt(); contrast = X.Contrast() {"tune": phase_tune, "test": phase_test, "full": phase_full}[phase](chunks, gt, contrast)