Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,11 @@ class MetaCognition:
Date: 2025-09-13
Test Result: pytest tests/test_translate_fix.py passed; python tools/train_guidance_on_arc.py --epochs 1
Notes: Canonicalised 'fill' parameter for translate; legacy 'fill_value' still accepted
[X] Step 4.3 UPDATE3 - Translate/recolor params normalised to integers preventing training failures
Date: 2025-09-13
Test Result: pytest tests/test_translate_fix.py tests/test_recolor_fix.py -q
Notes: Episode loader and DSL cast dy/dx/fill and mapping entries to int



```
Expand Down
23 changes: 13 additions & 10 deletions arc_solver/dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,19 @@ def op_pad(a: Array, out_h: int, out_w: int) -> Array:


def _canonical_params(name: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""Return a copy of ``params`` with legacy aliases normalised."""
if name == "recolor" and "mapping" not in params and "color_map" in params:
new_params = dict(params)
new_params["mapping"] = new_params.pop("color_map")
return new_params
if name == "translate" and "fill" not in params and "fill_value" in params:
new_params = dict(params)
new_params["fill"] = new_params.pop("fill_value")
return new_params
return params
"""Return a copy of ``params`` with legacy aliases normalised and typed."""
new_params = dict(params)
if name == "recolor":
mapping = new_params.get("mapping") or new_params.pop("color_map", {})
if mapping:
new_params["mapping"] = {int(k): int(v) for k, v in mapping.items()}
elif name == "translate":
if "fill" not in new_params and "fill_value" in new_params:
new_params["fill"] = new_params.pop("fill_value")
for key in ("dy", "dx", "fill"):
if key in new_params and new_params[key] is not None:
new_params[key] = int(new_params[key])
return new_params



Expand Down
6 changes: 6 additions & 0 deletions arc_solver/neural/episodic.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ def from_dict(cls, data: Dict[str, Any]) -> "Episode":
if op == "recolor":
mapping = params.get("mapping") or params.get("color_map") or {}
params = {"mapping": {int(k): int(v) for k, v in mapping.items()}}
elif op == "translate":
clean = {k: int(v) for k, v in params.items() if v is not None}
if "fill_value" in clean and "fill" not in clean:
clean["fill"] = clean.pop("fill_value")
params = clean

prog_ops.append((op, params))
programs.append(prog_ops)

Expand Down
25 changes: 25 additions & 0 deletions tests/test_translate_fix.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import json

from pathlib import Path
import sys

import numpy as np
from hypothesis import given, strategies as st


sys.path.append(str(Path(__file__).resolve().parents[1]))
from arc_solver.dsl import apply_program
from arc_solver.heuristics_complete import detect_basic_transformations
from arc_solver.neural.episodic import Episode



def test_translate_fill_value_alias_and_detection():
Expand All @@ -19,3 +25,22 @@ def test_translate_fill_value_alias_and_detection():
# heuristics now emit canonical parameter name
detected = detect_basic_transformations(inp, expected)
assert [("translate", {"dy": 1, "dx": 1, "fill": 0})] in detected


@given(st.integers(-3, 3), st.integers(-3, 3), st.integers(0, 9))
def test_episode_translate_roundtrip(dy: int, dx: int, fill: int) -> None:
"""Episode serialisation normalises translate parameters to ints."""
inp = np.array([[1]])
out = apply_program(inp, [("translate", {"dy": dy, "dx": dx, "fill": fill})])
episode = Episode(
task_signature="sig",
programs=[[('translate', {'dy': str(dy), 'dx': str(dx), 'fill_value': str(fill)})]],
train_pairs=[(inp, out)],
)
data = json.loads(json.dumps(episode.to_dict()))
loaded = Episode.from_dict(data)
op, params = loaded.programs[0][0]
assert op == 'translate'
assert all(isinstance(params[k], int) for k in ('dy', 'dx', 'fill'))
assert np.array_equal(apply_program(inp, [(op, params)]), out)

Loading