Quantum Phase Estimation (QPE)
April 25, 2023 · View on GitHub
Quantum phase estimation plays an important role as a subroutine in many quantum programs and lays the foundation for the famous Shor's algorithm. The task is usually formulated as:
Given a unitary operator and its eigenvector , find the associated eigenvalue such that ( phase unknown ). Since is unitary, we can say that $0\leq\varphi<1$.
Algorithm
To illustrate how phase estimation works, consider an example. Suppose
with eigenvector . Thus, the phase we want to estimate is since . In general, the QPE algorithm can be divided into the following 4 steps:
-
Pre-processing
The phase estimation algorithm requires two registers. The first part is called counting register and it contains an -qubit state all initialized to . They will estimate the unknown phase in binary form. The number of depends on the accuracy we want to achieve. The second part contains qubits and we need to initialize it to the eigenstate . In our case, and choose to begin with. We would expect bad precision with only 3 counting qubits. Thus, the overall initial state after preprocessing is: We can easily do this in our simulator:
qubit_num = 4 shots = 1000 phase = 2 * np.pi / 5 def main(): """ main """ # Create environment env = QuantumEnvironment() # Choose backend machine env.backend(BackendName.LocalBaiduSim2) # Initialize qubits q = [env.Q[i] for i in range(qubit_num)] # Prepare eigenstate |1> = X|0> on the ancilla qubit X(q[3]) -
Superposition
Next, we apply a layer of Hadamard gates to all zero-initialized counting qubits to prepare a uniform superposition state. The resulting superposition state is
In our case, and . Hence,
In our simulator, call the Hadamard gate with
# Superposition H(q[0]) H(q[1]) H(q[2]) -
Oracle
The oracle used in phase estimation algorithm is a bunch of controlled-unitaries by setting control on -th counting qubit and target on the ancilla qubits in eigenstate . Let's see what happens if we apply to eigenstate . Set , we get
U^{4}|u\rangle = U^{3}U|u\rangle = e^{2\pi i\varphi} U^{3}|u\rangle = e^{2\pi i($2^{1}$\varphi)}U^{2}|u\rangle = \cdots = e^{2\pi i ($2^{2}$\varphi)} |u\rangleThen, we can see a clear recursion relation where
After that, we need to figure out what controlled-unitaries do. Consider a general quantum state vector for counting qubits,
Apply the controlled-U gate and we get
This trick is usually called the phase kickback. Note that the overall effect is adding the unknown phase to the control qubit by applying a unitary to target ancilla qubits. This is opposite to common sense that the control bit should remain the same. Now, we apply the oracle ( Controlled-) to the whole state .
In our 3-qubit case,
So far, our quantum circuit looks like:

It may still seem confusing what means. It simply means repeating the controlled-unitary $2^j$ times.

In our simulator, the controlled-U gates can be generated easily by setting a general controlled 3-parameter rotation gate, which is defined as
It is easy to see that this rotation gate reduces to the given unitary when .
Here is the code.
# Control-U gates CU(0, 0, phase)(q[0], q[3]) CU(0, 0, phase)(q[1], q[3]) CU(0, 0, phase)(q[1], q[3]) CU(0, 0, phase)(q[2], q[3]) CU(0, 0, phase)(q[2], q[3]) CU(0, 0, phase)(q[2], q[3]) CU(0, 0, phase)(q[2], q[3]) -
Quantum Fourier Transform (QFT)
We are very close to the answer at this stage but it's in Fourier basis. We need to get back to the familiar computational basis to read out our 3-qubit phase estimation . We refer the details of QFT to another document. Before we explain what QFT does, let's introduce the notation to represent the binary fraction and each . In our 3-qubit case, we choose and the best precision we can reach for is between
and
We expect to see the probability amplitude in this range. If we want to increase the accuracy and narrow down this range, we have to increase the counting qubit number. In fact, we can always rewrite the state derived from last step as
To illustrate this, consider
$2^{1}$ \varphi = $2^{1}$* (0.\varphi_0\varphi_1\varphi_2) = 2*( \frac{1}{2}\varphi_0 + \frac{1}{4}\varphi_1 + \frac{1}{8}\varphi_2) = \varphi_0 + \frac{1}{2}\varphi_1 + \frac{1}{4}\varphi_2 = 0.\varphi_1\varphi_2Similarly,
$2^{2}$ \varphi = $2^{2}$* (0.\varphi_0\varphi_1\varphi_2) = 4*( \frac{1}{2}\varphi_0 + \frac{1}{4}\varphi_1 + \frac{1}{8}\varphi_2) = 2\varphi_0 + \varphi_1 + \frac{1}{2}\varphi_2 = 0.\varphi_2This can be generalized to
Now, we can understand what quantum fourier transform (QFT) does! It maps an n-bit binary string in computational basis into a so-called Fourier basis:
This is exactly the form of we have seen before. Then, we can apply 3-qubit inverse fourier transform to the counting qubits of
The matrix representation for this operation is
And the circuit model is constructed as
Notice the equality between
and
In our simulator, this inverse QFT can be implemented by
```python
# 3-qubit inverse QFT
SWAP(q[0], q[2])
H(q[0])
CU(0, 0, -np.pi / 2)(q[0], q[1])
H(q[1])
CU(0, 0, -np.pi / 4)(q[0], q[2])
CU(0, 0, -np.pi / 2)(q[1], q[2])
H(q[2])
```
-
Read out and measurement
After so many efforts, we finally reach the point where we read out the 3-qubit estimated phase in binary fraction.
Combine all the steps above, we get the following measurement results:
Here, we can see that the state with highest probability is which corresponds to the estimated phasewhich is off by $25% from the true value. But this is the best we can get using 3 counting qubits. If we use 5-qubits instead, the result will be much more accurate (error scales as \1/2^n$).
Code
Here is the complete code. Please try it, change the parameters and see what happens.
import numpy as np
import sys
sys.path.append('../../..') # "from QCompute import *" requires this
from QCompute import *
matchSdkVersion('Python 3.3.3')
qubit_num = 4
shots = 1000
phase = 2 * np.pi / 5
def main():
"""
main
"""
# Create environment
env = QEnv()
# Choose backend machine
env.backend(BackendName.LocalBaiduSim2)
# Initialize qubits
q = [env.Q[i] for i in range(qubit_num)]
# Prepare eigenstate |1> = X|0> on the ancilla qubit
X(q[3])
# Superposition
H(q[0])
H(q[1])
H(q[2])
# Control-U gates
CU(0, 0, phase)(q[0], q[3])
CU(0, 0, phase)(q[1], q[3])
CU(0, 0, phase)(q[1], q[3])
CU(0, 0, phase)(q[2], q[3])
CU(0, 0, phase)(q[2], q[3])
CU(0, 0, phase)(q[2], q[3])
CU(0, 0, phase)(q[2], q[3])
# 3-qubit inverse QFT
SWAP(q[0], q[2])
H(q[0])
CU(0, 0, -np.pi / 2)(q[0], q[1])
H(q[1])
CU(0, 0, -np.pi / 4)(q[0], q[2])
CU(0, 0, -np.pi / 2)(q[1], q[2])
H(q[2])
# Measurement result
MeasureZ(*env.Q.toListPair())
taskResult = env.commit(shots, fetchMeasure=True)
return taskResult['counts']
if __name__ == '__main__':
main()
Discussion
We can see that the algorithm itself is trying to find the eigenvalue of a given unitary. That's why it is also called eigenvalue estimation. Now, it's your turn to have a try. You may change the phase to and generate meaningful explanations.