Hadamard test

The HadamardTest algorithm in QDK/Chemistry estimates expectation values associated with a target unitary by measuring a single control qubit after a controlled evolution. Following QDK/Chemistry’s algorithm design principles, it takes a state-preparation Circuit (typically from StatePreparation), a UnitaryRepresentation (for example from HamiltonianUnitaryBuilder), and shot count as input, and returns CircuitExecutorData containing measurement bitstring counts.

Overview

The Hadamard test is a standard primitive for extracting overlap information from a controlled unitary application. Given a prepared system state \(\vert \psi\rangle\) and a unitary \(U\), the algorithm builds a circuit with one control qubit and the system register:

  1. Prepare the system in \(\vert \psi\rangle\)

  2. Apply \(H\) to the control qubit

  3. Apply controlled-\(U\) to the system register (controlled on the control qubit)

  4. Measure the control qubit in a selected basis

QDK/Chemistry supports control-qubit measurement in X and Y bases through the test_basis setting.

For the standard Hadamard-test circuit, test_basis="X" estimates \(\mathrm{Re}\langle \psi \vert U \vert \psi \rangle\) and test_basis="Y" estimates \(\mathrm{Im}\langle \psi \vert U \vert \psi \rangle\).

The algorithm returns the measurement counts for the control qubit, which can be converted to an expectation estimate:

\[\hat{m} = \frac{N_0 - N_1}{N_0 + N_1}\]

Typical workflow

A Hadamard-test workflow usually needs two prepared inputs:

  1. A state-preparation Circuit (often produced by StatePreparation from a reference wavefunction)

  2. A target UnitaryRepresentation (commonly a Hamiltonian time-evolution operator from HamiltonianUnitaryBuilder)

  3. A configured HadamardTest instance to execute the circuit and collect counts

In molecular pipelines, structure setup, SCF, and multi-configuration steps are typically used only to provide the wavefunction and Hamiltonian needed to build those two inputs.

Using the HadamardTest

Note

This algorithm is currently available only in the Python API.

This section demonstrates how to create, configure, and run a Hadamard test calculation.

Input requirements

The HadamardTest requires the following inputs:

State preparation circuit

A Circuit that prepares the target system state.

UnitaryRepresentation

A UnitaryRepresentation describing the unitary to apply under control. This is often generated from a qubit Hamiltonian via HamiltonianUnitaryBuilder.

Shots

A positive integer number of circuit executions used to estimate expectation values from counts.

Settings

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

  • test_basis - Measurement basis for the control qubit. Allowed values are "X" and "Y".

  • controlled_circuit_mapper - A AlgorithmRef to a ControlledCircuitMapper implementation used to synthesize controlled-\(U\).

  • circuit_executor - A AlgorithmRef to a CircuitExecutor implementation used to run the final circuit.

Note

The state preparation circuit and unitary should be qubit-compatible and represent the same target system.

Creating a Hadamard test algorithm

from qdk_chemistry.algorithms import create

# Create the default Hadamard test algorithm
hadamard_test = create("hadamard_test", "qdk")

Configuring settings

from qdk_chemistry.data import AlgorithmRef

# Configure Hadamard test settings
hadamard_test = create("hadamard_test", "qdk", test_basis="X")
hadamard_test.settings().set(
    "controlled_circuit_mapper",
    AlgorithmRef("controlled_circuit_mapper", "pauli_sequence"),
)
hadamard_test.settings().set(
    "circuit_executor",
    AlgorithmRef("circuit_executor", "qdk_full_state_simulator", seed=42),
)

# Change measurement basis on the same algorithm implementation if needed
# hadamard_test.settings().set("test_basis", "Y")

Running the calculation

import numpy as np
from qdk_chemistry.algorithms import create
from qdk_chemistry.data import MajoranaMapping, 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")
_, 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")
_, wfn_cas = cas_solver.run(hamiltonian, 1, 1)

# 5. Qubit mapping
n_spin_orbitals = 2 * hamiltonian.get_orbitals().get_num_molecular_orbitals()
qubit_mapper = create("qubit_mapper")
qubit_hamiltonian = qubit_mapper.run(
    hamiltonian, MajoranaMapping.jordan_wigner(n_spin_orbitals)
)

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

# 7. Build target unitary U
unitary_builder = create(
    "hamiltonian_unitary_builder",
    "trotter",
    time=float(np.pi / 48.0),
    power=10,
)
unitary = unitary_builder.run(qubit_hamiltonian)

# 8. Run Hadamard test and estimate expectation from counts
shots = 100
result = hadamard_test.run(state_preparation, unitary, shots=shots)
counts = result.bitstring_counts
observable_value = (counts.get("0", 0) - counts.get("1", 0)) / sum(counts.values())

print("bitstring_counts:", counts)
print("estimated expectation:", observable_value)

Available implementations

QDK/Chemistry’s HadamardTest currently provides one built-in implementation. You can discover available implementations programmatically:

from qdk_chemistry.algorithms import registry

# List all registered Hadamard test implementations
implementations = registry.available("hadamard_test")
print(implementations)  # e.g. ['qdk']

Factory name: "qdk"

Settings

Direct settings on HadamardTest:

Setting

Type

Description

test_basis

string

Measurement basis for the control qubit. Allowed values are "X" and "Y". Default is "X".

controlled_circuit_mapper

AlgorithmRef

Nested ControlledCircuitMapper used to synthesize controlled-\(U\).

circuit_executor

AlgorithmRef

Nested CircuitExecutor used to execute the generated circuit.

Interpreting results

The algorithm returns CircuitExecutorData, whose bitstring_counts can be converted to an expectation estimate:

\[\hat{m} = \frac{N_0 - N_1}{N_0 + N_1}\]

Here \(N_0 + N_1\) is the number of clean measurement outcomes recorded in bitstring_counts. This can differ from requested shots when an executor tracks loss separately. Finite sampling still introduces statistical uncertainty, so increasing shots typically improves estimator stability.

Further reading