Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quantum-kittens
GitHub Repository: quantum-kittens/platypus
Path: blob/main/notebooks/ch-algorithms/deutsch-jozsa.ipynb
3855 views
Kernel: Python 3

Deutsch-Jozsa Algorithm

In this section, we first introduce the Deutsch-Jozsa problem, and classical and quantum algorithms to solve it. We then implement the quantum algorithm using Qiskit, and run it on a simulator and device.

1. Introduction

The Deutsch-Jozsa algorithm, first introduced in Reference [1], was the first example of a quantum algorithm that performs better than the best classical algorithm. It showed that there can be advantages to using a quantum computer as a computational tool for a specific problem.

1.1 Deutsch-Jozsa Problem

We are given a hidden Boolean function ff, which takes as input a string of bits, and returns either 00 or 11, that is:

f({x0,x1,x2,...})0 or 1 , where xn is 0 or 1f(\{x_0,x_1,x_2,...\}) \rightarrow 0 \textrm{ or } 1 \textrm{ , where } x_n \textrm{ is } 0 \textrm{ or } 1

The property of the given Boolean function is that it is guaranteed to either be balanced or constant. A constant function returns all 00's or all 11's for any input, while a balanced function returns 00's for exactly half of all inputs and 11's for the other half. Our task is to determine whether the given function is balanced or constant.

Note that the Deutsch-Jozsa problem is an nn-bit extension of the single bit Deutsch problem.

1.2 The Classical Solution

Classically, in the best case, two queries to the oracle can determine if the hidden Boolean function, f(x)f(x), is balanced: e.g. if we get both f(0,0,0,...)0f(0,0,0,...)\rightarrow 0 and f(1,0,0,...)1f(1,0,0,...) \rightarrow 1, then we know the function is balanced as we have obtained the two different outputs.

In the worst case, if we continue to see the same output for each input we try, we will have to check exactly half of all possible inputs plus one in order to be certain that f(x)f(x) is constant. Since the total number of possible inputs is 2n2^n, this implies that we need 2n1+12^{n-1}+1 trial inputs to be certain that f(x)f(x) is constant in the worst case. For example, for a 44-bit string, if we checked 88 out of the 1616 possible combinations, getting all 00's, it is still possible that the 9th9^\textrm{th} input returns a 11 and f(x)f(x) is balanced. Probabilistically, this is a very unlikely event. In fact, if we get the same result continually in succession, we can express the probability that the function is constant as a function of kk inputs as:

Pconstant(k)=112k1for 1<k2n1P_\textrm{constant}(k) = 1 - \frac{1}{2^{k-1}} \qquad \textrm{for } 1 < k \leq 2^{n-1}

Realistically, we could opt to truncate our classical algorithm early, say if we were over x% confident. But if we want to be 100% confident, we would need to check 2n1+12^{n-1}+1 inputs.

1.3 Quantum Solution

Using a quantum computer, we can solve this problem with 100% confidence after only one call to the function f(x)f(x), provided we have the function ff implemented as a quantum oracle, which maps the state xy\vert x\rangle \vert y\rangle to xyf(x) \vert x\rangle \vert y \oplus f(x)\rangle, where \oplus is addition modulo 22. Below is the generic circuit for the Deutsch-Jozsa algorithm.

image1

Now, let's go through the steps of the algorithm:

  1. Prepare two quantum registers. The first is an nn-qubit register initialized to 0|0\rangle, and the second is a one-qubit register initialized to 1|1\rangle:

    ψ0=0n1\vert \psi_0 \rangle = \vert0\rangle^{\otimes n} \vert 1\rangle
  2. Apply a Hadamard gate to each qubit:

    ψ1=12n+1x=02n1x(01)\vert \psi_1 \rangle = \frac{1}{\sqrt{2^{n+1}}}\sum_{x=0}^{2^n-1} \vert x\rangle \left(|0\rangle - |1 \rangle \right)
  3. Apply the quantum oracle xy\vert x\rangle \vert y\rangle to xyf(x)\vert x\rangle \vert y \oplus f(x)\rangle: ψ2=12n+1x=02n1x(f(x)1f(x))=12n+1x=02n1(1)f(x)x(01) \begin{aligned} \lvert \psi_2 \rangle & = \frac{1}{\sqrt{2^{n+1}}}\sum_{x=0}^{2^n-1} \vert x\rangle (\vert f(x)\rangle - \vert 1 \oplus f(x)\rangle) \\ & = \frac{1}{\sqrt{2^{n+1}}}\sum_{x=0}^{2^n-1}(-1)^{f(x)}|x\rangle ( |0\rangle - |1\rangle ) \end{aligned}

    since for each x,f(x)x,f(x) is either 00 or 11.

  4. At this point the second single qubit register may be ignored. Apply a Hadamard gate to each qubit in the first register: ψ3=12nx=02n1(1)f(x)[y=02n1(1)xyy]=12ny=02n1[x=02n1(1)f(x)(1)xy]y \begin{aligned} \lvert \psi_3 \rangle & = \frac{1}{2^n}\sum_{x=0}^{2^n-1}(-1)^{f(x)} \left[ \sum_{y=0}^{2^n-1}(-1)^{x \cdot y} \vert y \rangle \right] \\ & = \frac{1}{2^n}\sum_{y=0}^{2^n-1} \left[ \sum_{x=0}^{2^n-1}(-1)^{f(x)}(-1)^{x \cdot y} \right] \vert y \rangle \end{aligned}

    where xy=x0y0x1y1xn1yn1x \cdot y = x_0y_0 \oplus x_1y_1 \oplus \ldots \oplus x_{n-1}y_{n-1} is the sum of the bitwise product.

  5. Measure the first register. Notice that the probability of measuring 0n=12nx=02n1(1)f(x)2\vert 0 \rangle ^{\otimes n} = \lvert \frac{1}{2^n}\sum_{x=0}^{2^n-1}(-1)^{f(x)} \rvert^2, which evaluates to 11 if f(x)f(x) is constant and 00 if f(x)f(x) is balanced.

1.4 Why Does This Work?

  • Constant Oracle

When the oracle is constant, it has no effect (up to a global phase) on the input qubits, and the quantum states before and after querying the oracle are the same. Since the H-gate is its own inverse, in Step 4 we reverse Step 2 to obtain the initial quantum state of 000|00\dots 0\rangle in the first register.

Hn[1000]=12n[1111]after UfHn12n[1111]=[1000]H^{\otimes n}\begin{bmatrix} 1 \\ 0 \\ 0 \\ \vdots \\ 0 \end{bmatrix} = \tfrac{1}{\sqrt{2^n}}\begin{bmatrix} 1 \\ 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} \quad \xrightarrow{\text{after } U_f} \quad H^{\otimes n}\tfrac{1}{\sqrt{2^n}}\begin{bmatrix} 1 \\ 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 0 \\ \vdots \\ 0 \end{bmatrix}
  • Balanced Oracle

After step 2, our input register is an equal superposition of all the states in the computational basis. When the oracle is balanced, phase kickback adds a negative phase to exactly half these states:

Uf12n[1111]=12n[1111]U_f \tfrac{1}{\sqrt{2^n}}\begin{bmatrix} 1 \\ 1 \\ 1 \\ \vdots \\ 1 \end{bmatrix} = \tfrac{1}{\sqrt{2^n}}\begin{bmatrix} -1 \\ 1 \\ -1 \\ \vdots \\ 1 \end{bmatrix}

The quantum state after querying the oracle is orthogonal to the quantum state before querying the oracle. Thus, in Step 4, when applying the H-gates, we must end up with a quantum state that is orthogonal to 000|00\dots 0\rangle. This means we should never measure the all-zero state.

2. Worked Example

Let's go through a specific example for a two bit balanced function:

Consider a two-bit function f(x0,x1)=x0x1f(x_0,x_1)=x_0 \oplus x_1 such that

f(0,0)=0f(0,0)=0

f(0,1)=1f(0,1)=1

f(1,0)=1f(1,0)=1

f(1,1)=0f(1,1)=0

The corresponding phase oracle of this two-bit oralce is Ufx1,x0=(1)f(x1,x0)xU_f \lvert x_1, x_0 \rangle = (-1)^{f(x_1, x_0)}\lvert x \rangle

We will now check if this oracle works as expected by taking a example state ψ0=000112\lvert \psi_0 \rangle = \lvert 0 0 \rangle_{01} \otimes \lvert 1 \rangle_{2}

  1. The first register of two qubits is initialized to 00|00\rangle and the second register qubit to 1|1\rangle

    (Note that we are using subscripts 0, 1, and 2 to index the qubits. A subscript of "01" indicates the state of the register containing qubits 0 and 1)

    ψ0=000112\lvert \psi_0 \rangle = \lvert 0 0 \rangle_{01} \otimes \lvert 1 \rangle_{2}
  2. Apply Hadamard on all qubits

    ψ1=12(00+01+10+11)0112(01)2\lvert \psi_1 \rangle = \frac{1}{2} \left( \lvert 0 0 \rangle + \lvert 0 1 \rangle + \lvert 1 0 \rangle + \lvert 1 1 \rangle \right)_{01} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2}
  3. The oracle function can be implemented as Qf=CX02CX12\text{Q}_f = CX_{02}CX_{12}, ψ2=122[0001(000100)2+0101(001101)2+1001(010110)2+1101(011111)2] \begin{aligned} \lvert \psi_2 \rangle = \frac{1}{2\sqrt{2}} \left[ \lvert 0 0 \rangle_{01} \otimes \left( \lvert 0 \oplus 0 \oplus 0 \rangle - \lvert 1 \oplus 0 \oplus 0 \rangle \right)_{2} \\ + \lvert 0 1 \rangle_{01} \otimes \left( \lvert 0 \oplus 0 \oplus 1 \rangle - \lvert 1 \oplus 0 \oplus 1 \rangle \right)_{2} \\ + \lvert 1 0 \rangle_{01} \otimes \left( \lvert 0 \oplus 1 \oplus 0 \rangle - \lvert 1 \oplus 1 \oplus 0 \rangle \right)_{2} \\ + \lvert 1 1 \rangle_{01} \otimes \left( \lvert 0 \oplus 1 \oplus 1 \rangle - \lvert 1 \oplus 1 \oplus 1 \rangle \right)_{2} \right] \end{aligned}

  4. Simplifying this, we get the following: ψ2=122[0001(01)20101(01)21001(01)2+1101(01)2]=12(000110+11)0112(01)2=12(01)012(01)112(01)2 \begin{aligned} \lvert \psi_2 \rangle & = \frac{1}{2\sqrt{2}} \left[ \lvert 0 0 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} - \lvert 0 1 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} - \lvert 1 0 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} + \lvert 1 1 \rangle_{01} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} \right] \\ & = \frac{1}{2} \left( \lvert 0 0 \rangle - \lvert 0 1 \rangle - \lvert 1 0 \rangle + \lvert 1 1 \rangle \right)_{01} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} \\ & = \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{0} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{1} \otimes \frac{1}{\sqrt{2}} \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2} \end{aligned}

  5. Apply Hadamard on the first register

    ψ3=1011(01)2\lvert \psi_3\rangle = \lvert 1 \rangle_{0} \otimes \lvert 1 \rangle_{1} \otimes \left( \lvert 0 \rangle - \lvert 1 \rangle \right)_{2}
  6. Measuring the first two qubits will give the non-zero 1111, indicating a balanced function.

3. Creating Quantum Oracles

Let's see some different ways we can create a quantum oracle.

For a constant function, it is simple:

  1. if f(x) = 0, then apply the II gate to the qubit in register 2.

  2. if f(x) = 1, then apply the XX gate to the qubit in register 2.

For a balanced function, there are many different circuits we can create. One of the ways we can guarantee our circuit is balanced is by performing a CNOT for each qubit in register 1, with the qubit in register 2 as the target. For example:

image2

In the image above, the top three qubits form the input register, and the bottom qubit is the output register. We can see which input states give which output in the table below:

Input states that output 0Input States that output 1
000001
011100
101010
110111

We can change the results while keeping them balanced by wrapping selected controls in X-gates. For example, see the circuit and its results table below:

other_balanced_circuit

Input states that output 0Input states that output 1
001000
010011
100101
111110

4. Qiskit Implementation

We now implement the Deutsch-Jozsa algorithm for the example of a three-bit function, with both constant and balanced oracles. First let's do our imports:

# initialization import numpy as np # importing Qiskit from qiskit import IBMQ, Aer from qiskit.providers.ibmq import least_busy from qiskit import QuantumCircuit, transpile # import basic plot tools from qiskit.visualization import plot_histogram

Next, we set the size of the input register for our oracle:

# set the length of the n-bit input string. n = 3

4.1 Constant Oracle

Let's start by creating a constant oracle, in this case the input has no effect on the output so we just randomly set the output qubit to be 0 or 1:

# set the length of the n-bit input string. n = 3 const_oracle = QuantumCircuit(n+1) output = np.random.randint(2) if output == 1: const_oracle.x(n) const_oracle.draw()
Image in a Jupyter notebook

4.2 Balanced Oracle

balanced_oracle = QuantumCircuit(n+1)

Next, we create a balanced oracle. As we saw in section 1b, we can create a balanced oracle by performing CNOTs with each input qubit as a control and the output bit as the target. We can vary the input states that give 0 or 1 by wrapping some of the controls in X-gates. Let's first choose a binary string of length n that dictates which controls to wrap:

b_str = "101"

Now we have this string, we can use it as a key to place our X-gates. For each qubit in our circuit, we place an X-gate if the corresponding digit in b_str is 1, or do nothing if the digit is 0.

balanced_oracle = QuantumCircuit(n+1) b_str = "101" # Place X-gates for qubit in range(len(b_str)): if b_str[qubit] == '1': balanced_oracle.x(qubit) balanced_oracle.draw()
Image in a Jupyter notebook

Next, we do our controlled-NOT gates, using each input qubit as a control, and the output qubit as a target:

balanced_oracle = QuantumCircuit(n+1) b_str = "101" # Place X-gates for qubit in range(len(b_str)): if b_str[qubit] == '1': balanced_oracle.x(qubit) # Use barrier as divider balanced_oracle.barrier() # Controlled-NOT gates for qubit in range(n): balanced_oracle.cx(qubit, n) balanced_oracle.barrier() balanced_oracle.draw()
Image in a Jupyter notebook

Finally, we repeat the code from two cells up to finish wrapping the controls in X-gates:

balanced_oracle = QuantumCircuit(n+1) b_str = "101" # Place X-gates for qubit in range(len(b_str)): if b_str[qubit] == '1': balanced_oracle.x(qubit) # Use barrier as divider balanced_oracle.barrier() # Controlled-NOT gates for qubit in range(n): balanced_oracle.cx(qubit, n) balanced_oracle.barrier() # Place X-gates for qubit in range(len(b_str)): if b_str[qubit] == '1': balanced_oracle.x(qubit) # Show oracle balanced_oracle.draw()
Image in a Jupyter notebook

We have just created a balanced oracle! All that's left to do is see if the Deutsch-Jozsa algorithm can solve it.

4.3 The Full Algorithm

Let's now put everything together. This first step in the algorithm is to initialize the input qubits in the state +|{+}\rangle and the output qubit in the state |{-}\rangle:

dj_circuit = QuantumCircuit(n+1, n) # Apply H-gates for qubit in range(n): dj_circuit.h(qubit) # Put qubit in state |-> dj_circuit.x(n) dj_circuit.h(n) dj_circuit.draw()
Image in a Jupyter notebook

Next, let's apply the oracle. Here we apply the balanced_oracle we created above:

dj_circuit = QuantumCircuit(n+1, n) # Apply H-gates for qubit in range(n): dj_circuit.h(qubit) # Put qubit in state |-> dj_circuit.x(n) dj_circuit.h(n) # Add oracle dj_circuit = dj_circuit.compose(balanced_oracle) dj_circuit.draw()
Image in a Jupyter notebook

Finally, we perform H-gates on the nn-input qubits, and measure our input register:

dj_circuit = QuantumCircuit(n+1, n) # Apply H-gates for qubit in range(n): dj_circuit.h(qubit) # Put qubit in state |-> dj_circuit.x(n) dj_circuit.h(n) # Add oracle dj_circuit = dj_circuit.compose(balanced_oracle) # Repeat H-gates for qubit in range(n): dj_circuit.h(qubit) dj_circuit.barrier() # Measure for i in range(n): dj_circuit.measure(i, i) # Display circuit dj_circuit.draw()
Image in a Jupyter notebook

Let's see the output:

# use local simulator aer_sim = Aer.get_backend('aer_simulator') results = aer_sim.run(dj_circuit).result() answer = results.get_counts() plot_histogram(answer)
Image in a Jupyter notebook
# ...we have a 0% chance of measuring 000. assert answer.get('000', 0) == 0

We can see from the results above that we have a 0% chance of measuring 000. This correctly predicts the function is balanced.

4.4 Generalised Circuits

Below, we provide a generalised function that creates Deutsch-Jozsa oracles and turns them into quantum gates. It takes the case, (either 'balanced' or 'constant', and n, the size of the input register:

def dj_oracle(case, n): # We need to make a QuantumCircuit object to return # This circuit has n+1 qubits: the size of the input, # plus one output qubit oracle_qc = QuantumCircuit(n+1) # First, let's deal with the case in which oracle is balanced if case == "balanced": # First generate a random number that tells us which CNOTs to # wrap in X-gates: b = np.random.randint(1,2**n) # Next, format 'b' as a binary string of length 'n', padded with zeros: b_str = format(b, '0'+str(n)+'b') # Next, we place the first X-gates. Each digit in our binary string # corresponds to a qubit, if the digit is 0, we do nothing, if it's 1 # we apply an X-gate to that qubit: for qubit in range(len(b_str)): if b_str[qubit] == '1': oracle_qc.x(qubit) # Do the controlled-NOT gates for each qubit, using the output qubit # as the target: for qubit in range(n): oracle_qc.cx(qubit, n) # Next, place the final X-gates for qubit in range(len(b_str)): if b_str[qubit] == '1': oracle_qc.x(qubit) # Case in which oracle is constant if case == "constant": # First decide what the fixed output of the oracle will be # (either always 0 or always 1) output = np.random.randint(2) if output == 1: oracle_qc.x(n) oracle_gate = oracle_qc.to_gate() oracle_gate.name = "Oracle" # To show when we display the circuit return oracle_gate

Let's also create a function that takes this oracle gate and performs the Deutsch-Jozsa algorithm on it:

def dj_algorithm(oracle, n): dj_circuit = QuantumCircuit(n+1, n) # Set up the output qubit: dj_circuit.x(n) dj_circuit.h(n) # And set up the input register: for qubit in range(n): dj_circuit.h(qubit) # Let's append the oracle gate to our circuit: dj_circuit.append(oracle, range(n+1)) # Finally, perform the H-gates again and measure: for qubit in range(n): dj_circuit.h(qubit) for i in range(n): dj_circuit.measure(i, i) return dj_circuit

Finally, let's use these functions to play around with the algorithm:

n = 4 oracle_gate = dj_oracle('balanced', n) dj_circuit = dj_algorithm(oracle_gate, n) dj_circuit.draw()
Image in a Jupyter notebook

And see the results of running this circuit:

transpiled_dj_circuit = transpile(dj_circuit, aer_sim) results = aer_sim.run(transpiled_dj_circuit).result() answer = results.get_counts() plot_histogram(answer)
Image in a Jupyter notebook

5. Experiment with Real Devices

We can run the circuit on the real device as shown below. We first look for the least-busy device that can handle our circuit.

# Load our saved IBMQ accounts and get the least busy backend device with greater than or equal to (n+1) qubits IBMQ.load_account() provider = IBMQ.get_provider(hub='ibm-q') backend = least_busy(provider.backends(filters=lambda x: x.configuration().n_qubits >= (n+1) and not x.configuration().simulator and x.status().operational==True)) print("least busy backend: ", backend)
least busy backend: ibmq_manila
# Run our circuit on the least busy backend. Monitor the execution of the job in the queue from qiskit.tools.monitor import job_monitor transpiled_dj_circuit = transpile(dj_circuit, backend, optimization_level=3) job = backend.run(transpiled_dj_circuit) job_monitor(job, interval=2)
Job Status: job has successfully run
# Get the results of the computation results = job.result() answer = results.get_counts() plot_histogram(answer)
Image in a Jupyter notebook

As we can see, the most likely result is 1111. The other results are due to errors in the quantum computation.

# ...the most likely result is 1111. assert max(answer, key=answer.get) == '1111'

6. Problems

  1. Are you able to create a balanced or constant oracle of a different form?

  2. The function dj_problem_oracle (below) returns a Deutsch-Jozsa oracle for n = 4 in the form of a gate. The gate takes 5 qubits as input where the final qubit (q_4) is the output qubit (as with the example oracles above). You can get different oracles by giving dj_problem_oracle different integers between 1 and 5. Use the Deutsch-Jozsa algorithm to decide whether each oracle is balanced or constant (Note: It is highly recommended you try this example using the aer_simulator instead of a real device).

from qiskit_textbook.problems import dj_problem_oracle oracle = dj_problem_oracle(1)

7. References

  1. David Deutsch and Richard Jozsa (1992). "Rapid solutions of problems by quantum computation". Proceedings of the Royal Society of London A. 439: 553–558. doi:10.1098/rspa.1992.0167.

  2. R. Cleve; A. Ekert; C. Macchiavello; M. Mosca (1998). "Quantum algorithms revisited". Proceedings of the Royal Society of London A. 454: 339–354. doi:10.1098/rspa.1998.0164.

import qiskit.tools.jupyter %qiskit_version_table