The Quantum Fourier Transform (QFT) is the quantum analogue of the discrete Fourier transform and one of the most important primitives in quantum computing, providing the exponential speedups behind Shor's algorithm and quantum phase estimation. This Amazon Braket notebook builds the QFT circuit from scratch and runs it on a Braket simulator. It shows how the transform is constructed from a regular pattern of Hadamard gates and controlled phase rotations applied across the qubit register, followed by swap gates that reverse the qubit order, and how this circuit uses only on the order of n^2 gates to act on the 2^n amplitudes of an n-qubit state, an exponential saving over the classical fast Fourier transform. The example also demonstrates the inverse QFT, which appears as a subroutine in phase estimation and many other algorithms. By constructing the circuit explicitly with Braket gates, applying it to chosen input states, and inspecting the resulting amplitudes through simulation, the notebook makes the abstract transform concrete and reusable. It is a key reference for anyone building higher-level quantum algorithms on Amazon Braket that depend on the QFT primitive.
# --- Setup cell added by QCR (not part of the original tutorial) ---# Source: amazon-braket/amazon-braket-examples @ 0c0818f315479aab9deebed7e7ed7533ac581923, Apache License 2.0.# Installs the example's dependencies. If a later cell still reports a missing# package, restart the runtime/kernel and run again from the top.
%pip install -q amazon-braket-sdk==1.117.3 matplotlib
QUANTUM FOURIER TRANSFORM
In this tutorial, we provide a detailed implementation of the Quantum Fourier Transform (QFT) and the inverse thereof, using Amazon Braket's SDK.
We provide two different implementations, with and without recursion.
The QFT is an important subroutine to many quantum algorithms, most famously Shor's algorithm for factoring and the quantum phase estimation (QPE) algorithm for estimating the eigenvalues of a unitary operator [1, 2].
The QFT can be performed efficiently on a quantum computer, using only single-qubit Hadamard gates and two-qubit controlled phase shift gates, where is the number of qubits.
We first review the basics of the quantum Fourier transform, and its relation to the discrete (classical) Fourier transform.
We then implement the QFT in code two ways: recursively and non-recursively.
This notebook also showcases Braket's circuit.subroutine functionality, which allows one to define custom methods and add them to the Circuit class.
TECHNICAL BACKGROUND OF QFT
Introduction: The Quantum Fourier Transform (QFT) is a quantum algorithm for applying the discrete Fourier transform to the amplitudes of a quantum state. The QFT is therefore a quantum analog of the classical discrete Fourier transform [1, 2].
Let us first review the classical discrete Fourier transform (DFT).
Given a vector , the discrete Fourier transform computes the vector for which
Quantum Fourier Transformation: In close analogy to the classical Fourier transform, the QFT takes an arbitrary input state (with the orthonormal basis states , , ..., ), and maps it to the output state
with coefficients
Binary representation (notation): We take , with being the number of qubits. In the binary representation, the states read explicitly
where
For example, in a four-qubit system and .
Similarly, we use the following notation to represent binary fractions:
Product representation of QFT: Very often, the product representation of the QFT proves useful. Accordingly, the QFT can be expressed (up to normalization) as
For example, the QFT on three qubits can be written as
Thus, for the simple input state with , it is easy to see that the QFT produces a uniform superposition of all computational basis vectors (since only the Hadamard gates act non-trivially in this case). Explicitly:
Inverse QFT: Below we will also implement the inverse QFT algorithm (denoted by , or sometimes ), which fulfills , where is the identity. For example, with the inverse QFT we can invert the above three qubit QFT as
Example for inverse QFT: For further illustration, we will run a concrete example of this kind below.
Specifically, consider the following example with , , and encoding the number 2 in binary representation.
Accordingly, we prepare the initial state (up to normalization)
which is equivalent to
This state can be prepared using the single-qubits gates and as follows:
Based on the analysis above we then expect .
We can generalize the above example to multiple qubits with the following state preparation code:
circ = Circuit()
circ.h(range(num_qubits))
for ii inrange(num_qubits - 1):
circ.rz(ii+1, math.pi/(2**ii))
CIRCUIT IMPLEMENTATION OF QFT
The QFT circuit can be implemented using only Hadamard gates, controlled phase gates, and SWAP gates. The Hadamard gate , the phase gate , and the SWAP gate are given by
The details of the calculation can be found in a number of resources (including a great exposition on Wikipedia [1]), but the circuit that implements this transformation reads as follows (note that we need to apply a series of swap gates to reverse the order of the qubits thereby recovering the QFT as described above):
Recursive Implemenation: By inspection, we can define the first part of this circuit (i.e., everything except the SWAP gates) recursively as shown below:
where in the above is the part of the circuit excluding the final SWAP gates.
IMPORTS and SETUP
In [1]:
# general importsimport math
import matplotlib.pyplot as plt
import numpy as np
# magic word for producing visualizations in notebook
%matplotlib inline
# set up device: Local Simulator
device = LocalSimulator()
IMPLEMENTATION OF THE QFT AND INVERSE QFT CIRCUITS
In this section we provide simple helper functions to implement the circuits for both QFT and inverse QFT.
Specifically, we demonstrate how such modular building blocks can be registered as subroutines, using @circuit.subroutine(register=True).
Recursive definition of the QFT
In [4]:
# qft subroutine without swapsdefqft_no_swap(qubits):
"""Subroutine of the QFT excluding the final SWAP gates, applied to the qubits argument.
Returns the a circuit object.
Args:
qubits (int): The list of qubits on which to apply the QFT
"""# On a single qubit, the QFT is just a Hadamard.iflen(qubits) == 1:
return Circuit().h(qubits)
# For more than one qubit, we define the QFT recursively (as shown on the right half of the image above):
qftcirc = Circuit()
# First add a Hadamard gate
qftcirc.h(qubits[0])
# Then apply the controlled rotations, with weights (angles) defined by the distance to the control qubit.for k, qubit inenumerate(qubits[1:]):
qftcirc.cphaseshift(qubit, qubits[0], 2 * math.pi / (2 ** (k + 2)))
# Now apply the above gates recursively to the rest of the qubits
qftcirc.add(qft_no_swap(qubits[1:]))
return qftcirc
# To complete the full QFT, add swap gates to reverse the order of the qubits@circuit.subroutine(register=True)defqft_recursive(qubits):
"""Construct a circuit object corresponding to the Quantum Fourier Transform (QFT)
algorithm, applied to the argument qubits.
Args:
qubits (int): The list of qubits on which to apply the QFT
"""
qftcirc = Circuit()
# First add the QFT subroutine above
qftcirc.add(qft_no_swap(qubits))
# Then add SWAP gates to reverse the order of the qubits:for i inrange(math.floor(len(qubits) / 2)):
qftcirc.swap(qubits[i], qubits[-i - 1])
return qftcirc
Non-recursive definition of the QFT
In [5]:
@circuit.subroutine(register=True)defqft(qubits):
"""Construct a circuit object corresponding to the Quantum Fourier Transform (QFT)
algorithm, applied to the argument qubits. Does not use recursion to generate the QFT.
Args:
qubits (int): The list of qubits on which to apply the QFT
"""
qftcirc = Circuit()
# get number of qubits
num_qubits = len(qubits)
for k inrange(num_qubits):
# First add a Hadamard gate
qftcirc.h(qubits[k])
# Then apply the controlled rotations, with weights (angles) defined by the distance to the control qubit.# Start on the qubit after qubit k, and iterate until the end. When num_qubits==1, this loop does not run.for j inrange(1, num_qubits - k):
angle = 2 * math.pi / (2 ** (j + 1))
qftcirc.cphaseshift(qubits[k + j], qubits[k], angle)
# Then add SWAP gates to reverse the order of the qubits:for i inrange(math.floor(num_qubits / 2)):
qftcirc.swap(qubits[i], qubits[-i - 1])
return qftcirc
Inverse QFT
In [6]:
@circuit.subroutine(register=True)definverse_qft(qubits):
"""Construct a circuit object corresponding to the inverse Quantum Fourier Transform (QFT)
algorithm, applied to the argument qubits. Does not use recursion to generate the circuit.
Args:
qubits (int): The list of qubits on which to apply the inverse QFT
"""# instantiate circuit object
qftcirc = Circuit()
# get number of qubits
num_qubits = len(qubits)
# First add SWAP gates to reverse the order of the qubits:for i inrange(math.floor(num_qubits / 2)):
qftcirc.swap(qubits[i], qubits[-i - 1])
# Start on the last qubit and work to the first.for k inreversed(range(num_qubits)):
# Apply the controlled rotations, with weights (angles) defined by the distance to the control qubit.# These angles are the negative of the angle used in the QFT.# Start on the last qubit and iterate until the qubit after k.# When num_qubits==1, this loop does not run.for j inreversed(range(1, num_qubits - k)):
angle = -2 * math.pi / (2 ** (j + 1))
qftcirc.cphaseshift(qubits[k + j], qubits[k], angle)
# Then add a Hadamard gate
qftcirc.h(qubits[k])
return qftcirc
VISUALIZATION OF THE QFT CIRCUIT
In order to check our implementation of the (inverse) QFT circuit, let us visualize them for small numbers of qubits.
By inspection, we can see that the and circuits will effectively cancel each other, as desired.
2 qubits:
In [7]:
# show inverse QFT example circuit
num_qubits = 2
qubits = range(num_qubits)
my_qft_circ = qft(qubits)
print("QFT CIRCUIT:")
print(my_qft_circ)
# show inverse QFT example circuitprint()
print("INVERSE-QFT CIRCUIT:")
my_iqft_circ = inverse_qft(qubits)
print(my_iqft_circ)
In the following we will verify that our QFT implementation works as expected with a few test examples:
First, we will apply the QFT algorithm to the input state . Here, we expect a uniform superposition of all basis vectors as output, as discussed above.
Second, following our theoretical discussion above, we will apply the inverse QFT to a particular input state for which we expect a simple computational basis state as output.
Finally, we verify that the and circuits cancel each other, effectively yielding the identity gate.
For the second example, we will prepare a specific input state using the following circuit:
circ = Circuit()
circ.h(range(num_qubits))
for ii inrange(num_qubits - 1):
circ.rz(ii+1, math.pi/(2**ii))
This circuit prepares the binary representation of , for whatever number of qubits is set.
Disclaimer: Note that if you choose to run this on a real QPU, then you are likely to see imperfect measurement results due to experimental imperfection such as noise.
NUMERICAL TEST EXAMPLE 1
First, let us apply the QFT algorithm to the input state . Here, we expect a uniform superposition of all basis vectors as output, as discussed above.
In [9]:
# check output for input |0,0,0> -> expect uniform distribution
num_qubits = 3
qubits = range(num_qubits)
my_qft_circ = qft(qubits)
# specify desired results_types
my_qft_circ.state_vector()
my_qft_circ.probability()
# Run the QFT on 3 qubits with all zeros input
task = device.run(my_qft_circ, shots=0)
result = task.result()
state_vector = result.values[0]
probs_values = result.values[1]
# format statevector for output
state_vec_pretty = np.round(state_vector, decimals=3)
state_vec_pretty = [ampl if np.abs(ampl) > 10 ** (-5) else0for ampl in state_vec_pretty]
# bitstrings
format_bitstring = "{0:0" + str(num_qubits) + "b}"
bitstring_keys = [format_bitstring.format(ii) for ii inrange(2**num_qubits)]
# Print the output state vectorprint("Exact statevector:\n", state_vec_pretty)
# plot probabalities
plt.bar(bitstring_keys, probs_values)
plt.xlabel("bitstrings")
plt.ylabel("probability");
Indeed, we do recover a uniform superposition of all basis vectors as output, as expected from our analysis above.
NUMERICAL TEST EXAMPLE 2
Following our theoretical discussion above, we will apply the inverse QFT to a particular input state for which we expect a simple computational basis state as output.
In [10]:
# prepare initial state
num_qubits = 3
qubits = range(num_qubits)
circ = Circuit()
circ.h(qubits)
for ii inrange(num_qubits - 1):
circ.rz(ii + 1, math.pi / (2**ii))
print("1. Printing circuit for state preparation:")
print(circ)
# add QFT circuit
circ.inverse_qft(qubits)
# print circuit including QFTprint()
print("2. Full circuit including inverse QFT:")
print(circ)
1. Printing circuit for state preparation:
T : |0| 1 |
q0 : -H----------
q1 : -H-Rz(3.14)-
q2 : -H-Rz(1.57)-
T : |0| 1 |
2. Full circuit including inverse QFT:
T : |0| 1 | 2 |3| 4 | 5 | 6 |7|
q0 : -H----------SWAP------------------PHASE(-0.79)-PHASE(-1.57)-H-
| | |
q1 : -H-Rz(3.14)-|------PHASE(-1.57)-H-|------------C--------------
| | |
q2 : -H-Rz(1.57)-SWAP-H-C--------------C---------------------------
T : |0| 1 | 2 |3| 4 | 5 | 6 |7|
In [11]:
# specify desired results_types
circ.state_vector()
circ.probability()
# Run the QFT on 3 circuits with all zeros input
task = device.run(circ, shots=0)
result = task.result()
state_vector = result.values[0]
probs_values = result.values[1]
# format statevector for output
state_vec_pretty = np.round(state_vector, decimals=3)
state_vec_pretty = [ampl if np.abs(ampl) > 10 ** (-5) else0for ampl in state_vec_pretty]
# bitstrings
format_bitstring = "{0:0" + str(num_qubits) + "b}"
bitstring_keys = [format_bitstring.format(ii) for ii inrange(2**num_qubits)]
# Print the output state vectorprint("Exact statevector:\n", state_vec_pretty)
# plot probabalities
plt.bar(bitstring_keys, probs_values)
plt.xlabel("bitstrings")
plt.ylabel("probability")
plt.xticks(rotation=90);
As expected, all measurements show a unique bitstring corresponding to the integer number set with our initial state preparation, as expected.
Next, let us verify that this works independent of the number of qubits.
In [12]:
# prepare initial state
num_qubits = 4
qubits = range(num_qubits)
circ = Circuit()
circ.h(qubits)
for ii inrange(num_qubits - 1):
circ.rz(ii + 1, math.pi / (2**ii))
print("1. Printing circuit for state preparation:")
print(circ)
# add QFT circuit
circ.inverse_qft(qubits)
# print circuit including QFTprint()
print("2. Full circuit including inverse QFT:")
print(circ)
# specify desired results_types
circ.state_vector()
circ.probability()
# Run the QFT on 3 circuits with all zeros input
task = device.run(circ, shots=0)
result = task.result()
state_vector = result.values[0]
probs_values = result.values[1]
# format statevector for output
state_vec_pretty = np.round(state_vector, decimals=3)
state_vec_pretty = [ampl if np.abs(ampl) > 10 ** (-5) else0for ampl in state_vec_pretty]
# bitstrings
format_bitstring = "{0:0" + str(num_qubits) + "b}"
bitstring_keys = [format_bitstring.format(ii) for ii inrange(2**num_qubits)]
# Print the output state vectorprint("Exact statevector:\n", state_vec_pretty)
# plot probabalities
plt.bar(bitstring_keys, probs_values)
plt.xlabel("bitstrings")
plt.ylabel("probability")
plt.xticks(rotation=90);
In this section, we verify that the and circuits cancel each other, effectively yielding the identity gate 𝟙.
In [13]:
# prepare initial state
num_qubits = 3
qubits = range(num_qubits)
circ = Circuit()
# add QFT circuit
circ.qft(qubits)
circ.inverse_qft(qubits)
# specify desired results_types
circ.state_vector()
circ.probability()
# Run the QFT on 3 circuits with all zeros input
task = device.run(circ, shots=0)
result = task.result()
state_vector = result.values[0]
probs_values = result.values[1]
# format statevector for output
state_vec_pretty = np.round(state_vector, decimals=3)
state_vec_pretty = [ampl if np.abs(ampl) > 10 ** (-5) else0for ampl in state_vec_pretty]
# bitstrings
format_bitstring = "{0:0" + str(num_qubits) + "b}"
bitstring_keys = [format_bitstring.format(ii) for ii inrange(2**num_qubits)]
# Print the output state vectorprint("Exact statevector:\n", state_vec_pretty)
# plot probabalities
plt.bar(bitstring_keys, probs_values)
plt.xlabel("bitstrings")
plt.ylabel("probability")
plt.xticks(rotation=90);
Exact statevector:
[(1+0j), 0, 0, 0, 0, 0, 0, 0]
In [14]:
# prepare initial state
num_qubits = 3
qubits = range(num_qubits)
circ = Circuit()
circ.x(0)
circ.x(2)
# add QFT circuit
circ.qft(qubits)
circ.inverse_qft(qubits)
# specify desired results_types
circ.state_vector()
circ.probability()
# Run the QFT on 3 circuits with all zeros input
task = device.run(circ, shots=0)
result = task.result()
state_vector = result.values[0]
probs_values = result.values[1]
# format statevector for output
state_vec_pretty = np.round(state_vector, decimals=3)
state_vec_pretty = [ampl if np.abs(ampl) > 10 ** (-5) else0for ampl in state_vec_pretty]
# bitstrings
format_bitstring = "{0:0" + str(num_qubits) + "b}"
bitstring_keys = [format_bitstring.format(ii) for ii inrange(2**num_qubits)]
# Print the output state vectorprint("Exact statevector:\n", state_vec_pretty)
# plot probabalities
plt.bar(bitstring_keys, probs_values)
plt.xlabel("bitstrings")
plt.ylabel("probability")
plt.xticks(rotation=90);
Exact statevector:
[0, 0, 0, 0, 0, (1+0j), 0, 0]
COMPARISON OF IMPLEMENTATIONS
In this section we compare the two implementations (recursive vs. non-recursive), showing that they produce the same circuits.
By inspection you can also check that the and circuits cancel each other, effectively yielding the identity gate 𝟙.
In [15]:
# set qubits to perform (inverse) QFT on
qubits = [1, 3, 6, 7]
# print circuitsprint("Circuit for QFT (using non-recursive implementation):")
print(qft(qubits))
print()
print("Circuit for QFT (using recursive implementation):")
print(qft_recursive(qubits))
print()
print("Circuit for inverse QFT:")
print(inverse_qft(qubits))
[2] Nielsen, Michael A., Chuang, Isaac L. (2010). Quantum Computation and Quantum Information (2nd ed.). Cambridge: Cambridge University Press.
Join the Discussion
Comments (0)
No comments yet. Be the first to share your thoughts!
Indexed by QCR Librarian
This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.
Join the Discussion
Comments (0)
No comments yet. Be the first to share your thoughts!