HW 6 - Quantum Algorithms
1: Deutsh-Josza Algorithm on a 4-Qubit System
Deutsch-Josza algorithm. The text (skip the codes) on Kasirajan, pp. 293-297 follows the
kets of a 4-qubit register and one helper (ancilla) qubit step by step for a balanced function.
a. Repeat all steps, but with a constant function with output 1.
b. What are the measurement options for the register qubits at the end? Did the algorithm
work correctly? Explain.
c. How would your final state (just before the measurement) change if your constant
function had output 1?
Let's begin with our setup:
For clarity, each
Now we apply the constant oracle function given in the figure above. We know it's the right oracle since:
just gets passed through - If
then we output in this case since . - If
then we output since still for our oracle.
As such:
Then we 'un-
We just measure the left 4 qubits, which are always
If instead we read a final state that had at least one state in the left 4 qubits where not all the qubits were 0, then the algorithm dictates that part of
2: Deutsh-Josza Algorithm w/ Hidden String
Deutsch-Josza algorithm. Kasirajan mentions a hidden string that is applied to the register
qubits right before and right after the oracle.
a. In Kasirajan’s worked example of the balanced function on pp. 293-297, no hidden string
was included. This effectively means that the hidden string was 0, or |0000>. In his code
and circuit diagram for the balanced function, he uses the hidden string 15, or |1111>. We
can see this in his circuit diagram in Fig. 7.14: X gates are applied to all four register
qubits right before and right after the oracle (sequence of cNOTs). Repeat the steps from
Kasirajan, but with the hidden string applied. How does this change affect the final state
vector compared to Kasirajan’s calculations? And how does it affect the measurement
outcome and the conclusion as to whether the function is constant or balanced?
b. Now do it one more time, but with a hidden string of your choice, i.e. pick a number from
1-14 and imprint it by placing X-gates onto the correct qubits before and after the oracle.
How does this change affect the final state vector compared to Kasirajan’s calculations?
And how does it affect the measurement outcome and the conclusion as to whether the
function is constant or balanced?
Consider the following application of the Deutsch-Josza Algorithm, where the oracle given is the series of controlled-NOT gates:
Kasirajan makes mention of a "hidden string" that is applied to the register qubits right before and after being sent to the oracle. He is talking about the
We'll use the given circuit, which has the hidden string
To help for this next part, use the truth table 7.2 provided to get what
where
Notice that all the kets with an odd number of 1's have -'s in front. Then we continue with our
So in this case the
We measure just the number = 0
instead. Furthermore, we measured a non-zero number, implying the function is balanced (as it is). This implies using the hidden string doesn't change anything (at least in this specific case).
Let's see if it changes if we use the hidden string number = 0101
. Doing our calculations again we get to:
It didn't change anything! As a result, the rest of our calculations reign exactly the same! But when we return back to our layer of hidden string
We need to swap
Notice that if
- add a 1 and remove a 0, then add a 1 then remove a 0 (net gain of 2 added 1's)
- add a 1 and remove a 0, then remove a 1 and add a 0 (net gain of 0 added 1's)
- do the same in reverse (net gain of 0 added 1's)
- add a 0 and remove a 1, repeat (neg gain of -2 added 1's)
In each case, we maintain the parity of the 1's, so then thegate doesn't do anything in this case. As a result our calculations are the same (just as we expected and saw with Kasirajan's calculations):
This is likely due to the fact that our number
had an even parity amount of 1's. Namely, all the examples done had a number where the number of 1's was even. If we had done a number like number = 0001
then we would've gotten a different result. This is because applying number
we get:
which is just a negative global phase of our other
3: Grover's Algorithm on a 3-Qubit System
Grover’s algorithm. The text (again skip the codes) on Kasirajan, pp. 323 (last line) -326
follows the kets through Grover’s algorithm for 3 qubits.
a. Fill in some details: Show how to get equations 7.137-7.139. Note here: Kasirajan talks
about each state being changed by a factor of
μ - a i. This fell out of the sky a little bit...
Use the diffusion operator from class, “2|ψ><ψ| - I” applied to your kets each iteration
and compare your result to Kasirajan’s for each step.
b. Now do one more Grover iteration. Based on your results, what is the optimum number
of Grover iterations for 3 qubits with one correct solution/one entry to find?
Using the following circuit that does Grover's Algorithm:
a: Filling in the Details
We're going to fill in some details that Kasirajan doesn't for his computation. First, he applies the layer of
We apply the first iteration of the oracle
Then we apply the Grover Diffusion Operator in more detail. First, we apply our Hadamards (Eq. 7.137):
so instead of diving into the calculation for the 3-qubit case specifically. Let's look at the broad sense of what's going on. Use instead the operator
Let's explore
Notice that we have here
Kasirajan here is actually considering some large quantity for
This is an approximation, so it's not exactly what we are expecting. But the approximation gives the general gist of what's going on here.
So for instance in the
Which is equation 7.137. But we can repeat this process, just considering the constant of 3 in front of
As
Which is exactly equation 7.138. Notice that essentially, every time because of the
4: Grover's Algorithm in General
Grover's algorithm. Kasirajan provides sample code for Grover's algorithm with 3 qubits.
a. Write a code that works for 4 qubits. Attach it to your HW.
b. Write a code that works for
c. Pick a number of qubits > 4 and an entry (binary number string) you would like to find in the database. Note that the entry you are looking for is encoded into the grover_oracle function: Apply X-gates to any qubit you want to be 0, and do nothing to any qubit you want to be 1. Then apply the controlsX gate, and then repeat the same X gates to the same qubits again. Run your algorithm and include the histogram here. Did your algorithm find your entry? How many Grover iterations were optimal?
For the purposes of this assignment, I actually just did the
from algorithms.groverSearch import groversAlgorithm
from qiskit import QuantumCircuit
num_qubits = 4
data = 1 # Data string you want to search for
qc = QuantumCircuit(num_qubits, num_qubits)
groversAlgorithm(qc, data)
# ... do other stuff
But now we can for the num_qubits
is some arbitrary number. What follows is the code to accomplish this:
algorithms/groverSearch.py
from algorithms.utilities import *
from typing import Callable
def generic_single_transform(qc : QuantumCircuit, method : Callable[[QuantumCircuit, QubitSpecifier], InstructionSet], func : Callable[[int], bool] = lambda x : True) -> None:
"""Apply a arbitrary gate to all qubits of the QC, given some filtering function `func` that applies it to only specific qubits
Args:
qc (QuantumCircuit): QC to apply the series of gates to.
func (Callable[[int], bool]): Function to test whether an index gets the transformation applied to itself. The default is to apply it to all qubits of `qc`.
method (Callable[[QuantumCircuit, QubitSpecifier], InstructionSet]): the method from `qc` that is the gate you want to apply.
"""
for i in range(0, qc.num_qubits):
if (func(i)):
method(qc, i)
def oracle(qc : QuantumCircuit, data : int) -> None:
"""Encodes a value to be searched within grover's algorithm.
Args:
qc (QuantumCircuit): QC to apply the encoding to.
data (int): The data to be encoded. Only the lowest `qc.num_qubits` bits are considered in `data`.
"""
# Do first pass of X gates
i = 1
pos = 0
xed_qubits = []
while i < 2**(qc.num_qubits):
if not i & data:
qc.x(pos)
xed_qubits.append(pos)
i = i << 1
pos += 1
# Essentially a CCC...CCCX gate for however many qubits we have.
qc.mcp(pi, [x for x in range(0, qc.num_qubits - 1)], qc.num_qubits - 1)
# Repeat the same pass.
for i in xed_qubits:
qc.x(i)
def diffusion(qc : QuantumCircuit) -> None:
"""Apply diffusion step to QC.
Args:
qc (QuantumCircuit): The QC to do the diffusion on.
"""
generic_single_transform(qc, QuantumCircuit.h)
generic_single_transform(qc, QuantumCircuit.x)
qc.mcp(pi, [x for x in range(0, qc.num_qubits - 1)], qc.num_qubits - 1)
generic_single_transform(qc, QuantumCircuit.x)
generic_single_transform(qc, QuantumCircuit.h)
def groversAlgorithm(qc : QuantumCircuit, data : int, iter : int = None) -> None:
"""Apply grover's algorithm to the current state of the passed QuantumCircuit.
Args:
qc (QuantumCircuit): The QuantumCircuit to apply the algorithm to.
data (int): The data to try to search for in the database.
iter (int): Number of iterations to do. By default the optimum is to do
"""
generic_single_transform(qc, QuantumCircuit.h)
qc.barrier()
if iter is None:
iter = 2**(qc.num_qubits-1) - 1
for i in range(0, iter):
oracle(qc, data)
qc.barrier()
diffusion(qc)
qc.barrier()
def main(num_qubits : int):
# Add 1 qubit for the ancilla
qc = QuantumCircuit(num_qubits)
groversAlgorithm(qc, 2)
print(qc)
if __name__ == "__main__":
main(4)
algorithms/groverSearch.py
There are some things required for running this code:
- Make sure you keep all files in the
algorithms
folder and run from the parent folder ofalgorithms
- If you run the file directly, you actually get the
num_qubits = 4
version, wheredata = 2
in that case, just like Kasirajan's version of the code. - Notice
groversAlgorithm
uses the defaultiter
value aswhere where is the number of qubits. This was the optimal value to use because the largest amplitude for increased by for integers where was the iteration number. If it ever got above then there are problems including the amplitudes of the other
Make sure to include the utilities file that has all the needed imports:
algorithms/utilities.py
from qiskit import *
import qiskit.quantum_info as qi
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit import InstructionSet
from qiskit.circuit.quantumcircuit import QubitSpecifier
from math import pi
def printCircuit(qc : QuantumCircuit, is_cmdline : bool = False):
print(qc)
qc_transpiled = transpile(qc)
stv = qi.Statevector.from_instruction(qc_transpiled)
if is_cmdline:
print(stv.draw('text'))
else:
return stv.draw('latex')
Running the following jupyter notebook:
groverSearch.ipynb
from algorithms.groverSearch import *
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
# Change these...
num_qubits = 7
data = 1
qc = QuantumCircuit(num_qubits, num_qubits)
groversAlgorithm(qc, data)
# Print info about the algorithm and statevector
print(qc)
qc_transpiled = transpile(qc)
# Measure data
for i in range(0, num_qubits):
qc.measure(i, i)
# Histogram data getting from measurement
backend = AerSimulator()
job = backend.run(qc.copy(), shots=1024)
result = job.result()
counts = result.get_counts()
stv = qi.Statevector.from_instruction(qc_transpiled)
stv.draw('latex')
plot_histogram(counts)
This will allow you to plot the circuit on the command-line as well as see the statevector at the end, and a histogram of all measurements from a 1024 shot
run of measurements.
Let's have fun with this. For example, running the default code where num_qubits = 7
and the data we search for is data = 1
gives:
See the 97% measurement on our searched term. The histogram turned out to measure:
We can try some other measurements for the (c) part of the question. Let's do num_qubits = 9
and data = 420
You can guess what the spike of 330 measurements is measuring right?