Skip to content

Commit 813a05d

Browse files
committed
Feature: Add Hadamard Test
1 parent 5f0e5e6 commit 813a05d

File tree

5 files changed

+517
-0
lines changed

5 files changed

+517
-0
lines changed
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
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$. The algorithm is relatively straightforward, so we will walk through it 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 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 a method, `hadamard_test_circuit`, for estimating both the expected values of both the real and imaginary parts of a given unitary operator. Furthermore, methods for the extended Hadamard test are also provided (TODO: write the extended Hadamard test). We will first go through an example of the basic Hadamard test."
67+
]
68+
},
69+
{
70+
"cell_type": "code",
71+
"execution_count": 41,
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\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": 42,
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+
"controlled_unitary = Circuit().unitary([0], np.array([[1, 0], [0, -1]]), \"U\")\n",
123+
"print(controlled_unitary)"
124+
]
125+
},
126+
{
127+
"cell_type": "markdown",
128+
"metadata": {},
129+
"source": [
130+
"### Real Part Estimation"
131+
]
132+
},
133+
{
134+
"cell_type": "code",
135+
"execution_count": 43,
136+
"metadata": {},
137+
"outputs": [
138+
{
139+
"name": "stdout",
140+
"output_type": "stream",
141+
"text": [
142+
"T : │ 0 │ 1 │ 2 │\n",
143+
" ┌───┐ ┌───┐ \n",
144+
"q0 : ─┤ H ├───●───┤ H ├─\n",
145+
" └───┘ │ └───┘ \n",
146+
" ┌─┴─┐ \n",
147+
"q1 : ───────┤ U ├───────\n",
148+
" └───┘ \n",
149+
"T : │ 0 │ 1 │ 2 │\n"
150+
]
151+
}
152+
],
153+
"source": [
154+
"ht_circuit = hadamard_test_circuit(controlled_unitary)\n",
155+
"print(ht_circuit)"
156+
]
157+
},
158+
{
159+
"cell_type": "markdown",
160+
"metadata": {},
161+
"source": [
162+
"Braket's algorithms do not measure qubits themselves--this is left to the user, particularly because the Hadamard test is a subroutine in many quantum algorithms--so we will need to add a measurement gate to the circuit to see the algorithm's results."
163+
]
164+
},
165+
{
166+
"cell_type": "code",
167+
"execution_count": 44,
168+
"metadata": {},
169+
"outputs": [
170+
{
171+
"name": "stdout",
172+
"output_type": "stream",
173+
"text": [
174+
"T : │ 0 │ 1 │ 2 │ 3 │\n",
175+
" ┌───┐ ┌───┐ ┌───┐ \n",
176+
"q0 : ─┤ H ├───●───┤ H ├─┤ M ├─\n",
177+
" └───┘ │ └───┘ └───┘ \n",
178+
" ┌─┴─┐ \n",
179+
"q1 : ───────┤ U ├─────────────\n",
180+
" └───┘ \n",
181+
"T : │ 0 │ 1 │ 2 │ 3 │\n"
182+
]
183+
}
184+
],
185+
"source": [
186+
"ht_circuit.measure(0)\n",
187+
"print(ht_circuit)"
188+
]
189+
},
190+
{
191+
"cell_type": "markdown",
192+
"metadata": {},
193+
"source": [
194+
"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."
195+
]
196+
},
197+
{
198+
"cell_type": "code",
199+
"execution_count": 45,
200+
"metadata": {},
201+
"outputs": [
202+
{
203+
"name": "stdout",
204+
"output_type": "stream",
205+
"text": [
206+
"1.0\n"
207+
]
208+
}
209+
],
210+
"source": [
211+
"device = LocalSimulator()\n",
212+
"task = device.run(ht_circuit, shots=1000)\n",
213+
"\n",
214+
"counts = task.result().measurement_counts\n",
215+
"p_zero = counts.get('0', 0) / 1000\n",
216+
"\n",
217+
"real_part = 2 * p_zero - 1\n",
218+
"\n",
219+
"print(real_part)"
220+
]
221+
},
222+
{
223+
"cell_type": "markdown",
224+
"metadata": {},
225+
"source": [
226+
"### Imaginary Part Estimation\n",
227+
"\n",
228+
"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."
229+
]
230+
},
231+
{
232+
"cell_type": "code",
233+
"execution_count": 46,
234+
"metadata": {},
235+
"outputs": [
236+
{
237+
"name": "stdout",
238+
"output_type": "stream",
239+
"text": [
240+
"T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n",
241+
" ┌───┐ ┌───┐ ┌───┐ ┌───┐ \n",
242+
"q0 : ─┤ H ├─┤ S ├───●───┤ H ├─┤ M ├─\n",
243+
" └───┘ └───┘ │ └───┘ └───┘ \n",
244+
" ┌─┴─┐ \n",
245+
"q1 : ─────────────┤ U ├─────────────\n",
246+
" └───┘ \n",
247+
"T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n"
248+
]
249+
}
250+
],
251+
"source": [
252+
"ht_circuit = hadamard_test_circuit(controlled_unitary, component='imaginary')\n",
253+
"ht_circuit.measure(0)\n",
254+
"print(ht_circuit)"
255+
]
256+
},
257+
{
258+
"cell_type": "markdown",
259+
"metadata": {},
260+
"source": [
261+
"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."
262+
]
263+
},
264+
{
265+
"cell_type": "code",
266+
"execution_count": 47,
267+
"metadata": {},
268+
"outputs": [
269+
{
270+
"name": "stdout",
271+
"output_type": "stream",
272+
"text": [
273+
"-0.014000000000000012\n"
274+
]
275+
}
276+
],
277+
"source": [
278+
"device = LocalSimulator()\n",
279+
"task = device.run(ht_circuit, shots=1000)\n",
280+
"\n",
281+
"counts = task.result().measurement_counts\n",
282+
"p_zero = counts.get('0', 0) / 1000\n",
283+
"\n",
284+
"imaginary_part = 2 * p_zero - 1\n",
285+
"\n",
286+
"print(imaginary_part)"
287+
]
288+
},
289+
{
290+
"cell_type": "markdown",
291+
"metadata": {},
292+
"source": [
293+
"## Run on a QPU \n",
294+
"\n",
295+
"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."
296+
]
297+
},
298+
{
299+
"cell_type": "code",
300+
"execution_count": 51,
301+
"metadata": {},
302+
"outputs": [],
303+
"source": [
304+
"# controlled_unitary = Circuit().z(0)\n",
305+
"# ht_circuit = hadamard_test_circuit(controlled_unitary)\n",
306+
"# ht_circuit.measure(0)\n",
307+
"# print(ht_circuit)\n",
308+
"\n",
309+
"# device = AwsDevice(\"arn:aws:braket:eu-north-1::device/qpu/iqm/Garnet\")\n",
310+
"# task = device.run(ht_circuit, shots=1000)\n",
311+
"\n",
312+
"# counts = task.result().measurement_counts\n",
313+
"# p_zero = counts.get('0', 0) / 1000\n",
314+
"\n",
315+
"# real_part = 2 * p_zero - 1\n",
316+
"\n",
317+
"# print(real_part)"
318+
]
319+
},
320+
{
321+
"cell_type": "code",
322+
"execution_count": 50,
323+
"metadata": {},
324+
"outputs": [
325+
{
326+
"name": "stdout",
327+
"output_type": "stream",
328+
"text": [
329+
"Task Summary\n",
330+
"{} \n",
331+
"\n",
332+
"Estimated cost to run this example: 0.00 USD\n"
333+
]
334+
}
335+
],
336+
"source": [
337+
"print(\"Task Summary\")\n",
338+
"print(f\"{tracker.quantum_tasks_statistics()} \\n\")\n",
339+
"print(\n",
340+
" f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost():.2f} USD\"\n",
341+
")"
342+
]
343+
},
344+
{
345+
"cell_type": "markdown",
346+
"metadata": {},
347+
"source": [
348+
"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)."
349+
]
350+
}
351+
],
352+
"metadata": {
353+
"kernelspec": {
354+
"display_name": "env",
355+
"language": "python",
356+
"name": "python3"
357+
},
358+
"language_info": {
359+
"codemirror_mode": {
360+
"name": "ipython",
361+
"version": 3
362+
},
363+
"file_extension": ".py",
364+
"mimetype": "text/x-python",
365+
"name": "python",
366+
"nbconvert_exporter": "python",
367+
"pygments_lexer": "ipython3",
368+
"version": "3.12.4"
369+
}
370+
},
371+
"nbformat": 4,
372+
"nbformat_minor": 2
373+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
14+
from braket.experimental.algorithms.hadamard_test.hadamard_test import ( # noqa: F401
15+
hadamard_test_circuit
16+
)

0 commit comments

Comments
 (0)