|
| 1 | +""" |
| 2 | +Synthetic, deterministic grid-transformation task generator for Esper. |
| 3 | +
|
| 4 | +Esper targets ARC-AGI, where every task has a unique, machine-checkable |
| 5 | +ground-truth output grid -- i.e. an *objective reward* (exact pixel match). |
| 6 | +Real ARC tasks also need a full forward pass and a primitive library, which the |
| 7 | +engine does not have yet. This generator instead emits *single deterministic |
| 8 | +transforms* (recolor, flip, transpose, shift). Each task therefore has an |
| 9 | +unambiguous target grid, so the Evolution-Strategy loop can be scored objectively |
| 10 | +(negative MSE via `calculate_fitness`, exact-match % via `exact_match`) and we can |
| 11 | +prove the learning loop actually converges before tackling full ARC reasoning. |
| 12 | +
|
| 13 | +Grids are written in the exact `.bin` layout the Mojo engine reads |
| 14 | +(`load_arc_grid` in `arc_io.mojo`): two little-endian int64 (rows, cols) followed |
| 15 | +by the flattened grid as float32. We reuse `_save_grid` from `arc_compiler.py` |
| 16 | +so the writer stays the single source of truth for that format. |
| 17 | +
|
| 18 | +Usage: |
| 19 | + python src/synth_tasks.py --transform flip_h --out data_bin --count 8 |
| 20 | + python src/synth_tasks.py --transform recolor --rows 6 --cols 6 --seed 0 |
| 21 | +""" |
| 22 | + |
| 23 | +import argparse |
| 24 | +import os |
| 25 | +import random as _random |
| 26 | + |
| 27 | +# Reuse the canonical .bin writer so the on-disk format cannot drift from the |
| 28 | +# compiler / engine contract. |
| 29 | +from arc_compiler import _save_grid |
| 30 | + |
| 31 | +# ARC grids use integer "colors" 0-9. |
| 32 | +NUM_COLORS = 10 |
| 33 | + |
| 34 | + |
| 35 | +# --------------------------------------------------------------------------- |
| 36 | +# Deterministic transforms. Each takes a 2-D list-of-lists grid and returns a |
| 37 | +# new grid of the same shape (so input and target share dimensions, which keeps |
| 38 | +# the engine's fixed-size fast-weight buffer valid). |
| 39 | +# --------------------------------------------------------------------------- |
| 40 | +def _identity(grid): |
| 41 | + return [row[:] for row in grid] |
| 42 | + |
| 43 | + |
| 44 | +def _flip_h(grid): |
| 45 | + return [list(reversed(row)) for row in grid] |
| 46 | + |
| 47 | + |
| 48 | +def _flip_v(grid): |
| 49 | + return [row[:] for row in reversed(grid)] |
| 50 | + |
| 51 | + |
| 52 | +def _transpose(grid): |
| 53 | + # Square grids only keep the same shape under transpose; the generator |
| 54 | + # enforces rows == cols when this transform is selected. |
| 55 | + return [list(col) for col in zip(*grid)] |
| 56 | + |
| 57 | + |
| 58 | +def _recolor(grid): |
| 59 | + # Fixed cyclic palette shift: color c -> (c + 1) % NUM_COLORS. |
| 60 | + return [[(c + 1) % NUM_COLORS for c in row] for row in grid] |
| 61 | + |
| 62 | + |
| 63 | +def _shift(grid): |
| 64 | + # Roll every row right by one (wrap-around). Deterministic and shape-stable. |
| 65 | + return [[row[-1]] + row[:-1] for row in grid] |
| 66 | + |
| 67 | + |
| 68 | +TRANSFORMS = { |
| 69 | + "identity": _identity, |
| 70 | + "flip_h": _flip_h, |
| 71 | + "flip_v": _flip_v, |
| 72 | + "transpose": _transpose, |
| 73 | + "recolor": _recolor, |
| 74 | + "shift": _shift, |
| 75 | +} |
| 76 | + |
| 77 | + |
| 78 | +def _random_grid(rows, cols, rng): |
| 79 | + return [[rng.randrange(NUM_COLORS) for _ in range(cols)] for _ in range(rows)] |
| 80 | + |
| 81 | + |
| 82 | +def generate_tasks(transform, out_dir, count, rows, cols, seed): |
| 83 | + """ |
| 84 | + Generate `count` (input, target) grid pairs for `transform` and write them as |
| 85 | + `.bin` files into `out_dir`. Returns the list of (input_path, target_path) |
| 86 | + tuples written. |
| 87 | + """ |
| 88 | + if transform not in TRANSFORMS: |
| 89 | + raise ValueError( |
| 90 | + "Unknown transform %r; choose from %s" |
| 91 | + % (transform, ", ".join(sorted(TRANSFORMS))) |
| 92 | + ) |
| 93 | + if transform == "transpose" and rows != cols: |
| 94 | + raise ValueError("transpose requires a square grid (rows == cols)") |
| 95 | + |
| 96 | + os.makedirs(out_dir, exist_ok=True) |
| 97 | + fn = TRANSFORMS[transform] |
| 98 | + rng = _random.Random(seed) |
| 99 | + |
| 100 | + pairs = [] |
| 101 | + for i in range(count): |
| 102 | + grid_in = _random_grid(rows, cols, rng) |
| 103 | + grid_out = fn(grid_in) |
| 104 | + |
| 105 | + in_path = os.path.join(out_dir, "%s_%d_in.bin" % (transform, i)) |
| 106 | + out_path = os.path.join(out_dir, "%s_%d_out.bin" % (transform, i)) |
| 107 | + _save_grid(grid_in, in_path) |
| 108 | + _save_grid(grid_out, out_path) |
| 109 | + pairs.append((in_path, out_path)) |
| 110 | + |
| 111 | + return pairs |
| 112 | + |
| 113 | + |
| 114 | +def _parse_args(): |
| 115 | + p = argparse.ArgumentParser(description=__doc__) |
| 116 | + p.add_argument( |
| 117 | + "--transform", |
| 118 | + default="flip_h", |
| 119 | + choices=sorted(TRANSFORMS), |
| 120 | + help="Which deterministic transform maps input -> target.", |
| 121 | + ) |
| 122 | + p.add_argument("--out", default="data_bin", help="Output directory for .bin files.") |
| 123 | + p.add_argument("--count", type=int, default=8, help="Number of task pairs to emit.") |
| 124 | + p.add_argument("--rows", type=int, default=6, help="Grid rows.") |
| 125 | + p.add_argument("--cols", type=int, default=6, help="Grid cols.") |
| 126 | + p.add_argument("--seed", type=int, default=0, help="RNG seed (reproducible tasks).") |
| 127 | + return p.parse_args() |
| 128 | + |
| 129 | + |
| 130 | +if __name__ == "__main__": |
| 131 | + args = _parse_args() |
| 132 | + written = generate_tasks( |
| 133 | + args.transform, args.out, args.count, args.rows, args.cols, args.seed |
| 134 | + ) |
| 135 | + print( |
| 136 | + "Generated %d %s task pair(s) (%dx%d) in %s/" |
| 137 | + % (len(written), args.transform, args.rows, args.cols, args.out) |
| 138 | + ) |
| 139 | + for in_path, out_path in written: |
| 140 | + print(" %s -> %s" % (os.path.basename(in_path), os.path.basename(out_path))) |
0 commit comments