The Puzzle

A standard Sudoku fills a 9×9 grid with digits. A Suirodoku fills the same grid with digits and colors — every cell contains both a number (1–9) and one of nine distinct colors.

Four rules define the game:

  • R1–R3 — Each row, column, and 3×3 block contains every digit exactly once and every color exactly once. Equivalent to two independent Sudoku on the same grid.
  • R4 — All 81 digit-color pairs are distinct. Since 9 × 9 = 81 cells and 9 × 9 = 81 possible pairs, the mapping from cells to pairs is a bijection.
Key Point

Rule R4 is the Graeco-Latin square orthogonality condition. This single global constraint is what makes Suirodoku fundamentally different from two overlaid Sudoku.

A complete Suirodoku grid showing all 81 unique digit-color pairs
A solved Suirodoku grid — every cell holds a unique digit-color pair.

The name comes from "Sudoku" with "iro" (, Japanese for "color") inserted. The puzzle draws from Euler's 1782 work on Graeco-Latin squares and the 1960 proof by Bose, Shrikhande, and Parker that they exist for all orders ≥ 3 except 2 and 6.

➡️ Quick Recap: Suirodoku = Sudoku + colors + global pair uniqueness. Four rules, one bijection, one hard puzzle.

The CSP Model — 243 Variables, 136 Constraints

A Suirodoku maps directly to a constraint satisfaction problem. The model uses three families of integer variables:

VariableCountDomain
digit[r][c]81{0, …, 8}
color[r][c]81{0, …, 8}
pair[r][c]81{0, …, 80}

And the constraint structure is remarkably clean — 55 AllDifferent constraints plus 81 linear encoding constraints:

ConstraintCount
AllDifferent(digits): rows + columns + blocks27
AllDifferent(colors): rows + columns + blocks27
AllDifferent(pairs): global, 81 variables1
Linear: pair = digit × 9 + color81
➡️ Quick Recap: Three variable families (digit, color, pair), 55 AllDifferent constraints, 81 linear links. That's the entire model.

The Encoding Trick — From 3,240 Constraints to One

The third variable family (pair) encodes each (digit, color) combination as a single integer:

k = digit × 9 + color

This maps the 81 possible pairs to {0, …, 80}. The global orthogonality condition — which would naively require 3,240 pairwise inequality constraints — reduces to one AddAllDifferent call on 81 variables.

Why this matters

CP-SAT handles AllDifferent natively with matching-based domain propagation. One AllDifferent on 81 variables propagates far more efficiently than 3,240 individual not-equal constraints. The solver prunes domains earlier and reaches solutions faster.

➡️ Quick Recap: Encoding pairs as digit × 9 + color turns a quadratic explosion into a single native constraint. Clean, efficient, elegant.

The Code — Under 40 Lines of Python

The complete model for generating a Suirodoku 9×9 grid:

from ortools.sat.python import cp_model import random model = cp_model.CpModel() n, b = 9, 3 # 81 digit variables + 81 color variables digit = [[model.NewIntVar(0, n-1, f'd_{r}_{c}') for c in range(n)] for r in range(n)] color = [[model.NewIntVar(0, n-1, f'k_{r}_{c}') for c in range(n)] for r in range(n)] # 27 AllDifferent on digits + 27 on colors for r in range(n): model.AddAllDifferent(digit[r]) model.AddAllDifferent(color[r]) for c in range(n): model.AddAllDifferent([digit[r][c] for r in range(n)]) model.AddAllDifferent([color[r][c] for r in range(n)]) for br in range(b): for bc in range(b): box = lambda v: [v[r][c] for r in range(br*b,(br+1)*b) for c in range(bc*b,(bc+1)*b)] model.AddAllDifferent(box(digit)) model.AddAllDifferent(box(color)) # 1 global AllDifferent on encoded pairs pair = [[model.NewIntVar(0, n*n-1, f'p_{r}_{c}') for c in range(n)] for r in range(n)] for r in range(n): for c in range(n): model.Add(pair[r][c] == digit[r][c] * n + color[r][c]) model.AddAllDifferent([pair[r][c] for r in range(n) for c in range(n)]) # Symmetry breaking: fix first row of digits for c in range(n): model.Add(digit[0][c] == c) # Solve solver = cp_model.CpSolver() solver.parameters.num_workers = 8 solver.parameters.max_time_in_seconds = 60 solver.parameters.random_seed = random.randint(0, 2**31-1) status = solver.Solve(model)
➡️ Quick Recap: The full model fits in under 40 lines of Python. OR-Tools does the heavy lifting — you just declare the variables and constraints.

Three Design Choices Worth Noting

Symmetry breaking

The constraint digit[0][c] = c fixes the first row of digits to (0, 1, 2, …, 8). Any Suirodoku can be relabeled to satisfy this, so no solutions are lost — but 9! = 362,880 equivalent search branches are eliminated.

Parallel search

With num_workers=8, CP-SAT runs multiple search strategies simultaneously using different heuristics. Combined with a random seed per attempt, this produces diverse solutions across repeated calls.

Timeout

60 seconds per attempt. If the solver doesn't find a solution, a new attempt starts with a fresh seed. Most successful solves complete in under 10 seconds.

➡️ Quick Recap: Symmetry breaking cuts the search space by 362,880×. Parallel workers with random seeds produce diverse grids. 60-second timeout keeps generation practical.
Coming Next — Part 2

What happens when this model runs at scale: exact enumeration, grid generation, and a surprising result about why most Sudoku can never become a Suirodoku.

Try the puzzle this solver generates.
Play Suirodoku free.

81 unique pairs
The bijection you just read about
Global leaderboard
Ranked play across 5 difficulties
Free, no ads
Play instantly in your browser
50 languages
Play in your language
Play Suirodoku

Frequently Asked Questions

What is CP-SAT?
CP-SAT is a constraint programming solver from Google OR-Tools. It combines SAT solving, constraint propagation, and linear programming to solve combinatorial optimization problems.
Why not use backtracking like most Sudoku solvers?
Backtracking works for standard Sudoku but struggles with the global pair uniqueness constraint. CP-SAT's AllDifferent propagation handles this natively, pruning the search space far more efficiently than trial-and-error backtracking.
Can I run this code myself?
Yes. Install OR-Tools (pip install ortools), copy the code, and run it. The source is also available on GitHub.

References: suirodoku.com · Zenodo: 10.5281/zenodo.18820236 · GitHub