diff --git a/AGENTS.md b/AGENTS.md index 50e837e..3b41d9d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 + ``` diff --git a/arc_solver/dsl.py b/arc_solver/dsl.py index 041def1..0e38066 100644 --- a/arc_solver/dsl.py +++ b/arc_solver/dsl.py @@ -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 diff --git a/arc_solver/neural/episodic.py b/arc_solver/neural/episodic.py index ac51295..b36c364 100644 --- a/arc_solver/neural/episodic.py +++ b/arc_solver/neural/episodic.py @@ -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) diff --git a/tests/test_translate_fix.py b/tests/test_translate_fix.py index a1dc69d..98e7a97 100644 --- a/tests/test_translate_fix.py +++ b/tests/test_translate_fix.py @@ -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(): @@ -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) +