File size: 2,375 Bytes
ba270e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
"""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"
    )