-
Notifications
You must be signed in to change notification settings - Fork 48
Feature: Add Hadamard Test #158
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,375 @@ | ||||||||||
| { | ||||||||||
| "cells": [ | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "# Hadamard Test\n", | ||||||||||
| "---\n", | ||||||||||
| "\n", | ||||||||||
| "\n", | ||||||||||
| "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. 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. For the original paper, see [1]. For a more modern treatment, see [2].\n", | ||||||||||
| "\n", | ||||||||||
| "In this notebook, we explore the Braket implementation of the Hadamard test for estimating the real and imaginary parts of the expected value of a unitary operator.\n" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "## References\n", | ||||||||||
| "\n", | ||||||||||
| "[1] R. Cleve, A. Ekert, C. Macchiavello, and M. Mosca (1998). Quantum algorithms revisited. [arXiv:quant-ph/9708016](https://arxiv.org/abs/quant-ph/9708016).\n", | ||||||||||
| "\n", | ||||||||||
| "[2] Nielsen, Michael A., Chuang, Isaac L. (2010). Quantum Computation and Quantum Information (2nd ed.). Cambridge: Cambridge University Press." | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "## Description\n", | ||||||||||
| "\n", | ||||||||||
| "Given a unitary operator $U$ and qubits $|0\\rangle|\\psi\\rangle$, the Hadamard test is used to estimate the expected value of $U$ on $|\\psi\\rangle$, i.e. $\\langle \\psi | U |\\psi \\rangle$. We will walk through the algorithm step by step. First, we apply a Hadamard gate to $|0\\rangle$, putting the system into the state,\n", | ||||||||||
| " \n", | ||||||||||
| "$$\n", | ||||||||||
| "\\dfrac{|0\\rangle + |1\\rangle}{\\sqrt{2}}|\\psi\\rangle.\n", | ||||||||||
| "$$ \n", | ||||||||||
| " \n", | ||||||||||
| "We then apply the unitary operator $U$ on $|\\psi\\rangle$, controlled on the first qubit, to obtain the state, \n", | ||||||||||
| "$$\n", | ||||||||||
| "\\dfrac{|0\\rangle |\\psi\\rangle + |1\\rangle U |\\psi\\rangle}{\\sqrt{2}} .\n", | ||||||||||
| "$$ \n", | ||||||||||
| "Then, a Hadamard gate is applied to the first qubit once more, giving the state,\n", | ||||||||||
| "$$\n", | ||||||||||
| "\\dfrac{|0\\rangle(|\\psi\\rangle + U |\\psi\\rangle) + |1\\rangle(|\\psi\\rangle - U |\\psi\\rangle)}{2}.\n", | ||||||||||
| "$$\n", | ||||||||||
| "\n", | ||||||||||
| "And this completes the Hadamard test. Now, we get the probability of measuring the first qubit in the state $|0\\rangle$ as\n", | ||||||||||
| "$$\n", | ||||||||||
| "\\begin{align*}\n", | ||||||||||
| "P_0 &= \\bigg\\lVert\\dfrac{|\\psi\\rangle + U|\\psi\\rangle}{2}\\bigg\\rVert^2 \\\\\n", | ||||||||||
| " &= \\dfrac{\\langle \\psi | \\psi \\rangle + \\langle \\psi | U |\\psi \\rangle + \\langle \\psi | U^\\dagger |\\psi \\rangle + \\langle \\psi | U^\\dagger U |\\psi \\rangle}{4} \\\\\n", | ||||||||||
| " &= \\dfrac{1 + 2\\langle \\psi | U |\\psi \\rangle + 1}{4}\\\\\n", | ||||||||||
| " &= \\dfrac{1 + Re\\langle \\psi | U |\\psi \\rangle}{2},\n", | ||||||||||
| "\\end{align*}\n", | ||||||||||
| "$$\n", | ||||||||||
| "allowing us to estimate the expected value of the real part as $ Re(\\langle \\psi | U |\\psi \\rangle) = 2 P_0 - 1$. The algorithm can be modified to estimate the imaginary part by applying a phase shift to the first qubit after the first Hadamard gate, the proof of which is left as an exercise or inspiration for further reading.\n" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "## Run on a local simulator\n", | ||||||||||
| "\n", | ||||||||||
| "Braket provides an implementation of the Hadamard test, `hadamard_test_circuit`, for estimating the expected values of both the real and imaginary parts of a given unitary operator, `U`.\n" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 5, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [], | ||||||||||
| "source": [ | ||||||||||
| "from notebook_plotting import plot_bitstrings_formatted\n", | ||||||||||
| "import numpy as np\n", | ||||||||||
| "\n", | ||||||||||
| "%matplotlib inline\n", | ||||||||||
| "\n", | ||||||||||
| "from braket.aws import AwsDevice\n", | ||||||||||
| "from braket.circuits import Circuit, Qubit\n", | ||||||||||
| "from braket.devices import LocalSimulator\n", | ||||||||||
| "from braket.tracking import Tracker\n", | ||||||||||
| "\n", | ||||||||||
| "from braket.experimental.algorithms.hadamard_test.hadamard_test import hadamard_test_circuit\n", | ||||||||||
| "\n", | ||||||||||
| "tracker = Tracker().start() # to track Braket costs" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "For our controlled unitary operator, we will use the Pauli-Z gate, which is represented by the matrix,\n", | ||||||||||
| "$$\n", | ||||||||||
| "\\begin{bmatrix}\n", | ||||||||||
| "1 & 0 \\\\\n", | ||||||||||
| "0 & -1\n", | ||||||||||
| "\\end{bmatrix}\n", | ||||||||||
| "$$\n", | ||||||||||
| "Though Braket has built-in support for the Pauli-Z gate, we will use the `unitary` method to apply the gate to the qubit. Consider playing around with different matrices to test your understanding of the algorithm." | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 6, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "T : │ 0 │\n", | ||||||||||
| " ┌───┐ \n", | ||||||||||
| "q0 : ─┤ U ├─\n", | ||||||||||
| " └───┘ \n", | ||||||||||
| "T : │ 0 │\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "pauli_z = np.array([[1, 0], [0, -1]])\n", | ||||||||||
| "controlled_unitary = Circuit().unitary([0], pauli_z, \"U\")\n", | ||||||||||
| "ancilla = Qubit(0)\n", | ||||||||||
| "print(controlled_unitary)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "### Real Part Estimation" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 8, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "T : │ 0 │ 1 │ 2 │\n", | ||||||||||
| " ┌───┐ ┌───┐ \n", | ||||||||||
| "q0 : ─┤ H ├───●───┤ H ├─\n", | ||||||||||
| " └───┘ │ └───┘ \n", | ||||||||||
| " ┌─┴─┐ \n", | ||||||||||
| "q1 : ───────┤ U ├───────\n", | ||||||||||
| " └───┘ \n", | ||||||||||
| "T : │ 0 │ 1 │ 2 │\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "ht_circuit = hadamard_test_circuit(ancilla, controlled_unitary)\n", | ||||||||||
| "print(ht_circuit)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "By default, Braket will measure all qubits. Since the Hadamard test only requires measuring the ancilla qubits, we will explicitly add a measurement instruction to the circuit to avoid unnecessary measurements." | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 9, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "T : │ 0 │ 1 │ 2 │ 3 │\n", | ||||||||||
| " ┌───┐ ┌───┐ ┌───┐ \n", | ||||||||||
| "q0 : ─┤ H ├───●───┤ H ├─┤ M ├─\n", | ||||||||||
| " └───┘ │ └───┘ └───┘ \n", | ||||||||||
| " ┌─┴─┐ \n", | ||||||||||
| "q1 : ───────┤ U ├─────────────\n", | ||||||||||
| " └───┘ \n", | ||||||||||
| "T : │ 0 │ 1 │ 2 │ 3 │\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "ht_circuit.measure(0)\n", | ||||||||||
| "print(ht_circuit)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "Now, we can run the circuit on a local simulator. Take note of the small bit of algebra we need to extract the real part, which can also be seen in the description of the algorithm above." | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 10, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "1.0\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "device = LocalSimulator()\n", | ||||||||||
| "task = device.run(ht_circuit, shots=1000)\n", | ||||||||||
| "\n", | ||||||||||
| "probs = task.result().measurement_probabilities\n", | ||||||||||
| "p_zero = probs.get('0', 0)\n", | ||||||||||
| "\n", | ||||||||||
| "real_part = 2 * p_zero - 1\n", | ||||||||||
| "\n", | ||||||||||
| "print(real_part)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "### Imaginary Part Estimation\n", | ||||||||||
| "\n", | ||||||||||
| "Braket's `hadamard_test_circuit` supports the real part estimation by default, but we can also estimate the imaginary part by setting the `component` argument to `'imaginary'`. We will use the same controlled unitary operator as before." | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 12, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n", | ||||||||||
| " ┌───┐ ┌───┐ ┌───┐ ┌───┐ \n", | ||||||||||
| "q0 : ─┤ H ├─┤ S ├───●───┤ H ├─┤ M ├─\n", | ||||||||||
| " └───┘ └───┘ │ └───┘ └───┘ \n", | ||||||||||
| " ┌─┴─┐ \n", | ||||||||||
| "q1 : ─────────────┤ U ├─────────────\n", | ||||||||||
| " └───┘ \n", | ||||||||||
| "T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "ht_circuit = hadamard_test_circuit(Qubit(0), controlled_unitary, component='imaginary')\n", | ||||||||||
| "ht_circuit.measure(0)\n", | ||||||||||
| "print(ht_circuit)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "The only difference in the circuit is the addition of a phase shift gate to the first qubit, which allows us to estimate the expected value of the imaginary part." | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 13, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "-0.01200000000000001\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "device = LocalSimulator()\n", | ||||||||||
| "task = device.run(ht_circuit, shots=1000)\n", | ||||||||||
| "\n", | ||||||||||
| "probs = task.result().measurement_probabilities\n", | ||||||||||
| "p_zero = probs.get('0', 0)\n", | ||||||||||
| "\n", | ||||||||||
| "imaginary_part = 2 * p_zero - 1\n", | ||||||||||
| "\n", | ||||||||||
| "print(imaginary_part)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "## Run on a QPU \n", | ||||||||||
| "\n", | ||||||||||
| "To run the Hadamard test on a QPU, we replace the LocalSimulator with an AwsDevice. The cost to run this experiment is $0.3 per task and $0.00145 per shot on the IQM Garnet device, that totals $1.75 USD. Because Garnet does not support arbitrary unitary operators, we will directly apply the Pauli-Z gate in this example." | ||||||||||
|
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.
Suggested change
|
||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 14, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [], | ||||||||||
| "source": [ | ||||||||||
| "# controlled_unitary = Circuit().z(0)\n", | ||||||||||
| "# ht_circuit = hadamard_test_circuit(controlled_unitary)\n", | ||||||||||
|
Comment on lines
+306
to
+307
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. I can uncomment this and run on Garnet before we merge the PR if you want.
Author
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. This would be great!
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.
Suggested change
|
||||||||||
| "# ht_circuit.measure(0)\n", | ||||||||||
| "# print(ht_circuit)\n", | ||||||||||
| "\n", | ||||||||||
| "# device = AwsDevice(\"arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet\")\n", | ||||||||||
| "# task = device.run(ht_circuit, shots=1000)\n", | ||||||||||
| "\n", | ||||||||||
| "# counts = task.result().measurement_counts\n", | ||||||||||
| "# p_zero = counts.get('0', 0) / 1000\n", | ||||||||||
| "\n", | ||||||||||
| "# real_part = 2 * p_zero - 1\n", | ||||||||||
| "\n", | ||||||||||
| "# print(real_part)" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "code", | ||||||||||
| "execution_count": 15, | ||||||||||
| "metadata": {}, | ||||||||||
| "outputs": [ | ||||||||||
| { | ||||||||||
| "name": "stdout", | ||||||||||
| "output_type": "stream", | ||||||||||
| "text": [ | ||||||||||
| "Task Summary\n", | ||||||||||
| "{} \n", | ||||||||||
| "\n", | ||||||||||
| "Estimated cost to run this example: 0.00 USD\n" | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "source": [ | ||||||||||
| "print(\"Task Summary\")\n", | ||||||||||
| "print(f\"{tracker.quantum_tasks_statistics()} \\n\")\n", | ||||||||||
| "print(\n", | ||||||||||
| " f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost():.2f} USD\"\n", | ||||||||||
| ")" | ||||||||||
| ] | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| "cell_type": "markdown", | ||||||||||
| "metadata": {}, | ||||||||||
| "source": [ | ||||||||||
| "Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2)." | ||||||||||
| ] | ||||||||||
| } | ||||||||||
| ], | ||||||||||
| "metadata": { | ||||||||||
| "kernelspec": { | ||||||||||
| "display_name": "env", | ||||||||||
| "language": "python", | ||||||||||
| "name": "python3" | ||||||||||
| }, | ||||||||||
| "language_info": { | ||||||||||
| "codemirror_mode": { | ||||||||||
| "name": "ipython", | ||||||||||
| "version": 3 | ||||||||||
| }, | ||||||||||
| "file_extension": ".py", | ||||||||||
| "mimetype": "text/x-python", | ||||||||||
| "name": "python", | ||||||||||
| "nbconvert_exporter": "python", | ||||||||||
| "pygments_lexer": "ipython3", | ||||||||||
| "version": "3.12.4" | ||||||||||
| } | ||||||||||
| }, | ||||||||||
| "nbformat": 4, | ||||||||||
| "nbformat_minor": 2 | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 where it helps extract eigenvalue information through controlled operations. 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. | ||
|
|
||
| <!-- | ||
| [metadata-name]: Hadamard Test | ||
| [metadata-tags]: Textbook | ||
| [metadata-url]: https://github.com/amazon-braket/amazon-braket-algorithm-library/tree/main/src/braket/experimental/algorithms/hadamard_test | ||
| --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be needed for modern Jupyter versions.