- Demos/
- Quantum Computing/
Intro to Amplitude Amplification
Intro to Amplitude Amplification
Published: May 06, 2024. Last updated: October 06, 2024.
Grover’s algorithm is one of the most important
developments in quantum computing. This technique is a special case of a quantum algorithm called
Amplitude Amplification (Amp Amp). In this demo, you will learn its basic principles and
how to implement it in PennyLane using the new AmplitudeAmplification
template. We also discuss
a useful extension of the algorithm called fixed-point amplitude amplification.

Amplitude Amplification
Our goal is to prepare an unknown state \(|\phi\rangle\) knowing certain property of the state. A first approach is to design a quantum circuit described by a unitary \(U\) that generates an initial state \(|\Psi\rangle= U|0\rangle\) that has a non-zero overlap with the target state \(|\phi\rangle.\) Any state can be represented in the computational basis as:
But we can find better representations 😈. One choice is to make \(|\phi\rangle\) an element of the basis. We can then write
where \(|\phi^{\perp}\rangle\) is some state orthogonal to \(|\phi\rangle,\) and \(\alpha, \beta \in \mathbb{R}.\) This allows us to represent the initial state in a two-dimensional space — a crucial advantage that we will exploit repeatedly. Notice that this representation is even simpler than a Bloch sphere since the amplitudes are real numbers, so we can visualize all operations inside a circle, as shown in the image below:

The two axes correspond to the states \(|\phi\rangle\) and \(|\phi^{\perp}\rangle.\) In the figure we also show the initial state \(|\Psi\rangle,\) which forms an angle of \(\theta=\arcsin(\alpha)\) with the x-axis.
Our aim is to amplify the amplitude \(\alpha\) to get closer to \(|\phi\rangle,\) hence the name Amplitude Amplification 1 😏. We will use this geometric picture to identify a sequence of operators that moves the initial vector \(|\Psi\rangle\) as close to \(|\phi\rangle\) as possible.
The algorithm
To obtain the state \(|\phi\rangle,\) we could just rotate the initial state counterclockwise by an angle \(\pi/2 -\theta.\) However, we don’t explicitly know \(|\phi\rangle,\) so it’s unclear how this could be done. This is where a great idea is born: what if instead of rotations we think of reflections?
The main insight of the Amp Amp algorithm is that there is a sequence of two reflections that allow us to effectively perform a rotation towards the target state. The first is the reflection with respect to \(|\phi^{\perp}\rangle\) and the second one is the reflection with respect to \(|\Psi\rangle.\)
Let’s go step by step. First we apply the reflection around \(|\phi^{\perp}\rangle:\)

This reflection may seem challenging to implement since we do not explicitly know \(|\phi^{\perp}\rangle.\) However, the operator performing the reflection is well-defined:
Amp Amp usually assumes access to an oracle implementing this reflection. For example, in a search problem, this is the usual Grover oracle that “marks” the target state with a phase flip. In practice, the way to build the oracle is just a classic check: if the given state meets the known property, we change its sign. This will become clearer later with an example.
After applying the first reflection we are moving away from \(|\phi\rangle\) — why do that? Well, sometimes it’s necessary to take a step backward to take two steps forward, and that is exactly what we will do. For this purpose, we use a second reflection with respect to \(|\Psi\rangle.\) This is easier to build since we know the operator \(U\) that generates \(|\Psi\rangle.\)

Reflection with respect to \(|\Psi\rangle.\)¶
Together, these two reflections are equivalent to rotating the state by \(2\theta\) degrees from its original position, where \(\theta\) is the angle that defines the initial state. To amplify the amplitude and approach the target state, we perform this sequence of rotations multiple times, i.e. \(\dots R_{\Psi}R_{\phi^{\perp}}R_{\Psi}R_{\phi^{\perp}}.\) More precisely, we repeat them \(k\) times, with \(k\) given by:
This expression is derived by recognizing that the angle of the resulting state after \(k\) iterations is \((2k + 1)\theta,\) and we aim for this value to be equal to \(\frac{\pi}{2}\) radians (i.e. \(90º\)).
As we will see below, Amp Amp can be applied to unstructured dataset searching problems. Let’s suppose that in a set of N elements we are looking for a single one, and we begin with an equal superposition state such that \(\alpha=\frac{1}{\sqrt{N}}.\) The number of iterations required in this case is \(k \sim \frac{\pi \sqrt{N}}{4},\) making the complexity of the algorithm \(\mathcal{O}(\sqrt{N}).\) This provides a quadratic speedup compared to the classical \(\mathcal{O}(N)\) brute-force approach.
Amplitude Amplification in PennyLane
Let’s take a look at a practical example using PennyLane: solving the zero-sum problem. In this task, we are given a list of \(n\) integers and our goal is to find all subsets of numbers whose sum is equal \(0.\) In this example, we use the following set of integers:
values = [1, -2, 3, 4, 5, -6]
n = len(values)
Can you find all the subsets that add to zero? 🤔
We will use Amplitude Amplification to solve the problem.
First we define a binary variable \(x_i\) that takes the value \(1\) if we include the \(i\)-th element in the
subset and takes the value \(0\) otherwise.
We encode the \(i\)-th variable in the \(i\)-th qubit of a quantum state. For instance, \(|110001\rangle\)
represents the subset \([1,-2,-6]\) consisting of the first, second, and sixth element in the set.
Later on, we will see how to solve it directly with AmplitudeAmplification
, but it is worthwhile to go
step by step showing each part of the algorithm.
We can now define the initial state:
This is a uniform superposition of all possible subsets so solutions are guaranteed to have non-zero amplitudes in \(|\Psi\rangle.\) Let’s generate the state and visualize it.
import pennylane as qml
import matplotlib.pyplot as plt
plt.style.use('pennylane.drawer.plot')
@qml.prod
# This decorator converts the quantum function U into an operator.
# It is useful for later use in the AmplitudeAmplification template.
def U(wires):
# create the generator U, such that U|0⟩ = |Ψ⟩
for wire in wires:
qml.Hadamard(wires=wire)
dev = qml.device("default.qubit")
@qml.qnode(dev)
def circuit():
U(wires = range(n))
return qml.state()
output = circuit()[:64].real
plt.bar(range(len(output)), output)
plt.ylim(-0.4, 0.6)
plt.ylabel("Amplitude")
plt.xlabel("|i⟩")
plt.axhline(0, color='black', linewidth=1)
plt.show()

Initially, the probability of getting any basis state is the same. The next step is to define the oracle that marks the elements that satisfy our property — that the sum of the subset is zero. To do this we first create an operator that stores the sum of the selected subset in some auxiliary qubits. This operator, which we call \(\text{Sum}\) , is defined as:
where \(v_i\) is the \(i\)-th integer in the input set. For more details of how we build this operation take a look at Basic arithmetic with the Quantum Fourier Transform.
import numpy as np
def Sum(wires_subset, wires_sum):
qml.QFT(wires = wires_sum)
for i, value in enumerate(values):
for j in range(len(wires_sum)):
qml.CRZ(value * np.pi / (2 ** j), wires=[wires_subset[i], wires_sum[j]])
qml.adjoint(qml.QFT)(wires=wires_sum)
To create the oracle that performs the reflection around \(|\phi^{\perp}\rangle,\) we apply the \(\text{Sum}\) operator to the state and then flip the sign of those states whose sum is \(0.\) This allows us to mark the searched elements. Then we apply the inverse of the sum to clean the auxiliary qubits.
@qml.prod
def oracle(wires_subset, wires_sum):
# Reflection on |ϕ⟂⟩
Sum(wires_subset, wires_sum)
qml.FlipSign(0, wires=wires_sum)
qml.adjoint(Sum)(wires_subset, wires_sum)
@qml.qnode(dev)
def circuit():
U(wires=range(n)) # Generate initial state
oracle(range(n), range(n, n+5)) # Apply the reflection on |ϕ⟂⟩
return qml.state()
output = circuit()[0::2 ** 5].real
plt.bar(range(len(output)), output)
plt.ylim(-0.4, 0.6)
plt.ylabel("Amplitude")
plt.xlabel("|i⟩")
plt.axhline(0, color='black', linewidth=1)
plt.show()

Great, we have flipped the sign of the solution states. Note that in this example we can check every possible subset explicitly, but this becomes exponentially hard for larger input sets. Still, this operator would correctly flip the sign of any solution.
The next step is to reflect on the \(|\Psi\rangle\) state.
This reflection operator can be built directly in PennyLane with Reflection
.
The final circuit is then:

@qml.qnode(dev)
def circuit():
U(wires=range(n)) # Generate initial state
oracle(range(n), range(n, n + 5)) # Apply the reflection on |ϕ⟂⟩
qml.Reflection(U(wires=range(n))) # Reflect on |Ψ⟩
return qml.state()
Let’s now look at the state \(|\Psi\rangle\) and see how it is changed.
output = circuit()[0::2 ** 5].real
plt.bar(range(len(output)), output)
plt.ylim(-0.4, 0.6)
plt.ylabel("Amplitude")
plt.xlabel("|i⟩")
plt.axhline(0, color='black', linewidth=1)
plt.show()

We have now amplified the amplitude of all the states that represent a solution to our problem. The four peaks are obtained in \(0\), \(27\), \(35\) and \(61,\) whose binary representation corresponds with \(|000000\rangle\), \(|011011\rangle,\) \(|100011\rangle\) and \(|111101\rangle\) respectively. These states satisfy the property that the sum of the subset is \(0.\)
Let’s use the AmplitudeAmplification
to code the problem in a more compact way.
This template expects as input, the unitary \(U\) that prepares \(|\Psi\rangle,\) the reflection with respect
to \(|\phi^{\perp}\rangle\) (i.e. the oracle), and the number of iterations.
We increase the number of iterations in order to study the evolution of the initial state:
@qml.qnode(dev)
def circuit(iters):
U(wires=range(n))
qml.AmplitudeAmplification(U = U(wires = range(n)),
O = oracle(range(n), range(n, n + 5)),
iters = iters)
return qml.probs(wires = range(n))
fig, axs = plt.subplots(2, 2, figsize=(14, 10))
for i in range(1,9,2):
output = circuit(iters=i)
ax = axs[i // 4, i //2 % 2]
ax.bar(range(len(output)), output)
ax.set_ylim(0, 0.6)
ax.set_title(f"Iteration {i}")
plt.tight_layout()
plt.subplots_adjust(bottom=0.1)
plt.axhline(0, color='black', linewidth=1)
plt.show()

We can see that as the number of iterations increases, the probability of success increases as well. But we should be careful to not overdo it: after 5 iterations in our example, the amplitudes have in fact decreased. This phenomenon is known as “overcooking” and is a consequence of rotating the state too much. It can be addressed using fixed-point amplitude amplification, which we discuss below.

Fixed-point Amplitude Amplification
In the above example, we have a problem: we do not know the number of solutions. Because of this we cannot calculate the exact number of iterations needed, so we do not know when to stop and might overcook the state. However, there is a variant of Amplitude Amplification that solves this issue: the fixed-point quantum search variant 2.
The idea behind this technique is to avoid overcooking by gradually reducing the intensity of the resulting rotation with the help of an auxiliary qubit. The speed at which we decrease this intensity is carefully studied in reference 2 and has a very interesting interpretation related to the approximation of the sign function 3.
To use this variant we simply set fixed_point = True
and indicate the auxiliary qubit.
Let’s see what happens with the same example as before:
@qml.qnode(dev)
def circuit(iters):
U(wires=range(n))
qml.AmplitudeAmplification(U = U(wires = range(n)),
O = oracle(range(n), range(n, n + 5)),
iters = iters,
fixed_point=True,
work_wire = n + 5)
return qml.probs(wires = range(n))
fig, axs = plt.subplots(2, 2, figsize=(14, 10))
for i in range(1,9,2):
output = circuit(iters=i)
ax = axs[i // 4, i //2 % 2]
ax.bar(range(len(output)), output)
ax.set_ylim(0, 0.6)
ax.set_title(f"Iteration {i}")
plt.tight_layout()
plt.subplots_adjust(bottom=0.1)
plt.axhline(0, color='black', linewidth=1)
plt.show()

/home/runner/work/qml/qml/.venv-build/lib/python3.10/site-packages/pennylane/templates/subroutines/amplitude_amplification.py:39: ComplexWarning: Casting complex values to real discards the imaginary part
float(2 * np.arctan(1 / (np.tan(2 * np.pi * j / iters) * np.sqrt(1 - gamma**2))))
Unlike before, we can see that the probability of success does not decrease for a large number of iterations.
Conclusion
In this demo we have shown the process of finding unknown states with Amplitude Amplification. We discussed some of its limitations and learned how to overcome them with the fixed-point version. This algorithm is important in a variety of workflows such as molecular simulation with QPE. This shouldn’t be surprising, as it is generally helpful to rapidly amplify the amplitude of a target state. Moreover, the idea of using the reflections can be extrapolated to algorithms such as Qubitization or QSVT. PennyLane supports the Amplitude Amplification algorithm and its variants such as fixed-point and Oblivious Amplitude Amplification 4. We encourage you to explore them and see how they can help you in your quantum algorithms.
References
- 1
Gilles Brassard, Peter Hoyer, Michele Mosca and Alain Tapp. “Quantum Amplitude Amplification and Estimation”, arXiv:quant-ph/0005055, 2000.
- 2(1,2)
Theodore J. Yoder, Guang Hao Low and Isaac L. Chuang. “Fixed-point quantum search with an optimal number of queries”, arXiv:1409.3305, 2014.
- 3
John M. Martyn, Zane M. Rossi, Andrew K. Tan, Isaac L. Chuang. “A Grand Unification of Quantum Algorithms”, PRX Quantum 2,040203, 2021.
- 4
Dominic W. Berry, et al. “Simulating Hamiltonian dynamics with a truncated Taylor series”, arXiv:1412.4687, 2014
About the authors
Guillermo Alonso
Quantum Scientist specializing in quantum algorithms
Juan Miguel Arrazola
Making quantum computers useful
Total running time of the script: (0 minutes 4.432 seconds)