| """ |
| ARC-AGI-2 Competition Solver |
| Dual-track approach: Program Synthesis (induction) + TTT (transduction) |
| |
| Competition constraints: |
| - 4× NVIDIA L4 GPUs (24GB VRAM each) |
| - 12-hour wall-clock time for 240 tasks (~3 min/task avg) |
| - No internet access |
| - Pass@2 scoring (2 attempts per task, exact match) |
| |
| Strategy: |
| Track A: SOAR-style program synthesis with Qwen-2.5-Coder-7B |
| Track B: TTT transduction with augmented inference + PoE |
| Ensemble: Combine both tracks with priority to verified solutions |
| """ |
| import os |
| import sys |
| import json |
| import time |
| import copy |
| import random |
| import traceback |
| from typing import List, Dict, Tuple, Optional, Any |
| from collections import defaultdict, Counter |
| import numpy as np |
|
|
| from arc_data import ( |
| load_arc_dataset_from_hf, grids_equal, |
| D8_TRANSFORMS, augment_task, reverse_d8, |
| create_color_permutation, reverse_color_permutation, |
| grid_to_string, string_to_grid |
| ) |
| from program_synthesis import ( |
| ProgramSynthesisEngine, evaluate_program_on_task, |
| weighted_majority_vote, extract_python_code, |
| format_examples_for_prompt, get_error_feedback, |
| _execute_transform, ARCPrimitives |
| ) |
|
|
|
|
| |
| |
| |
|
|
| class Config: |
| |
| PROGRAM_MODEL = "julien31/Soar-qwen-7b" |
| TTT_MODEL = "nvidia/Mistral-NeMo-Minitron-8B-Base" |
| |
| |
| N_PROGRAM_SAMPLES = 100 |
| N_PROGRAM_REFINEMENTS = 100 |
| PROGRAM_TEMPERATURE = 0.8 |
| PROGRAM_MAX_TOKENS = 1024 |
| |
| |
| TTT_LORA_RANK = 32 |
| TTT_STEPS = 64 |
| TTT_LR = 2e-4 |
| TTT_MAX_EXAMPLES = 250 |
| |
| |
| N_AUGMENTATIONS = 16 |
| DFS_THRESHOLD = 0.09 |
| |
| |
| TOTAL_TIME_HOURS = 12 |
| TIME_PER_TASK_SECONDS = 150 |
| |
| |
| N_GPUS = 4 |
|
|
|
|
| |
| |
| |
|
|
| class ARC_Solver: |
| """ |
| Main solver that combines both tracks. |
| Track A (program synthesis) provides verified solutions. |
| Track B (TTT transduction) fills gaps. |
| """ |
| |
| def __init__(self, config: Config = None): |
| self.config = config or Config() |
| self.program_engine = None |
| self.ttt_engine = None |
| self.results = {} |
| self.start_time = time.time() |
| |
| def load_models(self): |
| """Load models for both tracks.""" |
| import torch |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
| |
| print("Loading models...") |
| |
| |
| print(f" Loading {self.config.PROGRAM_MODEL}...") |
| try: |
| prog_tokenizer = AutoTokenizer.from_pretrained( |
| self.config.PROGRAM_MODEL, |
| trust_remote_code=True |
| ) |
| prog_model = AutoModelForCausalLM.from_pretrained( |
| self.config.PROGRAM_MODEL, |
| torch_dtype=torch.bfloat16, |
| device_map="auto", |
| trust_remote_code=True, |
| ) |
| prog_model.eval() |
| |
| self.program_engine = ProgramSynthesisEngine( |
| model=prog_model, |
| tokenizer=prog_tokenizer, |
| max_samples=self.config.N_PROGRAM_SAMPLES, |
| max_refinements=self.config.N_PROGRAM_REFINEMENTS, |
| temperature=self.config.PROGRAM_TEMPERATURE, |
| ) |
| print(" ✓ Program synthesis model loaded") |
| except Exception as e: |
| print(f" ✗ Failed to load program model: {e}") |
| |
| print("Models loaded.") |
| |
| def time_remaining(self) -> float: |
| """Get remaining time in seconds.""" |
| elapsed = time.time() - self.start_time |
| return self.config.TOTAL_TIME_HOURS * 3600 - elapsed |
| |
| def solve_with_programs(self, task: Dict, task_id: str, |
| time_budget: float = 120) -> List[List[List[int]]]: |
| """ |
| Track A: Solve using program synthesis. |
| Programs that pass ALL training examples are verified solutions. |
| """ |
| if self.program_engine is None: |
| return [] |
| |
| start = time.time() |
| |
| |
| programs = self.program_engine.sample_programs(task) |
| |
| |
| if time.time() - start > time_budget * 0.6: |
| pass |
| else: |
| |
| programs = self.program_engine.refine_programs(task, programs) |
| |
| |
| verified = [(c, a, o) for c, a, o in programs if a == 1.0 and o is not None] |
| partial = [(c, a, o) for c, a, o in programs if 0 < a < 1.0 and o is not None] |
| |
| if verified: |
| |
| preds = weighted_majority_vote(verified, top_k=2) |
| print(f" [PROG] Task {task_id}: {len(verified)} verified programs → {len(preds)} predictions") |
| return preds |
| |
| if partial: |
| preds = weighted_majority_vote(partial, top_k=2) |
| print(f" [PROG] Task {task_id}: {len(partial)} partial programs → {len(preds)} predictions (unverified)") |
| return preds |
| |
| print(f" [PROG] Task {task_id}: No valid programs found") |
| return [] |
| |
| def solve_with_augmented_programs(self, task: Dict, task_id: str, |
| time_budget: float = 120) -> List[List[List[int]]]: |
| """ |
| Enhanced program synthesis using D8 augmentations. |
| Try solving augmented versions of the task. |
| """ |
| if self.program_engine is None: |
| return [] |
| |
| all_programs = [] |
| |
| for t_name, _ in D8_TRANSFORMS[:4]: |
| aug_task = augment_task(task, transform_name=t_name) |
| programs = self.program_engine.sample_programs(aug_task, n_samples=10) |
| |
| |
| for code, acc, test_out in programs: |
| if test_out is not None: |
| original_out = reverse_d8(test_out, t_name) |
| all_programs.append((code, acc, original_out)) |
| |
| |
| verified = [(c, a, o) for c, a, o in all_programs if a == 1.0 and o is not None] |
| if verified: |
| return weighted_majority_vote(verified, top_k=2) |
| |
| partial = [(c, a, o) for c, a, o in all_programs if a > 0 and o is not None] |
| if partial: |
| return weighted_majority_vote(partial, top_k=2) |
| |
| return [] |
| |
| def solve_task(self, task: Dict, task_id: str = "unknown") -> List[List[List[int]]]: |
| """ |
| Solve a single ARC task using the ensemble strategy. |
| Priority: |
| 1. Verified program solutions (100% on training examples) |
| 2. TTT transduction with voting |
| 3. Partial program solutions (>50% on training examples) |
| 4. Any available prediction |
| """ |
| time_budget = min(self.config.TIME_PER_TASK_SECONDS, self.time_remaining() - 60) |
| |
| if time_budget <= 0: |
| print(f" Task {task_id}: OUT OF TIME") |
| return [] |
| |
| predictions = [] |
| |
| |
| prog_preds = self.solve_with_programs(task, task_id, time_budget=time_budget * 0.7) |
| |
| |
| if prog_preds: |
| predictions = prog_preds |
| |
| |
| if not predictions and time_budget > 30: |
| aug_preds = self.solve_with_augmented_programs(task, task_id, time_budget=time_budget * 0.3) |
| if aug_preds: |
| predictions = aug_preds |
| |
| |
| if len(predictions) == 0: |
| |
| return [] |
| elif len(predictions) == 1: |
| |
| predictions.append(predictions[0]) |
| |
| return predictions[:2] |
| |
| def solve_all(self, tasks: List[Dict], task_ids: Optional[List[str]] = None) -> Dict: |
| """ |
| Solve all tasks. Returns dict of task_id -> [pred1, pred2]. |
| """ |
| if task_ids is None: |
| task_ids = [str(i) for i in range(len(tasks))] |
| |
| results = {} |
| solved = 0 |
| |
| for i, (task, task_id) in enumerate(zip(tasks, task_ids)): |
| elapsed = time.time() - self.start_time |
| remaining = self.config.TOTAL_TIME_HOURS * 3600 - elapsed |
| |
| print(f"\n[{i+1}/{len(tasks)}] Task {task_id} " |
| f"(elapsed: {elapsed/3600:.2f}h, remaining: {remaining/3600:.2f}h)") |
| |
| if remaining <= 60: |
| print(" TIME'S UP - stopping") |
| break |
| |
| try: |
| preds = self.solve_task(task, task_id) |
| if preds: |
| results[task_id] = preds |
| solved += 1 |
| else: |
| results[task_id] = [] |
| except Exception as e: |
| print(f" ERROR: {e}") |
| traceback.print_exc() |
| results[task_id] = [] |
| |
| total_time = time.time() - self.start_time |
| print(f"\n{'='*60}") |
| print(f"RESULTS: {solved}/{len(tasks)} tasks produced predictions") |
| print(f"Total time: {total_time/3600:.2f}h") |
| print(f"{'='*60}") |
| |
| return results |
| |
| def evaluate(self, tasks: List[Dict], results: Dict, |
| task_ids: Optional[List[str]] = None) -> Dict: |
| """Evaluate results against ground truth (for tasks with known outputs).""" |
| if task_ids is None: |
| task_ids = [str(i) for i in range(len(tasks))] |
| |
| correct = 0 |
| evaluated = 0 |
| |
| for task, task_id in zip(tasks, task_ids): |
| gt = task["test"][0].get("output") |
| if gt is None: |
| continue |
| |
| evaluated += 1 |
| preds = results.get(task_id, []) |
| |
| for pred in preds: |
| if grids_equal(pred, gt): |
| correct += 1 |
| break |
| |
| accuracy = correct / evaluated if evaluated > 0 else 0 |
| |
| print(f"\nEvaluation:") |
| print(f" Correct: {correct}/{evaluated}") |
| print(f" Pass@2 Accuracy: {accuracy:.4f} ({accuracy*100:.2f}%)") |
| |
| return { |
| "correct": correct, |
| "evaluated": evaluated, |
| "accuracy": accuracy, |
| } |
|
|
|
|
| |
| |
| |
|
|
| class HeuristicSolver: |
| """ |
| Simple heuristic solver for common ARC patterns. |
| No ML model needed — uses pattern matching. |
| """ |
| |
| @staticmethod |
| def try_identity(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output == input.""" |
| for pair in task["train"]: |
| if not grids_equal(pair["input"], pair["output"]): |
| return None |
| return copy.deepcopy(task["test"][0]["input"]) |
| |
| @staticmethod |
| def try_color_replacement(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output is input with colors replaced.""" |
| |
| inp0 = task["train"][0]["input"] |
| out0 = task["train"][0]["output"] |
| |
| if len(inp0) != len(out0) or len(inp0[0]) != len(out0[0]): |
| return None |
| |
| color_map = {} |
| for r in range(len(inp0)): |
| for c in range(len(inp0[0])): |
| ic = inp0[r][c] |
| oc = out0[r][c] |
| if ic in color_map: |
| if color_map[ic] != oc: |
| return None |
| color_map[ic] = oc |
| |
| |
| for pair in task["train"][1:]: |
| inp, out = pair["input"], pair["output"] |
| if len(inp) != len(out) or len(inp[0]) != len(out[0]): |
| return None |
| for r in range(len(inp)): |
| for c in range(len(inp[0])): |
| expected = color_map.get(inp[r][c]) |
| if expected is None or expected != out[r][c]: |
| return None |
| |
| |
| test_inp = task["test"][0]["input"] |
| result = [] |
| for row in test_inp: |
| result.append([color_map.get(c, c) for c in row]) |
| return result |
| |
| @staticmethod |
| def try_rotation(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output is a rotation of input.""" |
| for k in [1, 2, 3]: |
| all_match = True |
| for pair in task["train"]: |
| rotated = np.rot90(np.array(pair["input"]), k=-k).tolist() |
| if not grids_equal(rotated, pair["output"]): |
| all_match = False |
| break |
| if all_match: |
| return np.rot90(np.array(task["test"][0]["input"]), k=-k).tolist() |
| return None |
| |
| @staticmethod |
| def try_flip(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output is a flip of input.""" |
| for flip_fn in [np.fliplr, np.flipud]: |
| all_match = True |
| for pair in task["train"]: |
| flipped = flip_fn(np.array(pair["input"])).tolist() |
| if not grids_equal(flipped, pair["output"]): |
| all_match = False |
| break |
| if all_match: |
| return flip_fn(np.array(task["test"][0]["input"])).tolist() |
| return None |
| |
| @staticmethod |
| def try_transpose(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output is transpose of input.""" |
| all_match = True |
| for pair in task["train"]: |
| transposed = np.array(pair["input"]).T.tolist() |
| if not grids_equal(transposed, pair["output"]): |
| all_match = False |
| break |
| if all_match: |
| return np.array(task["test"][0]["input"]).T.tolist() |
| return None |
| |
| @staticmethod |
| def try_crop_to_nonzero(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output is the input cropped to non-zero bounding box.""" |
| for pair in task["train"]: |
| arr = np.array(pair["input"]) |
| nonzero = np.argwhere(arr != 0) |
| if len(nonzero) == 0: |
| return None |
| r1, c1 = nonzero.min(axis=0) |
| r2, c2 = nonzero.max(axis=0) |
| cropped = arr[r1:r2+1, c1:c2+1].tolist() |
| if not grids_equal(cropped, pair["output"]): |
| return None |
| |
| test_arr = np.array(task["test"][0]["input"]) |
| nonzero = np.argwhere(test_arr != 0) |
| if len(nonzero) == 0: |
| return None |
| r1, c1 = nonzero.min(axis=0) |
| r2, c2 = nonzero.max(axis=0) |
| return test_arr[r1:r2+1, c1:c2+1].tolist() |
| |
| @staticmethod |
| def try_scale(task: Dict) -> Optional[List[List[int]]]: |
| """Check if output is input scaled by some factor.""" |
| for factor in [2, 3, 4, 5]: |
| all_match = True |
| for pair in task["train"]: |
| inp = pair["input"] |
| out = pair["output"] |
| if len(out) != len(inp) * factor or len(out[0]) != len(inp[0]) * factor: |
| all_match = False |
| break |
| |
| for r in range(len(inp)): |
| for c in range(len(inp[0])): |
| for dr in range(factor): |
| for dc in range(factor): |
| if out[r*factor+dr][c*factor+dc] != inp[r][c]: |
| all_match = False |
| break |
| if not all_match: |
| break |
| if not all_match: |
| break |
| if not all_match: |
| break |
| if not all_match: |
| break |
| if all_match: |
| inp = task["test"][0]["input"] |
| result = [] |
| for row in inp: |
| for dr in range(factor): |
| result.append([c for c in row for dc in range(factor)]) |
| return result |
| return None |
| |
| def solve(self, task: Dict) -> Optional[List[List[int]]]: |
| """Try all heuristic solvers.""" |
| solvers = [ |
| self.try_identity, |
| self.try_color_replacement, |
| self.try_rotation, |
| self.try_flip, |
| self.try_transpose, |
| self.try_crop_to_nonzero, |
| self.try_scale, |
| ] |
| |
| for solver in solvers: |
| try: |
| result = solver(task) |
| if result is not None: |
| return result |
| except Exception: |
| continue |
| |
| return None |
|
|
|
|
| |
| |
| |
|
|
| def main(): |
| """Main evaluation pipeline.""" |
| print("=" * 60) |
| print("ARC-AGI-2 Solver v1.0") |
| print("Dual-track: Program Synthesis + Test-Time Training") |
| print("=" * 60) |
| |
| |
| print("\nLoading ARC-AGI-2 training data...") |
| tasks = load_arc_dataset_from_hf("arc-agi-community/arc-agi-2", "train") |
| print(f"Loaded {len(tasks)} tasks") |
| |
| |
| print("\n--- Heuristic Solver Evaluation ---") |
| heuristic = HeuristicSolver() |
| h_correct = 0 |
| h_total = 0 |
| |
| for i, task in enumerate(tasks): |
| gt = task["test"][0].get("output") |
| if gt is None: |
| continue |
| h_total += 1 |
| |
| pred = heuristic.solve(task) |
| if pred is not None and grids_equal(pred, gt): |
| h_correct += 1 |
| |
| print(f"Heuristic solver: {h_correct}/{h_total} = {h_correct/h_total*100:.1f}%") |
| |
| |
| print("\n--- Full Solver ---") |
| solver = ARC_Solver() |
| |
| try: |
| solver.load_models() |
| |
| |
| task_ids = [str(i) for i in range(len(tasks))] |
| results = solver.solve_all(tasks, task_ids) |
| |
| |
| eval_results = solver.evaluate(tasks, results, task_ids) |
| |
| except Exception as e: |
| print(f"Model loading failed (expected on CPU): {e}") |
| print("Running heuristic-only evaluation...") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|