Avra98's picture
Initial code dump (rebuttal-ready snapshot)
76de008 verified
from __future__ import annotations
import argparse
import json
import random
from pathlib import Path
from typing import Iterable, List, Sequence, Tuple
import numpy as np
GRID_SIZE = 9
BOX_SIZE = 3
ALL_VALUES = tuple(range(1, 10))
def parse_args() -> argparse.Namespace:
root = Path(__file__).resolve().parent.parent
default_output = root / "data" / "sudoku_t3_30empty_value_qwen_text.jsonl"
p = argparse.ArgumentParser()
p.add_argument("--output", type=str, default=str(default_output))
p.add_argument("--num_puzzles", type=int, default=20000)
p.add_argument("--empties", type=int, default=30)
p.add_argument("--seed", type=int, default=0)
return p.parse_args()
def permute_groups(rng: random.Random, values: Sequence[int], group_size: int) -> List[int]:
groups = [list(values[idx : idx + group_size]) for idx in range(0, len(values), group_size)]
rng.shuffle(groups)
out: List[int] = []
for group in groups:
rng.shuffle(group)
out.extend(group)
return out
def base_grid() -> np.ndarray:
return np.asarray(
[[((rr * BOX_SIZE + rr // BOX_SIZE + cc) % GRID_SIZE) + 1 for cc in range(GRID_SIZE)] for rr in range(GRID_SIZE)],
dtype=int,
)
def random_solved_grid(rng: random.Random) -> np.ndarray:
grid = base_grid().copy()
digits = list(ALL_VALUES)
rng.shuffle(digits)
digit_map = {src: dst for src, dst in zip(ALL_VALUES, digits, strict=True)}
grid = np.vectorize(lambda value: digit_map[int(value)], otypes=[int])(grid)
row_order = permute_groups(rng, list(range(GRID_SIZE)), BOX_SIZE)
col_order = permute_groups(rng, list(range(GRID_SIZE)), BOX_SIZE)
grid = grid[row_order, :]
grid = grid[:, col_order]
if rng.random() < 0.5:
grid = grid.T
return np.asarray(grid, dtype=int)
def row_major_empty_locs(grid: np.ndarray) -> List[Tuple[int, int]]:
return [(int(r), int(c)) for r, c in np.argwhere(np.asarray(grid, dtype=int) == 0).tolist()]
def make_prompt(grid: np.ndarray) -> str:
tuples = [f"({r + 1},{c + 1},{int(grid[r, c])})" for r in range(GRID_SIZE) for c in range(GRID_SIZE)]
return (
"9x9 Sudoku board encoded as (row,col,value) tuples in row-major order.\n"
"Value 0 means the cell is empty.\n"
+ " ".join(tuples)
)
def make_example(solved: np.ndarray, *, empties: int, rng: random.Random) -> dict:
if empties <= 0 or empties >= GRID_SIZE * GRID_SIZE:
raise ValueError(f"empties must be between 1 and {GRID_SIZE * GRID_SIZE - 1}")
cells = list(range(GRID_SIZE * GRID_SIZE))
rng.shuffle(cells)
masked_cells = sorted(cells[:empties])
puzzle = np.asarray(solved, dtype=int).copy()
for cell in masked_cells:
rr, cc = divmod(int(cell), GRID_SIZE)
puzzle[rr, cc] = 0
empty_locs_1based = [(rr + 1, cc + 1) for rr, cc in row_major_empty_locs(puzzle)]
target_triples_1based = [(rr + 1, cc + 1, int(solved[rr, cc])) for rr, cc in row_major_empty_locs(puzzle)]
completion_values = [int(value) for _, _, value in target_triples_1based]
return {
"prompt": make_prompt(puzzle),
"completion": json.dumps(completion_values, separators=(",", ":")),
"metadata": {
"grid_size": GRID_SIZE,
"box_size": BOX_SIZE,
"empties": int(empties),
"empty_locs_1based": empty_locs_1based,
"target_triples_1based": target_triples_1based,
},
}
def generate_examples(num_puzzles: int, *, empties: int, seed: int) -> Iterable[dict]:
rng = random.Random(int(seed))
for _ in range(int(num_puzzles)):
solved = random_solved_grid(rng)
yield make_example(solved, empties=int(empties), rng=rng)
def main() -> None:
args = parse_args()
output_path = Path(args.output).resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
with output_path.open("w", encoding="utf-8") as f:
for row in generate_examples(args.num_puzzles, empties=args.empties, seed=args.seed):
f.write(json.dumps(row, separators=(",", ":")) + "\n")
print(f"Wrote {int(args.num_puzzles)} puzzles to {output_path}")
if __name__ == "__main__":
main()