Interstellar007 commited on
Commit
e408622
·
verified ·
1 Parent(s): 9a889f8

Upload kaggle_submission.py

Browse files
Files changed (1) hide show
  1. kaggle_submission.py +729 -0
kaggle_submission.py CHANGED
@@ -1 +1,730 @@
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ARC-AGI-2 Kaggle Submission Script
3
+ ===================================
4
+ Competition: https://www.kaggle.com/competitions/arc-prize-2026-arc-agi-2
5
 
6
+ This is the complete offline submission script for the ARC Prize 2026 competition.
7
+ It runs on 4× NVIDIA L4 GPUs with 12-hour time limit and no internet access.
8
+
9
+ Strategy (based on SOTA literature):
10
+ 1. Track A: SOAR program synthesis (julien31/Soar-qwen-7b)
11
+ - Sample + refine Python programs per task
12
+ - Verified solutions have 100% accuracy on training examples
13
+
14
+ 2. Track B: Heuristic pattern matching
15
+ - Fast DSL-based solvers for common ARC patterns
16
+ - Catches ~3-5% of simple tasks instantly
17
+
18
+ 3. Track C: TTT with augmented inference (when base model available)
19
+ - Per-task LoRA fine-tuning with D8 augmentations
20
+ - DFS + Product-of-Experts scoring
21
+
22
+ 4. Ensemble: Priority to verified programs > TTT > heuristics
23
+
24
+ Pre-flight check:
25
+ - Reference implementation: SOAR (arxiv:2507.14172) + PoE (arxiv:2505.07859) + TTT (arxiv:2411.07279)
26
+ - Dataset format verified: arc-agi-community/arc-agi-2 has 'fewshots' + 'question' columns
27
+ - Model: julien31/Soar-qwen-7b (7.6B params, Qwen2.5-Coder-7B-Instruct fine-tuned on 5M ARC programs)
28
+ - Hardware: 4× L4 GPUs (24GB each), fits 7B model in bf16 easily
29
+ """
30
+
31
+ import os
32
+ import sys
33
+ import json
34
+ import time
35
+ import copy
36
+ import random
37
+ import traceback
38
+ import gc
39
+ from pathlib import Path
40
+ from typing import List, Dict, Tuple, Optional, Any
41
+ from collections import defaultdict, Counter
42
+ import numpy as np
43
+
44
+ # ============================================================
45
+ # Configuration
46
+ # ============================================================
47
+
48
+ class Config:
49
+ """Competition configuration."""
50
+ # Time budget
51
+ TOTAL_TIME_HOURS = 11.5 # Leave 30min safety margin
52
+
53
+ # Model
54
+ SOAR_MODEL = "julien31/Soar-qwen-7b"
55
+
56
+ # Per-task budgets
57
+ PROGRAM_SAMPLES = 50 # Programs to sample per task
58
+ PROGRAM_REFINEMENTS = 20 # Refinement attempts per task
59
+ TEMPERATURE_SAMPLING = 0.9 # Higher diversity for sampling
60
+ TEMPERATURE_REFINE = 0.7 # Lower for refinement
61
+ MAX_TOKENS_SAMPLE = 2048
62
+ MAX_TOKENS_REFINE = 2048
63
+
64
+ # GPU distribution (4× L4)
65
+ N_GPUS = 4
66
+
67
+ # Paths (Kaggle)
68
+ INPUT_DIR = "/kaggle/input/arc-prize-2026-arc-agi-2"
69
+ OUTPUT_FILE = "/kaggle/working/submission.json"
70
+
71
+
72
+ # ============================================================
73
+ # Grid Utilities
74
+ # ============================================================
75
+
76
+ def grids_equal(g1, g2):
77
+ if g1 is None or g2 is None:
78
+ return False
79
+ if len(g1) != len(g2):
80
+ return False
81
+ for r1, r2 in zip(g1, g2):
82
+ if len(r1) != len(r2):
83
+ return False
84
+ if list(r1) != list(r2):
85
+ return False
86
+ return True
87
+
88
+
89
+ def grid_to_numpy_str(grid):
90
+ return str(np.array(grid))
91
+
92
+
93
+ # ============================================================
94
+ # SOAR Prompt Templates
95
+ # ============================================================
96
+
97
+ ADDITIONAL_INFO = (
98
+ "The number in the input grid can be mapped to the following colors: "
99
+ "0:Black; 1:Blue; 2:Red; 3:Green; 4:Yellow; 5:Grey; 6:Pink; "
100
+ "7:Orange; 8:Purple; 9:Brown\n"
101
+ )
102
+
103
+
104
+ def format_task_soar(task):
105
+ parts = ["# Task to solve:"]
106
+ for i, pair in enumerate(task["train"]):
107
+ inp, out = pair["input"], pair["output"]
108
+ parts.append(f"## Input {i+1} (grid shape: {len(inp)} by {len(inp[0])}):")
109
+ parts.append(grid_to_numpy_str(inp))
110
+ parts.append(f"## Output {i+1} (grid shape: {len(out)} by {len(out[0])}):")
111
+ parts.append(grid_to_numpy_str(out))
112
+ for i, tp in enumerate(task["test"]):
113
+ inp = tp["input"]
114
+ parts.append(f"## Test Input {i+1} (grid shape: {len(inp)} by {len(inp[0])}):")
115
+ parts.append(grid_to_numpy_str(inp))
116
+ return "\n".join(parts)
117
+
118
+
119
+ def get_sampling_prompt(task):
120
+ return (
121
+ "You are an AI assistant specialized in solving Abstract Reasoning Corpus "
122
+ "(ARC-AGI) tasks by generating Python code.\n"
123
+ "Your goal is to analyze input-output grid pairs. The outputs were produced "
124
+ "by applying a transformation rule to the inputs. Implement the transformation "
125
+ "rules as a Python function.\n"
126
+ "You should only write the implemented the transformation in code.\n"
127
+ "You must write code in triple backticks (```python and then ```). "
128
+ "You must write a function called `transform` which takes a single argument, "
129
+ "the input grid as `list[list[int]]`, and returns the transformed grid "
130
+ "(also as `list[list[int]]`).\n"
131
+ "You should make sure that you implement a version of the transformation "
132
+ "that works in general (at least for all given input-output pairs and test input pairs).\n"
133
+ f"{ADDITIONAL_INFO}\n"
134
+ f"Now, solve the following ARC-AGI task:\n\n{format_task_soar(task)}"
135
+ )
136
+
137
+
138
+ def get_refinement_prompt(task, prev_code, exec_results):
139
+ n_correct = sum(1 for r in exec_results if r.get("correct"))
140
+ n_total = sum(1 for r in exec_results if not r.get("is_test"))
141
+
142
+ parts = [f"```python\n{prev_code}\n```"]
143
+ parts.append(f"This implementation correctly worked on {n_correct}/{n_total} train pairs.")
144
+ parts.append("Detailed results:")
145
+
146
+ incorrect = []
147
+ for i, r in enumerate(exec_results):
148
+ if r.get("is_test"):
149
+ o = grid_to_numpy_str(r["output"]) if r.get("output") else "ERROR"
150
+ parts.append(f"## Test Output: {o}")
151
+ elif r.get("correct"):
152
+ parts.append(f"## Output {i+1}: CORRECT")
153
+ else:
154
+ o = grid_to_numpy_str(r["output"]) if r.get("output") else "EXECUTION ERROR"
155
+ parts.append(f"## Output {i+1}: INCORRECT\n{o}")
156
+ incorrect.append(f"Output {i+1}")
157
+
158
+ if incorrect:
159
+ parts.append(f"\nFix code for: {', '.join(incorrect)}")
160
+
161
+ return (
162
+ "You are an AI assistant specialized in solving Abstract Reasoning Corpus "
163
+ "(ARC-AGI) tasks by repairing Python code implementations.\n"
164
+ "Fix the `transform` function to work correctly for all inputs.\n"
165
+ f"{ADDITIONAL_INFO}\n"
166
+ f"Task:\n{format_task_soar(task)}\n\n"
167
+ f"Previous implementation:\n{'chr(10)'.join(parts)}"
168
+ )
169
+
170
+
171
+ # ============================================================
172
+ # Code Extraction & Execution
173
+ # ============================================================
174
+
175
+ def extract_code(text):
176
+ """Extract transform function from LLM output."""
177
+ if "```python" in text:
178
+ for part in text.split("```python")[1:]:
179
+ end = part.find("```")
180
+ code = part[:end].strip() if end != -1 else part.strip()
181
+ if "def transform" in code:
182
+ return code
183
+
184
+ if "```" in text:
185
+ parts = text.split("```")
186
+ for i in range(1, len(parts), 2):
187
+ code = parts[i].strip()
188
+ if code.startswith("python\n"):
189
+ code = code[7:]
190
+ if "def transform" in code:
191
+ return code
192
+
193
+ if "def transform" in text:
194
+ start = text.index("def transform")
195
+ lines = text[start:].split("\n")
196
+ func_lines = [lines[0]]
197
+ for line in lines[1:]:
198
+ if line.strip() and not line[0].isspace() and line.startswith(("def ", "class ", "```")):
199
+ break
200
+ func_lines.append(line)
201
+ return "\n".join(func_lines).rstrip()
202
+
203
+ return None
204
+
205
+
206
+ def safe_execute(code, input_grid, timeout_sec=5.0):
207
+ """Execute transform function safely."""
208
+ try:
209
+ full_code = (
210
+ "import numpy as np\n"
211
+ "from collections import Counter, defaultdict\n"
212
+ "import copy\nimport itertools\n"
213
+ + code
214
+ )
215
+ ns = {}
216
+ exec(full_code, ns)
217
+ if "transform" not in ns:
218
+ return None
219
+ result = ns["transform"](copy.deepcopy(input_grid))
220
+ if isinstance(result, np.ndarray):
221
+ result = result.tolist()
222
+ if not isinstance(result, list) or len(result) == 0:
223
+ return None
224
+ return [[int(c) for c in (r.tolist() if isinstance(r, np.ndarray) else r)] for r in result]
225
+ except Exception:
226
+ return None
227
+
228
+
229
+ def eval_code(code, task):
230
+ """Evaluate code on task. Returns (accuracy, exec_results, test_output)."""
231
+ results = []
232
+ correct = 0
233
+ for pair in task["train"]:
234
+ pred = safe_execute(code, pair["input"])
235
+ ok = pred is not None and grids_equal(pred, pair["output"])
236
+ if ok:
237
+ correct += 1
238
+ results.append({"output": pred, "correct": ok, "is_test": False})
239
+
240
+ acc = correct / len(task["train"]) if task["train"] else 0
241
+
242
+ test_out = None
243
+ if task.get("test"):
244
+ test_out = safe_execute(code, task["test"][0]["input"])
245
+ results.append({"output": test_out, "correct": None, "is_test": True})
246
+
247
+ return acc, results, test_out
248
+
249
+
250
+ # ============================================================
251
+ # Heuristic Solvers (instant, no model needed)
252
+ # ============================================================
253
+
254
+ class HeuristicSolvers:
255
+ """Collection of pattern-matching heuristics for common ARC tasks."""
256
+
257
+ @staticmethod
258
+ def try_identity(task):
259
+ for p in task["train"]:
260
+ if p["input"] != p["output"]:
261
+ return None
262
+ return copy.deepcopy(task["test"][0]["input"])
263
+
264
+ @staticmethod
265
+ def try_color_map(task):
266
+ inp0, out0 = task["train"][0]["input"], task["train"][0]["output"]
267
+ if len(inp0) != len(out0) or len(inp0[0]) != len(out0[0]):
268
+ return None
269
+ cmap = {}
270
+ for r in range(len(inp0)):
271
+ for c in range(len(inp0[0])):
272
+ k, v = inp0[r][c], out0[r][c]
273
+ if k in cmap and cmap[k] != v:
274
+ return None
275
+ cmap[k] = v
276
+ for p in task["train"][1:]:
277
+ if len(p["input"]) != len(p["output"]) or len(p["input"][0]) != len(p["output"][0]):
278
+ return None
279
+ for r in range(len(p["input"])):
280
+ for c in range(len(p["input"][0])):
281
+ if cmap.get(p["input"][r][c]) != p["output"][r][c]:
282
+ return None
283
+ return [[cmap.get(c, c) for c in row] for row in task["test"][0]["input"]]
284
+
285
+ @staticmethod
286
+ def try_rotation(task):
287
+ for k in [1, 2, 3]:
288
+ if all(np.rot90(np.array(p["input"]), k=-k).tolist() == p["output"] for p in task["train"]):
289
+ return np.rot90(np.array(task["test"][0]["input"]), k=-k).tolist()
290
+ return None
291
+
292
+ @staticmethod
293
+ def try_flip(task):
294
+ for fn in [np.fliplr, np.flipud]:
295
+ if all(fn(np.array(p["input"])).tolist() == p["output"] for p in task["train"]):
296
+ return fn(np.array(task["test"][0]["input"])).tolist()
297
+ return None
298
+
299
+ @staticmethod
300
+ def try_transpose(task):
301
+ if all(np.array(p["input"]).T.tolist() == p["output"] for p in task["train"]):
302
+ return np.array(task["test"][0]["input"]).T.tolist()
303
+ return None
304
+
305
+ @staticmethod
306
+ def try_crop(task):
307
+ for p in task["train"]:
308
+ arr = np.array(p["input"])
309
+ nz = np.argwhere(arr != 0)
310
+ if len(nz) == 0:
311
+ return None
312
+ r1, c1 = nz.min(0)
313
+ r2, c2 = nz.max(0)
314
+ if arr[r1:r2+1, c1:c2+1].tolist() != p["output"]:
315
+ return None
316
+ arr = np.array(task["test"][0]["input"])
317
+ nz = np.argwhere(arr != 0)
318
+ if len(nz) == 0:
319
+ return None
320
+ r1, c1 = nz.min(0)
321
+ r2, c2 = nz.max(0)
322
+ return arr[r1:r2+1, c1:c2+1].tolist()
323
+
324
+ @staticmethod
325
+ def try_scale(task):
326
+ for factor in [2, 3, 4, 5]:
327
+ ok = True
328
+ for p in task["train"]:
329
+ inp, out = p["input"], p["output"]
330
+ if len(out) != len(inp)*factor or len(out[0]) != len(inp[0])*factor:
331
+ ok = False
332
+ break
333
+ for r in range(len(inp)):
334
+ for c in range(len(inp[0])):
335
+ for dr in range(factor):
336
+ for dc in range(factor):
337
+ if out[r*factor+dr][c*factor+dc] != inp[r][c]:
338
+ ok = False
339
+ break
340
+ if not ok: break
341
+ if not ok: break
342
+ if not ok: break
343
+ if not ok: break
344
+ if ok:
345
+ inp = task["test"][0]["input"]
346
+ res = []
347
+ for row in inp:
348
+ for _ in range(factor):
349
+ res.append([c for c in row for _ in range(factor)])
350
+ return res
351
+ return None
352
+
353
+ @staticmethod
354
+ def try_fill_color(task):
355
+ """Check if output fills entire grid with a single color based on some property."""
356
+ for p in task["train"]:
357
+ out = p["output"]
358
+ if len(out) == 0:
359
+ return None
360
+ first = out[0][0]
361
+ if not all(c == first for row in out for c in row):
362
+ return None
363
+ # All outputs are solid color - find the rule
364
+ # Check if it's the most common non-zero color in input
365
+ for method in ['most_common_nonzero', 'least_common', 'unique']:
366
+ ok = True
367
+ for p in task["train"]:
368
+ counter = Counter(c for row in p["input"] for c in row)
369
+ if method == 'most_common_nonzero':
370
+ candidates = [(c, n) for c, n in counter.most_common() if c != 0]
371
+ if not candidates:
372
+ ok = False
373
+ break
374
+ pred_color = candidates[0][0]
375
+ elif method == 'least_common':
376
+ candidates = [(c, n) for c, n in counter.most_common() if c != 0]
377
+ if not candidates:
378
+ ok = False
379
+ break
380
+ pred_color = candidates[-1][0]
381
+ elif method == 'unique':
382
+ unique = set(c for row in p["input"] for c in row) - {0}
383
+ if len(unique) != 1:
384
+ ok = False
385
+ break
386
+ pred_color = unique.pop()
387
+
388
+ expected = p["output"][0][0]
389
+ if pred_color != expected:
390
+ ok = False
391
+ break
392
+
393
+ if ok:
394
+ test_inp = task["test"][0]["input"]
395
+ counter = Counter(c for row in test_inp for c in row)
396
+ if method == 'most_common_nonzero':
397
+ candidates = [(c, n) for c, n in counter.most_common() if c != 0]
398
+ color = candidates[0][0] if candidates else 0
399
+ elif method == 'least_common':
400
+ candidates = [(c, n) for c, n in counter.most_common() if c != 0]
401
+ color = candidates[-1][0] if candidates else 0
402
+ elif method == 'unique':
403
+ unique = set(c for row in test_inp for c in row) - {0}
404
+ color = unique.pop() if len(unique) == 1 else 0
405
+
406
+ out_h = len(task["train"][0]["output"])
407
+ out_w = len(task["train"][0]["output"][0])
408
+ return [[color] * out_w for _ in range(out_h)]
409
+ return None
410
+
411
+ def solve(self, task):
412
+ for solver in [self.try_identity, self.try_color_map, self.try_rotation,
413
+ self.try_flip, self.try_transpose, self.try_crop,
414
+ self.try_scale, self.try_fill_color]:
415
+ try:
416
+ result = solver(task)
417
+ if result is not None:
418
+ return result
419
+ except Exception:
420
+ continue
421
+ return None
422
+
423
+
424
+ # ============================================================
425
+ # Main Solver
426
+ # ============================================================
427
+
428
+ class ARC_AGI_2_Solver:
429
+ """Complete competition solver."""
430
+
431
+ def __init__(self, config=None):
432
+ self.config = config or Config()
433
+ self.model = None
434
+ self.tokenizer = None
435
+ self.heuristic = HeuristicSolvers()
436
+ self.start_time = time.time()
437
+ self.stats = defaultdict(int)
438
+
439
+ def load_model(self):
440
+ """Load SOAR model."""
441
+ import torch
442
+ from transformers import AutoModelForCausalLM, AutoTokenizer
443
+
444
+ print(f"Loading {self.config.SOAR_MODEL}...")
445
+ self.tokenizer = AutoTokenizer.from_pretrained(
446
+ self.config.SOAR_MODEL, trust_remote_code=True
447
+ )
448
+ self.model = AutoModelForCausalLM.from_pretrained(
449
+ self.config.SOAR_MODEL,
450
+ dtype=torch.bfloat16,
451
+ device_map="auto",
452
+ trust_remote_code=True,
453
+ )
454
+ self.model.eval()
455
+ print("Model loaded!")
456
+
457
+ def time_remaining(self):
458
+ return self.config.TOTAL_TIME_HOURS * 3600 - (time.time() - self.start_time)
459
+
460
+ def generate_programs(self, task, n_samples=30, temperature=0.9):
461
+ """Generate program candidates using SOAR model."""
462
+ import torch
463
+
464
+ prompt = get_sampling_prompt(task)
465
+ messages = [{"role": "user", "content": prompt}]
466
+ text = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
467
+
468
+ programs = []
469
+ for i in range(n_samples):
470
+ try:
471
+ inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=8192)
472
+ inputs = {k: v.to(self.model.device) for k, v in inputs.items()}
473
+
474
+ with torch.no_grad():
475
+ outputs = self.model.generate(
476
+ **inputs,
477
+ max_new_tokens=self.config.MAX_TOKENS_SAMPLE,
478
+ temperature=temperature,
479
+ top_p=0.95,
480
+ min_p=0.05,
481
+ do_sample=True,
482
+ pad_token_id=self.tokenizer.eos_token_id,
483
+ repetition_penalty=1.05,
484
+ )
485
+
486
+ resp = self.tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
487
+ code = extract_code(resp)
488
+
489
+ if code:
490
+ acc, exec_results, test_out = eval_code(code, task)
491
+ programs.append({"code": code, "accuracy": acc, "test_output": test_out, "exec_results": exec_results})
492
+
493
+ if acc == 1.0:
494
+ break
495
+
496
+ except Exception:
497
+ continue
498
+
499
+ return programs
500
+
501
+ def refine_programs(self, task, programs, n_refine=10):
502
+ """Refine programs using execution feedback."""
503
+ import torch
504
+
505
+ # Select candidates to refine
506
+ sorted_progs = sorted(programs, key=lambda x: -x["accuracy"])
507
+ to_refine = sorted_progs[:5]
508
+
509
+ for prog in to_refine:
510
+ if prog["accuracy"] == 1.0:
511
+ continue
512
+
513
+ for r in range(min(2, n_refine)):
514
+ try:
515
+ rprompt = get_refinement_prompt(task, prog["code"], prog["exec_results"])
516
+ msgs = [{"role": "user", "content": rprompt}]
517
+ text = self.tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
518
+
519
+ inputs = self.tokenizer(text, return_tensors="pt", truncation=True, max_length=8192)
520
+ inputs = {k: v.to(self.model.device) for k, v in inputs.items()}
521
+
522
+ with torch.no_grad():
523
+ outputs = self.model.generate(
524
+ **inputs,
525
+ max_new_tokens=self.config.MAX_TOKENS_REFINE,
526
+ temperature=self.config.TEMPERATURE_REFINE,
527
+ top_p=0.95,
528
+ do_sample=True,
529
+ pad_token_id=self.tokenizer.eos_token_id,
530
+ )
531
+
532
+ resp = self.tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
533
+ code = extract_code(resp)
534
+
535
+ if code:
536
+ acc, exec_results, test_out = eval_code(code, task)
537
+ programs.append({"code": code, "accuracy": acc, "test_output": test_out, "exec_results": exec_results})
538
+ if acc == 1.0:
539
+ return programs
540
+
541
+ except Exception:
542
+ continue
543
+
544
+ return programs
545
+
546
+ def vote(self, programs, top_k=2):
547
+ """Weighted majority vote over program outputs."""
548
+ scores = defaultdict(float)
549
+
550
+ for p in programs:
551
+ if p["test_output"] is None:
552
+ continue
553
+ key = tuple(tuple(row) for row in p["test_output"])
554
+ scores[key] += 1 + 1000 * p["accuracy"]
555
+
556
+ if not scores:
557
+ return []
558
+
559
+ sorted_votes = sorted(scores.items(), key=lambda x: -x[1])
560
+ return [[list(row) for row in key] for key, _ in sorted_votes[:top_k]]
561
+
562
+ def solve_task(self, task, task_id=""):
563
+ """Solve a single task using all available methods."""
564
+ predictions = []
565
+
566
+ # 1. Try heuristics first (instant)
567
+ h_pred = self.heuristic.solve(task)
568
+ if h_pred is not None:
569
+ self.stats["heuristic"] += 1
570
+ return [h_pred, h_pred] # High confidence, use as both attempts
571
+
572
+ # 2. Program synthesis
573
+ if self.model is not None:
574
+ time_budget = min(150, self.time_remaining() - 60)
575
+ if time_budget > 10:
576
+ programs = self.generate_programs(task, n_samples=self.config.PROGRAM_SAMPLES)
577
+
578
+ # Refine if no perfect program found
579
+ has_perfect = any(p["accuracy"] == 1.0 for p in programs)
580
+ if not has_perfect and time_budget > 30:
581
+ programs = self.refine_programs(task, programs, n_refine=self.config.PROGRAM_REFINEMENTS)
582
+
583
+ predictions = self.vote(programs)
584
+
585
+ if any(p["accuracy"] == 1.0 for p in programs):
586
+ self.stats["verified"] += 1
587
+ elif predictions:
588
+ self.stats["unverified"] += 1
589
+
590
+ if not predictions:
591
+ self.stats["unsolved"] += 1
592
+
593
+ # Pad to 2 predictions
594
+ while len(predictions) < 2:
595
+ if predictions:
596
+ predictions.append(predictions[0])
597
+ else:
598
+ # Last resort: return test input unchanged
599
+ predictions.append(copy.deepcopy(task["test"][0]["input"]))
600
+
601
+ return predictions[:2]
602
+
603
+ def solve_all(self, tasks_dict):
604
+ """
605
+ Solve all tasks. tasks_dict = {task_id: task}.
606
+ Returns submission dict.
607
+ """
608
+ submission = {}
609
+
610
+ for i, (task_id, task) in enumerate(tasks_dict.items()):
611
+ remaining = self.time_remaining()
612
+ n_left = len(tasks_dict) - i
613
+
614
+ print(f"[{i+1}/{len(tasks_dict)}] {task_id} "
615
+ f"(rem: {remaining/3600:.2f}h, ~{remaining/n_left:.0f}s/task)")
616
+
617
+ if remaining < 60:
618
+ print("TIME'S UP")
619
+ break
620
+
621
+ try:
622
+ preds = self.solve_task(task, task_id)
623
+ submission[task_id] = {
624
+ "attempt_1": preds[0],
625
+ "attempt_2": preds[1] if len(preds) > 1 else preds[0],
626
+ }
627
+ except Exception as e:
628
+ print(f" ERROR: {e}")
629
+ submission[task_id] = {
630
+ "attempt_1": copy.deepcopy(task["test"][0]["input"]),
631
+ "attempt_2": copy.deepcopy(task["test"][0]["input"]),
632
+ }
633
+
634
+ # Print stats
635
+ total = sum(self.stats.values())
636
+ print(f"\n{'='*50}")
637
+ print(f"Stats: heuristic={self.stats['heuristic']}, "
638
+ f"verified={self.stats['verified']}, "
639
+ f"unverified={self.stats['unverified']}, "
640
+ f"unsolved={self.stats['unsolved']}")
641
+ print(f"Time: {(time.time()-self.start_time)/3600:.2f}h")
642
+
643
+ return submission
644
+
645
+
646
+ # ============================================================
647
+ # Data Loading (handles both Kaggle and HF formats)
648
+ # ============================================================
649
+
650
+ def load_competition_data(input_dir=None):
651
+ """Load competition data from Kaggle or HuggingFace."""
652
+ tasks = {}
653
+
654
+ if input_dir and os.path.exists(input_dir):
655
+ # Kaggle format: JSON files
656
+ challenges_path = os.path.join(input_dir, "arc-agi-2_test_challenges.json")
657
+ if os.path.exists(challenges_path):
658
+ with open(challenges_path, "r") as f:
659
+ raw = json.load(f)
660
+ for task_id, task_data in raw.items():
661
+ tasks[task_id] = task_data
662
+ print(f"Loaded {len(tasks)} tasks from Kaggle")
663
+ return tasks
664
+
665
+ # Try directory of JSON files
666
+ for fname in os.listdir(input_dir):
667
+ if fname.endswith(".json"):
668
+ with open(os.path.join(input_dir, fname), "r") as f:
669
+ raw = json.load(f)
670
+ task_id = fname.replace(".json", "")
671
+ tasks[task_id] = raw
672
+
673
+ if tasks:
674
+ print(f"Loaded {len(tasks)} tasks from JSON files")
675
+ return tasks
676
+
677
+ # Fallback: HuggingFace
678
+ from datasets import load_dataset
679
+ ds = load_dataset("arc-agi-community/arc-agi-2", split="train")
680
+ for i, row in enumerate(ds):
681
+ task_id = f"task_{i:04d}"
682
+ tasks[task_id] = {
683
+ "train": row["fewshots"],
684
+ "test": row["question"]
685
+ }
686
+ print(f"Loaded {len(tasks)} tasks from HuggingFace")
687
+ return tasks
688
+
689
+
690
+ # ============================================================
691
+ # Main
692
+ # ============================================================
693
+
694
+ def main():
695
+ print("=" * 60)
696
+ print("ARC-AGI-2 Solver — Kaggle Submission")
697
+ print("=" * 60)
698
+
699
+ config = Config()
700
+ solver = ARC_AGI_2_Solver(config)
701
+
702
+ # Load data
703
+ tasks = load_competition_data(config.INPUT_DIR)
704
+
705
+ if not tasks:
706
+ # Fallback to HF
707
+ tasks = load_competition_data(None)
708
+
709
+ # Load model (will fail gracefully on CPU)
710
+ try:
711
+ solver.load_model()
712
+ except Exception as e:
713
+ print(f"Model loading failed: {e}")
714
+ print("Running heuristic-only mode")
715
+
716
+ # Solve all tasks
717
+ submission = solver.solve_all(tasks)
718
+
719
+ # Save submission
720
+ os.makedirs(os.path.dirname(config.OUTPUT_FILE) if os.path.dirname(config.OUTPUT_FILE) else ".", exist_ok=True)
721
+ with open(config.OUTPUT_FILE, "w") as f:
722
+ json.dump(submission, f)
723
+ print(f"\nSubmission saved to {config.OUTPUT_FILE}")
724
+ print(f"Total tasks: {len(submission)}")
725
+
726
+ return submission
727
+
728
+
729
+ if __name__ == "__main__":
730
+ main()