Grover search: sudoku solver

In this example we are going to solve a sudoku puzzle. Since we have few qubits, we cannot solve a real 9x9 sudoku puzzle; our toy examples uses a 2x2 matrix where a valid solution is when in every row and every column there are no repeated values (0 or 1). We encode these xor-ing the values for each row and column. Since we want a specific solution, we add a constraint constr: we want the [0][0] element to be True.

sudoku_check is already an oracle so we can instantiate the Grover algorithm without value.

from qlasskit import qlassf, Qmatrix
from qlasskit.algorithms import Grover


@qlassf
def sudoku_check(m: Qmatrix[bool, 2, 2]) -> bool:
    constr = m[0][0]
    sub0 = m[0][0] ^ m[0][1]
    sub1 = m[1][0] ^ m[1][1]
    sub2 = m[0][0] ^ m[1][0]
    sub3 = m[0][1] ^ m[1][1]
    return sub0 and sub1 and sub2 and sub3 and constr


q_algo = Grover(sudoku_check)

Then we use our prefered framework and simulator for sampling the result; this is an example using qiskit with aer_simulator.

We obtain that the solution for this puzzle is the matrix [[True, False], [False, True]].

from qiskit import QuantumCircuit, transpile
from qiskit.visualization import plot_histogram
from qiskit_aer import AerSimulator

qc = q_algo.export("qiskit")
qc.measure_all()
simulator = AerSimulator()
circ = transpile(qc, simulator)
result = simulator.run(circ).result()
counts = result.get_counts(circ)

counts_readable = q_algo.decode_counts(counts, discard_lower=20)

plot_histogram(counts_readable)
_images/9f08912e4e65a804eca4fb134847c8a3bc12317434a1ed0644e867d3c9c329a4.png

We can exploit matplotlib for drawing the result sudoku matrix as follows:

import matplotlib.pyplot as plt


def draw_matrix(matrix):
    fig, ax = plt.subplots()
    ax.matshow(matrix, cmap="viridis")

    for i in range(len(matrix)):
        for j in range(len(matrix[0])):
            ax.text(
                j,
                i,
                str(matrix[i][j]),
                va="center",
                ha="center",
                fontsize=12,
                color="black",
            )

    plt.show()


m_res = list(filter(lambda x: x[1] > 500, counts_readable.items()))[0][0]
draw_matrix(m_res)
_images/b211163ef807d483c21a42e982d6e7cc7a9e840405a6300666fcf33cb675de7d.png

We can create a more realistic sudoku game using numbers instead of booleans, but the resources required will scale exponentially. In the following code snippets, we recreate sudoku_check using Qint[2] and a 4x4 matrix. The sum of each column and row must be equal to 6 (3+2+1+0). As we can see, the resulting circuit of the checker requires more than 100 qubits, way above our simulation capabilities.

from qlasskit import Qint, Qint3


@qlassf
def sudoku_check(m: Qmatrix[Qint[2], 4, 4]) -> bool:
    res = True

    # Constraints
    res = (m[0][2] == 3) and (m[0][0] == 1)

    # Check every row and column
    for i in range(len(m)):
        c = (Qint3(0) + m[i][0] + m[i][1] + m[i][2] + m[i][3]) == 6
        r = (Qint3(0) + m[0][i] + m[1][i] + m[2][i] + m[3][i]) == 6
        res = res and c and r

    return res


print(sudoku_check.circuit())
QCircuit<sudoku_check>(725 gates, 130 qubits)