-
Notifications
You must be signed in to change notification settings - Fork 2
Add Hadamard test algorithm #405
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
v-boqinzhang
wants to merge
27
commits into
main
Choose a base branch
from
gh-feature/hadamard_test
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
c5f276e
first draft
v-boqinzhang 68bab4f
debug
v-boqinzhang 4a582cb
pre-commit
v-boqinzhang aff56cc
add testsuit
v-boqinzhang 668a215
add tests for basisY
v-boqinzhang 8622e05
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang 7867745
resolve comments 1
v-boqinzhang fa78df6
resolve comments 2
v-boqinzhang 682a83b
resolve comments 3
v-boqinzhang 8c4a5b3
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang cd725df
rename class names
v-boqinzhang e5f6f1e
add enum HadamardTestBasis
v-boqinzhang fd12777
pre-commit
v-boqinzhang 33ccfb8
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang 0bdad7d
use QsharpFactoryData
v-boqinzhang e28e109
move QiskitHadamardTest to plugins/qiskit
v-boqinzhang 7c9727b
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang b59b057
complete Hadamard test method
v-boqinzhang 9f924f0
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang c2ee618
rename: remove _generator
v-boqinzhang 3090018
add HadamardTestBasis Z
v-boqinzhang 276eb6e
resolve comments 4
v-boqinzhang 7ca03d7
Merge branch 'gh-branch/main' into gh-feature/hadamard_test
v-boqinzhang 111dd38
modify the input of _run_impl
v-boqinzhang 44110b8
input mapper and simulator instance, but with default
v-boqinzhang b1571e3
simplify tests
v-boqinzhang aaf964c
fix user input mapper bug
v-boqinzhang File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
python/src/qdk_chemistry/algorithms/hadamard_test/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
195
python/src/qdk_chemistry/algorithms/hadamard_test/base.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
|
v-boqinzhang marked this conversation as resolved.
|
||
| mapper: ControlledEvolutionCircuitMapper | None = None, | ||
| simulator: CircuitExecutor | None = None, | ||
| simulator_seed: int = 42, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| test_basis: HadamardTestBasis = HadamardTestBasis.X, | ||
| ) -> CircuitExecutorData: | ||
|
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, | ||
|
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" | ||
82 changes: 82 additions & 0 deletions
82
python/src/qdk_chemistry/algorithms/hadamard_test/hadamard_test.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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, | ||
|
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" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.