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:
Prepare the system in \(\vert \psi\rangle\)
Apply \(H\) to the control qubit
Apply controlled-\(U\) to the system register (controlled on the control qubit)
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:
Typical workflow
A Hadamard-test workflow usually needs two prepared inputs:
A state-preparation
Circuit(often produced by StatePreparation from a reference wavefunction)A target
UnitaryRepresentation(commonly a Hamiltonian time-evolution operator from HamiltonianUnitaryBuilder)A configured
HadamardTestinstance 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
Circuitthat prepares the target system state.- UnitaryRepresentation
A
UnitaryRepresentationdescribing 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
HadamardTestis configured via its settings object, which includes:test_basis- Measurement basis for the control qubit. Allowed values are"X"and"Y".controlled_circuit_mapper- AAlgorithmRefto a ControlledCircuitMapper implementation used to synthesize controlled-\(U\).circuit_executor- AAlgorithmRefto 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 |
|---|---|---|
|
string |
Measurement basis for the control qubit. Allowed values are |
|
AlgorithmRef |
Nested ControlledCircuitMapper used to synthesize controlled-\(U\). |
|
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:
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
The above examples can be downloaded as a complete Python script.
HamiltonianUnitaryBuilder: Build target unitaries such as time evolution
StatePreparation: Build state-preparation circuits from wavefunctions
ControlledCircuitMapper: Synthesize controlled-unitary circuits
CircuitExecutor: Execute circuits and collect bitstring counts
Settings: Configure algorithm settings
Factory Pattern: Create algorithms by type and implementation name