core/evals/constraint_oracle/signature.py
Shay a098519bc1 feat(constraint): R2 setup oracle + gold + ADR-0211 — the ruler (R2 C2)
evals/constraint_oracle: span-free canonical setup signature (unknowns/facts/constraints/query; terms merged+sorted, lhs constant folded into rhs), a 13-fixture gold (7 solved / 3 solver_refuses / 3 reader_refuses) with a closed expect+refusal taxonomy, and a gold-validation runner. No reader yet — this proves the ruler is coherent (13/13 valid), not capability.

ADR-0211 ratifies the R2 finite-integer two-category constraint compiler: the IR, the canonical signature, the closed taxonomy, the four wrong=0 gates (setup oracle / reader refusal / exact solver / answer-choice contradiction flag), off-serving disjointness, and the C1-C9 ladder. Cross-refs ADR-0207/0175/0083/0055-0057; reuses the R1 setup_oracle pattern. Off-serving (no generate.derivation / reliability_gate). 13 oracle tests incl. per-branch meaningful-fail.
2026-06-07 07:19:29 -07:00

89 lines
3.5 KiB
Python

"""Span-free canonical signatures for the R2 constraint setup oracle.
A *signature* is a deterministic, order-independent, span-free projection of a constraint
SETUP — what a reader claims a problem says, stripped of input spans and surface tokens. Two
setups are equivalent iff their signatures are equal. Used to compare a reader's comprehended
:class:`ConstraintProblem` against the independent gold (and, until the reader lands in C5+,
to prove the gold itself canonicalizes stably).
The R2 twin of ``evals.setup_oracle.signature``. Pure, deterministic; no clock, no randomness.
"""
from __future__ import annotations
from typing import Any
from generate.constraint_comprehension.expr import LinearConstraint, LinearExpr
from generate.constraint_comprehension.model import (
AttributeFact,
ConstraintProblem,
ConstraintQuery,
Unknown,
)
def canonical_linear(expr: LinearExpr) -> tuple[tuple[tuple[str, int], ...], int]:
"""Merge duplicate symbols, drop zero coefficients, sort by symbol -> ``(terms, constant)``."""
merged: dict[str, int] = {}
for symbol, coeff in expr.terms:
merged[symbol] = merged.get(symbol, 0) + coeff
terms = tuple(sorted((s, c) for s, c in merged.items() if c != 0))
return terms, expr.constant
def canonical_constraint(c: LinearConstraint) -> tuple[tuple[tuple[str, int], ...], str, int]:
"""A span-free canonical equation: ``(merged sorted lhs terms, relation, rhs - lhs constant)``.
Folding the lhs constant into the rhs makes ``x + y + 0 = 6`` and ``x + y = 6`` equal; the
source span never participates — two constraints are setup-equal iff lhs/relation/rhs match.
"""
terms, constant = canonical_linear(c.lhs)
return terms, c.relation, c.rhs - constant
def unknowns_signature(unknowns: tuple[Unknown, ...]) -> tuple[tuple[str, str, str], ...]:
"""Sorted ``(symbol, unit, domain)`` per unknown — surface ``entity`` is provenance, excluded."""
return tuple(sorted((u.symbol, u.unit, u.domain) for u in unknowns))
def constraints_signature(
constraints: tuple[LinearConstraint, ...],
) -> tuple[tuple[tuple[tuple[str, int], ...], str, int], ...]:
"""Order-independent canonical signature of the whole linear system."""
return tuple(sorted((canonical_constraint(c) for c in constraints), key=repr))
def query_signature(query: ConstraintQuery) -> tuple[str, str]:
return (query.symbol, query.unit)
def attribute_facts_signature(
facts: tuple[AttributeFact, ...],
) -> tuple[tuple[str, str, int], ...]:
"""Sorted ``(category, measured_unit, value)`` — the per-category coefficient provenance."""
return tuple(sorted((f.category, f.measured_unit, f.value) for f in facts))
def constraint_setup_signature(problem: ConstraintProblem) -> dict[str, Any]:
"""The composite setup signature: unknowns ∧ facts ∧ constraints ∧ query.
A reader matches gold iff every component is equal. Returned as a dict so a mismatch
localizes which axis diverged (mirroring the R1 setup oracle's per-axis ``*_match`` detail).
"""
return {
"unknowns": unknowns_signature(problem.unknowns),
"facts": attribute_facts_signature(problem.facts),
"constraints": constraints_signature(problem.constraints),
"query": query_signature(problem.query),
}
__all__ = [
"attribute_facts_signature",
"canonical_constraint",
"canonical_linear",
"constraint_setup_signature",
"constraints_signature",
"query_signature",
"unknowns_signature",
]