import math
from typing import *
import numpy as np
import matplotlib.pyplot as plt
from qiskit.quantum_info import Statevector, Operator
from qiskit.visualization import plot_bloch_vector
from qiskit import QuantumCircuit
from qiskit_aer import AerSimulator
sim = AerSimulator()
from util import zero, one, statevector_to_bloch_vector
Shor’s Part 2/5: The Quantum Fourier Transform#
The Quantum Fourier Transform (QFT), by analogy with the classical Fourier Transform, represents a quantum state in the “frequency” domain. It is a fundamental building block in Shor’s algorithm where the periodicities in data can be extracted to factor integers efficiently.
References
Introduction to Classical and Quantum Computing, Chapter 7.7.3
https://qiskit.org/textbook/ch-algorithms/quantum-fourier-transform.html
Introduction to Quantum Information Science: Lectures 19 and 20 by Scott Aaronson
Quantum Computation and Quantum Information: Chapter 5, Nielsen and Chuang
Definition#
The Quantum Fourier Transform (QFT) over \(t\) qubits (\(T = 2^t\)) is defined as the \(T \times T\) matrix \(F_T\) whose \(j\)-th and \(k\)-th entry is
where
is a root of unity.
def root_of_unity(j: int, k: int, T: int) -> np.complex64:
return np.exp(2 * np.pi * 1j * j * k / T)
def mk_F(T: int) -> Operator:
return 1/np.sqrt(T) * Operator([[root_of_unity(j, k, T) for j in range(0, T)] for k in range(0, T)])
Aside: Roots of unity#
We can visualize a root of unity on the unit circle.
def plot_roots_of_unity(k: int, T: int) -> None:
fig = plt.figure(figsize=(4, 4)); ax = fig.add_subplot()
ax.set_aspect(1.0/ax.get_data_ratio(), adjustable='box')
# Plot
jks = [(j, k) for j in range(T)]
txt = [f"exp(2$\\pi i\\cdot{j}\\cdot{k}/{T})$" for i, (j, k) in enumerate(jks)]
zs = [np.exp(2*np.pi*j*k/T*1j) for j, k in jks]
for i, t in enumerate(txt):
plt.annotate(t, (zs[i].real, zs[i].imag))
# Plot meta-data
plt.plot([z.real for z in zs], [z.imag for z in zs], linestyle="none", marker='x')
plt.axhline(y=0.0, color="r", linestyle="-"); plt.axvline(x=0.0, color="r", linestyle="-")
plt.xlabel("Real"); plt.ylabel("Imaginary"); plt.title(r"$e^{2 \pi i \cdot j \cdot k/T}$");
# k = 1, so it takes us 8 traverses to reach our starting point
plot_roots_of_unity(1, 8)
# k = 7, so it takes us 8 traverses to reach the starting point
plot_roots_of_unity(7, 8)
# k = 2, so it takes us 4 traverses to reach the starting point
plot_roots_of_unity(2, 8)
Root of Unity Encodes Period Information#
For any arbitrary starting point, the number of times needed to cycle back to that point encodes information about the period \(T\). For instance,
8 traverses implies either \(k = 1\) or \(k = 7\) and \(T = 8\).
Similarly, 2 traverses implies either \(k = 2\) and \(T = 8\) or \(k = 6\) and \(T = 8\).
Thus a root of unity \(\omega_T^{jk}\) encodes the number of times needed to reach the starting point, without needing to know the starting point!
Examples#
To unpack the formal definition of a QFT, we’ll give a few examples now.
Example: QFT on 1 Qubit#
Unpacking the definition for \(T = 2\), we obtain
This is a Hadamard gate!
F_2 = mk_F(2)
F_2.draw("latex")
\(F_2\) on basis#
We can visualize the effect of \(F_2\) on the computational basis. In particular, it maps the computational basis into the \(|+\rangle\) and \(|-\rangle\) basis.
fig = plt.figure(figsize = [6, 9])
ax1 = fig.add_subplot(1, 2, 1, projection="3d")
ax2 = fig.add_subplot(1, 2, 2, projection="3d")
# Using global phase
plot_bloch_vector(statevector_to_bloch_vector(zero.evolve(F_2)), ax=ax1, title="|0>")
plot_bloch_vector(statevector_to_bloch_vector(one.evolve(F_2)), ax=ax2, title="|0>")
Example: QFT on 2 Qubits#
F_4 = mk_F(4)
F_4.draw("latex")
\(F_4\) on basis#
\(F_4\) performs a similar style of transform on the computational basis to \(F_2\). In particular, it maps the computational basis into superpositions of the computational basis.
(zero^zero).evolve(F_4).draw("latex")
(zero^one).evolve(F_4).draw("latex")
(one^zero).evolve(F_4).draw("latex")
(one^one).evolve(F_4).draw("latex")
Example: QFT on 3 Qubits#
The QFT for 3 qubits is a \(8 \times 8\) matrix.
mk_F(8).draw("latex")
QFT Encodes Information in Phase#
The QFT transform the computational basis into another basis that encodes information in the global phase. To see this, we can rewrite the \(F_T\) with the following formula
where \(|\overline{j}\rangle\) is the binary encoding in \(t\) bits of the number \(j\).
Using this alternative characterization of the QFT, we can see that it maps each element in the computational basis into a superposition of computational basis elements offset by a global phase as in
Thus the QFT enables the storage of information in the global phase. To foreshadow what’s to come, quantum phase estimation, will be a procedure used to read out information stored in the phase.
Binary Representation of QFT#
Define the notation
Then we can alternatively write the QFT \(F_T\) as
Example: F_2#
Thus
and
Example: F_4#
Thus 1.
and
QFT Circuit#
It isn’t obvious that we can construct a circuit that implements the QFT with a polynomial number of gates. Thankfull, we can rely on a recurrence relation to make it possible to construct a QFT in \(O(t^2)\) gates.
Observation 1#
Applying H to first qubit produces
Observation 2#
Let
be a rotation gate.
Let \(CR_k^t\) be a controlled rotation gate that applies \(R_k\) to qubit \(t\) if qubit \(k\) is set.
Then applying \(CR_{t}^0 \dots CR_{2}^0\) to the first qubit produces
Observation 3#
Observe that
This recurrence relation can be used to efficiently construct a QFT.
Example: QFT on 2 qubits#
qft2 = QuantumCircuit(2)
qft2.h(1)
qft2.cp(np.pi/2, 0, 1)
qft2.barrier()
qft2.h(0)
qft2.swap(0, 1) # little endian ...
qft2.draw(output="mpl", style="iqp")
np.allclose(Operator(qft2), F_4)
True
Example: QFT on 3 qubits#
qft3 = QuantumCircuit(3)
qft3.h(2)
qft3.cp(np.pi/2, 2, 1)
qft3.cp(np.pi/4, 2, 0)
qft3.barrier()
qft3.h(1)
qft3.cp(np.pi/2, 1, 0)
qft3.barrier()
qft3.h(0)
qft3.swap(0, 2) # little endian ...
qft3.draw(output="mpl", style="iqp")
np.allclose(Operator(qft3), mk_F(8))
True
Arbitrary qubits#
You can use the recurrence relation to write the circuit for arbitrary quibits.
See https://qiskit.org/textbook/ch-algorithms/quantum-fourier-transform.html
def qft(circuit, n):
def go(circuit, n):
if n == 0:
return circuit
else:
n -= 1
# Apply H
circuit.h(n)
# Apply CR
for qubit in range(n):
circuit.cp(np.pi/2**(n-qubit), qubit, n)
# Recurrence relation
return go(circuit, n)
circuit = go(circuit, n)
# Take care of little endian
for qubit in range(n//2):
circuit.swap(qubit, n-qubit-1)
return circuit
qft3_ = QuantumCircuit(3)
qft3_ = qft(qft3_, 3)
qft3_.draw(output="mpl", style="iqp")
np.allclose(Operator(qft3), Operator(qft3_))
True
How many gates?#
Note that
Thus we require \(O(t^2)\) gates.
Summary#
The QFT performs a change-of-basis that enables us to store information in the global phase.
Later, we will use the quantum phase estimation (QPE) algorithm to read this information out.