core/evals/relational_metric/runner.py
Shay 145d797196 feat(field-wedge): geometric field reader — relational-metric lane wrong=0 (Phase W.1)
Measurement #1 of the field-reasoner falsifiable experiment: does the CL(4,1) field,
given an honest metric encoding, read forward-substitutable quantitative-relational
problems from TEXT with wrong==0? It does — 14/15 correct, 0 wrong, 1 refused
(precision ceiling), scored against an independent arithmetic oracle.

- generate/relational_field_reader.py: reads problem text into conformal points on
  the e1 number line; additive/part-whole relations are conformal TRANSLATOR versors
  (versor_apply(T_delta, embed[x]) == embed[x+delta], exact); the answer reads back
  by projective dehomogenization. Refusal-first: fences multiplicative/ratio (the
  sign/orientation-blind cases), the precision ceiling, non-forward-substitutable
  references, negatives. A per-step exactness self-check turns any f64 translator
  drift into a refusal (precision_drift) — it NEVER commits a wrong integer. Its
  parser is an independent reimplementation importing no generate.derivation/math_*.
- evals/relational_metric/: independent arithmetic oracle (computes gold from the
  STRUCTURE, shares no code with the reader), 15-case fixture, and a runner that
  enforces gold integrity + wrong==0.
- INV-25: relational_metric registered in INDEPENDENT_GOLD_LANES (oracle proven
  code-disjoint from the field reader and the algebra engine). The independently
  golded panel is now three domains: deductive, dimensional, relational-metric.

Green: smoke 87, 53 architectural invariants, 16 new tests; deductive + dimensional
lanes unperturbed (wrong=0).
2026-06-04 19:34:43 -07:00

98 lines
3.3 KiB
Python

"""Relational-metric lane runner — field reader vs the independent arithmetic gold.
For each committed case:
1. the independent oracle recomputes the gold from the STRUCTURED relations
(gold-integrity: a committed gold that the oracle cannot reproduce is rejected,
so the gold can never be field-derived — INV-25);
2. the geometric field reader reads the TEXT into an answer (or refuses);
3. the field's committed answer is scored against that gold.
Buckets: ``correct`` (field == gold), ``wrong`` (field committed a different
integer), ``refused`` (field declined). ``wrong`` must be 0 — the runner exits 1
otherwise. A refusal is honest coverage, not a wrong answer.
Run: PYTHONPATH=. .venv/bin/python -m evals.relational_metric.runner
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from typing import Any
from evals.relational_metric.oracle import OracleError, oracle_answer
from generate.relational_field_reader import read_relational
_CASES = Path(__file__).resolve().parent / "v1" / "cases.jsonl"
def _load_cases() -> list[dict[str, Any]]:
cases: list[dict[str, Any]] = []
for line in _CASES.read_text(encoding="utf-8").splitlines():
if line.strip():
cases.append(json.loads(line))
return cases
def run() -> dict[str, Any]:
"""Run the lane and return a structured report."""
cases = _load_cases()
correct = 0
wrong: list[dict[str, Any]] = []
refused: list[str] = []
gold_integrity_failures: list[str] = []
for case in cases:
cid = case["id"]
committed_gold = case["gold"]
# 1 — independent gold integrity (the oracle must reproduce the committed gold)
try:
recomputed = oracle_answer(case["relations"], case["query"])
except OracleError:
gold_integrity_failures.append(f"{cid}: oracle could not reproduce gold")
continue
if recomputed != committed_gold:
gold_integrity_failures.append(
f"{cid}: oracle={recomputed} != committed gold={committed_gold}"
)
continue
# 2/3 — the field reads the TEXT, scored against the independent gold
reading = read_relational(case["text"])
if reading.refused:
refused.append(f"{cid}: {reading.refusal_reason}")
elif reading.answer == committed_gold:
correct += 1
else:
wrong.append(
{"id": cid, "field": reading.answer, "gold": committed_gold}
)
return {
"total": len(cases),
"correct": correct,
"wrong": len(wrong),
"refused": len(refused),
"wrong_detail": wrong,
"refused_detail": refused,
"gold_integrity_failures": gold_integrity_failures,
}
def main() -> int:
report = run()
print(json.dumps({k: v for k, v in report.items()
if k not in ("wrong_detail",)}, indent=2))
if report["gold_integrity_failures"]:
print("GOLD INTEGRITY FAILURE:", report["gold_integrity_failures"], file=sys.stderr)
return 1
if report["wrong"] > 0:
print("WRONG ANSWERS (wrong!=0 breach):", report["wrong_detail"], file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())