diff --git a/src/braket/experimental/algorithms/hadamard_test/hadamard_test.md b/src/braket/experimental/algorithms/hadamard_test/hadamard_test.md new file mode 100644 index 00000000..a02be3fc --- /dev/null +++ b/src/braket/experimental/algorithms/hadamard_test/hadamard_test.md @@ -0,0 +1,7 @@ +The Hadamard test is a quantum circuit that allows estimation of the real and imaginary parts of the expected value of a unitary operator. It is a fundamental subroutine used in many quantum algorithms, including quantum phase estimation and amplitude estimation. The test works by applying a controlled-unitary operation between an auxiliary qubit and the system of interest, with the measurement statistics of the auxiliary qubit encoding information about the unitary's expectation value. + + diff --git a/src/braket/experimental/algorithms/hadamard_test/hadamard_test.py b/src/braket/experimental/algorithms/hadamard_test/hadamard_test.py new file mode 100644 index 00000000..f5781847 --- /dev/null +++ b/src/braket/experimental/algorithms/hadamard_test/hadamard_test.py @@ -0,0 +1,50 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from braket.circuits import Circuit, circuit, Instruction + + +@circuit.subroutine(register=True) +def hadamard_test(controlled_unitary: Circuit, phase: str = 'real') -> Circuit: + """Implements the Hadamard test circuit for estimating real or imaginary parts + of the expected value of a unitary operator. + + Args: + controlled_unitary (Circuit): The unitary operation to be controlled + phase (str): Either 'real' or 'imaginary' to determine which component to estimate + + Returns: + Circuit: The complete Hadamard test circuit + """ + if phase not in ['real', 'imaginary']: + raise ValueError("Phase must be either 'real' or 'imaginary'") + + circ = Circuit() + + circ.h(0) + if phase == 'imaginary': + circ.s(0).adjoint() + + # Add control qubit to the unitary circuit + for inst in controlled_unitary.instructions: + targets = [q + 1 for q in inst.target] + controlled_inst = Instruction( + operator=inst.operator, + target=targets, + control=0 + ) + circ.add_instruction(controlled_inst) + + circ.h(0) + + return circ diff --git a/test/unit_tests/braket/experimental/algorithms/hadamard_test/test_hadamard_test.py b/test/unit_tests/braket/experimental/algorithms/hadamard_test/test_hadamard_test.py new file mode 100644 index 00000000..143b2843 --- /dev/null +++ b/test/unit_tests/braket/experimental/algorithms/hadamard_test/test_hadamard_test.py @@ -0,0 +1,85 @@ +import numpy as np +import pytest +from braket.circuits import Circuit, ResultType +from braket.devices import LocalSimulator + +from braket.experimental.algorithms.hadamard_test.hadamard_test import hadamard_test + + +def test_hadamard_test_real(): + unitary = Circuit().h(0) + + test_circuit = hadamard_test(unitary, phase='real') + test_circuit.measure(0) + + device = LocalSimulator() + task = device.run(test_circuit, shots=1000) + + counts = task.result().measurement_counts + p_zero = counts.get('0', 0) / 1000 + real_part = 2 * p_zero - 1 + + assert np.isclose(real_part, 1/np.sqrt(2), atol=0.1) + + +def test_hadamard_test_imaginary(): + unitary = Circuit().s(0) + + test_circuit = hadamard_test(unitary, phase='imaginary') + test_circuit.measure(0) + + device = LocalSimulator() + task = device.run(test_circuit, shots=10000) + + counts = task.result().measurement_counts + p_zero = counts.get('0', 0) / 10000 + imag_part = 2 * p_zero - 1 + + assert np.isclose(imag_part, 0.0, atol=0.1) + + +def test_hadamard_test_identity(): + unitary = Circuit().i(0) + + real_circuit = hadamard_test(unitary, phase='real') + imag_circuit = hadamard_test(unitary, phase='imaginary') + real_circuit.measure(0) + imag_circuit.measure(0) + + device = LocalSimulator() + + real_task = device.run(real_circuit, shots=1000) + real_counts = real_task.result().measurement_counts + p_zero_real = real_counts.get('0', 0) / 1000 + real_part = 2 * p_zero_real - 1 + + imag_task = device.run(imag_circuit, shots=1000) + imag_counts = imag_task.result().measurement_counts + p_zero_imag = imag_counts.get('0', 0) / 1000 + imag_part = 2 * p_zero_imag - 1 + + assert np.isclose(real_part, 1.0, atol=0.1) + assert np.isclose(imag_part, 0.0, atol=0.1) + + +@pytest.mark.parametrize("phase", ["real", "imaginary"]) +def test_hadamard_test_shots_0(phase): + unitary = Circuit().h(0) + test_circuit = hadamard_test(unitary, phase=phase) + test_circuit.add_result_type(ResultType.Probability(target=[0])) + + device = LocalSimulator() + task = device.run(test_circuit, shots=0) + + p_zero = task.result().values[0][0] + + if phase == 'real': + assert np.isclose(2 * p_zero - 1, 1/np.sqrt(2), atol=0.1) + else: + assert np.isclose(2 * p_zero - 1, 0.0, atol=0.1) + + +@pytest.mark.xfail(raises=ValueError) +def test_hadamard_test_invalid_phase(): + unitary = Circuit().h(0) + hadamard_test(unitary, phase='invalid')