|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "# Hadamard Test\n", |
| 8 | + "---\n", |
| 9 | + "\n", |
| 10 | + "\n", |
| 11 | + "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", |
| 12 | + "\n", |
| 13 | + "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" |
| 14 | + ] |
| 15 | + }, |
| 16 | + { |
| 17 | + "cell_type": "markdown", |
| 18 | + "metadata": {}, |
| 19 | + "source": [ |
| 20 | + "## References\n", |
| 21 | + "\n", |
| 22 | + "[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", |
| 23 | + "\n", |
| 24 | + "[2] Nielsen, Michael A., Chuang, Isaac L. (2010). Quantum Computation and Quantum Information (2nd ed.). Cambridge: Cambridge University Press." |
| 25 | + ] |
| 26 | + }, |
| 27 | + { |
| 28 | + "cell_type": "markdown", |
| 29 | + "metadata": {}, |
| 30 | + "source": [ |
| 31 | + "## Description\n", |
| 32 | + "\n", |
| 33 | + "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", |
| 34 | + " \n", |
| 35 | + "$$\n", |
| 36 | + "\\dfrac{|0\\rangle + |1\\rangle}{\\sqrt{2}}|\\psi\\rangle.\n", |
| 37 | + "$$ \n", |
| 38 | + " \n", |
| 39 | + "We then apply the unitary operator $U$ on $|\\psi\\rangle$, controlled on the first qubit, to obtain the state, \n", |
| 40 | + "$$\n", |
| 41 | + "\\dfrac{|0\\rangle |\\psi\\rangle + |1\\rangle U |\\psi\\rangle}{\\sqrt{2}} .\n", |
| 42 | + "$$ \n", |
| 43 | + "Then, a Hadamard gate is applied to the first qubit once more, giving the state,\n", |
| 44 | + "$$\n", |
| 45 | + "\\dfrac{|0\\rangle(|\\psi\\rangle + U |\\psi\\rangle) + |1\\rangle(|\\psi\\rangle - U |\\psi\\rangle)}{2}.\n", |
| 46 | + "$$\n", |
| 47 | + "\n", |
| 48 | + "And this completes the Hadamard test. Now, we get the probability of measuring the first qubit in the state $|0\\rangle$ as\n", |
| 49 | + "$$\n", |
| 50 | + "\\begin{align*}\n", |
| 51 | + "P_0 &= \\bigg\\lVert\\dfrac{|\\psi\\rangle + U|\\psi\\rangle}{2}\\bigg\\rVert^2 \\\\\n", |
| 52 | + " &= \\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", |
| 53 | + " &= \\dfrac{1 + 2\\langle \\psi | U |\\psi \\rangle + 1}{4}\\\\\n", |
| 54 | + " &= \\dfrac{1 + Re\\langle \\psi | U |\\psi \\rangle}{2},\n", |
| 55 | + "\\end{align*}\n", |
| 56 | + "$$\n", |
| 57 | + "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" |
| 58 | + ] |
| 59 | + }, |
| 60 | + { |
| 61 | + "cell_type": "markdown", |
| 62 | + "metadata": {}, |
| 63 | + "source": [ |
| 64 | + "## Run on a local simulator\n", |
| 65 | + "\n", |
| 66 | + "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" |
| 67 | + ] |
| 68 | + }, |
| 69 | + { |
| 70 | + "cell_type": "code", |
| 71 | + "execution_count": 5, |
| 72 | + "metadata": {}, |
| 73 | + "outputs": [], |
| 74 | + "source": [ |
| 75 | + "from notebook_plotting import plot_bitstrings_formatted\n", |
| 76 | + "import numpy as np\n", |
| 77 | + "\n", |
| 78 | + "%matplotlib inline\n", |
| 79 | + "\n", |
| 80 | + "from braket.aws import AwsDevice\n", |
| 81 | + "from braket.circuits import Circuit, Qubit\n", |
| 82 | + "from braket.devices import LocalSimulator\n", |
| 83 | + "from braket.tracking import Tracker\n", |
| 84 | + "\n", |
| 85 | + "from braket.experimental.algorithms.hadamard_test.hadamard_test import hadamard_test_circuit\n", |
| 86 | + "\n", |
| 87 | + "tracker = Tracker().start() # to track Braket costs" |
| 88 | + ] |
| 89 | + }, |
| 90 | + { |
| 91 | + "cell_type": "markdown", |
| 92 | + "metadata": {}, |
| 93 | + "source": [ |
| 94 | + "For our controlled unitary operator, we will use the Pauli-Z gate, which is represented by the matrix,\n", |
| 95 | + "$$\n", |
| 96 | + "\\begin{bmatrix}\n", |
| 97 | + "1 & 0 \\\\\n", |
| 98 | + "0 & -1\n", |
| 99 | + "\\end{bmatrix}\n", |
| 100 | + "$$\n", |
| 101 | + "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." |
| 102 | + ] |
| 103 | + }, |
| 104 | + { |
| 105 | + "cell_type": "code", |
| 106 | + "execution_count": 6, |
| 107 | + "metadata": {}, |
| 108 | + "outputs": [ |
| 109 | + { |
| 110 | + "name": "stdout", |
| 111 | + "output_type": "stream", |
| 112 | + "text": [ |
| 113 | + "T : │ 0 │\n", |
| 114 | + " ┌───┐ \n", |
| 115 | + "q0 : ─┤ U ├─\n", |
| 116 | + " └───┘ \n", |
| 117 | + "T : │ 0 │\n" |
| 118 | + ] |
| 119 | + } |
| 120 | + ], |
| 121 | + "source": [ |
| 122 | + "pauli_z = np.array([[1, 0], [0, -1]])\n", |
| 123 | + "controlled_unitary = Circuit().unitary([0], pauli_z, \"U\")\n", |
| 124 | + "ancilla = Qubit(0)\n", |
| 125 | + "print(controlled_unitary)" |
| 126 | + ] |
| 127 | + }, |
| 128 | + { |
| 129 | + "cell_type": "markdown", |
| 130 | + "metadata": {}, |
| 131 | + "source": [ |
| 132 | + "### Real Part Estimation" |
| 133 | + ] |
| 134 | + }, |
| 135 | + { |
| 136 | + "cell_type": "code", |
| 137 | + "execution_count": 8, |
| 138 | + "metadata": {}, |
| 139 | + "outputs": [ |
| 140 | + { |
| 141 | + "name": "stdout", |
| 142 | + "output_type": "stream", |
| 143 | + "text": [ |
| 144 | + "T : │ 0 │ 1 │ 2 │\n", |
| 145 | + " ┌───┐ ┌───┐ \n", |
| 146 | + "q0 : ─┤ H ├───●───┤ H ├─\n", |
| 147 | + " └───┘ │ └───┘ \n", |
| 148 | + " ┌─┴─┐ \n", |
| 149 | + "q1 : ───────┤ U ├───────\n", |
| 150 | + " └───┘ \n", |
| 151 | + "T : │ 0 │ 1 │ 2 │\n" |
| 152 | + ] |
| 153 | + } |
| 154 | + ], |
| 155 | + "source": [ |
| 156 | + "ht_circuit = hadamard_test_circuit(ancilla, controlled_unitary)\n", |
| 157 | + "print(ht_circuit)" |
| 158 | + ] |
| 159 | + }, |
| 160 | + { |
| 161 | + "cell_type": "markdown", |
| 162 | + "metadata": {}, |
| 163 | + "source": [ |
| 164 | + "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." |
| 165 | + ] |
| 166 | + }, |
| 167 | + { |
| 168 | + "cell_type": "code", |
| 169 | + "execution_count": 9, |
| 170 | + "metadata": {}, |
| 171 | + "outputs": [ |
| 172 | + { |
| 173 | + "name": "stdout", |
| 174 | + "output_type": "stream", |
| 175 | + "text": [ |
| 176 | + "T : │ 0 │ 1 │ 2 │ 3 │\n", |
| 177 | + " ┌───┐ ┌───┐ ┌───┐ \n", |
| 178 | + "q0 : ─┤ H ├───●───┤ H ├─┤ M ├─\n", |
| 179 | + " └───┘ │ └───┘ └───┘ \n", |
| 180 | + " ┌─┴─┐ \n", |
| 181 | + "q1 : ───────┤ U ├─────────────\n", |
| 182 | + " └───┘ \n", |
| 183 | + "T : │ 0 │ 1 │ 2 │ 3 │\n" |
| 184 | + ] |
| 185 | + } |
| 186 | + ], |
| 187 | + "source": [ |
| 188 | + "ht_circuit.measure(0)\n", |
| 189 | + "print(ht_circuit)" |
| 190 | + ] |
| 191 | + }, |
| 192 | + { |
| 193 | + "cell_type": "markdown", |
| 194 | + "metadata": {}, |
| 195 | + "source": [ |
| 196 | + "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." |
| 197 | + ] |
| 198 | + }, |
| 199 | + { |
| 200 | + "cell_type": "code", |
| 201 | + "execution_count": 10, |
| 202 | + "metadata": {}, |
| 203 | + "outputs": [ |
| 204 | + { |
| 205 | + "name": "stdout", |
| 206 | + "output_type": "stream", |
| 207 | + "text": [ |
| 208 | + "1.0\n" |
| 209 | + ] |
| 210 | + } |
| 211 | + ], |
| 212 | + "source": [ |
| 213 | + "device = LocalSimulator()\n", |
| 214 | + "task = device.run(ht_circuit, shots=1000)\n", |
| 215 | + "\n", |
| 216 | + "probs = task.result().measurement_probabilities\n", |
| 217 | + "p_zero = probs.get('0', 0)\n", |
| 218 | + "\n", |
| 219 | + "real_part = 2 * p_zero - 1\n", |
| 220 | + "\n", |
| 221 | + "print(real_part)" |
| 222 | + ] |
| 223 | + }, |
| 224 | + { |
| 225 | + "cell_type": "markdown", |
| 226 | + "metadata": {}, |
| 227 | + "source": [ |
| 228 | + "### Imaginary Part Estimation\n", |
| 229 | + "\n", |
| 230 | + "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." |
| 231 | + ] |
| 232 | + }, |
| 233 | + { |
| 234 | + "cell_type": "code", |
| 235 | + "execution_count": 12, |
| 236 | + "metadata": {}, |
| 237 | + "outputs": [ |
| 238 | + { |
| 239 | + "name": "stdout", |
| 240 | + "output_type": "stream", |
| 241 | + "text": [ |
| 242 | + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n", |
| 243 | + " ┌───┐ ┌───┐ ┌───┐ ┌───┐ \n", |
| 244 | + "q0 : ─┤ H ├─┤ S ├───●───┤ H ├─┤ M ├─\n", |
| 245 | + " └───┘ └───┘ │ └───┘ └───┘ \n", |
| 246 | + " ┌─┴─┐ \n", |
| 247 | + "q1 : ─────────────┤ U ├─────────────\n", |
| 248 | + " └───┘ \n", |
| 249 | + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n" |
| 250 | + ] |
| 251 | + } |
| 252 | + ], |
| 253 | + "source": [ |
| 254 | + "ht_circuit = hadamard_test_circuit(Qubit(0), controlled_unitary, component='imaginary')\n", |
| 255 | + "ht_circuit.measure(0)\n", |
| 256 | + "print(ht_circuit)" |
| 257 | + ] |
| 258 | + }, |
| 259 | + { |
| 260 | + "cell_type": "markdown", |
| 261 | + "metadata": {}, |
| 262 | + "source": [ |
| 263 | + "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." |
| 264 | + ] |
| 265 | + }, |
| 266 | + { |
| 267 | + "cell_type": "code", |
| 268 | + "execution_count": 13, |
| 269 | + "metadata": {}, |
| 270 | + "outputs": [ |
| 271 | + { |
| 272 | + "name": "stdout", |
| 273 | + "output_type": "stream", |
| 274 | + "text": [ |
| 275 | + "-0.01200000000000001\n" |
| 276 | + ] |
| 277 | + } |
| 278 | + ], |
| 279 | + "source": [ |
| 280 | + "device = LocalSimulator()\n", |
| 281 | + "task = device.run(ht_circuit, shots=1000)\n", |
| 282 | + "\n", |
| 283 | + "probs = task.result().measurement_probabilities\n", |
| 284 | + "p_zero = probs.get('0', 0)\n", |
| 285 | + "\n", |
| 286 | + "imaginary_part = 2 * p_zero - 1\n", |
| 287 | + "\n", |
| 288 | + "print(imaginary_part)" |
| 289 | + ] |
| 290 | + }, |
| 291 | + { |
| 292 | + "cell_type": "markdown", |
| 293 | + "metadata": {}, |
| 294 | + "source": [ |
| 295 | + "## Run on a QPU \n", |
| 296 | + "\n", |
| 297 | + "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." |
| 298 | + ] |
| 299 | + }, |
| 300 | + { |
| 301 | + "cell_type": "code", |
| 302 | + "execution_count": 14, |
| 303 | + "metadata": {}, |
| 304 | + "outputs": [], |
| 305 | + "source": [ |
| 306 | + "# controlled_unitary = Circuit().z(0)\n", |
| 307 | + "# ht_circuit = hadamard_test_circuit(controlled_unitary)\n", |
| 308 | + "# ht_circuit.measure(0)\n", |
| 309 | + "# print(ht_circuit)\n", |
| 310 | + "\n", |
| 311 | + "# device = AwsDevice(\"arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet\")\n", |
| 312 | + "# task = device.run(ht_circuit, shots=1000)\n", |
| 313 | + "\n", |
| 314 | + "# counts = task.result().measurement_counts\n", |
| 315 | + "# p_zero = counts.get('0', 0) / 1000\n", |
| 316 | + "\n", |
| 317 | + "# real_part = 2 * p_zero - 1\n", |
| 318 | + "\n", |
| 319 | + "# print(real_part)" |
| 320 | + ] |
| 321 | + }, |
| 322 | + { |
| 323 | + "cell_type": "code", |
| 324 | + "execution_count": 15, |
| 325 | + "metadata": {}, |
| 326 | + "outputs": [ |
| 327 | + { |
| 328 | + "name": "stdout", |
| 329 | + "output_type": "stream", |
| 330 | + "text": [ |
| 331 | + "Task Summary\n", |
| 332 | + "{} \n", |
| 333 | + "\n", |
| 334 | + "Estimated cost to run this example: 0.00 USD\n" |
| 335 | + ] |
| 336 | + } |
| 337 | + ], |
| 338 | + "source": [ |
| 339 | + "print(\"Task Summary\")\n", |
| 340 | + "print(f\"{tracker.quantum_tasks_statistics()} \\n\")\n", |
| 341 | + "print(\n", |
| 342 | + " f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost():.2f} USD\"\n", |
| 343 | + ")" |
| 344 | + ] |
| 345 | + }, |
| 346 | + { |
| 347 | + "cell_type": "markdown", |
| 348 | + "metadata": {}, |
| 349 | + "source": [ |
| 350 | + "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)." |
| 351 | + ] |
| 352 | + } |
| 353 | + ], |
| 354 | + "metadata": { |
| 355 | + "kernelspec": { |
| 356 | + "display_name": "env", |
| 357 | + "language": "python", |
| 358 | + "name": "python3" |
| 359 | + }, |
| 360 | + "language_info": { |
| 361 | + "codemirror_mode": { |
| 362 | + "name": "ipython", |
| 363 | + "version": 3 |
| 364 | + }, |
| 365 | + "file_extension": ".py", |
| 366 | + "mimetype": "text/x-python", |
| 367 | + "name": "python", |
| 368 | + "nbconvert_exporter": "python", |
| 369 | + "pygments_lexer": "ipython3", |
| 370 | + "version": "3.12.4" |
| 371 | + } |
| 372 | + }, |
| 373 | + "nbformat": 4, |
| 374 | + "nbformat_minor": 2 |
| 375 | +} |
0 commit comments