ARC-AGI / itt_solver /solver_core.py
rogermt's picture
Fix #2: Clean up duplicate imports and paste blocks in solver_core.py
6062e9a verified
import numpy as np
from collections import Counter, deque
def initialize_potential(grid):
return np.array(grid, dtype=float)
def discrete_gradient(phi):
gx = np.zeros_like(phi)
gy = np.zeros_like(phi)
gx[:-1, :] = phi[1:, :] - phi[:-1, :]
gy[:, :-1] = phi[:, 1:] - phi[:, :-1]
mag = np.sqrt(gx**2 + gy**2)
return gx, gy, mag
def dirichlet_energy(phi):
_, _, mag = discrete_gradient(phi)
return np.sum(mag**2)
def sigma_l1(phi_current, phi_target):
return np.sum(np.abs(phi_target - phi_current))
def tile_transform(phi_in, out_shape):
h_in, w_in = phi_in.shape
h_out, w_out = out_shape
out = np.zeros(out_shape, dtype=float)
for i in range(h_out):
for j in range(w_out):
out[i, j] = phi_in[i % h_in, j % w_in]
return out
def fill_enclosed(phi, boundary_mask=None):
"""
Fill zero regions that are fully enclosed by non-zero boundary.
Returns a new array with enclosed holes filled with modal neighbor color.
"""
arr = np.array(phi, dtype=int)
h, w = arr.shape
if boundary_mask is None:
boundary = (arr != 0)
else:
boundary = np.array(boundary_mask, dtype=bool)
visited = np.zeros((h, w), dtype=bool)
filled = arr.copy()
def flood(start_r, start_c):
q = deque()
q.append((start_r, start_c))
comp = []
touches_border = False
visited[start_r, start_c] = True
while q:
r, c = q.popleft()
comp.append((r, c))
if r == 0 or c == 0 or r == h-1 or c == w-1:
touches_border = True
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
nr, nc = r+dr, c+dc
if 0 <= nr < h and 0 <= nc < w and not visited[nr, nc]:
if arr[nr, nc] == 0:
visited[nr, nc] = True
q.append((nr, nc))
return comp, touches_border
for i in range(h):
for j in range(w):
if arr[i, j] == 0 and not visited[i, j]:
comp, touches_border = flood(i, j)
if not touches_border:
neighbor_vals = []
for (r, c) in comp:
for dr, dc in ((1,0),(-1,0),(0,1),(0,-1)):
nr, nc = r+dr, c+dc
if 0 <= nr < h and 0 <= nc < w:
v = arr[nr, nc]
if v != 0:
neighbor_vals.append(int(v))
if neighbor_vals:
mode_color = Counter(neighbor_vals).most_common(1)[0][0]
else:
mode_color = 1
for (r, c) in comp:
filled[r, c] = mode_color
return filled
class Transform:
def __init__(self, func, name, params=None):
self.func = func
self.name = name
self.params = params or {}
def apply(self, phi):
return self.func(phi, **self.params)
def compose(self, other):
def composed(phi):
return other.apply(self.apply(phi))
return Transform(lambda p: composed(p), f"{self.name}{other.name}")
def __repr__(self):
return f"<Transform {self.name}>"
def beam_minimize(phi_in, phi_target, atomic_library, beam_width=4, max_depth=4, lock_coeff=0.1):
identity = Transform(lambda p: p, "Id")
beam = [(identity, phi_in, 0.0)]
best = None
for depth in range(max_depth):
candidates = []
for T_cur, _, _ in beam:
for T_atomic in atomic_library:
T_new = T_cur.compose(T_atomic)
phi_new = T_new.apply(phi_in)
residue = sigma_l1(phi_new, phi_target)
energy = dirichlet_energy(phi_new)
score = residue + lock_coeff * energy
candidates.append((T_new, phi_new, score))
if not candidates:
break
candidates.sort(key=lambda x: x[2])
beam = candidates[:beam_width]
best = beam[0]
if sigma_l1(best[1], phi_target) == 0:
break
return best[0], best[1]