The same puzzle, two methods

We solved one easy puzzle (35 empty cells) twice with the 8B LoRA from post 3 (15 cycles).

  • Run 1: for digits 1 through 9 in turn, the VLM lists the cells where the digit cannot go, and any cell that is the only place left is filled.
  • Run 2: the same method, but every time a cell is filled, the code immediately fills any box, row or column with only one empty cell left.

Run 1: stopped with 2 cells left

Over 16 judgements it made 38 moves, and 33 were correct. It stopped with 2 empty cells left, unable to find anywhere else to play. Of the 16 judgements, 9 listed the impossible cells without missing any.

All 5 wrong moves came from wrongly marking a possible cell as impossible. With one more place crossed out, a wrong cell was left as the only place. The demo did not put wrong moves on the board; it only recorded them.

Run 2: finished in 10 judgements

Run 2 filled all 35 cells in 10 judgements. The VLM's judgements filled 15 cells, the code then filled 20, and there were no wrong cells. 8 of the 10 judgements were complete, but the missed cells did not change any answer.

When digit 2 filled two cells, boxes, rows and columns with a single empty cell appeared, and the code filled five cells in a row. Digit 7 and digit 3 in the second round also chained five cells each. With fewer judgements to make, the model had fewer chances to be wrong.

The order cells were filled in run 2. Gold is the VLM's judgement, green is the code
The order cells were filled in run 2. Gold is the VLM's judgement, green is the code

Earlier answers make the next ones

The model from post 1 filled row by row from the top left and never used earlier answers as the basis for the next cell. Run 2 fills cells scattered across the board, and each newly filled cell becomes the basis for the next. That is the same order in which people solve.

Remaining empty cells by number of judgements on the same puzzle
Remaining empty cells by number of judgements on the same puzzle

The actual code for this step

This is the code that runs after each VLM judgement in run 2. If a box, row or column has only one empty cell left, it fills it with the missing digit.

vlm_digit_strategy.pypython · 55 lines
def _missing_one(values: list[int]) -> int | None:
    present = {v for v in values if v != 0}
    miss = [d for d in range(1, 10) if d not in present]
    return miss[0] if len(miss) == 1 else None


def last_empty_unit_moves(
    board: list[list[int]],
) -> list[tuple[int, int, int, str]]:
    """박스·행·열에 빈칸이 정확히 1개면 빠진 숫자로 채움."""
    found: dict[tuple[int, int], tuple[int, str]] = {}

    def add(r: int, c: int, digit: int, reason: str) -> None:
        key = (r, c)
        if key not in found:
            found[key] = (digit, reason)
        elif found[key][0] == digit:
            prev = found[key][1]
            if reason not in prev:
                found[key] = (digit, f"{prev}+{reason}")

    for br in range(3):
        for bc in range(3):
            cells = [
                (r, c)
                for r in range(br * 3, br * 3 + 3)
                for c in range(bc * 3, bc * 3 + 3)
            ]
            empties = [(r, c) for r, c in cells if board[r][c] == 0]
            if len(empties) != 1:
                continue
            digit = _missing_one([board[r][c] for r, c in cells])
            if digit is not None:
                add(empties[0][0], empties[0][1], digit, "code_last_box")

    for r in range(9):
        empties = [(r, c) for c in range(9) if board[r][c] == 0]
        if len(empties) != 1:
            continue
        digit = _missing_one([board[r][c] for c in range(9)])
        if digit is not None:
            add(empties[0][0], empties[0][1], digit, "code_last_row")

    for c in range(9):
        empties = [(r, c) for r in range(9) if board[r][c] == 0]
        if len(empties) != 1:
            continue
        digit = _missing_one([board[r][c] for r in range(9)])
        if digit is not None:
            add(empties[0][0], empties[0][1], digit, "code_last_col")

    return [
        (r, c, digit, reason)
        for (r, c), (digit, reason) in sorted(found.items())
    ]

The next post is "5. An AI that helps people".