sudoku petri-net ode ddm

Sudoku Petri-Net Model

This post shows how Sudoku—a constraint satisfaction puzzle—can be modeled as a Petri net and analyzed using ODE simulation. We'll use a 4×4 mini-Sudoku to keep things comprehensible.

The Puzzle

  Initial:            Solution:
+---+---+---+---+    +---+---+---+---+
| 1 |   | . | . |    | 1 | 2 | 4 | 3 |
+---+---+---+---+    +---+---+---+---+
| . | . | 2 | . |    | 3 | 4 | 2 | 1 |
+===+===+===+===+ -> +===+===+===+===+
| . | 3 | . | . |    | 2 | 3 | 1 | 4 |
+---+---+---+---+    +---+---+---+---+
| . | . | . | 4 |    | 4 | 1 | 3 | 2 |
+---+---+---+---+    +---+---+---+---+

Constraints: Each row, column, and 2×2 block
             must contain digits 1-4 exactly once.

Petri Net Structure

The model has three layers:

Layer 1: CELLS         Layer 2: HISTORY        Layer 3: CONSTRAINTS
+-----------------+    +-----------------+     +-----------------+
| 16 cell places  | -> | 64 history      | --> | 12 collector    |
| (empty = token) |    | places          |     | transitions     |
+-----------------+    +-----------------+     +-----------------+
                                                       |
                                                       v
                                               +-------------+
                                               |   solved    |
                                               | (0-12 tok)  |
                                               +-------------+

Layer 1: Cell Places

Each cell holds a token when empty:

      C0    C1    C2    C3
    +-----+-----+-----+-----+
R0  | P00 | P01 | P02 | P03 |   P00=0 (has 1)
    |  0  |  1  |  1  |  1  |   P01=1 (empty)
    +-----+-----+-----+-----+
R1  | P10 | P11 | P12 | P13 |   P12=0 (has 2)
    |  1  |  1  |  0  |  1  |
    +-----+-----+-----+-----+
R2  | P20 | P21 | P22 | P23 |   P21=0 (has 3)
    |  1  |  0  |  1  |  1  |
    +-----+-----+-----+-----+
R3  | P30 | P31 | P32 | P33 |   P33=0 (has 4)
    |  1  |  1  |  1  |  0  |
    +-----+-----+-----+-----+

Layer 2: Digit Transitions & History

For each cell × digit combination, a transition writes the digit:

Transition D2_01:            History place _D2_01:
"Write digit 2 at (0,1)"     "Digit 2 is at (0,1)"

      +------+
P01 --| D2   |---> _D2_01
(1)   | _01  |     (0->1)
      +------+

Firing consumes cell token, creates history token.

Total: 16 cells × 4 digits = 64 digit transitions and 64 history places.

Layer 3: Constraint Collectors

When all 4 digits appear in a row/column/block, a collector fires:

Row 0 Complete:

  _D1_0? --+
  _D2_0? --+---> [Row0_Complete] ---> solved
  _D3_0? --+
  _D4_0? --+     (fires when row 0 has 1,2,3,4)

4 Row + 4 Column + 4 Block collectors = 12 total

The solved place accumulates tokens: 12 tokens = puzzle solved.

How It Works

Step-by-step for cell (0,1):

1. Initial: P01 = 1 (cell empty)

2. Player considers digit 2:
   Transition D2_01 is ENABLED (P01 has token)

3. Firing D2_01:
   • P01: 1 → 0 (cell now filled)
   • _D2_01: 0 → 1 (history records "2 at (0,1)")

4. When Row 0 has all digits placed:
   Row0_Complete fires → solved gets +1 token

ODE Simulation

Converting to ODEs lets us predict outcomes without exhaustive search:

For each candidate move:
  1. Create hypothetical state
  2. Run ODE simulation (t=0 to t=3.0)
  3. Measure token flow to 'solved' place
  4. Higher flow = move more likely leads to solution

Example: Evaluating Cell (0,1)

Cell (0,1) is empty. Which digit should go there?

Current Board:           Candidate Scores:
+---+---+---+---+        Digit  solved_flow
| 1 | ? | . | . |        -----  -----------
+---+---+---+---+          1    blocked (row has 1)
| . | . | 2 | . |          2    0.31  <-- best
+===+===+===+===+          3    blocked (block has 3)
| . | 3 | . | . |          4    0.28
+---+---+---+---+
| . | . | . | 4 |
+---+---+---+---+

ODE shows digit 2 produces highest flow to 'solved'.

The model structure encodes all constraints—conflicts appear as zero-flow transitions, valid moves as positive flow.

The Full Picture

+-------------------------------------------------------------+
|                    4x4 SUDOKU PETRI NET                     |
+-------------------------------------------------------------+
|                                                             |
|  CELLS (16)         HISTORY (64)          SOLVED            |
|  +--+--+--+--+      +-------------+      +--------+         |
|  |  |  |  |  |      | _D1_00..03  |      |        |         |
|  +--+--+--+--+  D#  | _D2_00..03  |  R0  |        |         |
|  |  |  |  |  | ---> | _D3_00..03  | ---> |  0-12  |         |
|  +--+--+--+--+  64  | _D4_00..03  |  C0  | tokens |         |
|  |  |  |  |  | txns |     ...     |  B0  |        |         |
|  +--+--+--+--+      | (64 places) |  12  |        |         |
|  |  |  |  |  |      +-------------+ txns +--------+         |
|  +--+--+--+--+                                              |
|                                                             |
|  Token = empty          Token = digit placed                |
|  No token = filled      12 tokens = solved                  |
+-------------------------------------------------------------+

Scaling to 9×9

The same pattern scales:

Component 4×4 9×9
Cell places 16 81
Digits 4 9
History places 64 729
Digit transitions 64 729
Row collectors 4 9
Column collectors 4 9
Block collectors 4 9
Max solved tokens 12 27

Tuning the ODE

A naive ODE simulation of the full 9×9 model (729 transitions!) would be prohibitively slow. We tune for insight, not precision:

Parameter         Default    Tuned      Why
─────────────────────────────────────────────────────
Time horizon      t=10.0     t=3.0      We only need relative rankings
Abstol            1e-6       1e-4       Looser tolerance, faster solve
Reltol            1e-3       1e-3       Already reasonable
Step size (dt)    0.01       0.2        Larger steps, fewer iterations
Max iterations    100000     1000       Early termination OK

The Key Insight

We don't need the ODE to converge to exact token counts. We need it to rank candidates:

Move A: solved flow = 0.31  ← Best
Move B: solved flow = 0.28
Move C: solved flow = 0.15
Move D: blocked (conflict)

Short simulations with loose tolerances still preserve ordering. A move that produces higher flow at t=3.0 will generally produce higher flow at t=10.0.

Performance Impact

Model Naive Tuned Speedup
4×4 (64 transitions) 0.8s 0.04s 20×
Tic-tac-toe (34 transitions) 2.1s 0.1s 21×
9×9 (729 transitions) ~60s ~3s 20×

This makes interactive analysis feasible—evaluating all candidates for a cell takes seconds, not minutes.

Why This Matters

  1. No hardcoded solver — constraints emerge from topology
  2. ODE reveals structure — flow patterns show which moves lead to solutions
  3. Composable — same pattern works for any constraint satisfaction problem
  4. Analyzable — can prove properties about reachability and deadlocks

Try It

cd examples/sudoku
go run ./cmd --size 4x4 --ode --analyze

Data generated with go-pflow

For more on this approach: Declarative Differential Models