A quantum algorithm that uses the Hadamard test to estimate the expectation value of a unitary operator with respect to a given quantum state. This algorithm is fundamental in quantum computing and has applications in various quantum algorithms, including quantum phase estimation and variational quantum algorithms.
Resources
1Install
npx skillscat add unitarylab/quantum-skills/hadamard-test Install via the SkillsCat registry.
Hadamard Test
Purpose
The Hadamard Test uses a single ancilla qubit to estimate the complex expectation value $\langle\psi|U|\psi\rangle$ for a unitary $U$ and state $|\psi\rangle$. It is a fundamental subroutine in quantum algorithms, supporting expectation value estimation, state overlap testing (swap test), and single-bit phase estimation.
Use this skill when you need to:
- Estimate $\text{Re}\langle\psi|U|\psi\rangle$ or $\text{Im}\langle\psi|U|\psi\rangle$.
- Measure $|\langle\phi|\psi\rangle|^2$ (overlap between two states) via the swap test.
- Reconstruct a complex eigenphase from real and imaginary parts.
One-Step Run Example Command
python ./scripts/algorithm.pyOverview
Three supported modes:
'expectation': Estimates $\text{Re}$ or $\text{Im}$ of $\langle\psi|U|\psi\rangle$ by measuring $\langle Z\rangle_{\text{anc}} = p(0) - p(1)$.'swap_test': Estimates $|\langle\phi|\psi\rangle|^2$ using a controlled-SWAP circuit.'phase_estimation': Runs both real and imaginary Hadamard tests to reconstruct the full complex eigenphase.
The core circuit (for mode 'expectation'):
- Ancilla qubit in $|0\rangle$.
- Apply $H$ to ancilla (optionally $S^\dagger$ for imaginary part).
- Optionally prepare $|\psi\rangle$ on target.
- Apply controlled-$U$ (ancilla controls, target receives $U$).
- Apply $H$ to ancilla.
- Measure ancilla: $\langle Z\rangle = p(0) - p(1) = \text{Re}\langle\psi|U|\psi\rangle$.
Prerequisites
- Quantum gates: H, S, $S^\dagger$, controlled-U.
- Python:
numpy,Circuit,State.
Using the Provided Implementation
from unitarylab.algorithms import HadamardTestAlgorithm
from unitarylab.core import Circuit
import numpy as np
# Example: estimate Re(<+|RZ(0.8)|+>) = cos(0.4)
U = Circuit(1, name="RZ_0.8", backend='torch')
U.rz(0.8, 0)
prepare_psi = Circuit(1, name="|+>", backend='torch')
prepare_psi.h(0)
algo = HadamardTestAlgorithm()
# Estimate real part
result_re = algo.run(mode='expectation', U=U, prepare_psi=prepare_psi,
imag=False, shots=20000, backend='torch')
print(result_re['est_val']) # ≈ cos(0.4) ≈ 0.9211
# Estimate imaginary part
result_im = algo.run(mode='expectation', U=U, prepare_psi=prepare_psi,
imag=True, shots=20000, backend='torch')
print(result_im['est_val']) # ≈ -sin(0.4) ≈ -0.3894Core Parameters Explained
| Parameter | Type | Default | Description |
|---|---|---|---|
mode |
str |
'expectation' |
One of 'expectation', 'swap_test', 'phase_estimation'. |
U |
Circuit or None |
None |
Unitary operator. Required for 'expectation' and 'phase_estimation'. |
prepare_psi |
Circuit or None |
None |
Prepares $|\psi\rangle$. Required for 'swap_test' and optional for others. |
prepare_phi |
Circuit or None |
None |
Prepares $|\phi\rangle$. Required only for 'swap_test'. |
imag |
bool |
False |
If True, estimates imaginary part (inserts $S^\dagger$ before ancilla). Only valid for 'expectation'. |
shots |
int |
20000 |
Number of measurement samples for statistical estimation. |
backend |
str |
'torch' |
Simulation backend. 'torch' required. |
algo_dir |
str or None |
None |
Directory to save SVG circuit diagram. |
Common misunderstandings:
imag=Trueinserts $S^\dagger$ (not $S$) on the ancilla before the first $H$, so the result is $\text{Im}\langle\psi|U|\psi\rangle$ (not its negative).- For
'swap_test',Uis not needed; the overlap is measured via controlled-SWAP. - Shot noise is simulated via
numpy.random.binomial; the exact statevector probability is known internally.
Return Fields
| Key | Type | Description |
|---|---|---|
estimated_value |
float or dict |
Estimated value. A single float for 'expectation'; a dict {'real': ..., 'imag': ..., 'phase': ..., 'magnitude': ...} for 'phase_estimation'; a float for 'swap_test'. |
circuit_path |
str |
Path to SVG circuit diagram. |
message |
str |
Human-readable result summary. |
plot |
str |
ASCII art result panel. |
Implementation Architecture
HadamardTestAlgorithm in algorithm.py organizes execution into five stages within run(), with a single circuit-building helper and shot-noise simulation.
run(mode, U, prepare_psi, prepare_phi, imag, shots, backend, algo_dir) — Five Stages:
| Stage | Code Action | Algorithmic Role |
|---|---|---|
| 1 — Validation | Checks mode string and required parameters per mode |
Prevents invalid mode / missing input combinations |
| 2 — Circuit Construction | Dispatches to _build_hadamard_test_circuit() based on mode; for swap_test builds joint prep and U_swap; for phase_estimation builds both real and imag circuits |
Creates one or two Circuit objects stored in circuits dict |
| 3 — Simulation + Sampling | For each circuit: circ.execute() → State.probabilities_dict([0]) → extracts p0_exact; simulates shot noise via numpy.random.binomial(shots, p0_exact) |
Runs statevector simulation; converts ancilla probabilities to noisy <Z> estimate |
| 4 — Post-Processing | Accumulates measurements from <Z> = p0 - p1; computes final est_val per mode |
Expectation: returns <Z> directly; Swap test: clips to [0,1]; Phase estimation: calls _estimate_phi_from_real_imag() |
| 5 — Export | circ.draw(filename=..., title=...) for each circuit |
Saves SVG circuit diagram(s) |
Helper Methods:
_build_hadamard_test_circuit(U, prepare_psi, imag, backend)— Constructs the single-ancilla circuit. Ancilla is qubit 0; target is qubits1..n. AppliesHto ancilla, optionallySdagfor imaginary part, appendsprepare_psito target, then appendsUas a controlled unitary (U.control([anc], control_sequence='1')), appliesHagain to ancilla._estimate_phi_from_real_imag(cos_est, sin_est)— Computesatan2(sin_est, cos_est) / (2π) % 1.0to extract the eigenphase $\phi \in [0,1)$._as_statevector(result)— Convertsexecute()output to a normalizednumpyarray._update_last_result/_build_return— Store all runtime fields and package result dict.
Mode dispatch logic:
'expectation': builds one circuit withimagflag'swap_test': builds joint state|φ⊗ψ⟩and controlled-SWAP unitary; runs real circuit only'phase_estimation': builds two circuits (real + imag); combines via_estimate_phi_from_real_imag()
Data flow: mode selection → _build_hadamard_test_circuit() per branch → execute() → ancilla p0 → shot noise → <Z> → mode-specific reduction → est_val → _build_return().
Understanding the Key Quantum Components
The ancilla qubit (qubit 0) acts as a quantum probe. After the circuit, its measurement probabilities encode the inner product:
- Real test: $p(0) - p(1) = \text{Re}\langle\psi|U|\psi\rangle$
- Imaginary test: $p(0) - p(1) = \text{Im}\langle\psi|U|\psi\rangle$
2. Phase Kickback via Controlled-$U$
The controlled-$U$ gate (controlled by the ancilla, applied to the target register) creates quantum interference. When the ancilla is in superposition $\frac{1}{\sqrt{2}}(|0\rangle + |1\rangle)$, the target evolves conditionally, and the ancilla accumulates relative phase information about $U$.
3. $S^\dagger$ for Imaginary Part
Inserting $S^\dagger$ after the first $H$ rotates the ancilla by $-\pi/2$, effectively shifting the measurement basis to extract the imaginary part.
4. Swap Test Circuit
For mode 'swap_test', a SWAP gate on the joint system $|\phi\rangle|\psi\rangle$ is used. The controlled-SWAP with ancilla in superposition produces:
$$p(0) = \frac{1}{2}(1 + |\langle\phi|\psi\rangle|^2)$$
So $|\langle\phi|\psi\rangle|^2 = 2p(0) - 1$.
Theory-to-Code Mapping
| README / Theory Concept | Code Object or Location |
|---|---|
| Ancilla in superposition $\frac{1}{\sqrt{2}}( | 0\rangle+ |
| $S^\dagger$ rotation for imaginary part | qc.sdag(anc) inserted if imag=True |
| State preparation $ | \psi\rangle$ on target |
| Controlled-$U$ gate | U.control([anc], control_sequence='1') appended to target register |
| Final $H$ measurement on ancilla | Second qc.h(anc) closes the Hadamard test |
| $\langle Z\rangle = p(0) - p(1)$ | exp_val = p0 - p1 in Stage 3 |
| Shot noise model | c0 = rng.binomial(shots, p0_exact); p0 = c0 / shots |
| SWAP test: $p(0) = (1 + |\langle\phi | \psi\rangle|^2)/2$ |
| Phase $\phi = \text{atan2}(\text{Im}, \text{Re}) / 2\pi$ | _estimate_phi_from_real_imag(re_est, im_est) |
Return value estimated_value |
est_val in _build_return() — single float for all modes |
Notes on encapsulation: The swap test is implemented by constructing a new U_swap from SWAP gates and a joint preparation Circuit, then running the exact same _build_hadamard_test_circuit code path. No special circuit structure is needed for the swap test case beyond this reuse. Shot noise is applied as a post-simulation binomial sampling; the underlying statevector probability is exact.
Mathematical Deep Dive
Starting from $|0\rangle_{\text{anc}}|\psi\rangle_{\text{tgt}}$, after the full circuit:
$$p(0) = \frac{1 + \text{Re}\langle\psi|U|\psi\rangle}{2}, \quad p(1) = \frac{1 - \text{Re}\langle\psi|U|\psi\rangle}{2}$$
$$\langle Z\rangle_{\text{anc}} = p(0) - p(1) = \text{Re}\langle\psi|U|\psi\rangle$$
Imaginary Hadamard test (with $S^\dagger$ inserted):
$$p(0) - p(1) = \text{Im}\langle\psi|U|\psi\rangle$$
Combined complex estimation:
$$\langle\psi|U|\psi\rangle = \text{Re} + i \cdot \text{Im}$$
Phase estimation mode:
$$|e^{i\phi}| = \sqrt{\text{Re}^2 + \text{Im}^2}, \quad \phi = \text{atan2}(\text{Im}, \text{Re})$$
Hands-On Example
from unitarylab.algorithms import HadamardTestAlgorithm
from unitarylab.core import Circuit
import numpy as np
# Swap test: estimate |<phi|psi>|^2
n = 2
prep_phi = Circuit(n, name="phi", backend='torch')
prep_phi.h(0); prep_phi.h(1) # |++>
prep_psi = Circuit(n, name="psi", backend='torch')
prep_psi.h(0); prep_psi.cx(0, 1) # Bell state
algo = HadamardTestAlgorithm()
result = algo.run(
mode='swap_test',
prepare_psi=prep_psi,
prepare_phi=prep_phi,
shots=40000,
backend='torch'
)
print(f"Overlap^2: {result['estimated_value']:.4f}")
# |<Bell|++>|^2 = |(<00|+<11|)/sqrt(2) * (|++>)|^2 = 0.5
# Phase estimation mode
U2 = Circuit(1, name="T_gate", backend='torch')
U2.t(0) # T|0> = |0> so <0|T|0> = 1
result2 = algo.run(mode='phase_estimation', U=U2, shots=20000)
print(result2['estimated_value'])Implementing Your Own Version
from unitarylab.core import Circuit
from typing import Optional
def hadamard_test_circuit(U: Circuit, prepare_psi=None, imag=False) -> Circuit:
n = U.get_num_qubits()
qc = Circuit(1 + n, name="HadamardTest")
anc = 0
tgt = list(range(1, 1 + n))
qc.h(anc)
if imag:
qc.sdag(anc)
if prepare_psi is not None:
qc.append(prepare_psi, target=tgt)
qc.append(U, target=tgt, control=[anc], control_sequence="1")
qc.h(anc)
return qcDebugging Tips
'swap_test'requires bothprepare_psiandprepare_phi: Both must have the same number of qubits.- Shot noise makes estimate vary: Increase
shotsfor a more stable estimate. The exact value is computed from the statevector; shot noise is simulated for realism. - Result close to 0 for real part: If $|\psi\rangle$ is an eigenstate of $U$ with eigenvalue $\pm 1$, the imaginary part is 0. Use
imag=Trueto check. mode='expectation'withprepare_psi=None: The target starts in $|0\rangle$, so you get $\langle 0|U|0\rangle$.- Wrong mode string: The mode must be exactly
'expectation','swap_test', or'phase_estimation'.