test(comprehend): generative wrong=0 hardening — reader is faithful or refuses
Proves the wrong=0 invariant GENERALIZES beyond the 8-case gold lanes. Over 1000 randomly generated single-word problems across all three domains, the reader either refuses or reproduces the EXACT verdict the independent oracle gives on the ground-truth structure the prose encodes — it never changes the answer. Non-circular: the generated structure S is ground truth; we render prose P(S), then compare oracle(project(comprehend(P(S)))) to oracle(S) computed directly. The oracle is independent of the reader, so agreement = lossless carry and refusal = honest decline; both are wrong=0. Verified to BITE: flipping a comparator direction makes the total_ordering case produce a reversed sort the test catches. Single-word vocab on purpose — multi-word NPs are the known gold-canonicalization wall the reader refuses; the generator stays in the readable regime where faithfulness is the whole claim. Anti-overfit: random vocab proves the templates key on function words + order, not on memorized gold content.
This commit is contained in:
parent
e831ed2615
commit
d1908ec4c8
1 changed files with 188 additions and 0 deletions
188
tests/test_comprehension_wrong_zero_property.py
Normal file
188
tests/test_comprehension_wrong_zero_property.py
Normal file
|
|
@ -0,0 +1,188 @@
|
||||||
|
"""Generative wrong=0 hardening for the comprehension organ (anti-overfit).
|
||||||
|
|
||||||
|
The end-to-end lane tests prove wrong=0 on the 8-case staged gold lanes. This file
|
||||||
|
proves the same invariant GENERALIZES: over hundreds of randomly generated
|
||||||
|
single-word problems, the reader is FAITHFUL — it either refuses, or it reproduces
|
||||||
|
the exact verdict the independent oracle gives on the ground-truth structure the
|
||||||
|
prose encodes. It NEVER changes the answer.
|
||||||
|
|
||||||
|
Why this is non-circular: the generated structure ``S`` is the ground truth. We
|
||||||
|
render prose ``P(S)`` with a trivial single-word renderer, then run the
|
||||||
|
comprehension path ``oracle(project(comprehend(P(S))))`` and compare it to
|
||||||
|
``oracle(S)`` computed directly. The oracle is independent of the reader; agreement
|
||||||
|
means the reader carried the meaning losslessly, refusal means it declined — both
|
||||||
|
are wrong=0. A reader that silently mis-read would commit a DIFFERENT verdict and
|
||||||
|
the test would bite.
|
||||||
|
|
||||||
|
Single-word vocab on purpose: multi-word NPs are a known gold-canonicalization wall
|
||||||
|
(the reader refuses them), so the generator stays in the readable single-word
|
||||||
|
regime where faithfulness is the whole claim.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
from evals.set_membership.oracle import OracleError as SetError
|
||||||
|
from evals.set_membership.oracle import oracle_answer as set_oracle
|
||||||
|
from evals.syllogism.oracle import OracleError as SylError
|
||||||
|
from evals.syllogism.oracle import oracle_answer as syl_oracle
|
||||||
|
from evals.total_ordering.oracle import OracleError as OrdError
|
||||||
|
from evals.total_ordering.oracle import oracle_answer as ord_oracle
|
||||||
|
from generate.meaning_graph.projectors import (
|
||||||
|
to_set_membership,
|
||||||
|
to_syllogism,
|
||||||
|
to_total_ordering,
|
||||||
|
)
|
||||||
|
from generate.meaning_graph.reader import Refusal, comprehend
|
||||||
|
|
||||||
|
_TERMS = [f"t{i}" for i in range(8)] # single-word identifiers -> trivial plurals
|
||||||
|
|
||||||
|
|
||||||
|
def _committed(comp_or_refusal, projector, oracle, error):
|
||||||
|
"""Run the comprehension path; return the committed oracle answer or None
|
||||||
|
(None == refused / unprojectable / oracle-refused — all wrong=0-safe)."""
|
||||||
|
if isinstance(comp_or_refusal, Refusal):
|
||||||
|
return None
|
||||||
|
projected = projector(comp_or_refusal)
|
||||||
|
if projected is None:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return oracle(*projected)
|
||||||
|
except error:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Syllogism — the richest grammar (A/E/I/O premises + therefore-conclusion)
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
_FORM_PREMISE = {
|
||||||
|
"A": lambda s, p: f"All {s}s are {p}s.",
|
||||||
|
"E": lambda s, p: f"No {s}s are {p}s.",
|
||||||
|
"I": lambda s, p: f"Some {s}s are {p}s.",
|
||||||
|
"O": lambda s, p: f"Some {s}s are not {p}s.",
|
||||||
|
}
|
||||||
|
_FORM_CONCLUSION = {
|
||||||
|
"A": lambda s, p: f"Therefore all {s}s are {p}s.",
|
||||||
|
"E": lambda s, p: f"Therefore no {s}s are {p}s.",
|
||||||
|
"I": lambda s, p: f"Therefore some {s}s are {p}s.",
|
||||||
|
"O": lambda s, p: f"Therefore some {s}s are not {p}s.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_syllogism_reader_is_faithful_or_refuses() -> None:
|
||||||
|
rng = random.Random(20260605)
|
||||||
|
checked = committed = 0
|
||||||
|
for _ in range(400):
|
||||||
|
a, b, c = rng.sample(_TERMS, 3)
|
||||||
|
# Two premises + a conclusion over the three terms; random forms.
|
||||||
|
(s1, p1), (s2, p2), (sc, pc) = (
|
||||||
|
rng.sample([a, b, c], 2),
|
||||||
|
rng.sample([a, b, c], 2),
|
||||||
|
rng.sample([a, b, c], 2),
|
||||||
|
)
|
||||||
|
f1, f2, fc = (rng.choice("AEIO"), rng.choice("AEIO"), rng.choice("AEIO"))
|
||||||
|
prose = " ".join(
|
||||||
|
[_FORM_PREMISE[f1](s1, p1), _FORM_PREMISE[f2](s2, p2), _FORM_CONCLUSION[fc](sc, pc)]
|
||||||
|
)
|
||||||
|
structure = {
|
||||||
|
"terms": sorted({a, b, c}),
|
||||||
|
"domain_size": 3,
|
||||||
|
"premises": [
|
||||||
|
{"form": f1, "subject": s1, "predicate": p1},
|
||||||
|
{"form": f2, "subject": s2, "predicate": p2},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
query = {"kind": "validity", "conclusion": {"form": fc, "subject": sc, "predicate": pc}}
|
||||||
|
try:
|
||||||
|
expected = syl_oracle(structure, query)
|
||||||
|
except SylError:
|
||||||
|
expected = None # inconsistent premises -> ground truth refuses
|
||||||
|
got = _committed(comprehend(prose), to_syllogism, syl_oracle, SylError)
|
||||||
|
checked += 1
|
||||||
|
if got is not None:
|
||||||
|
committed += 1
|
||||||
|
assert got == expected, (prose, got, expected)
|
||||||
|
assert committed > 50 # the generator actually exercises the committed path
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Total ordering — random strict chains, sort + compare queries
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
def test_total_ordering_reader_is_faithful_or_refuses() -> None:
|
||||||
|
rng = random.Random(7)
|
||||||
|
committed = 0
|
||||||
|
for _ in range(300):
|
||||||
|
n = rng.randint(2, 5)
|
||||||
|
chain = rng.sample(_TERMS, n) # chain[0] < chain[1] < ... (strict)
|
||||||
|
rels = [{"less": chain[i], "greater": chain[i + 1]} for i in range(n - 1)]
|
||||||
|
# Render the chain as comparative clauses joined into one sentence.
|
||||||
|
clauses = [f"{lo} is below {hi}" for lo, hi in zip(chain, chain[1:])]
|
||||||
|
facts = ", and ".join(clauses) + "."
|
||||||
|
if rng.random() < 0.5:
|
||||||
|
order = rng.choice(["ascending", "descending"])
|
||||||
|
prose = f"{facts} Sort {order}."
|
||||||
|
query = {"kind": "sort", "order": order}
|
||||||
|
else:
|
||||||
|
x, y = rng.sample(chain, 2)
|
||||||
|
prose = f"{facts} Compare {x} with {y}."
|
||||||
|
query = {"kind": "compare", "left": x, "right": y}
|
||||||
|
structure = {"items": sorted(set(chain)), "relations": rels}
|
||||||
|
try:
|
||||||
|
expected = ord_oracle(structure, query)
|
||||||
|
except OrdError:
|
||||||
|
expected = None
|
||||||
|
got = _committed(comprehend(prose), to_total_ordering, ord_oracle, OrdError)
|
||||||
|
if got is not None:
|
||||||
|
committed += 1
|
||||||
|
assert got == expected, (prose, got, expected)
|
||||||
|
assert committed > 50
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
# Set membership — random members + subset chains, member + subset queries
|
||||||
|
# --------------------------------------------------------------------------- #
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_membership_reader_is_faithful_or_refuses() -> None:
|
||||||
|
rng = random.Random(99)
|
||||||
|
committed = 0
|
||||||
|
for _ in range(300):
|
||||||
|
classes = rng.sample(_TERMS, rng.randint(2, 4))
|
||||||
|
individuals = [f"e{i}" for i in range(rng.randint(1, 3))]
|
||||||
|
member_facts = [(ind, rng.choice(classes)) for ind in individuals]
|
||||||
|
# a short subset chain over a prefix of the classes
|
||||||
|
subset_facts = [
|
||||||
|
(classes[i], classes[i + 1]) for i in range(len(classes) - 1) if rng.random() < 0.7
|
||||||
|
]
|
||||||
|
member_lines = [f"{ind} is a {cls}." for ind, cls in member_facts]
|
||||||
|
subset_lines = [f"All {a}s are {b}s." for a, b in subset_facts]
|
||||||
|
sets = [
|
||||||
|
{"id": c, "members": sorted({i for i, cl in member_facts if cl == c})} for c in classes
|
||||||
|
]
|
||||||
|
structure = {
|
||||||
|
"elements": sorted(individuals),
|
||||||
|
"sets": sets,
|
||||||
|
"subsets": [{"subset": a, "superset": b} for a, b in subset_facts],
|
||||||
|
}
|
||||||
|
if rng.random() < 0.5 and individuals:
|
||||||
|
ind = rng.choice(individuals)
|
||||||
|
target = rng.choice(classes)
|
||||||
|
prose = " ".join(member_lines + subset_lines + [f"Is {ind} a {target}?"])
|
||||||
|
query = {"kind": "member", "element": ind, "set": target}
|
||||||
|
else:
|
||||||
|
a, b = rng.sample(classes, 2)
|
||||||
|
prose = " ".join(member_lines + subset_lines + [f"Are all {a}s {b}s?"])
|
||||||
|
query = {"kind": "subset", "subset": a, "superset": b}
|
||||||
|
try:
|
||||||
|
expected = set_oracle(structure, query)
|
||||||
|
except SetError:
|
||||||
|
expected = None
|
||||||
|
got = _committed(comprehend(prose), to_set_membership, set_oracle, SetError)
|
||||||
|
if got is not None:
|
||||||
|
committed += 1
|
||||||
|
assert got == expected, (prose, got, expected)
|
||||||
|
assert committed > 50
|
||||||
Loading…
Reference in a new issue