Amplitude amplification

The AmplitudeAmplification algorithm increases the probability of measuring a state in a chosen subspace. It takes two Circuit objects:

  • state_prep_oracle prepares the initial state.

  • good_state_oracle flips a flag qubit for the good subspace.

The circuit first prepares the initial state. Each round then flags the good subspace and reflects about the prepared state. If the initial probability of the marked subspace is \(a\), the probability after \(k\) rounds is

\[p_k = \sin^2\!\big((2k+1)\arcsin\sqrt{a}\big).\]

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 and reads the register width from a resource estimate of the state preparation. The returned circuit measures the whole register. It also carries the same amplification without measurement as a Q# callable, so a caller can append its own measurement instead. See below for an example of amplifying a measurement-free QPE circuit.

Amplitude amplified QPE

Build a measurement-free QPE circuit, mark the target phase bin, and amplify. phase_marking_oracle() reads the register layout from the QPE circuit, so only the target bins have to be given:

import math

import numpy as np
from qdk_chemistry.algorithms import create, phase_marking_oracle
from qdk_chemistry.data import AlgorithmRef, Circuit, QubitOperator
from qdk_chemistry.data.circuit import QsharpFactoryData
from qdk_chemistry.utils.qsharp import QSHARP_UTILS

# 1. A two-qubit Hamiltonian
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>
state_vector = [0.0, 0.0, 0.0, 0.0]
state_vector[3] = 0.3
state_vector[0] = math.sqrt(1.0 - 0.3**2)
prep_parameters = {
    "rowMap": [1, 0],
    "stateVector": state_vector,
    "expansionOps": [],
    "numQubits": 2,
}
state_preparation = Circuit(
    qsharp_factory=QsharpFactoryData(
        program=QSHARP_UTILS.StatePreparation.MakeStatePreparationCircuit,
        parameter=prep_parameters,
    ),
    qsharp_op=QSHARP_UTILS.StatePreparation.MakeStatePreparationOp(prep_parameters),
)

# 3. Build a measurement-free QPE circuit. This whole circuit is the preparation that
# gets amplified, so the phase register and its ancillas stay inside the amplified
# register and every round reflects about the full prepared state.
num_bits = 4
builder = create(
    "qpe_circuit_builder",
    "qdk_standard",
    num_bits=num_bits,
    controlled_circuit_mapper=AlgorithmRef(
        "controlled_circuit_mapper", "prepare_select_prepare"
    ),
    unitary_builder=AlgorithmRef(
        "hamiltonian_unitary_builder", "lcu", quantum_walk=True
    ),
    measure_phase=False,
)
state_prep_oracle = builder.run(
    state_preparation=state_preparation, qubit_hamiltonian=qubit_hamiltonian
)[0]

# 4. Mark the phase bins holding the target eigenvalue. QPE writes the phase phi of
# the eigenvalue exp(2 pi i phi) into bin round(phi * 2**num_bits), so the half-open
# window (8, 9) accepts bin 8 alone, that is phi = 0.5.
target_phase_bins = (8, 9)
good_state_oracle = phase_marking_oracle(state_prep_oracle, target_phase_bins)

# The same window can be named by energy instead. The walk maps E to
# phi = arccos(E / lambda) / 2 pi, with lambda the L1 norm of the Hamiltonian, and
# marks both signs of that phase. Here it selects bin 8 again.
good_state_oracle = phase_marking_oracle(
    state_prep_oracle,
    target_energy_range=(-np.inf, -0.99 * qubit_hamiltonian.schatten_norm),
    qubit_hamiltonian=qubit_hamiltonian,
)

# 5. Amplify, then execute
amplitude_amplification = create("amplitude_amplification", "qdk_base", rounds=2)
circuit = amplitude_amplification.run(state_prep_oracle, good_state_oracle)

executor = create("circuit_executor", "qdk_sparse_state_simulator")
shots = 400
counts = executor.run(circuit, shots=shots).bitstring_counts

The target can also be named as an energy window. This only applies to a QPE circuit built on a qubitization walk, whose eigenvalues are \(e^{\pm i\arccos(E/\lambda)}\) for \(\lambda\) the L1 norm of the Hamiltonian.

Alternatively, the marked phase window can be replaced by a reflection onto the target eigenspace built with quantum signal processing on a block encoding of the Hamiltonian, amplifying an initial state preparation directly. See Lin and Tong, arXiv:2002.12508, and its use in arXiv:2510.07273, Section 2.

Settings

Setting

Type

Description

rounds

int

Number of Grover iterates (default 1). Must be non-negative.

Further Reading

  • PhaseEstimation: the un-amplified algorithm.

  • QpeCircuitBuilder: builds the coherent preparation this algorithm amplifies.

  • 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.