I will answer this question since I actually was wondering how to do it and found a way to solve it.
First of all, this is not an actual good use of grover algorithm from what I understand, specifically because Grover is not made to search a list but to search a computing space. The O(sqrt(N))
complexity gain over O(N)
is actually achieved when you want to find a specific result of a function but if you do it on an actual database you need to store the value of the list within the quantum circuit and it will take O(N)
.
But I guess that on some specific cases where the database is already quantum prepared and the search space is hard to compute on classical computers, then the following implementation may be useful.
Now to the problem.
You may already know how to mark a value using the Grover algorithm, it is actually well explained on the Qiskit tutorial.
To recap : you initialize a superposed state, you mark the result with an oracle and then you use a diffusion step to shift the states -- and you repeat it a number of times to get a proper amplitude.
But in your case you also want to map this value to a list index. To do so you can use a quantum register between your list value and list index.
Let say that you have a 3x3
matrix with each box containing a value between 0 and 8 :
import numpy as np
import random
from random import sample
rows = 3
cols = 3
arr = np.zeros((rows, cols))
samples = list(np.arange(0, rows*cols))
sample_list = sample(samples, k=rows*cols)
real = random.choice(sample_list)
for i in range(0, rows):
for j in range(0, cols):
arr[i, j] = sample_list[i*cols + j]
You want to find a value inside this matrix (let say 3 in position (?))

You want to first map each index of the list representing the matrix to their value using a quantum register which is well designed CNOT gate with different control state
This can be not so easy to see but this is a quantum register :

where R(00)
is linked to V(11)
, R(10)
to V(10)
, R(01)
to V(01)
and R(11)
to V(00)
Obviously you can map whatever value you want.
So, you have your list with value linked to index, now you how do you apply Grover on the value? Those are the overall step :
- You initialize the index qubits to a the superposition state
- You map the value to the index using CNOT gates
- You use an oracle on the value linked to the index
- You redo step 2 in order to inverse the value states to |0>
- You use the diffusion operation on the index space
and then this is just a repetition of 2 to 5 steps.
Let's say that you have an oracle specifically linked to a value :
#clause = 3
def oracle_n(qc, clause, clause_qbits, n, output):
clause_binary = format(clause, f'0{n}b')[::-1] # 0011
qc.append(circuit.library.MCXGate(n, ctrl_state=clause_binary), clause_qbits[0:n] + [output])
The diffuser is the same as the one from the qiskit tutorial :
def diffuser(nqubits):
qc = QuantumCircuit(nqubits)
# Apply transformation |s> -> |00..0> (H-gates)
for qubit in range(nqubits):
qc.h(qubit)
# Apply transformation |00..0> -> |11..1> (X-gates)
for qubit in range(nqubits):
qc.x(qubit)
# Do multi-controlled-Z gate
qc.h(nqubits-1)
qc.mct(list(range(nqubits-1)), nqubits-1) # multi-controlled-toffoli
qc.h(nqubits-1)
# Apply transformation |11..1> -> |00..0>
for qubit in range(nqubits):
qc.x(qubit)
# Apply transformation |00..0> -> |s>
for qubit in range(nqubits):
qc.h(qubit)
# We will return the diffuser as a gate
return qc
And a step to map matrix to indices :
def forward_qram_n(qc, register_qubits, data, n, matrix):
for i in range(0, len(matrix)):
mask = format(matrix[i], f'0{n}b')
mask_i = format(i, f'0{n}b')
integer_bit = 0
for bit in mask_i:
if bit == '0':
qc.x(register_qubits[integer_bit])
integer_bit += 1
integer_bit = 0
for bit in mask:
if bit == '1':
qc.append(circuit.library.MCXGate(n), register_qubits[0:n] + [data[integer_bit]])
integer_bit += 1
integer_bit = 0
for bit in mask_i:
if bit == '0':
qc.x(register_qubits[integer_bit])
integer_bit += 1
qc.barrier()
Then you just have to follow the steps I mentionned :
n = math.ceil(math.log(rows*cols, 2)) # 4
register_qubits = QuantumRegister(n, name='r') # 4 qbits for register
var_qubits = QuantumRegister(n, name='v') # 4 qbits for value
output_qubit = QuantumRegister(1, name='out') # 1 qbit for output
cbits = ClassicalRegister(n+n, name='cbits') # 8 cbits for register + value
qc = QuantumCircuit(register_qubits, var_qubits, output_qubit, cbits)
qc.initialize([1, -1]/np.sqrt(2), output_qubit)
qc.h(register_qubits)
qc.barrier() # for visual separation
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
clause = real # 3
oracle_n(qc, clause, var_qubits, n, output_qubit)
qc.barrier()
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier() # for visual separation
qc.append(diffuser(n), register_qubits)
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier()
oracle_n(qc, clause, var_qubits, output_qubit)
qc.barrier()
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier()
qc.append(diffuser(n), register_qubits)
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier()
oracle_n(qc, clause, var_qubits, n, output_qubit)
qc.barrier()
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier()
qc.append(diffuser(n), register_qubits)
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier()
oracle_n(qc, clause, var_qubits, n, output_qubit)
qc.barrier()
forward_qram_n(qc, register_qubits, var_qubits, n, sample_list)
qc.barrier()
qc.append(diffuser(n), register_qubits)
qc.measure(register_qubits, cbits[0:n])
qc.measure(var_qubits, cbits[n:n+n])
qc = qc.reverse_bits()
qc.draw('mpl')
This will give you the following circuit (actually too long I will crop it):

A certain number of times and will end with the last diffusion :

This gives the probability :

Which is the good guess (0101 => 5 is the 6th position and this is how we encoded 3).