Phase estimation

The PhaseEstimation algorithm in QDK/Chemistry extracts eigenvalues from a quantum state by measuring the phase accumulated under repeated application of a unitary operator. Following QDK/Chemistry’s algorithm design principles, it takes a state-preparation Circuit (from StatePreparation), a QubitOperator (from QubitMapper or a model Hamiltonian), a QpeCircuitBuilder (which encapsulates the unitary builder and controlled circuit mapper), and a CircuitExecutor as input and returns a QpeResult containing the measured phase, reconstructed energy, and alias-resolution metadata.

Overview

Quantum Phase Estimation (QPE) is one of the foundational quantum algorithms for chemistry. Given a unitary \(U\) and an initial state \(|\psi\rangle\) that has significant overlap with an eigenstate of \(U\), QPE measures the eigenphase \(\varphi\) such that \(U|\psi\rangle = e^{2\pi i \varphi}|\psi\rangle\).

QDK/Chemistry supports two types of unitaries for QPE:

  • Time evolution\(U = e^{-iHt}\) constructed via Trotter-Suzuki decomposition. Energy is recovered as \(E = \theta / t\) where \(\theta = 2\pi\varphi\) is mapped to \((-\pi, \pi]\).

  • Qubitization — The walk operator \(W\) constructed via LCU block encoding. Energy is recovered as \(E = \lambda \cos(\theta)\) where \(\lambda\) is the L1 norm of the Hamiltonian.

The QPE algorithm itself is agnostic to how the unitary is constructed — the choice of unitary builder determines the phase-to-energy mapping used in post-processing.

QDK/Chemistry provides two QPE approaches, each suited to different hardware constraints:

Iterative Quantum Phase Estimation (IQPE)

Kitaev’s single-ancilla algorithm [Kit95] that extracts phase bits one at a time, from most significant to least significant, using adaptive feedback corrections between iterations. This approach minimizes ancilla requirements and is particularly suited to near-term hardware.

Standard QFT-based Quantum Phase Estimation

The textbook multi-ancilla approach [NC10] that uses a register of \(n\) ancilla qubits and an inverse Quantum Fourier Transform to extract all phase bits simultaneously. This approach achieves all precision bits in a single circuit execution but requires more ancilla qubits and longer circuit depth.

Both implementations share the same interface and produce QpeResult objects with automatic phase-wrapping, energy alias detection, and full serialization support. See QpeResult for details on the result data class.

Typical workflow

A complete QPE workflow connects several stages of the QDK/Chemistry pipeline. The most common path starts from a molecular system:

  1. Prepare a reference Wavefunction from a multi-configuration calculation

  2. Generate a state-preparation Circuit using StatePreparation

  3. Map the molecular Hamiltonian to qubit operators using QubitMapper

  4. Choose a method for constructing the target unitary — either Trotter time-evolution or LCU block encoding (qubitization)

  5. Run phase estimation to obtain the energy eigenvalue

Alternatively, phase estimation can be used with model Hamiltonians (e.g., Hubbard, Heisenberg, or Ising spin models) or with a user-supplied Circuit and QubitOperator directly — the full molecular-structure pipeline is not required.

Using the PhaseEstimation

Note

This algorithm is currently available only in the Python API.

This section demonstrates how to create, configure, and run a phase estimation calculation. The run method returns a QpeResult containing the measured phase, energy, alias candidates, and measurement metadata.

Input requirements

The PhaseEstimation requires the following inputs:

State preparation circuit

A Circuit that prepares the target quantum state on the system register. This is typically generated by the StatePreparation algorithm from a Wavefunction, but can also be any user-constructed circuit (e.g., a custom initial state for a model Hamiltonian).

QubitOperator

A QubitOperator containing the Pauli-string representation of the Hamiltonian. This can be obtained from the QubitMapper algorithm, constructed from a model Hamiltonian, or built directly by the user.

Settings

The PhaseEstimation is configured via its settings object, which includes:

Note

The state preparation circuit and qubit Hamiltonian must be compatible — they should use the same qubit encoding and be derived from the same underlying system.

The qpe_circuit_builder is configured with nested algorithm references for the unitary builder and controlled circuit mapper, as shown in the configuration examples below.

Creating a phase estimation algorithm

from qdk_chemistry.algorithms import create

# Create the default (iterative) phase estimation algorithm
iqpe = create("phase_estimation", "qdk_iterative")

# Or create the standard QFT-based variant
qpe = create("phase_estimation", "qdk_standard")

Configuring settings

Settings vary by implementation. See Available implementations below for implementation-specific options.

# Configure iterative phase estimation
from qdk_chemistry.data import AlgorithmRef

# Create iterative qpe circuit builder
iqpe_circuit_builder = AlgorithmRef(
    "qpe_circuit_builder",
    "qdk_iterative",
    num_bits=10,
    controlled_circuit_mapper=AlgorithmRef(
        "controlled_circuit_mapper", "pauli_sequence"
    ),
    unitary_builder=AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1),
)
iqpe = create("phase_estimation", "qdk_iterative", shots_per_bit=10)
iqpe.settings().set("qpe_circuit_builder", iqpe_circuit_builder)
# Configure standard QFT-based phase estimation
qpe_circuit_builder = AlgorithmRef(
    "qpe_circuit_builder",
    "qdk_standard",
    num_bits=10,
    controlled_circuit_mapper=AlgorithmRef(
        "controlled_circuit_mapper", "pauli_sequence"
    ),
    unitary_builder=AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1),
)
qpe = create("phase_estimation", "qdk_standard")
qpe.settings().set("shots", 100)
qpe.settings().set("qpe_circuit_builder", qpe_circuit_builder)

Running the calculation

Once configured, the PhaseEstimation can be executed with all required dependencies:

import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import Structure

# 1. Setup molecule
coords = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]])
symbols = ["H", "H"]
structure = Structure(coords, symbols=symbols)

# 2. SCF
scf_solver = create("scf_solver")
E_scf, wfn_scf = scf_solver.run(
    structure, charge=0, spin_multiplicity=1, basis_or_guess="sto-3g"
)

# 3. Hamiltonian construction
hamiltonian_constructor = create("hamiltonian_constructor")
hamiltonian = hamiltonian_constructor.run(wfn_scf.get_orbitals())

# 4. Multi-configuration calculation (reference state)
cas_solver = create("multi_configuration_calculator")
E_cas, wfn_cas = cas_solver.run(hamiltonian, 1, 1)

# 5. Qubit mapping
from qdk_chemistry.data import MajoranaMapping

n_spin_orbitals = 2 * hamiltonian.get_orbitals().get_num_molecular_orbitals()
qubit_mapper = create("qubit_mapper")
qubit_ham = qubit_mapper.run(
    hamiltonian, MajoranaMapping.jordan_wigner(n_spin_orbitals)
)

# 6. State preparation
state_prep = create("state_prep", "sparse_isometry_gf2x")
circuit = state_prep.run(wfn_cas)

# 7. Create and run IQPE with nested algorithm settings
from qdk_chemistry.data import AlgorithmRef

iqpe = create("phase_estimation", "qdk_iterative", shots_per_bit=3)

# 8. Configure nested algorithms — the circuit builder holds num_bits, unitary_builder, and circuit_mapper
iqpe_circuit_builder = AlgorithmRef(
    "qpe_circuit_builder",
    "qdk_iterative",
    num_bits=10,
    controlled_circuit_mapper=AlgorithmRef(
        "controlled_circuit_mapper", "pauli_sequence"
    ),
    unitary_builder=AlgorithmRef(
        "hamiltonian_unitary_builder", "trotter", order=2, time=0.1
    ),
)
iqpe.settings().set(
    "qpe_circuit_builder",
    iqpe_circuit_builder,
)
iqpe.settings().set(
    "circuit_executor",
    AlgorithmRef("circuit_executor", "qdk_full_state_simulator", seed=42),
)

result = iqpe.run(
    state_preparation=circuit,
    qubit_hamiltonian=qubit_ham,
)

# 9. Inspect results
print(result.get_summary())

Available implementations

QDK/Chemistry’s PhaseEstimation provides a unified interface for phase estimation methods. You can discover available implementations programmatically:

from qdk_chemistry.algorithms import registry

# List all registered phase estimation implementations
implementations = registry.available("phase_estimation")
print(implementations)  # e.g. ['qdk_iterative', 'qdk_standard']

Iterative phase estimation (IQPE)

Factory name: "qdk_iterative"

Kitaev’s iterative algorithm [Kit95] uses a single ancilla qubit to extract phase bits sequentially, from the most significant bit (MSB) to the least significant bit (LSB). At each iteration \(k\) (from \(0\) to \(n-1\), where \(n\) is the total number of bits):

  1. Prepare the ancilla in \(|+\rangle\)

  2. Apply the controlled evolution \(C\text{-}U^{2^{n-k-1}}\) between the ancilla and the system register

  3. Apply a phase correction \(R_z(-\Phi_k)\) based on previously measured bits

  4. Measure the ancilla to determine bit \(k\)

The phase feedback is updated using Kitaev’s recursion:

\[\Phi_{k} = \frac{\Phi_{k+1}}{2} + \frac{\pi \cdot b_k}{2}\]

where \(b_k\) is the measured bit. Each bit is determined by a majority vote over multiple circuit executions (controlled by shots_per_bit).

Settings

Direct settings on IterativePhaseEstimation:

Setting

Type

Description

shots_per_bit

int

Number of circuit executions per bit, used for majority-vote determination. Default is 3.

Nested algorithm configuration (via qpe_circuit_builder):

See Phase estimation circuit builder for configuring:

  • num_bits — Number of phase bits to extract

  • unitary_buildertime — Time parameter \(t\) in \(U = e^{-iHt}\) (Trotter)

  • unitary_builderquantum_walk — Enable walk operator for qubitization (LCU)

  • controlled_circuit_mapper — Circuit synthesis strategy

Standard QFT-based phase estimation

Factory name: "qdk_standard"

The standard QPE algorithm [NC10] uses a register of \(n\) ancilla qubits to extract all phase bits simultaneously. The circuit structure consists of:

  1. Initialize \(n\) ancilla qubits in \(|+\rangle\)

  2. For each ancilla qubit \(j \in \{0, \ldots, n-1\}\), apply the controlled evolution \(C\text{-}U^{2^j}\)

  3. Apply an inverse Quantum Fourier Transform (iQFT) to the ancilla register

  4. Measure all ancilla qubits

The phase is extracted from the dominant bitstring in the measurement results.

Settings

Direct settings on StandardPhaseEstimation:

Setting

Type

Description

shots

int

Total measurement shots for the full circuit. Default is 3.

Nested algorithm configuration (via qpe_circuit_builder):

See Phase estimation circuit builder for configuring:

  • num_bits — Number of ancilla qubits (phase register size)

  • unitary_buildertime — Time parameter \(t\) in \(U = e^{-iHt}\) (Trotter)

  • unitary_builderquantum_walk — Enable walk operator for qubitization (LCU)

  • qft_do_swaps — Whether to include swap gates in the inverse QFT (Qiskit only)

  • controlled_circuit_mapper — Circuit synthesis strategy

Phase-to-energy extraction

The method for converting a measured phase \(\varphi \in [0, 1)\) to energy depends on the type of unitary used.

Time evolution (Trotter)

When QPE acts on \(U = e^{-iHt}\), the phase angle \(\theta = 2\pi\varphi\) is mapped to \((-\pi, \pi]\) and energy is:

\[E = \frac{\theta}{t}\]

Aliasing can be resolved by enumerating candidates \(E_{\mathrm{raw}} + k \cdot 2\pi/t\) and selecting the one closest to a reference energy (e.g., CASCI).

# Configure iterative phase estimation
from qdk_chemistry.data import AlgorithmRef

# Create iterative qpe circuit builder
iqpe_circuit_builder = AlgorithmRef(
    "qpe_circuit_builder",
    "qdk_iterative",
    num_bits=10,
    controlled_circuit_mapper=AlgorithmRef(
        "controlled_circuit_mapper", "pauli_sequence"
    ),
    unitary_builder=AlgorithmRef("hamiltonian_unitary_builder", "trotter", time=0.1),
)
iqpe = create("phase_estimation", "qdk_iterative", shots_per_bit=10)
iqpe.settings().set("qpe_circuit_builder", iqpe_circuit_builder)

Qubitization (LCU block encoding)

When QPE acts on the qubitization walk operator \(W\), the eigenvalues are \(e^{\pm i \arccos(E/\lambda)}\) and energy is recovered as:

\[E = \lambda \cos(\theta)\]

where \(\lambda = \sum_j |\alpha_j|\) is the L1 norm of the Hamiltonian coefficients.

To use qubitization QPE, the LCU builder must be configured with quantum_walk=True. This wraps the raw block encoding in a quantum walk operator \(W = (2|0\rangle\langle 0| - I) \cdot B[H]\), which is required for the \(\arccos\) eigenvalue-phase relationship.

# Configure phase estimation with qubitization (LCU block encoding)
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import AlgorithmRef

# Use LCU builder with quantum_walk=True for qubitization
iqpe_qubitization = create("phase_estimation", "qdk_iterative", shots_per_bit=5)

iqpe_circuit_builder = AlgorithmRef(
    "qpe_circuit_builder",
    "qdk_iterative",
    num_bits=10,
    controlled_circuit_mapper=AlgorithmRef(
        "controlled_circuit_mapper", "prepare_select_prepare"
    ),
    unitary_builder=AlgorithmRef(
        "hamiltonian_unitary_builder", "lcu", quantum_walk=True
    ),
)
iqpe_qubitization.settings().set("qpe_circuit_builder", iqpe_circuit_builder)
iqpe_qubitization.settings().set(
    "circuit_executor",
    AlgorithmRef("circuit_executor", "qdk_sparse_state_simulator", seed=42),
)

See QpeResult for the full data class documentation.

Further reading

  • The above examples can be downloaded as a complete Python script.

  • QpeCircuitBuilder: Abstract base class for phase estimation circuit builders

  • HamiltonianUnitaryBuilder: Hamiltonian simulation via Trotter-Suzuki decomposition or block-encoding methods

  • CircuitExecutor: Quantum circuit execution backends

  • StatePreparation: Load wavefunctions onto qubits as quantum circuits

  • QubitMapper: Map fermionic Hamiltonians to qubit operators

  • QpeResult: Phase estimation result data class

  • See the examples/qpe_stretched_n2.ipynb notebook for a complete end-to-end QPE workflow

  • Settings: Configuration settings for algorithms

  • Factory Pattern: Understanding algorithm creation