"""Hypothesis registry: parses hypotheses/H*.md (YAML front matter), validates required fields, optionally executes the linked verification commands, and emits research_log/discovery_log.jsonl + paper/tables/hypotheses.tex. Front matter schema (all required): id, title, axis (A|B|C|D), kind (algebraic|algorithmic|sha256|methodology|formal), statement, motivation, assumptions, expected_contribution, related_work (list of bib keys), possible_counterexamples, verification_strategy, computational_budget, status (planned|running|evaluated), classification (new_verified|known_reproduced|conditional| empirical_observation|rejected|unresolved|pending), evidence (list of result files), verdict (one sentence). """ import argparse, datetime, json, pathlib, re, subprocess, sys import yaml ROOT = pathlib.Path(__file__).resolve().parents[2] REQ = ["id", "title", "axis", "kind", "statement", "motivation", "assumptions", "expected_contribution", "related_work", "possible_counterexamples", "verification_strategy", "computational_budget", "status", "classification", "evidence", "verdict"] CLASSES = {"new_verified", "known_reproduced", "conditional", "empirical_observation", "rejected", "unresolved", "pending"} LABEL = {"new_verified": "New verified result", "known_reproduced": "Known result reproduced", "conditional": "Conditional result", "empirical_observation": "Empirical observation", "rejected": "Rejected conjecture", "unresolved": "Unresolved direction", "pending": "Pending"} def load(): hyps = [] for f in sorted((ROOT / "hypotheses").glob("H*.md")): txt = f.read_text() m = re.match(r"^---\n(.*?)\n---\n(.*)$", txt, re.S) if not m: raise SystemExit(f"{f}: missing front matter") meta = yaml.safe_load(m.group(1)); meta["body"] = m.group(2).strip(); meta["file"] = f.name missing = [k for k in REQ if k not in meta] if missing: raise SystemExit(f"{f}: missing fields {missing}") if meta["classification"] not in CLASSES: raise SystemExit(f"{f}: bad classification {meta['classification']}") for ev in meta["evidence"]: if not (ROOT / ev).exists(): print(f"WARNING {f.name}: evidence file missing: {ev}") hyps.append(meta) return hyps def tex_escape(s: str) -> str: s = s.replace("π̄", r"$\bar\pi$").replace("\u0304", "").replace("^", r"\^{}").replace("~", r"\textasciitilde{}") for a, b in [("&", r"\&"), ("%", r"\%"), ("_", r"\_"), ("#", r"\#"), ("√", r"$\surd$"), ("≈", r"$\approx$"), ("≤", r"$\le$"), ("≥", r"$\ge$"), ("→", r"$\to$"), ("⟹", r"$\Rightarrow$"), ("×", r"$\times$"), ("²", r"$^2$"), ("³", r"$^3$"), ("Ω", r"$\Omega$"), ("φ", r"$\varphi$"), ("λ", r"$\lambda$"), ("β", r"$\beta$"), ("Σ", r"$\Sigma$"), ("σ", r"$\sigma$"), ("δ", r"$\delta$"), ("π", r"$\pi$"), ("ω", r"$\omega$"), ("χ", r"$\chi$"), ("α", r"$\alpha$"), ("−", "--"), ("–", "--"), ("—", "---"), ("·", r"$\cdot$"), ("∈", r"$\in$"), ("⊕", r"$\oplus$"), ("≡", r"$\equiv$"), ("∞", r"$\infty$"), ("ℓ", r"$\ell$"), ("ε", r"$\varepsilon$"), ("≠", r"$\ne$"), ("∘", r"$\circ$"), ("‖", r"$\|$"), ("⁻¹", r"$^{-1}$"), ("⁻⁴", r"$^{-4}$"), ("⁻⁵", r"$^{-5}$"), ("⁴", r"$^4$"), ("⁵", r"$^5$"), ("₀", r"$_0$"), ("₁", r"$_1$"), ("₂", r"$_2$"), ("±", r"$\pm$"), ("…", r"\ldots{}"), ("µ", r"$\mu$"), ("•", r"$\cdot$"), ("≫", r"$\gg$"), ("≪", r"$\ll$"), ("′", "'"), ("ô", "\\^o"), ("Ø", "O")]: s = s.replace(a, b) greek = {"θ": "theta", "γ": "gamma", "μ": "mu", "ν": "nu", "ρ": "rho", "τ": "tau", "κ": "kappa", "η": "eta", "ζ": "zeta", "ξ": "xi", "ψ": "psi", "Δ": "Delta", "Γ": "Gamma", "Θ": "Theta", "Λ": "Lambda", "Π": "Pi", "Φ": "Phi", "Ψ": "Psi"} for a, b in greek.items(): s = s.replace(a, "$\\" + b + "$") bad = sorted({c for c in s if ord(c) > 127 and c not in "éèàçêôûîïüöäñÉÈ"}) if bad: print("WARNING tex_escape: unmapped characters", bad) return s def main(): ap = argparse.ArgumentParser(); ap.add_argument("--run", action="store_true", help="execute verification commands in front matter `commands`") a = ap.parse_args() hyps = load() counts = {} for h in hyps: counts[h["kind"]] = counts.get(h["kind"], 0) + 1 log = ROOT / "research_log/discovery_log.jsonl" with log.open("w") as fh: for h in hyps: if a.run and h.get("commands"): for cmd in h["commands"]: print(f"[{h['id']}] $ {cmd}") subprocess.run(cmd, shell=True, check=True, cwd=ROOT) fh.write(json.dumps({"id": h["id"], "title": h["title"], "axis": h["axis"], "kind": h["kind"], "status": h["status"], "classification": h["classification"], "verdict": h["verdict"], "evidence": h["evidence"], "logged_utc": datetime.datetime.now(datetime.timezone.utc).isoformat(timespec="seconds")}) + "\n") # LaTeX table (longtable) for the paper rows = [] for h in hyps: rows.append(f"{h['id']} & {h['axis']} & {tex_escape(h['title'])} & {LABEL[h['classification']]} & {tex_escape(h['verdict'])} \\\\") tex = ["% generated by experiments/automated_discovery/registry.py — do not edit", r"\begin{longtable}{@{}p{0.8cm}p{0.5cm}p{4.2cm}p{2.6cm}p{6.4cm}@{}}", r"\caption{Hypothesis portfolio and evaluation outcome. Classification vocabulary follows the mandate (new verified result; known result independently reproduced; conditional result; empirical observation; rejected conjecture; unresolved direction).}\label{tab:hypotheses}\\", r"\toprule ID & Axis & Hypothesis & Classification & Verdict \\ \midrule \endfirsthead", r"\toprule ID & Axis & Hypothesis & Classification & Verdict \\ \midrule \endhead", r"\bottomrule \endfoot"] + rows + [r"\end{longtable}"] (ROOT / "paper/tables").mkdir(exist_ok=True, parents=True) (ROOT / "paper/tables/hypotheses.tex").write_text("\n".join(tex) + "\n") summary = {"n_hypotheses": len(hyps), "by_kind": counts, "by_classification": {c: sum(1 for h in hyps if h["classification"] == c) for c in CLASSES}} (ROOT / "results/automated_discovery").mkdir(exist_ok=True, parents=True) (ROOT / "results/automated_discovery/hypothesis_summary.json").write_text(json.dumps(summary, indent=1)) print(json.dumps(summary, indent=1)) if __name__ == "__main__": main()