2

I'm specifically looking at implementing Durr, Hoyer, A Quantum Algorithm for Finding the Minimum in qiskit, finding the index with the smallest value in a list using grover's algorithm.

Say you have a list L, [1, 4, 0, 2] for example, and an oracle that takes the superposition of all possible indexes of that list, n. How do you write the oracle in such a way that you can use the value of n to get L[n]?

phildo
  • 21
  • 1

1 Answers1

0

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 (?)) enter image description here

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 : enter image description here

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 :

  1. You initialize the index qubits to a the superposition state
  2. You map the value to the index using CNOT gates
  3. You use an oracle on the value linked to the index
  4. You redo step 2 in order to inverse the value states to |0>
  5. 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):

enter image description here enter image description here enter image description here

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

enter image description here

This gives the probability :

enter image description here

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

Orphee Faucoz
  • 1,220
  • 5
  • 9