Reading Week 7 - Quantum Algorithms
Ch 7.3: Deutsch-Jozsa Oracle
An oracle is a black-box function that implements a complex algorithm to find an answer to a complex problem. However, the answer has to be only a "Yes" or "No" (ie: a decision problem). Deutsch-Jozsa oracle is one such function.
7.3.1: Deutsch Oracle
Given some function
Constant functions:
notice that all of the possible constant functions are just that. Constants. Given any
For balanced functions:
which is either the identity function, or the bit-flip function.
7.3.2: Deutsch-Jozsa Oracle
Another layer of complexity! The Deutsch-Jozsa oracle is a generalization of the Deutsch oracle for a string of
Classically it's easy to solve the problem. Repeatedly call
For half of the inputs, if we get a 0 (or 1) then we need to call the function one more time to make sure it returns a 1 (or a 0) to be a balanced function. We have to call the function
For the quantum version we have the following:
- This ciruit uses an
qubit data register and an ancilla qubit - The black box
implements the oracle function
We do the following:
- Start the system with the data register and acnilla qubit initialized to
's
- Apply an
gate to the ancilla qubit to flip it to a . We have - Apply
to both the data register bits and the ancilla bits. For the data register, in general:
As such, for the
Which we can rewrite as:
where
As such, for our steps above we have:
getting back to our circuit, apply
By applying the oracle function
Focus on the term
- If
then this term evaluates to - If
then this term evaluates to
Thus we can write this term as
The ancilla qubit is unchanged from this operation!
We don't need the ancilla qubit so we can drop it. The final step is to apply
At this state we are ready to measure the data register. We just want to make sure (really we only care about) that the data in there projects to
Whose probability is:
As a 4 qubit data register input:
The truth table for the balanced function would be:
Notice here that the output
For a constant function, we can just:
- If the ancilla qubit is
apply and get - If it's a
instead, don't do anything
This is a constant function of just.
import qiskit
import math
from qiskit import *
from qiskit import IBMQ
from qiskit.visualization import plot_histogram, plot_gate_map
from qiskit.visualization import plot_circuit_layout
from qiskit.visualization import plot_bloch_multivector
from qiskit import QuantumRegister, ClassicalRegister, transpile
from qiskit import QuantumCircuit, execute, Aer
from qiskit.providers.ibmq import least_busy
from qiskit.tools.monitor import job_monitor
%matplotlib inline
%config InlineBackend.figure_format = 'svg'
# load the IBM Quantum account
account = IBMQ.load_account()
# Get the provider and the backend
provider = IBMQ.get_provider(group='open')
backend = Aer.get_backend('qasm_simulator')
# The balanced function
# qc – the quantum circuit
# n – number of bits to use
# input – the input bit string
def balanced_func (qc, n, input):
# first apply the input as a bitmap to the data register
for i in range (0, input.bit_length()):
if ((1 << i) & input ):
qc.x(i)
qc.barrier()
#setup the CNOT gates
for i in range (n):
qc.cx(i, n)
qc.barrier()
# apply the input one more time to restore state
for i in range (0, input.bit_length()):
if ((1 << i) & input ):
qc.x(i)
# The constant function
# qc – the quantum circuit
# n – number of bits to use
# constant – set this to true for constant functions.
# False if not.
def constant_func (qc,n,constant=True):
qc.barrier()
# just apply a NOT gate to the ancilla qubit to output 1
if (True == constant):
qc.x(n)
# The Deutsch Jozsa algorithm
# qc – the quantum circuit
# n – the number of bits to use
# input – the input bit string
# function – set this to “constant” for constant functions.
# constant – set this to True for constant functions.
def deutsch_jozsa(qc, n, input, function, constant = True):
# set the ancilla qubit to state 1.
qc.x(n)
#setup the H gates for both data register and ancilla qubit
for i in range (n+1):
qc.h(i)
# call constant or balanced function as required
if ("constant" == function ):
constant_func(qc,n,constant)
else:
balanced_func(qc, n,input )
qc.barrier()
#setup the H gates for the data register and setup measurement
for i in range (n):
qc.h(i)
qc.barrier()
#measure the data register
for i in range (n):
qc.measure(i,i)
### MAIN CODE ###
number = 15 # The hidden string
numberofqubits = 5
shots = 1024
q = QuantumRegister(numberofqubits, 'q')
c = ClassicalRegister(numberofqubits, 'c')
qc = QuantumCircuit(q,c)
deutsch_jozsa(qc, numberofqubits - 1, number, "constant", True)
#qc.draw('mpl')
backend = Aer.get_backend("qasm_simulator")
job = execute(qc, backend=backend, shots=shots)
counts = job.result().get_counts()
plot_histogram(counts)
- Flip the ancilla bit to
using an gate. - Apply
to all the data bits , along with for our ancilla. - Apply our oracle function
- Re-Hadamard our
bits. If we get a it is balanced. If we get a it is constant.
Ch 7.8: Grover's Search Algorithm
This algorithm uses amplitude amplification, a new technique that will be apparent in other algorithms. It specifically exhibits a quadratic speedup.
The algorithm is a search algorithm. Classical search algorithms would take
- Our database isn't guarunteed to be sorted.
- Takes a number corresponding to an entry in the database and performs a test to see if it's in there
- Takes about
steps so it's .
Given a set of
- The element
is unique, so only one has it that - The size of the database is a power of 2, namely
. Padding can help us achieve this. - The data is labeled as
-bit Boolean strings - The boolean function
maps
Namely we can change
Let's assume we define an oracle function
Sadly this isn't unitary. It takes an
As such, let's try to define our oracle function
Consider the block diagram below:
Here, we start with all the qubits at
All the qubits have the same amplitude
You can see this effect on the top bar graph.
Now that the amplitude of
where here
Hence, the amplitude for most terms is about
We can now move on to the next part. This part is called the Grover diffusion operator, or the amplitude purification. It does the following:
In our example, all non
Both the steps of
We can see that for the
This is big! A classical implementation is
- Apply
to get everything in a superposition - Apply our oracle
- Apply Grover's Diffusion Operator:
where is the average of the amplitudes
3-qubit Implementation
We make an oracle encoding
Then apply Grover's diffusion operator:
The goal of the oracle is to implement
Using the gate in Figure 7.33, we can implement
See the code listing below:
# The three control Toffoli gate
# qc - the quantum register
# control1, control2, control3 - The control registers
# anc - a temporary work register
# target - the target register, where the transform is applied
def cccx(qc,control1, control2, control3, anc, target):
qc.ccx(control1,control2,anc)
qc.ccx(control3,anc,target)
qc.ccx(control1,control2,anc)
qc.ccx(control3,anc,target)
# The Grover's Oracle
# qc - the quantum register
# x1, x2, x3 - The input register x
# anc - a temporary work register
# target - the target register, where the transform is applied
def grover_oracle( qc, x1, x2, x3, anc, target):
qc.x(x3)
qc.x(x1)
cccx(qc, x1, x2, x3, anc, target)
qc.x(x1)
qc.x(x3)
# The CCZ gate
# qc - the quantum register
# control1, control2 - The control registers
# target - the target register, where the Z transform is applied
def ccz(qc, control1, control2, target):
qc.h(target)
qc.ccx(control1, control2, target)
qc.h(target)
# The Grover's Diffusion Operator
# qc - the quantum register
# x1, x2, x3 - The input register x
# target - A temporary register
def grover_diffusion_operator(qc, x1, x2, x3, target):
qc.h(x1)
qc.h(x2)
qc.h(x3)
qc.h(target) # Bring this back to state 1 for next stages
qc.x(x1)
qc.x(x2)
qc.x(x3)
ccz(qc, x1, x2, x3 )
qc.x(x1)
qc.x(x2)
qc.x(x3)
qc.h(x1)
qc.h(x2)
qc.h(x3)
#
# Grover's algorithm
#
shots = 1024
q = QuantumRegister(3 , 'q')
t = QuantumRegister(2 , 't')
c = ClassicalRegister(3 , 'c')
qc = QuantumCircuit(q,t, c)
qc.h(q[0])
qc.h(q[1])
qc.h(q[2])
qc.h(t[0])
grover_oracle (qc, q[0], q[1], q[2], t[1], t[0])
grover_diffusion_operator (qc, q[0], q[1], q[2],t[0])
grover_oracle (qc, q[0], q[1], q[2], t[1], t[0])
grover_diffusion_operator (qc, q[0], q[1], q[2],t[0])
grover_oracle (qc, q[0], q[1], q[2], t[1], t[0])
grover_diffusion_operator (qc, q[0], q[1], q[2],t[0])
qc.measure(q[0],c[0])
qc.measure(q[1],c[1])
qc.measure(q[2],c[2])
backend = Aer.get_backend("qasm_simulator")
job = execute(qc, backend=backend, shots=shots)
counts = job.result().get_counts()
plot_histogram(counts)
To finish our discussion, let's derive the math. Start with the system
Then apply a NOT gate to
Then apply a
Then apply Grover-Diffusion. The action of the
At the end of the second iteration, we now have a factor of
And after one last pass: