Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c5f276e
first draft
v-boqinzhang Mar 13, 2026
68bab4f
debug
v-boqinzhang Mar 13, 2026
4a582cb
pre-commit
v-boqinzhang Mar 13, 2026
aff56cc
add testsuit
v-boqinzhang Mar 16, 2026
668a215
add tests for basisY
v-boqinzhang Mar 16, 2026
8622e05
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang Mar 16, 2026
7867745
resolve comments 1
v-boqinzhang Mar 16, 2026
fa78df6
resolve comments 2
v-boqinzhang Mar 16, 2026
682a83b
resolve comments 3
v-boqinzhang Mar 17, 2026
8c4a5b3
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang Mar 17, 2026
cd725df
rename class names
v-boqinzhang Mar 19, 2026
e5f6f1e
add enum HadamardTestBasis
v-boqinzhang Mar 24, 2026
fd12777
pre-commit
v-boqinzhang Mar 24, 2026
33ccfb8
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang Mar 24, 2026
0bdad7d
use QsharpFactoryData
v-boqinzhang Mar 24, 2026
e28e109
move QiskitHadamardTest to plugins/qiskit
v-boqinzhang Mar 24, 2026
7c9727b
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang Mar 25, 2026
b59b057
complete Hadamard test method
v-boqinzhang Mar 30, 2026
9f924f0
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang Mar 30, 2026
c2ee618
rename: remove _generator
v-boqinzhang Mar 30, 2026
3090018
add HadamardTestBasis Z
v-boqinzhang Mar 30, 2026
276eb6e
resolve comments 4
v-boqinzhang Mar 31, 2026
7ca03d7
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang Apr 7, 2026
111dd38
modify the input of _run_impl
v-boqinzhang Apr 7, 2026
44110b8
input mapper and simulator instance, but with default
v-boqinzhang Apr 8, 2026
b1571e3
simplify tests
v-boqinzhang Apr 8, 2026
aaf964c
fix user input mapper bug
v-boqinzhang Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions python/src/qdk_chemistry/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from qdk_chemistry.algorithms.dynamical_correlation_calculator import DynamicalCorrelationCalculator
from qdk_chemistry.algorithms.energy_estimator.energy_estimator import EnergyEstimator
from qdk_chemistry.algorithms.energy_estimator.qdk import QdkEnergyEstimator
from qdk_chemistry.algorithms.hadamard_test.base import HadamardTest
from qdk_chemistry.algorithms.hamiltonian_constructor import (
HamiltonianConstructor,
QdkHamiltonianConstructor,
Expand Down Expand Up @@ -63,6 +64,7 @@
"ControlledEvolutionCircuitMapper",
"DynamicalCorrelationCalculator",
"EnergyEstimator",
"HadamardTest",
"HamiltonianConstructor",
Comment thread
v-boqinzhang marked this conversation as resolved.
"MultiConfigurationCalculator",
"MultiConfigurationScf",
Expand Down
13 changes: 13 additions & 0 deletions python/src/qdk_chemistry/algorithms/hadamard_test/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""QDK/Chemistry Hadamard test algorithms module.

This module provides factories for constructing Hadamard test circuit
generators.
"""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from .base import HadamardTestFactory

__all__: list[str] = ["HadamardTestFactory"]
195 changes: 195 additions & 0 deletions python/src/qdk_chemistry/algorithms/hadamard_test/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
"""QDK/Chemistry Hadamard test circuit generator abstractions and utilities."""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
from abc import abstractmethod
from enum import Enum
from typing import Any

from qdk_chemistry.algorithms.base import Algorithm, AlgorithmFactory
from qdk_chemistry.algorithms.circuit_executor.base import CircuitExecutor
from qdk_chemistry.algorithms.time_evolution.controlled_circuit_mapper.base import ControlledEvolutionCircuitMapper
from qdk_chemistry.data import (
Circuit,
CircuitExecutorData,
ControlledTimeEvolutionUnitary,
SettingNotFound,
TimeEvolutionUnitary,
)

__all__: list[str] = [
"HadamardTest",
"HadamardTestBasis",
"HadamardTestFactory",
"basis_to_qsharp_pauli",
]


class HadamardTestBasis(Enum):
"""Measurement bases supported by the Hadamard test control qubit."""

X = "X"
Y = "Y"
Z = "Z"

def __str__(self) -> str:
"""Return the string label ("X", "Y", or "Z") for this basis."""
return str(self.value)


def basis_to_qsharp_pauli(basis: HadamardTestBasis) -> Any:
"""Map a ``HadamardTestBasis`` to ``qsharp.Pauli`` for Q# interop."""
try:
from qdk import qsharp as _qsharp # noqa: PLC0415
except ModuleNotFoundError as err:
raise ModuleNotFoundError(
"qdk.qsharp is required to convert Hadamard test bases into qsharp.Pauli values."
) from err

return getattr(_qsharp.Pauli, basis.value)


class HadamardTest(Algorithm):
"""Abstract base class for Hadamard test generators."""

def __init__(self):
"""Initialize a Hadamard test generator."""
super().__init__()

def type_name(self) -> str:
"""Return the algorithm type name as hadamard_test."""
return "hadamard_test"

def _run_impl(
self,
state_preparation_circuit: Circuit,
time_evolution_unitary: TimeEvolutionUnitary,
shots: int,
unitary_power: int,
Comment thread
v-boqinzhang marked this conversation as resolved.
mapper: ControlledEvolutionCircuitMapper | None = None,
simulator: CircuitExecutor | None = None,
simulator_seed: int = 42,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seed can be removed from this level since it can be defined within CircuitExecutor.

test_basis: HadamardTestBasis = HadamardTestBasis.X,
) -> CircuitExecutorData:
Comment thread
v-boqinzhang marked this conversation as resolved.
r"""Run the Hadamard test by building and executing a backend-specific circuit.

Args:
state_preparation_circuit: Circuit that prepares the trial state on system qubits.
time_evolution_unitary: Time evolution unitary :math:`\exp(-i H \Delta t)`.
shots: Number of shots to execute the circuit.
unitary_power: Power :math:`n` used to form the controlled unitary :math:`U^n`. If ``mapper`` is
provided, this value must match ``mapper.settings().get("power")``.
mapper: Controlled evolution circuit mapper. If ``None``, the default ``pauli_sequence`` mapper is used.
If provided, it is used as-is and must already have its ``power`` setting configured.
simulator: Circuit executor. If ``None``, the default ``qdk_full_state_simulator`` is used.
simulator_seed: Random seed used for reproducible sampling when ``simulator`` is ``None``.
test_basis: Measurement basis for the control qubit (``HadamardTestBasis.X``, ``HadamardTestBasis.Y``,
or ``HadamardTestBasis.Z``).

Returns:
CircuitExecutorData returned directly by the given simulator.

"""
if not isinstance(test_basis, HadamardTestBasis):
raise TypeError("test_basis must be an instance of HadamardTestBasis.")
if not isinstance(time_evolution_unitary, TimeEvolutionUnitary):
raise TypeError("time_evolution_unitary must be an instance of TimeEvolutionUnitary.")
num_system_qubits = time_evolution_unitary.get_num_qubits()
if not isinstance(unitary_power, int):
raise TypeError("unitary_power must be an integer.")
if unitary_power < 1:
raise ValueError("unitary_power must be a positive integer.")
if not isinstance(simulator_seed, int):
raise TypeError("simulator_seed must be an integer.")
if not isinstance(shots, int):
raise TypeError("shots must be an integer.")
if shots <= 0:
raise ValueError("shots must be a positive integer.")

from qdk_chemistry.algorithms import create # noqa: PLC0415

controlled_evolution = ControlledTimeEvolutionUnitary(
time_evolution_unitary=time_evolution_unitary,
control_indices=[0],
)

if mapper is None:
try:
mapper = create("controlled_evolution_circuit_mapper", "pauli_sequence")
except KeyError as err:
raise ValueError("Unknown controlled evolution circuit mapper type: pauli_sequence.") from err
mapper.settings().update("power", unitary_power)
elif not isinstance(mapper, ControlledEvolutionCircuitMapper):
raise TypeError("mapper must be an instance of ControlledEvolutionCircuitMapper or None.")
else:
try:
mapper_power = mapper.settings().get("power")
except SettingNotFound as err:
raise ValueError(
"Provided mapper must define a 'power' setting when mapper is supplied explicitly."
) from err
if mapper_power != unitary_power:
raise ValueError(
"unitary_power must match mapper.settings().get('power') when mapper is supplied explicitly."
)
ctrl_time_evol_unitary_circuit = mapper.run(controlled_evolution=controlled_evolution)

circuit = self._build_hadamard_test_circuit(
state_preparation_circuit,
num_system_qubits,
ctrl_time_evol_unitary_circuit,
test_basis,
)

if simulator is None:
try:
simulator = create("circuit_executor", "qdk_full_state_simulator")
except KeyError as err:
raise ValueError("Unknown simulator type: qdk_full_state_simulator.") from err
simulator.settings().update("seed", simulator_seed)
elif not isinstance(simulator, CircuitExecutor):
raise TypeError("simulator must be an instance of CircuitExecutor or None.")

return simulator.run(circuit, shots=shots)

@abstractmethod
def _build_hadamard_test_circuit(
self,
state_preparation_circuit: Circuit,
num_system_qubits: int,
Comment thread
v-boqinzhang marked this conversation as resolved.
ctrl_time_evol_unitary_circuit: Circuit,
test_basis: HadamardTestBasis = HadamardTestBasis.X,
) -> Circuit:
r"""Build the Hadamard test circuit for a given state and controlled unitary.

Currently, the function only accepts the controlled unitary circuit whose index of ancilla qubit is 0.

Args:
state_preparation_circuit: Circuit that prepares the trial state on system qubits.
num_system_qubits: Number of qubits in the system register.
ctrl_time_evol_unitary_circuit: Controlled evolution circuit implementing the target unitary.
test_basis: Measurement basis for the control qubit (``HadamardTestBasis.X``, ``HadamardTestBasis.Y``,
or ``HadamardTestBasis.Z``).

Returns:
Circuit representing the Hadamard test workflow for the selected backend.

"""


class HadamardTestFactory(AlgorithmFactory):
"""Factory class for creating Hadamard test generator instances."""

def __init__(self):
"""Initialize the HadamardTestFactory."""
super().__init__()

def algorithm_type_name(self) -> str:
"""Return the algorithm type name as hadamard_test."""
return "hadamard_test"

def default_algorithm_name(self) -> str:
"""Return 'qdk_hadamard_test' as the default algorithm name."""
return "qdk_hadamard_test"
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
"""Hadamard test circuit generator implementations for Q# and Qiskit backends."""

# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See LICENSE.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from qdk_chemistry.algorithms.hadamard_test.base import (
HadamardTest,
HadamardTestBasis,
basis_to_qsharp_pauli,
)
from qdk_chemistry.data import Circuit
from qdk_chemistry.data.circuit import QsharpFactoryData
from qdk_chemistry.utils import Logger
from qdk_chemistry.utils.qsharp import QSHARP_UTILS

__all__: list[str] = ["QdkHadamardTest"]


class QdkHadamardTest(HadamardTest):
"""Hadamard test circuit generator based on Q# framework."""

def __init__(
self,
):
"""Initialize QdkHadamardTest."""
Logger.trace_entering()
super().__init__()

def _build_hadamard_test_circuit(
self,
state_preparation_circuit: Circuit,
num_system_qubits: int,
ctrl_time_evol_unitary_circuit: Circuit,
Comment thread
v-boqinzhang marked this conversation as resolved.
test_basis: HadamardTestBasis = HadamardTestBasis.X,
) -> Circuit:
r"""Build a Hadamard test circuit using the Q# backend.

Currently, the function only accepts the controlled unitary circuit whose index of ancilla qubit is 0.

Args:
state_preparation_circuit: Circuit that prepares the trial state on system qubits.
num_system_qubits: Number of qubits in the system register.
ctrl_time_evol_unitary_circuit: Controlled evolution circuit implementing the target unitary.
test_basis: Measurement basis for the control qubit (``HadamardTestBasis.X``, ``HadamardTestBasis.Y``,
or ``HadamardTestBasis.Z``).

Returns:
Circuit containing compiled and rendered Q# Hadamard test artifacts.

"""
Logger.debug(f"Building qsharp circuit for measurement on {test_basis.value} basis.")

qsharp_basis = basis_to_qsharp_pauli(test_basis)

state_prep_op = state_preparation_circuit._qsharp_op # noqa: SLF001
if state_prep_op is None:
raise ValueError("Input state_preparation_circuit cannot be used for QdkHadamardTest.")

ctrl_evol_op = ctrl_time_evol_unitary_circuit._qsharp_op # noqa: SLF001
if ctrl_evol_op is None:
raise ValueError("Input ctrl_time_evol_unitary_circuit cannot be used for QdkHadamardTest.")

hadamard_parameters = {
"statePrep": state_prep_op,
"repControlledEvolution": ctrl_evol_op,
"testBasis": qsharp_basis,
"control": 0,
"systems": [i + 1 for i in range(num_system_qubits)],
}

return Circuit(
qsharp_factory=QsharpFactoryData(
program=QSHARP_UTILS.HadamardTest.HadamardTest,
parameter=hadamard_parameters,
)
)

def name(self) -> str:
"""Return the name of the QdkHadamardTest algorithm."""
return "qdk_hadamard_test"
6 changes: 6 additions & 0 deletions python/src/qdk_chemistry/algorithms/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ def _register_python_factories():
"""
from qdk_chemistry.algorithms.circuit_executor import CircuitExecutorFactory # noqa: PLC0415
from qdk_chemistry.algorithms.energy_estimator import EnergyEstimatorFactory # noqa: PLC0415
from qdk_chemistry.algorithms.hadamard_test import HadamardTestFactory # noqa: PLC0415
from qdk_chemistry.algorithms.phase_estimation import PhaseEstimationFactory # noqa: PLC0415
from qdk_chemistry.algorithms.qubit_hamiltonian_solver import QubitHamiltonianSolverFactory # noqa: PLC0415
from qdk_chemistry.algorithms.qubit_mapper import QubitMapperFactory # noqa: PLC0415
Expand All @@ -523,6 +524,7 @@ def _register_python_factories():
register_factory(ControlledEvolutionCircuitMapperFactory())
register_factory(CircuitExecutorFactory())
register_factory(PhaseEstimationFactory())
register_factory(HadamardTestFactory())


_ = _register_cpp_factories()
Expand Down Expand Up @@ -579,6 +581,9 @@ def _register_python_algorithms():
QdkSparseStateSimulator,
)
from qdk_chemistry.algorithms.energy_estimator.qdk import QdkEnergyEstimator # noqa: PLC0415
from qdk_chemistry.algorithms.hadamard_test.hadamard_test import ( # noqa: PLC0415
QdkHadamardTest,
)
from qdk_chemistry.algorithms.phase_estimation.iterative_phase_estimation import ( # noqa: PLC0415
IterativePhaseEstimation,
)
Expand Down Expand Up @@ -610,6 +615,7 @@ def _register_python_algorithms():
register(lambda: QdkFullStateSimulator())
register(lambda: QdkSparseStateSimulator())
register(lambda: IterativePhaseEstimation())
register(lambda: QdkHadamardTest())


_register_python_algorithms()
5 changes: 4 additions & 1 deletion python/src/qdk_chemistry/plugins/qiskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,19 @@ def qiskit_load():
Logger.trace_entering()

from qdk_chemistry.algorithms import register # noqa: PLC0415
from qdk_chemistry.plugins.qiskit.hadamard_test import QiskitHadamardTest # noqa: PLC0415
from qdk_chemistry.plugins.qiskit.regular_isometry import RegularIsometryStatePreparation # noqa: PLC0415
from qdk_chemistry.plugins.qiskit.standard_phase_estimation import QiskitStandardPhaseEstimation # noqa: PLC0415

register(lambda: RegularIsometryStatePreparation())
register(lambda: QiskitStandardPhaseEstimation())
register(lambda: QiskitHadamardTest())

Logger.debug(
"Qiskit plugins loaded: "
f"[{RegularIsometryStatePreparation().type_name()}: {RegularIsometryStatePreparation().name()}], "
f"[{QiskitStandardPhaseEstimation().type_name()}: {QiskitStandardPhaseEstimation().name()}]."
f"[{QiskitStandardPhaseEstimation().type_name()}: {QiskitStandardPhaseEstimation().name()}], "
f"[{QiskitHadamardTest().type_name()}: {QiskitHadamardTest().name()}]."
)


Expand Down
Loading
Loading