Amplitude amplification
The AmplitudeAmplification
algorithm increases the probability of measuring a state in a chosen subspace.
It takes two Circuit objects:
state_prep_oracleprepares the initial state from \(|0\rangle\).good_state_oraclemarks the good subspace: applied to(register, flag)it flips the flag qubit when the register holds a good state, and leaves the register itself untouched.
The circuit first prepares the initial state. Each round then flags the good subspace and reflects. If the initial probability of the marked subspace is \(a\), the probability after \(k\) rounds is
This gives the \(O(1/\sqrt{a})\) query scaling. More rounds are not
always better: after the first maximum, additional rounds reduce the success
probability. Choose rounds from an estimate of state overlap \(a\).
Using amplitude amplification
Note
This algorithm is currently available only in the Python API.
Creating an amplitude amplification algorithm
from qdk_chemistry.algorithms import create
# Number of Grover iterates. Choose it from an estimate of the overlap a,
# the success probability after k rounds is sin^2((2k+1) arcsin(sqrt(a))).
amplitude_amplification = create("amplitude_amplification", "qdk_base", rounds=2)
run takes the two oracles as Circuit. See below for an
example of amplifying an eigenstate found by QPE.
QPE subspace marking oracle
The QPESubspaceMarking
algorithm (amplitude_amplification_oracle / qdk_qpe_subspace) builds a
good_state_oracle for an eigenspace. It runs the nested phase estimation on the register
it is handed, flips the flag when the phase lands in a bin whose energy is at least
energy_lower_bound, then undoes the estimation so the register comes back as it was
found. run takes only the qubit_hamiltonian: the register already holds the state
under test, so no state preparation is needed.
The oracle reflects about the marked eigenspaces only when the estimation is exact, that is
when every eigenphase of the state under test is a multiple of \(2^{-n}\) for
num_bits \(n\). Off a bin the phase register comes back spread rather than to
\(|0\rangle\), so the ancillas the oracle releases carry away part of the state.
import math
import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import (
AlgorithmRef,
Configuration,
ModelOrbitals,
QubitOperator,
StateVectorContainer,
Wavefunction,
)
# 1. A two-qubit Hamiltonian. Its spectrum is {+lambda, 0, 0, -lambda}, with |11> on top.
qubit_hamiltonian = QubitOperator(
pauli_strings=["ZI", "IZ"], coefficients=np.array([-math.pi / 4.0, -math.pi / 4.0])
)
# 2. A guiding state with 0.3 amplitude on the target eigenvector |11>.
amplitude = 0.3
guiding_state = Wavefunction(
StateVectorContainer(
np.array([math.sqrt(1.0 - amplitude**2), amplitude]),
[Configuration.from_bitstring("00"), Configuration.from_bitstring("11")],
ModelOrbitals(2),
)
)
state_preparation = create("state_prep", "dense_pure_state").run(guiding_state)
# 3. To mark the target state, a QPE is run on the prepared register, and a flag
# is flipped when the QPE phase lands in the desired range. The bound is halfway
# up the band, so |11> at +lambda is marked and the rest of the spectrum is not.
good_state_oracle = create(
"amplitude_amplification_oracle",
"qdk_qpe_subspace",
energy_lower_bound=qubit_hamiltonian.schatten_norm / 2,
qpe_circuit_builder=AlgorithmRef(
"qpe_circuit_builder",
"qdk_standard",
num_bits=4,
unitary_builder=AlgorithmRef(
"hamiltonian_unitary_builder", "lcu", quantum_walk=True
),
controlled_circuit_mapper=AlgorithmRef(
"controlled_circuit_mapper", "prepare_select_prepare"
),
),
).run(qubit_hamiltonian)
# 4. Amplify the initial state against the qpe subspace marking oracle.
amplitude_amplification = create("amplitude_amplification", "qdk_base", rounds=2)
circuit = amplitude_amplification.run(state_preparation, good_state_oracle)
# 5. Run the circuit and measure. Rounds 0 and 2 side by side show what the
# amplification bought: |11> starts at the 9% the guiding state gives it and ends
# up dominating the shots, matching sin^2((2k+1) arcsin(0.3)).
executor = create("circuit_executor", "qdk_sparse_state_simulator")
shots = 400
counts = executor.run(circuit, shots=shots).bitstring_counts
unamplified = create("amplitude_amplification", "qdk_base", rounds=0).run(
state_preparation, good_state_oracle
)
before = executor.run(unamplified, shots=shots).bitstring_counts
print(f"rounds=0: |11> in {before.get('11', 0) / shots:.0%} of shots, {before}")
print(f"rounds=2: |11> in {counts.get('11', 0) / shots:.0%} of shots, {counts}")
Alternatively, the qpe subspace marking oracle can be replaced by a quantum signal processing oracle on a block encoding of the Hamiltonian. See Lin and Tong, arXiv:2002.12508, and its use in arXiv:2510.07273, Section 2.
Settings
amplitude_amplification / qdk_base:
Setting |
Type |
Description |
|---|---|---|
|
|
Number of Grover iterates (default 1). Must be non-negative. |
amplitude_amplification_oracle / qdk_qpe_subspace:
Setting |
Type |
Description |
|---|---|---|
|
|
Lowest energy the marked subspace may hold. Must be finite. Default: |
|
|
The phase estimation to mark. Default: |
Further Reading
Lin, L. Lecture Notes on Quantum Algorithms for Scientific Computation, arXiv:2201.08309, Chapter 2.
Brassard, G., Høyer, P., Mosca, M., and Tapp, A. Quantum Amplitude Amplification and Estimation, arXiv:quant-ph/0005055.
Lin, L. and Tong, Y. Near-optimal ground state preparation, arXiv:2002.12508: the signal-processing eigenspace reflection.