""" MATH benchmark suite. The MATH dataset consists of 12,500 problems from mathematics competitions (AMC, AIME, etc.) covering topics like algebra, counting, geometry, number theory, and more. Difficulty levels 1–5. Paper: https://arxiv.org/abs/2103.03874 """ from __future__ import annotations import json import os import re import sys from pathlib import Path from typing import Any from .base import BenchmarkResult, BenchmarkSuite # --------------------------------------------------------------------------- # Built-in mini dataset (15 representative problems) # --------------------------------------------------------------------------- _BUILTIN_PROBLEMS: list[dict[str, Any]] = [ { "id": "math-001", "problem": "What is the value of $2^{10}$?", "answer": "1024", "subject": "algebra", "level": 1, }, { "id": "math-002", "problem": "What is $15 \\% $ of $200$?", "answer": "30", "subject": "algebra", "level": 1, }, { "id": "math-003", "problem": "Solve for $x$: $3x + 7 = 22$.", "answer": "5", "subject": "algebra", "level": 1, }, { "id": "math-004", "problem": "What is the sum of the first 10 positive integers?", "answer": "55", "subject": "algebra", "level": 1, }, { "id": "math-005", "problem": "What is $\\gcd(12, 18)$?", "answer": "6", "subject": "number_theory", "level": 1, }, { "id": "math-006", "problem": "How many ways can you choose 3 items from a set of 5 items?", "answer": "10", "subject": "counting", "level": 2, }, { "id": "math-007", "problem": "What is the area of a triangle with base 10 and height 6?", "answer": "30", "subject": "geometry", "level": 1, }, { "id": "math-008", "problem": "Evaluate $\\sum_{k=1}^{5} k^2$.", "answer": "55", "subject": "algebra", "level": 2, }, { "id": "math-009", "problem": "Find the remainder when $2^{20}$ is divided by $7$.", "answer": "4", "subject": "number_theory", "level": 3, }, { "id": "math-010", "problem": "What is the least common multiple of 12 and 18?", "answer": "36", "subject": "number_theory", "level": 2, }, { "id": "math-011", "problem": "A bag contains 3 red and 5 blue marbles. What is the probability of drawing a red marble? Express as a simplified fraction.", "answer": "3/8", "subject": "counting", "level": 2, }, { "id": "math-012", "problem": "What is the value of $\\sqrt{144}$?", "answer": "12", "subject": "algebra", "level": 1, }, { "id": "math-013", "problem": "If $f(x) = 2x^2 - 3x + 1$, what is $f(3)$?", "answer": "10", "subject": "algebra", "level": 2, }, { "id": "math-014", "problem": "How many prime numbers are there between 1 and 20?", "answer": "8", "subject": "number_theory", "level": 2, }, { "id": "math-015", "problem": "What is the value of $3! + 4!$?", "answer": "30", "subject": "counting", "level": 1, }, ] def _normalize_answer(text: str) -> str: """Normalize a math answer for comparison. Strips whitespace, dollar signs, \\boxed{}, etc. """ text = text.strip() # Remove \boxed{...} m = re.search(r"\\boxed\{(.+?)\}", text) if m: text = m.group(1) # Remove dollar signs and whitespace text = text.replace("$", "").replace(",", "").strip() # Try to normalize fractions text = text.replace("\\frac{", "").replace("}{", "/").replace("}", "") return text.strip() class MATHBenchmark(BenchmarkSuite): """MATH: competition mathematics problems.""" name = "MATH" description = "Competition mathematics problems (algebra, geometry, number theory, etc.)" category = "math" def load_dataset(self) -> list[dict[str, Any]]: jsonl_path = Path(self.data_dir) / "math.jsonl" if jsonl_path.exists(): problems: list[dict[str, Any]] = [] with open(jsonl_path) as fh: for line in fh: line = line.strip() if line: problems.append(json.loads(line)) if self.verbose: print(f" Loaded {len(problems)} problems from {jsonl_path}") return problems if self.verbose: print(f" {jsonl_path} not found — using built-in 15-problem subset") return list(_BUILTIN_PROBLEMS) def build_prompt(self, problem: dict[str, Any]) -> str: prob = problem["problem"] return ( f"Solve the following math problem. Write ONLY the final numerical " f"answer (a single number or simple expression) to a file called answer.txt.\n\n" f"Problem: {prob}\n\n" f"Save just the answer to answer.txt — no explanation, no work, just the answer." ) def recover_output_files( self, problem: dict[str, Any], workspace: str, agent_output: str, metadata: dict[str, Any], ) -> None: del problem answer_path = Path(workspace) / "answer.txt" if answer_path.exists(): return answer = _normalize_answer(agent_output) if answer: answer_path.write_text(answer + "\n", encoding="utf-8") metadata["recovered_answer_from_output"] = True def evaluate(self, problem: dict[str, Any], workspace: str) -> BenchmarkResult: pid = problem.get("id", str(problem.get("task_id", "unknown"))) expected_raw = str(problem["answer"]) answer_file = os.path.join(workspace, "answer.txt") if not os.path.exists(answer_file): return BenchmarkResult( problem_id=pid, passed=False, expected=expected_raw, error="answer.txt not found", ) with open(answer_file) as fh: actual_raw = fh.read().strip() expected_norm = _normalize_answer(expected_raw) actual_norm = _normalize_answer(actual_raw) # Try numeric comparison try: passed = abs(float(actual_norm) - float(expected_norm)) < 1e-6 except (ValueError, ZeroDivisionError): passed = actual_norm == expected_norm return BenchmarkResult( problem_id=pid, passed=passed, expected=expected_raw, actual=actual_raw, error="" if passed else f"expected={expected_raw}, got={actual_raw}", )