| |
| """Deterministic reproduction for OpenReview hgMZraPlSv. |
| |
| This audit combines exhaustive finite semantics with exact execution of the |
| authors' benchmark exporter and a byte-level audit of the initial-release |
| baseline records. Lean itself is intentionally not claimed as executed: the |
| Lean/Pantograph implementation and paper proofs are pinned and structurally |
| audited, while every numerical or finite claim below is recomputed here. |
| """ |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import hashlib |
| import importlib.util |
| import json |
| import math |
| import tarfile |
| import tempfile |
| from collections import defaultdict |
| from pathlib import Path |
|
|
|
|
| PAPER_ID = "hgMZraPlSv" |
| CURRENT_COMMIT = "3e8bafd18a080fe01263ff93b9c3db78d993e1a1" |
| INITIAL_COMMIT = "39489d1f0c32b521845429e1cb26c48639d8f823" |
| EXPECTED = { |
| "paper_pdf": "bba3d0293dda50476bd07d7dab987d4ed5ded4fa21132837f35c4feff72403e3", |
| "paper_source": "b08c84c35b90d0128fc6ff512b60b07ead29ef6de847c8834948df660b00034a", |
| "current_archive": "b144a1051b840f4b7f59428151ba8bf6f27e7665a3b9f950d165c39055ef2315", |
| "initial_archive": "cef6b9340b797355ab6e3fb425ee02a1e6c2cc7b76eff6fc77d79bf6e99753fe", |
| } |
| BENCHMARKS = { |
| "formal_math500": ("FormalMath500", 387), |
| "minif2f_solving": ("MiniF2FSolving", 375), |
| "putnam_solving": ("PutnamBenchSolving", 324), |
| } |
| REQUIRED_SCHEMA = { |
| "conclusions", |
| "formal_answer", |
| "formal_answer_type", |
| "header", |
| "hypotheses", |
| "independent_variables", |
| "informal_answer", |
| "informal_problem", |
| "metainfo", |
| } |
|
|
|
|
| def sha256(path: Path) -> str: |
| digest = hashlib.sha256() |
| with path.open("rb") as handle: |
| for block in iter(lambda: handle.read(1 << 20), b""): |
| digest.update(block) |
| return digest.hexdigest() |
|
|
|
|
| def dump(path: Path, value: object) -> None: |
| path.parent.mkdir(parents=True, exist_ok=True) |
| path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") |
|
|
|
|
| def extract(archive: Path, destination: Path) -> Path: |
| with tarfile.open(archive, "r:gz") as handle: |
| |
| |
| |
| members = [member for member in handle.getmembers() if member.isfile() or member.isdir()] |
| handle.extractall(destination, members=members, filter="data") |
| roots = [p for p in destination.iterdir() if p.is_dir()] |
| if len(roots) != 1: |
| raise RuntimeError(f"expected one archive root in {archive}, got {roots}") |
| return roots[0] |
|
|
|
|
| def assert_tokens(text: str, tokens: list[str], label: str) -> dict: |
| missing = [token for token in tokens if token not in text] |
| if missing: |
| raise RuntimeError(f"{label} lacks pinned tokens: {missing}") |
| return {"artifact": label, "required_tokens": len(tokens), "missing": missing} |
|
|
|
|
| def deterministic_mdp() -> dict: |
| """Exhaust all 256 hole/goal states under eight deterministic actions.""" |
|
|
| def transition(state: tuple[int, int], action: int) -> tuple[int, int]: |
| holes, goals = state |
| if action < 4: |
| return holes & ~(1 << action), goals |
| return holes, goals & ~(1 << (action - 4)) |
|
|
| states = [(holes, goals) for holes in range(16) for goals in range(16)] |
| actions = list(range(8)) |
| comparisons = 0 |
| terminal_rewards = 0 |
| for state in states: |
| for action in actions: |
| expected = transition(state, action) |
| for _ in range(4): |
| comparisons += 1 |
| if transition(state, action) != expected: |
| raise RuntimeError("nominal transition is not deterministic") |
| terminal_rewards += int(expected == (0, 0)) |
|
|
| calls = defaultdict(int) |
|
|
| def hidden_history_mutant(state: tuple[int, int], action: int) -> tuple[int, int]: |
| calls[(state, action)] += 1 |
| nominal = transition(state, action) |
| if calls[(state, action)] % 2: |
| return nominal |
| return nominal[0] ^ 1, nominal[1] |
|
|
| mutant_detected = hidden_history_mutant((7, 11), 2) != hidden_history_mutant((7, 11), 2) |
| return { |
| "states": len(states), |
| "actions": len(actions), |
| "repeated_transition_comparisons": comparisons, |
| "deterministic_violations": 0, |
| "terminal_reward_hits": terminal_rewards, |
| "hidden_history_mutant_detected": mutant_detected, |
| } |
|
|
|
|
| def predicate_semantics() -> dict: |
| """Exhaust D-FPS forward/backward implications over domains 0..7.""" |
|
|
| total = forward = backward = iff = forward_only = 0 |
| by_n = [] |
| for n in range(8): |
| limit = 1 << n |
| row = {"n": n, "pairs": 0, "forward_complete": 0, "backward_sound": 0, "iff": 0} |
| for truth in range(limit): |
| for answer in range(limit): |
| row["pairs"] += 1 |
| total += 1 |
| complete = truth & ~answer == 0 |
| sound = answer & ~truth == 0 |
| forward += int(complete) |
| backward += int(sound) |
| iff += int(complete and sound) |
| forward_only += int(complete and not sound) |
| row["forward_complete"] += int(complete) |
| row["backward_sound"] += int(sound) |
| row["iff"] += int(complete and sound) |
| by_n.append(row) |
| return { |
| "domains": list(range(8)), |
| "predicate_pairs": total, |
| "forward_complete_pairs": forward, |
| "backward_sound_pairs": backward, |
| "complete_and_sound_pairs": iff, |
| "forward_only_complete_but_unsound": forward_only, |
| "expected_closed_forms": { |
| "pairs": sum(4**n for n in range(8)), |
| "one_direction": sum(3**n for n in range(8)), |
| "iff": sum(2**n for n in range(8)), |
| }, |
| "by_domain": by_n, |
| } |
|
|
|
|
| def fps_soundness() -> dict: |
| total = accepted = rejected = violations = 0 |
| mutant_false_accepts = 0 |
| by_n = [] |
| for n in range(1, 10): |
| row = {"n": n, "answer_predicate_cases": 0, "accepted": 0, "rejected": 0} |
| for predicate in range(1 << n): |
| for answer in range(n): |
| truth = bool(predicate & (1 << answer)) |
| proof_exists = truth |
| total += 1 |
| accepted += int(proof_exists) |
| rejected += int(not proof_exists) |
| violations += int(proof_exists and not truth) |
| mutant_false_accepts += int(not truth) |
| row["answer_predicate_cases"] += 1 |
| row["accepted"] += int(proof_exists) |
| row["rejected"] += int(not proof_exists) |
| by_n.append(row) |
| return { |
| "domains": list(range(1, 10)), |
| "answer_predicate_cases": total, |
| "accepted_true_memberships": accepted, |
| "rejected_false_memberships": rejected, |
| "soundness_violations": violations, |
| "proof_field_omission_mutant_false_accepts": mutant_false_accepts, |
| "by_domain": by_n, |
| } |
|
|
|
|
| def load_jsonl(path: Path) -> list[dict]: |
| return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line.strip()] |
|
|
|
|
| def benchmark_export_audit(current: Path, scratch: Path) -> dict: |
| data_root = current / "data" |
| project = data_root / "formal_problem_solving" |
| exporter_path = current / "common/export_fps_benchmark.py" |
| spec = importlib.util.spec_from_file_location("official_export_fps_benchmark", exporter_path) |
| if spec is None or spec.loader is None: |
| raise RuntimeError("cannot import official exporter") |
| exporter = importlib.util.module_from_spec(spec) |
| spec.loader.exec_module(exporter) |
|
|
| records_total = 0 |
| exact_exports = 0 |
| schemas_ok = 0 |
| unique_total = 0 |
| details = [] |
| all_fingerprints: set[str] = set() |
| for key, (lean_dir, expected_count) in BENCHMARKS.items(): |
| records = load_jsonl(data_root / f"{key}.jsonl") |
| if len(records) != expected_count: |
| raise RuntimeError(f"{key} count drift: {len(records)}") |
| fingerprints = [] |
| for record in records: |
| schemas_ok += int(REQUIRED_SCHEMA.issubset(record)) |
| fingerprint = hashlib.sha256( |
| json.dumps( |
| {name: record[name] for name in sorted(REQUIRED_SCHEMA)}, |
| sort_keys=True, |
| ensure_ascii=False, |
| ).encode() |
| ).hexdigest() |
| fingerprints.append(fingerprint) |
| all_fingerprints.add(fingerprint) |
| if len(set(fingerprints)) != expected_count: |
| raise RuntimeError(f"duplicate benchmark record in {key}") |
| unique_total += len(set(fingerprints)) |
| exporter.export_benchmark(data_root, scratch, key) |
| mismatches = [] |
| for index in range(1, expected_count + 1): |
| produced = scratch / "FormalProblemSolving" / lean_dir / f"{index}.lean" |
| official = project / "FormalProblemSolving" / lean_dir / f"{index}.lean" |
| if produced.read_bytes() == official.read_bytes(): |
| exact_exports += 1 |
| else: |
| mismatches.append(index) |
| records_total += expected_count |
| details.append({"benchmark": key, "records": expected_count, "byte_mismatches": mismatches}) |
|
|
| mutation_target = scratch / "FormalProblemSolving" / "FormalMath500" / "1.lean" |
| official_target = project / "FormalProblemSolving" / "FormalMath500" / "1.lean" |
| mutated = mutation_target.read_bytes() + b"-- destructive mutation\n" |
| export_mutant_detected = hashlib.sha256(mutated).digest() != hashlib.sha256(official_target.read_bytes()).digest() |
| return { |
| "benchmark_counts": {key: count for key, (_, count) in BENCHMARKS.items()}, |
| "records_total": records_total, |
| "records_with_required_schema": schemas_ok, |
| "unique_within_benchmarks": unique_total, |
| "unique_global_fingerprints": len(all_fingerprints), |
| "lean_files_regenerated_byte_exact": exact_exports, |
| "lean_files_expected": records_total, |
| "export_mutant_detected": export_mutant_detected, |
| "details": details, |
| } |
|
|
|
|
| def baseline_table_audit(initial: Path) -> dict: |
| files = sorted((initial / "baseline_results").glob("**/solving.*.jsonl")) |
| if len(files) != 18: |
| raise RuntimeError(f"expected 18 initial-release baseline files, got {len(files)}") |
| rows_total = 0 |
| methods = [] |
| maxima: dict[str, dict] = {} |
| submission_maxima: dict[str, int] = defaultdict(int) |
| for path in files: |
| benchmark = path.name.removeprefix("solving.").removesuffix(".jsonl") |
| denominator = BENCHMARKS[benchmark][1] |
| rows = load_jsonl(path) |
| solved = sum(bool(row.get("submission")) and row.get("eq_proof") is not None for row in rows) |
| submitted = sum(bool(row.get("submission")) for row in rows) |
| rows_total += len(rows) |
| method = str(path.parent.relative_to(initial / "baseline_results")) |
| rate = 100.0 * solved / denominator |
| methods.append( |
| { |
| "method": method, |
| "benchmark": benchmark, |
| "records_present": len(rows), |
| "denominator": denominator, |
| "solved": solved, |
| "solved_percent": round(rate, 12), |
| "submitted_without_equivalence": submitted - solved, |
| } |
| ) |
| if benchmark not in maxima or solved > maxima[benchmark]["solved"]: |
| maxima[benchmark] = {"method": method, "solved": solved, "denominator": denominator} |
| submission_maxima[benchmark] = max(submission_maxima[benchmark], submitted) |
| for benchmark, row in maxima.items(): |
| row["percent"] = round(100.0 * row["solved"] / row["denominator"], 2) |
| return { |
| "files": len(files), |
| "method_problem_records_present": rows_total, |
| "method_problem_slots": 6 * sum(count for _, count in BENCHMARKS.values()), |
| "missing_records_counted_unsolved": 6 * sum(count for _, count in BENCHMARKS.values()) - rows_total, |
| "methods": methods, |
| "maxima": maxima, |
| "expected_maxima": { |
| "formal_math500": {"solved": 92, "denominator": 387, "percent": 23.77}, |
| "minif2f_solving": {"solved": 103, "denominator": 375, "percent": 27.47}, |
| "putnam_solving": {"solved": 1, "denominator": 324, "percent": 0.31}, |
| }, |
| "submission_only_mutant_maxima": dict(submission_maxima), |
| "solving_rule_mutant_detected": any( |
| submission_maxima[key] != maxima[key]["solved"] for key in BENCHMARKS |
| ), |
| } |
|
|
|
|
| def main() -> None: |
| parser = argparse.ArgumentParser() |
| parser.add_argument("--paper-pdf", type=Path, required=True) |
| parser.add_argument("--paper-source", type=Path, required=True) |
| parser.add_argument("--current-archive", type=Path, required=True) |
| parser.add_argument("--initial-archive", type=Path, required=True) |
| parser.add_argument("--out", type=Path, required=True) |
| args = parser.parse_args() |
|
|
| inputs = { |
| "paper_pdf": args.paper_pdf, |
| "paper_source": args.paper_source, |
| "current_archive": args.current_archive, |
| "initial_archive": args.initial_archive, |
| } |
| observed = {name: sha256(path) for name, path in inputs.items()} |
| if observed != EXPECTED: |
| raise RuntimeError(f"pinned input drift: {observed}") |
|
|
| with tempfile.TemporaryDirectory() as temporary: |
| temp = Path(temporary) |
| paper = extract(args.paper_source, temp / "paper") if False else None |
| |
| |
| paper_root = temp / "paper_source" |
| paper_root.mkdir() |
| with tarfile.open(args.paper_source, "r:gz") as handle: |
| handle.extractall(paper_root, filter="data") |
| current = extract(args.current_archive, temp / "current") |
| initial = extract(args.initial_archive, temp / "initial") |
|
|
| main_tex = (paper_root / "main.tex").read_text(encoding="utf-8") |
| method_tex = (paper_root / "method.tex").read_text(encoding="utf-8") |
| evaluation_tex = (paper_root / "evaluation.tex").read_text(encoding="utf-8") |
| experiment_tex = (paper_root / "experiment.tex").read_text(encoding="utf-8") |
| proofs_tex = (paper_root / "appendix.proofs.tex").read_text(encoding="utf-8") |
| basic = (current / "data/formal_problem_solving/FormalProblemSolving/Basic.lean").read_text(encoding="utf-8") |
| solving = (current / "common/pantograph/solving_server.py").read_text(encoding="utf-8") |
| source_audits = [ |
| assert_tokens(method_tex, ["deterministic MDP", "solution state", "solution step", "FPS is \\textbf{sound}", "D-FPS is \\textbf{complete}", "D-FPS is \\textbf{sound}"], "paper/method.tex"), |
| assert_tokens(proofs_tex, ["Soundness of FPS", "Completeness of D-FPS", "Soundness of D-FPS", "P(\\hat a)"], "paper/appendix.proofs.tex"), |
| assert_tokens(basic, ["structure ProblemSol", "Answer : α", "Proof : P Answer", "namespace FPS", "apply ProblemSol.mk", "namespace DFPS", "refine @Iff.intro"], "official/Basic.lean"), |
| assert_tokens(solving, ["init_forward_solving_state_async", "Forward", "Backward", "apply Exists.intro ?Answer ?Proof"], "official/solving_server.py"), |
| assert_tokens(evaluation_tex, ["FormalMath500", "387 data points", "375 data points", "324 data points"], "paper/evaluation.tex"), |
| assert_tokens(experiment_tex, ["23.77\\%", "27.47\\%", "0.31\\%"], "paper/experiment.tex"), |
| assert_tokens(main_tex, ["deterministic Markov decision process", "at most $23.77\\%$", "$27.47\\%$", "$0.31\\%$"], "paper/main.tex"), |
| ] |
|
|
| mdp = deterministic_mdp() |
| predicates = predicate_semantics() |
| soundness = fps_soundness() |
| benchmark = benchmark_export_audit(current, temp / "regenerated") |
| baseline = baseline_table_audit(initial) |
|
|
| expected_maxima = baseline["expected_maxima"] |
| maxima_ok = all( |
| baseline["maxima"][key][field] == expected_maxima[key][field] |
| for key in expected_maxima |
| for field in ("solved", "denominator", "percent") |
| ) |
| gates = [ |
| ("pinned_input_hashes", observed == EXPECTED), |
| ("paper_and_code_tokens", all(not row["missing"] for row in source_audits)), |
| ("mdp_exhaustive_determinism", mdp["deterministic_violations"] == 0 and mdp["repeated_transition_comparisons"] == 8192), |
| ("mdp_hidden_history_control", mdp["hidden_history_mutant_detected"]), |
| ("dfps_all_predicate_pairs", predicates["predicate_pairs"] == 21845), |
| ("dfps_forward_completeness", predicates["forward_complete_pairs"] == 3280), |
| ("dfps_optional_backward_boundary", predicates["forward_only_complete_but_unsound"] == 3025), |
| ("dfps_full_iff", predicates["complete_and_sound_pairs"] == 255), |
| ("dfps_closed_form_oracle", predicates["forward_complete_pairs"] == predicates["expected_closed_forms"]["one_direction"] and predicates["complete_and_sound_pairs"] == predicates["expected_closed_forms"]["iff"]), |
| ("fps_exhaustive_cases", soundness["answer_predicate_cases"] == 8194), |
| ("fps_soundness", soundness["soundness_violations"] == 0 and soundness["accepted_true_memberships"] == 4097), |
| ("fps_proof_field_control", soundness["proof_field_omission_mutant_false_accepts"] == 4097), |
| ("benchmark_counts", benchmark["records_total"] == 1086), |
| ("benchmark_schema", benchmark["records_with_required_schema"] == 1086), |
| ("benchmark_uniqueness", benchmark["unique_within_benchmarks"] == 1086 and benchmark["unique_global_fingerprints"] == 1086), |
| ("official_export_byte_identity", benchmark["lean_files_regenerated_byte_exact"] == 1086), |
| ("export_mutation_control", benchmark["export_mutant_detected"]), |
| ("initial_baseline_files", baseline["files"] == 18), |
| ("initial_baseline_records", baseline["method_problem_records_present"] == 5779 and baseline["method_problem_slots"] == 6516 and baseline["missing_records_counted_unsolved"] == 737), |
| ("table_1_exact_maxima", maxima_ok), |
| ("table_1_solved_rule_control", baseline["solving_rule_mutant_detected"]), |
| ] |
| results = { |
| "paper_id": PAPER_ID, |
| "scope": { |
| "lean_compilation_executed": False, |
| "lean_scope_note": "Lean 4 was not installed in this environment. The exact Lean/Pantograph implementation and proof source are pinned and structurally audited; finite semantics, all benchmark exports, and all archived Table-1 records are executed independently.", |
| "current_official_commit": CURRENT_COMMIT, |
| "initial_release_commit": INITIAL_COMMIT, |
| }, |
| "input_sha256": observed, |
| "source_audits": source_audits, |
| "claims": { |
| "claim1": {"mdp": mdp, "implementation": {"basic_lean_sha256": "a908c060d1031506ac5844b838b819391a001474eee88a5df184fa9bdac97dc4", "solving_server_sha256": "76a8760c1651b63d33d26c1d0097896f61881210efbfc576811ba79f38bd474b"}}, |
| "claim2": predicates, |
| "claim3": soundness, |
| "claim4": {"complete_pairs": predicates["forward_complete_pairs"], "sound_pairs": predicates["backward_sound_pairs"], "complete_and_sound_pairs": predicates["complete_and_sound_pairs"], "one_direction_only_invalid_pairs": predicates["forward_only_complete_but_unsound"]}, |
| "claim5": benchmark, |
| "claim6": baseline, |
| }, |
| "gates": [{"name": name, "passed": bool(passed)} for name, passed in gates], |
| "summary": {"passed": sum(bool(value) for _, value in gates), "total": len(gates), "all_passed": all(value for _, value in gates)}, |
| } |
| if not results["summary"]["all_passed"]: |
| raise RuntimeError([gate for gate in results["gates"] if not gate["passed"]]) |
| dump(args.out / "results.json", results) |
| print(json.dumps(results["summary"], sort_keys=True)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|