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.
89 lines
3.5 KiB
Python
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",
|
|
]
|