"""MATH final-answer normalization and equality grading.""" from __future__ import annotations import re class InfraError(RuntimeError): """The server could not grade a submission because its own data is invalid.""" _STRIP_RE = re.compile(r"[\\,;\s]") def normalize_answer(value: object) -> str: """Canonicalize a MATH final-answer string for comparison. Lowercases, converts LaTeX fractions/sqrt to plain forms, collapses whitespace/commas/ backslashes, and trims stray braces/dollars so equivalent LaTeX and plain forms of the same value agree. This is deliberately tolerant; it is not a full LaTeX semantic normalizer. """ text = str(value).strip() # Strip only dollar signs (and any outer paired braces like ${...}$); internal braces matter. text = text.strip("$") while text.startswith("{") and text.endswith("}") and "\\" not in text[:1]: text = text[1:-1].strip() # Convert \frac{a}{b}, \dfrac, \tfrac -> a/b (recursively, innermost first). for _ in range(8): new = re.sub( r"\\dfrac|\\tfrac|\\frac", r"\\frac", text, ) new = re.sub( r"\\frac\s*\{([^{}]*)\}\s*\{([^{}]*)\}", lambda m: f"{m.group(1).strip()}/{m.group(2).strip()}", new, ) # \sqrt{x} -> sqrt(x) new = re.sub( r"\\sqrt(?:\[[^\]]*\])?\s*\{([^{}]*)\}", r"sqrt(\1)", new, ) # \left / \right -> empty new = re.sub(r"\\left|\\right", "", new) if new == text: break text = new text = _STRIP_RE.sub("", text) text = text.strip("$") text = re.sub(r"\s+", "", text) return text.lower() def compare_answers(predicted: object, expected: str) -> bool: return normalize_answer(predicted) == normalize_answer(expected) def verification_report(predicted: object, expected: str) -> str: """Render a short, non-revealing grader report without leaking the reference value.""" if compare_answers(predicted, expected): return "## Answer Verification\nNormalized match: PASS\n" return ( "## Answer Verification\nNormalized match: FAIL\n" "Predicted was accepted as a valid attempt; the normalized form did not equal the " "reference answer. No reference value is exposed.\n" )