├── .gitignore
├── Attendee Guide
├── QGSS23_Schedule Graphic.pdf
└── Schedule
├── Lecture Notes
├── Lecture 1.1 - Single States.pdf
├── Lecture 1.2 - Multiple States.pdf
├── Lecture 2 - Quantum Circuits.pdf
├── Lecture 3 - Entanglement in Action.pdf
├── Lecture 4.1 - Quantum Query Algorithms.pdf
├── Lecture 4.2 - Foundations of Quantum Algorithms.pdf
├── Lecture 5- Phase Estimation and Factoring.pdf
├── Lecture 6 - Quantum Computing Hardware & Superconducting Circuits.pdf
├── Lecture 7 - Bonus Content.zip
├── Lecture 7 - Introduction to Noise.pdf.zip
├── Lecture 7 - Introduction to Quantum Noise Bonus.pdf
├── Lecture 8.1 - Iterative Quantum Phase Estimation - Moving Beyond Traditional QPE.pdf
├── Lecture 8.2 - Variational Quantum Eigensolver.pdf
├── Lecture 9.1 - Noise Mitigation Part 1.pdf
└── Lecture 9.2 - Noise Mitigation Part 2.pdf
├── README.md
├── content
├── lab1
│ ├── lab1.ipynb
│ └── resources
│ │ ├── func_table.png
│ │ └── qft.png
├── lab2
│ └── lab2.ipynb
├── lab3
│ ├── lab3.ipynb
│ └── resources
│ │ ├── Shor_circuit.png
│ │ ├── qpe4_circuit.png
│ │ └── qpe_tex_qz.png
├── lab4
│ ├── lab4.ipynb
│ └── resources
│ │ ├── step1-circuit.png
│ │ └── step2-circuit.png
└── lab5
│ └── lab5.ipynb
└── solutions
├── lab1
└── lab1-solution.ipynb
├── lab2
└── lab2-solution.ipynb
├── lab4
└── lab4-solution.ipynb
└── lab5
└── lab5-solution.ipynb
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .ipynb_checkpoints
3 |
4 | .vscode/
5 |
--------------------------------------------------------------------------------
/Attendee Guide/QGSS23_Schedule Graphic.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Attendee Guide/QGSS23_Schedule Graphic.pdf
--------------------------------------------------------------------------------
/Attendee Guide/Schedule:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 1.1 - Single States.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 1.1 - Single States.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 1.2 - Multiple States.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 1.2 - Multiple States.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 2 - Quantum Circuits.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 2 - Quantum Circuits.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 3 - Entanglement in Action.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 3 - Entanglement in Action.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 4.1 - Quantum Query Algorithms.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 4.1 - Quantum Query Algorithms.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 4.2 - Foundations of Quantum Algorithms.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 4.2 - Foundations of Quantum Algorithms.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 5- Phase Estimation and Factoring.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 5- Phase Estimation and Factoring.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 6 - Quantum Computing Hardware & Superconducting Circuits.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 6 - Quantum Computing Hardware & Superconducting Circuits.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 7 - Bonus Content.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 7 - Bonus Content.zip
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 7 - Introduction to Noise.pdf.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 7 - Introduction to Noise.pdf.zip
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 7 - Introduction to Quantum Noise Bonus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 7 - Introduction to Quantum Noise Bonus.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 8.1 - Iterative Quantum Phase Estimation - Moving Beyond Traditional QPE.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 8.1 - Iterative Quantum Phase Estimation - Moving Beyond Traditional QPE.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 8.2 - Variational Quantum Eigensolver.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 8.2 - Variational Quantum Eigensolver.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 9.1 - Noise Mitigation Part 1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 9.1 - Noise Mitigation Part 1.pdf
--------------------------------------------------------------------------------
/Lecture Notes/Lecture 9.2 - Noise Mitigation Part 2.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/Lecture Notes/Lecture 9.2 - Noise Mitigation Part 2.pdf
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Welcome to the Qiskit Global Summer School 2023: Theory to Implementation Hub
2 |
3 | ## 🎥 [Click here to view all QGSS 2023 lectures and content](http://qisk.it/QGSS23playlist)
4 |
5 | ### About
6 |
7 | The Qiskit Global Summer School 2023 was a two-week intensive summer program designed to empower the quantum researchers and developers of tomorrow with the know-how to explore the world of quantum computing, as well as refresh and sharpen the industry professional's skills.
8 |
9 | Here in the QGSS-23 HUB, you can find the summer program's 2023 in-person attendee guide, schedule and every one of the labs issued to participating students and their solutions.
10 |
11 | Enjoy and we hope to see you next year!!
12 |
--------------------------------------------------------------------------------
/content/lab1/lab1.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "d9bef481",
6 | "metadata": {},
7 | "source": [
8 | "# Qiskit Global Summer School 2023 - Lab 1\n",
9 | "\n",
10 | "This lab shows you how to use Qiskit to implement some of the key concepts you learned in the first 3 lectures of the Qiskit Global Summer School 2023."
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "id": "d4749ad4",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "# required imports:\n",
21 | "from qiskit.visualization import array_to_latex\n",
22 | "from qiskit.quantum_info import Statevector, random_statevector\n",
23 | "from qiskit.quantum_info.operators import Operator, Pauli\n",
24 | "from qiskit import QuantumCircuit\n",
25 | "from qiskit.circuit.library import HGate, CXGate\n",
26 | "import numpy as np"
27 | ]
28 | },
29 | {
30 | "cell_type": "markdown",
31 | "id": "742da035",
32 | "metadata": {},
33 | "source": [
34 | "## Vectors and Dirac Notation"
35 | ]
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "id": "808ec86f",
40 | "metadata": {},
41 | "source": [
42 | "In the lectures you learned different ways of representing quantum states, including how to use bra-ket (Dirac) notation.\n",
43 | "\n",
44 | "Although bra-ket notation cannot be represented exactly in code, we can represent their vector and matrix equivalent with python.\n",
45 | "\n",
46 | "E.g. we can represent $|0\\rangle$ using a python list:"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "id": "b93989c8",
53 | "metadata": {},
54 | "outputs": [],
55 | "source": [
56 | "ket0 = [[1],[0]]"
57 | ]
58 | },
59 | {
60 | "cell_type": "markdown",
61 | "id": "34428069",
62 | "metadata": {},
63 | "source": [
64 | "And we can use one of Qiskit's visualisation tools to make our vectors nicer to look at:"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "id": "ee9b7eb0",
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "array_to_latex(ket0)"
75 | ]
76 | },
77 | {
78 | "cell_type": "markdown",
79 | "id": "a61be47b",
80 | "metadata": {},
81 | "source": [
82 | "We can do the same with $\\langle0|$:"
83 | ]
84 | },
85 | {
86 | "cell_type": "code",
87 | "execution_count": null,
88 | "id": "25f9ff7e",
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "bra0 = [1,0]\n",
93 | "array_to_latex(bra0)"
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "id": "2cc359db",
99 | "metadata": {},
100 | "source": [
101 | "
Ex 1 - create $|1\\rangle$ and $\\langle1|$ with python lists
"
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": null,
107 | "id": "59a7be22",
108 | "metadata": {},
109 | "outputs": [],
110 | "source": [
111 | "ket1 = # put your answer answer here for |1⟩\n",
112 | "bra1 = # put answer here for ⟨1|"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "id": "dcc474ad",
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex1 \n",
123 | "\n",
124 | "grade_lab1_ex1([ket1, bra1])"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "id": "21b44ec9",
130 | "metadata": {},
131 | "source": [
132 | "## Qiskit `Statevector` Class\n",
133 | "\n",
134 | "In the lectures you learned about using state vectors to represent quantum states. You can represent quantum state vectors in code using Qiskit's [`Statevector` class](https://qiskit.org/documentation/stubs/qiskit.quantum_info.Statevector.html).\n",
135 | "\n",
136 | "Qiskit's `Statevector` class can take different forms of input (e.g. python list, numpy array, another state vector) to construct a state vector.\n",
137 | "\n",
138 | "Let's take the `bra0` object we created earlier and convert it to a `Statevector` object:"
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": null,
144 | "id": "3ac7420a",
145 | "metadata": {},
146 | "outputs": [],
147 | "source": [
148 | "sv_bra0 = Statevector(bra0)\n",
149 | "\n",
150 | "sv_bra0"
151 | ]
152 | },
153 | {
154 | "cell_type": "markdown",
155 | "id": "9d3015ec",
156 | "metadata": {},
157 | "source": [
158 | "The `Statevector` class has its own `draw()` method:"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "id": "c7d0a57c",
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "sv_bra0.draw('latex')"
169 | ]
170 | },
171 | {
172 | "cell_type": "markdown",
173 | "id": "9a443d7b",
174 | "metadata": {},
175 | "source": [
176 | "We can create more complex statevectors with multiple qubits like this:"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "id": "dc70f9c7",
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "sv_eq = Statevector([1/2, 3/4, 4/5, 6/8])\n",
187 | "\n",
188 | "sv_eq.draw('latex')"
189 | ]
190 | },
191 | {
192 | "cell_type": "markdown",
193 | "id": "c7e00788",
194 | "metadata": {},
195 | "source": [
196 | "Note that the vector above is not a valid state vector as it is not normalised. \n",
197 | "We can check this with the `is_valid()` method:"
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "id": "8459bf73",
204 | "metadata": {},
205 | "outputs": [],
206 | "source": [
207 | "sv_eq.is_valid()"
208 | ]
209 | },
210 | {
211 | "cell_type": "markdown",
212 | "id": "a4b83945",
213 | "metadata": {},
214 | "source": [
215 | " Ex 2 - create your own valid statevector object using the `Statevector` class
"
216 | ]
217 | },
218 | {
219 | "cell_type": "code",
220 | "execution_count": null,
221 | "id": "02cfaf2f",
222 | "metadata": {},
223 | "outputs": [],
224 | "source": [
225 | "sv_valid = # create your statevector here"
226 | ]
227 | },
228 | {
229 | "cell_type": "code",
230 | "execution_count": null,
231 | "id": "ed497a1a",
232 | "metadata": {},
233 | "outputs": [],
234 | "source": [
235 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex2 \n",
236 | "\n",
237 | "grade_lab1_ex2(sv_valid)"
238 | ]
239 | },
240 | {
241 | "cell_type": "markdown",
242 | "id": "3faad35b",
243 | "metadata": {},
244 | "source": [
245 | "## Qiskit `Operator` Class\n",
246 | "\n",
247 | "The [`Operator` class](https://qiskit.org/documentation/stubs/qiskit.quantum_info.Operator.html#qiskit.quantum_info.Operator) is used in Qiskit to represent matrix operators acting on a quantum system. It has several methods to build composite operators using tensor products of smaller operators, and to compose operators.\n",
248 | "\n",
249 | "One way we can initialise a Qiskit `Operator` is by using a python list, like the one we created earlier:"
250 | ]
251 | },
252 | {
253 | "cell_type": "code",
254 | "execution_count": null,
255 | "id": "180a544f",
256 | "metadata": {},
257 | "outputs": [],
258 | "source": [
259 | "op_bra0 = Operator(bra0)\n",
260 | "\n",
261 | "op_bra0"
262 | ]
263 | },
264 | {
265 | "cell_type": "markdown",
266 | "id": "dc578df9",
267 | "metadata": {},
268 | "source": [
269 | "The Operator class comes with some handy methods for working with operators, for example we can find the tensor product of 2 operators by using the `tensor()` method:"
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": null,
275 | "id": "7aed9441",
276 | "metadata": {},
277 | "outputs": [],
278 | "source": [
279 | "op_ket0 = Operator(ket0)\n",
280 | "op_bra0.tensor(op_ket0)"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "id": "e244f5a0",
286 | "metadata": {},
287 | "source": [
288 | "We'll use the `Operator` and `Statevector` classes more in the following exercises."
289 | ]
290 | },
291 | {
292 | "cell_type": "markdown",
293 | "id": "208cf2b6",
294 | "metadata": {},
295 | "source": [
296 | "## Inner & Outer Product\n",
297 | "\n",
298 | "In the lectures you covered the concepts of the inner and outer product. We can explore these concepts in code using numpy methods `.dot()` (the inner product is a generalised form of the dot product) and `.outer()`.\n",
299 | "\n",
300 | "For example, we can find the inner product $\\langle0|0\\rangle$ like this:"
301 | ]
302 | },
303 | {
304 | "cell_type": "code",
305 | "execution_count": null,
306 | "id": "cf3cb816",
307 | "metadata": {},
308 | "outputs": [],
309 | "source": [
310 | "braket = np.dot(op_bra0,op_ket0)\n",
311 | "array_to_latex(braket)"
312 | ]
313 | },
314 | {
315 | "cell_type": "markdown",
316 | "id": "e5d604d7",
317 | "metadata": {},
318 | "source": [
319 | "and the outer product $|0\\rangle\\langle0|$ like this:"
320 | ]
321 | },
322 | {
323 | "cell_type": "code",
324 | "execution_count": null,
325 | "id": "73232e3d",
326 | "metadata": {},
327 | "outputs": [],
328 | "source": [
329 | "ketbra = np.outer(ket0,bra0)\n",
330 | "array_to_latex(ketbra)"
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": null,
336 | "id": "98e2ba66",
337 | "metadata": {},
338 | "outputs": [],
339 | "source": [
340 | "braket = np.dot(op_bra0,op_ket0)\n",
341 | "array_to_latex(braket)"
342 | ]
343 | },
344 | {
345 | "cell_type": "markdown",
346 | "id": "cdca2a30",
347 | "metadata": {},
348 | "source": [
349 | "Note: the numpy methods we used above work with Qiskit Operators as well as regular python lists."
350 | ]
351 | },
352 | {
353 | "cell_type": "markdown",
354 | "id": "2dc9ed02",
355 | "metadata": {},
356 | "source": [
357 | " Ex 3 - use numpy to find the result of the following inner and outer products: $\\langle1|0\\rangle, \\langle0|1\\rangle, \\langle1|1\\rangle, |1\\rangle\\langle0|, |0\\rangle\\langle1|$ and $|1\\rangle\\langle1| $
"
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": null,
363 | "id": "72e1bb34",
364 | "metadata": {},
365 | "outputs": [],
366 | "source": [
367 | "bra1ket0 = # put your answer for ⟨1|0⟩ here\n",
368 | "\n",
369 | "bra0ket1 = # put your answer for ⟨0|1⟩ here\n",
370 | "\n",
371 | "bra1ket1 = # put your answer for ⟨1|1⟩ here\n",
372 | "\n",
373 | "ket1bra0 = # put your answer for |1⟩⟨0| here\n",
374 | "\n",
375 | "ket0bra1 = # put your answer for |0⟩⟨1| here\n",
376 | "\n",
377 | "ket1bra1 = # put your answer for |1⟩⟨1| here"
378 | ]
379 | },
380 | {
381 | "cell_type": "code",
382 | "execution_count": null,
383 | "id": "37b9cf70",
384 | "metadata": {},
385 | "outputs": [],
386 | "source": [
387 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex3 \n",
388 | "\n",
389 | "grade_lab1_ex3([bra1ket0, bra0ket1, bra1ket1, ket1bra0, ket0bra1, ket1bra1])"
390 | ]
391 | },
392 | {
393 | "cell_type": "markdown",
394 | "id": "7c03e06d",
395 | "metadata": {},
396 | "source": [
397 | " \n",
398 | "
Ex 4 - when the inner product of 2 quantum states is equal to 0, those states are orthogonal. Which of the following states are orthogonal?
\n",
399 | "
a) $\\vert 0\\rangle$ and $\\vert 1\\rangle$
\n",
400 | "
b) $\\vert 0\\rangle$ and $\\vert 0\\rangle$
\n",
401 | "
c) $\\vert 1\\rangle$ and $\\vert 1\\rangle$
\n",
402 | "
"
403 | ]
404 | },
405 | {
406 | "cell_type": "code",
407 | "execution_count": null,
408 | "id": "6e061263",
409 | "metadata": {},
410 | "outputs": [],
411 | "source": [
412 | "# add or remove your answer from this list\n",
413 | "answer = ['a', 'b', 'c']"
414 | ]
415 | },
416 | {
417 | "cell_type": "code",
418 | "execution_count": null,
419 | "id": "bf314ab5",
420 | "metadata": {},
421 | "outputs": [],
422 | "source": [
423 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex4 \n",
424 | "\n",
425 | "grade_lab1_ex4(answer)"
426 | ]
427 | },
428 | {
429 | "cell_type": "markdown",
430 | "id": "e695b37a",
431 | "metadata": {},
432 | "source": [
433 | "## Deterministic operations\n",
434 | "\n",
435 | "As mentioned in the lectures, there are 4 single bit deterministic operations: \n",
436 | "f1 = constant-0 \n",
437 | "f2 = identity \n",
438 | "f3 = bit flip / not \n",
439 | "f4 = constant-1\n",
440 | "\n",
441 | "$$\n",
442 | "\\begin{array}{c|c}\n",
443 | " a & f_1(a)\\\\\n",
444 | " \\hline\n",
445 | " 0 & 0\\\\\n",
446 | " 1 & 0\n",
447 | "\\end{array}\n",
448 | "\\qquad\n",
449 | "\\begin{array}{c|c}\n",
450 | " a & f_2(a)\\\\\n",
451 | " \\hline\n",
452 | " 0 & 0\\\\\n",
453 | " 1 & 1\n",
454 | "\\end{array}\n",
455 | "\\qquad\n",
456 | "\\begin{array}{c|c}\n",
457 | " a & f_3(a)\\\\\n",
458 | " \\hline\n",
459 | " 0 & 1\\\\\n",
460 | " 1 & 0\n",
461 | "\\end{array}\n",
462 | "\\qquad\n",
463 | "\\begin{array}{c|c}\n",
464 | " a & f_4(a)\\\\\n",
465 | " \\hline\n",
466 | " 0 & 1\\\\\n",
467 | " 1 & 1\n",
468 | "\\end{array}\n",
469 | "$$\n",
470 | "\n",
471 | "We can create Qiskit Operators for these 4 operations, by passing their matrix representations as arguments to the `Operator` class.\n",
472 | "\n",
473 | "E.g. for constant-0 we can create the corresponding matrix m1 like so:"
474 | ]
475 | },
476 | {
477 | "cell_type": "code",
478 | "execution_count": null,
479 | "id": "8edc4262",
480 | "metadata": {},
481 | "outputs": [],
482 | "source": [
483 | "m1 = Operator([[1,1],[0,0]])\n",
484 | "array_to_latex(m1)"
485 | ]
486 | },
487 | {
488 | "cell_type": "markdown",
489 | "id": "2792a781",
490 | "metadata": {},
491 | "source": [
492 | "and similarly for m3:"
493 | ]
494 | },
495 | {
496 | "cell_type": "code",
497 | "execution_count": null,
498 | "id": "9cc2ff6c",
499 | "metadata": {},
500 | "outputs": [],
501 | "source": [
502 | "m3 = Operator([[0,1],[1,0]])\n",
503 | "array_to_latex(m3)"
504 | ]
505 | },
506 | {
507 | "cell_type": "markdown",
508 | "id": "765e8f0e",
509 | "metadata": {},
510 | "source": [
511 | "We can also use builtin python mutliplication operations (e.g. `@`, `.dot`, or `.matmul`) to check the following equation: $ M|a\\rangle = f|a\\rangle $\n",
512 | "\n",
513 | "e.g. $ M1|0\\rangle = f1|0\\rangle $ = 0"
514 | ]
515 | },
516 | {
517 | "cell_type": "code",
518 | "execution_count": null,
519 | "id": "a283a8bd",
520 | "metadata": {},
521 | "outputs": [],
522 | "source": [
523 | "array_to_latex(m1@ket0)"
524 | ]
525 | },
526 | {
527 | "cell_type": "markdown",
528 | "id": "f044b3f6",
529 | "metadata": {},
530 | "source": [
531 | " Ex 5 - create Qiskit Operators for m2 and m4 (hint: check out the lectures to find the appropriate matrices)
"
532 | ]
533 | },
534 | {
535 | "cell_type": "code",
536 | "execution_count": null,
537 | "id": "31927dfa",
538 | "metadata": {},
539 | "outputs": [],
540 | "source": [
541 | "m2 = # create an operator for m2 here\n",
542 | "m4 = # create and operator for m4 here"
543 | ]
544 | },
545 | {
546 | "cell_type": "code",
547 | "execution_count": null,
548 | "id": "62081ac6",
549 | "metadata": {},
550 | "outputs": [],
551 | "source": [
552 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex5\n",
553 | "\n",
554 | "grade_lab1_ex5([m2, m4])"
555 | ]
556 | },
557 | {
558 | "cell_type": "markdown",
559 | "id": "d611468d",
560 | "metadata": {},
561 | "source": [
562 | "## Probabilistic operations\n",
563 | "\n",
564 | "A Controlled-NOT (or CNOT) operation is a probabilistic operation you can apply on 2 qubits.\n",
565 | "\n",
566 | "Applying a CNOT on a state (X,Y) involves performing a NOT operation on Y when X is 1, otherwise do nothing.\n",
567 | "X is the control bit, Y is the target bit.\n",
568 | "\n",
569 | "We can implement a CNOT gate (and many other quantum gates) using a class from [Qiskit's circuit library](https://qiskit.org/documentation/apidoc/circuit_library.html):"
570 | ]
571 | },
572 | {
573 | "cell_type": "code",
574 | "execution_count": null,
575 | "id": "d6aec987",
576 | "metadata": {},
577 | "outputs": [],
578 | "source": [
579 | "cnot = CXGate()\n",
580 | "\n",
581 | "array_to_latex(cnot)"
582 | ]
583 | },
584 | {
585 | "cell_type": "markdown",
586 | "id": "49ee11bb",
587 | "metadata": {},
588 | "source": [
589 | "Note: this matrix is different from the one that appeared in the lesson because `CXGate()` takes the right qubit to be the control rather than the left qubit."
590 | ]
591 | },
592 | {
593 | "cell_type": "markdown",
594 | "id": "bd0665bc",
595 | "metadata": {},
596 | "source": [
597 | "## Unitary Operations\n",
598 | "\n",
599 | "An operator is unitary if: $ UU^{\\dagger} = \\mathbb{1} = U^{\\dagger} U$\n",
600 | "\n",
601 | "We can check if an operator is Unitary using Qiskit with the `is_unitary()` method:"
602 | ]
603 | },
604 | {
605 | "cell_type": "code",
606 | "execution_count": null,
607 | "id": "83e80fd5",
608 | "metadata": {},
609 | "outputs": [],
610 | "source": [
611 | "m3.is_unitary()"
612 | ]
613 | },
614 | {
615 | "cell_type": "markdown",
616 | "id": "ef61f742",
617 | "metadata": {},
618 | "source": [
619 | "With small operators like m3 we could probably figure this out easily by ourselves, but with more complex operators it becomes more convenient to use the Qiskit function:"
620 | ]
621 | },
622 | {
623 | "cell_type": "code",
624 | "execution_count": null,
625 | "id": "90fa7840",
626 | "metadata": {},
627 | "outputs": [],
628 | "source": [
629 | "random = Operator(np.array([[ 0.50778085-0.44607116j, -0.1523741 +0.14128434j, 0.44607116+0.50778085j,\n",
630 | " -0.14128434-0.1523741j ],\n",
631 | " [ 0.16855994+0.12151822j, 0.55868196+0.38038841j, -0.12151822+0.16855994j,\n",
632 | " -0.38038841+0.55868196j],\n",
633 | " [ 0.50778085-0.44607116j, -0.1523741 +0.14128434j, -0.44607116-0.50778085j,\n",
634 | " 0.14128434+0.1523741j ],\n",
635 | " [ 0.16855994+0.12151822j, 0.55868196+0.38038841j, 0.12151822-0.16855994j,\n",
636 | " 0.38038841-0.55868196j]]))\n",
637 | "\n",
638 | "random.is_unitary()"
639 | ]
640 | },
641 | {
642 | "cell_type": "markdown",
643 | "id": "e6778ed6",
644 | "metadata": {},
645 | "source": [
646 | " Ex 6 - create an operator using the `Operator` class that is not unitary
"
647 | ]
648 | },
649 | {
650 | "cell_type": "code",
651 | "execution_count": null,
652 | "id": "c43f5bbf",
653 | "metadata": {},
654 | "outputs": [],
655 | "source": [
656 | "non_unitary_op = # create your operator here"
657 | ]
658 | },
659 | {
660 | "cell_type": "code",
661 | "execution_count": null,
662 | "id": "c159e466",
663 | "metadata": {},
664 | "outputs": [],
665 | "source": [
666 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex6\n",
667 | "\n",
668 | "grade_lab1_ex6(non_unitary_op)"
669 | ]
670 | },
671 | {
672 | "cell_type": "markdown",
673 | "id": "220179f8",
674 | "metadata": {},
675 | "source": [
676 | "### Qubit Unitary Operations - Pauli Operations\n",
677 | "\n",
678 | "Some of the most common unitary operations in quantum computing are the Pauli operations. Qiskit's `Pauli` classes make it easy to interact with Pauli operators in code:"
679 | ]
680 | },
681 | {
682 | "cell_type": "markdown",
683 | "id": "26c5fec2",
684 | "metadata": {},
685 | "source": [
686 | "E.g. Pauli X ($\\sigma_x$), the bit flip:"
687 | ]
688 | },
689 | {
690 | "cell_type": "code",
691 | "execution_count": null,
692 | "id": "290462f3",
693 | "metadata": {},
694 | "outputs": [],
695 | "source": [
696 | "pauli_x = Pauli('X')\n",
697 | "\n",
698 | "array_to_latex(pauli_x)"
699 | ]
700 | },
701 | {
702 | "cell_type": "markdown",
703 | "id": "ff2d5d30",
704 | "metadata": {},
705 | "source": [
706 | "Pauli Y ($\\sigma_y$):"
707 | ]
708 | },
709 | {
710 | "cell_type": "code",
711 | "execution_count": null,
712 | "id": "21b32f55",
713 | "metadata": {},
714 | "outputs": [],
715 | "source": [
716 | "pauli_y = Pauli('Y')\n",
717 | "\n",
718 | "array_to_latex(pauli_y)"
719 | ]
720 | },
721 | {
722 | "cell_type": "markdown",
723 | "id": "c9850043",
724 | "metadata": {},
725 | "source": [
726 | "Pauli Z ($\\sigma_z$), the phase flip:"
727 | ]
728 | },
729 | {
730 | "cell_type": "code",
731 | "execution_count": null,
732 | "id": "9df1dff2",
733 | "metadata": {},
734 | "outputs": [],
735 | "source": [
736 | "pauli_z = Pauli('Z')\n",
737 | "\n",
738 | "array_to_latex(pauli_z)"
739 | ]
740 | },
741 | {
742 | "cell_type": "markdown",
743 | "id": "bdfa41f0",
744 | "metadata": {},
745 | "source": [
746 | "We can use the `Operator` class with the `Pauli` class:"
747 | ]
748 | },
749 | {
750 | "cell_type": "code",
751 | "execution_count": null,
752 | "id": "71eb657f",
753 | "metadata": {},
754 | "outputs": [],
755 | "source": [
756 | "op_x = Operator(pauli_x)\n",
757 | "\n",
758 | "op_x"
759 | ]
760 | },
761 | {
762 | "cell_type": "markdown",
763 | "id": "22a1dc55",
764 | "metadata": {},
765 | "source": [
766 | "Let's use the `Operator` class and numpy to find the outcome of $\\sigma_x|0\\rangle$"
767 | ]
768 | },
769 | {
770 | "cell_type": "code",
771 | "execution_count": null,
772 | "id": "a3b46f65",
773 | "metadata": {},
774 | "outputs": [],
775 | "source": [
776 | "op_new = np.dot(op_x,ket0)\n",
777 | "\n",
778 | "array_to_latex(op_new)"
779 | ]
780 | },
781 | {
782 | "cell_type": "markdown",
783 | "id": "0434735a",
784 | "metadata": {},
785 | "source": [
786 | " Ex 7 - Apply the Pauli-Z operator on $|1\\rangle$
"
787 | ]
788 | },
789 | {
790 | "cell_type": "code",
791 | "execution_count": null,
792 | "id": "11ea5267",
793 | "metadata": {},
794 | "outputs": [],
795 | "source": [
796 | "result = # do your operations here"
797 | ]
798 | },
799 | {
800 | "cell_type": "code",
801 | "execution_count": null,
802 | "id": "694825bd",
803 | "metadata": {},
804 | "outputs": [],
805 | "source": [
806 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex7\n",
807 | "\n",
808 | "grade_lab1_ex7(result)"
809 | ]
810 | },
811 | {
812 | "cell_type": "markdown",
813 | "id": "677ab1ec",
814 | "metadata": {},
815 | "source": [
816 | "### Qubit Unitary Operations - Hadamard\n"
817 | ]
818 | },
819 | {
820 | "cell_type": "markdown",
821 | "id": "1fa9f846",
822 | "metadata": {},
823 | "source": [
824 | "The Hadamard gate is one of the most important unitary operations in quantum computing. We can implement a Hadamard gate (and many other quantum gates) using a class from [Qiskit's circuit library](https://qiskit.org/documentation/apidoc/circuit_library.html):"
825 | ]
826 | },
827 | {
828 | "cell_type": "code",
829 | "execution_count": null,
830 | "id": "74251a81",
831 | "metadata": {},
832 | "outputs": [],
833 | "source": [
834 | "hadamard = HGate()\n",
835 | "\n",
836 | "array_to_latex(hadamard)"
837 | ]
838 | },
839 | {
840 | "cell_type": "markdown",
841 | "id": "08b41da3",
842 | "metadata": {},
843 | "source": [
844 | "You can convert many Qiskit classes to operators to make use of functions specific to the `Operator` class, such as `is_unitary`"
845 | ]
846 | },
847 | {
848 | "cell_type": "code",
849 | "execution_count": null,
850 | "id": "ebc2f8df",
851 | "metadata": {},
852 | "outputs": [],
853 | "source": [
854 | "hop = Operator(hadamard)\n",
855 | "hop.is_unitary()"
856 | ]
857 | },
858 | {
859 | "cell_type": "markdown",
860 | "id": "aa5207cc",
861 | "metadata": {},
862 | "source": [
863 | "## Quantum Circuits\n",
864 | "\n",
865 | "In the lectures you learned how to create a Quantum Circuit using a CNOT and a Hadamard gate. This circuit creates the Bell State $|\\phi^+\\rangle$. We can implement this using Qiskit's `QuantumCircuit` class:"
866 | ]
867 | },
868 | {
869 | "cell_type": "code",
870 | "execution_count": null,
871 | "id": "c1b3e5a9",
872 | "metadata": {},
873 | "outputs": [],
874 | "source": [
875 | "bell = QuantumCircuit(2)\n",
876 | "\n",
877 | "bell.h(0) # apply an H gate to the circuit\n",
878 | "bell.cx(0,1) # apply a CNOT gate to the circuit\n",
879 | "\n",
880 | "bell.draw(output=\"mpl\")"
881 | ]
882 | },
883 | {
884 | "cell_type": "markdown",
885 | "id": "1b2a93c8",
886 | "metadata": {},
887 | "source": [
888 | "If we want to check what the matrix representation is of this quantum state we can convert the circuit directly to an operator:"
889 | ]
890 | },
891 | {
892 | "cell_type": "code",
893 | "execution_count": null,
894 | "id": "ac8dab7c",
895 | "metadata": {},
896 | "outputs": [],
897 | "source": [
898 | "bell_op = Operator(bell)\n",
899 | "\n",
900 | "array_to_latex(bell_op)"
901 | ]
902 | },
903 | {
904 | "cell_type": "markdown",
905 | "id": "b2487769",
906 | "metadata": {},
907 | "source": [
908 | " Ex 8 - the GHZ state is similar to the Bell State but applied to 3 qubits. Create a quantum circuit outputting the GHZ state
"
909 | ]
910 | },
911 | {
912 | "cell_type": "code",
913 | "execution_count": null,
914 | "id": "f7469f9f",
915 | "metadata": {},
916 | "outputs": [],
917 | "source": [
918 | "ghz = QuantumCircuit(3)\n",
919 | "\n",
920 | "##############################\n",
921 | "# add gates to your circuit here\n",
922 | "\n",
923 | "\n",
924 | "\n",
925 | "##############################\n",
926 | "\n",
927 | "ghz.draw(output='mpl')"
928 | ]
929 | },
930 | {
931 | "cell_type": "code",
932 | "execution_count": null,
933 | "id": "ffb9113c",
934 | "metadata": {},
935 | "outputs": [],
936 | "source": [
937 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex8\n",
938 | "\n",
939 | "grade_lab1_ex8(ghz)"
940 | ]
941 | },
942 | {
943 | "cell_type": "markdown",
944 | "id": "dec5eb53",
945 | "metadata": {},
946 | "source": [
947 | "## Measuring Quantum states"
948 | ]
949 | },
950 | {
951 | "cell_type": "markdown",
952 | "id": "9d05a3cb",
953 | "metadata": {},
954 | "source": [
955 | "As explained in the lectures you can find the probability of measurement outcomes by taking the absolute value squared of the entries of a quantum state vector.\n",
956 | "\n",
957 | "For example, when measuring the + state: \n",
958 | "\n",
959 | "$ |+\\rangle = \\frac{1}{\\sqrt2}|0\\rangle + \\frac{1}{\\sqrt2}|1\\rangle $\n",
960 | "\n",
961 | "The probability of measuring 0 or 1 is given by the following:\n",
962 | "\n",
963 | "$ Pr(0) = |\\frac{1}{\\sqrt2}|^2 = \\frac{1}{2}$ \n",
964 | "$ Pr(1) = |\\frac{1}{\\sqrt2}|^2 = \\frac{1}{2}$"
965 | ]
966 | },
967 | {
968 | "cell_type": "markdown",
969 | "id": "09b17e63",
970 | "metadata": {},
971 | "source": [
972 | "Let's create a $|+\\rangle$ using the `Statevector` class:"
973 | ]
974 | },
975 | {
976 | "cell_type": "code",
977 | "execution_count": null,
978 | "id": "819fdc56",
979 | "metadata": {},
980 | "outputs": [],
981 | "source": [
982 | "plus_state = Statevector.from_label(\"+\")\n",
983 | "\n",
984 | "plus_state.draw('latex')"
985 | ]
986 | },
987 | {
988 | "cell_type": "code",
989 | "execution_count": null,
990 | "id": "4c7849e7",
991 | "metadata": {},
992 | "outputs": [],
993 | "source": [
994 | "plus_state"
995 | ]
996 | },
997 | {
998 | "cell_type": "markdown",
999 | "id": "e9367460",
1000 | "metadata": {},
1001 | "source": [
1002 | "Now we can get the probability of measuring 0 or 1:"
1003 | ]
1004 | },
1005 | {
1006 | "cell_type": "code",
1007 | "execution_count": null,
1008 | "id": "4b954ae3",
1009 | "metadata": {},
1010 | "outputs": [],
1011 | "source": [
1012 | "plus_state.probabilities_dict()"
1013 | ]
1014 | },
1015 | {
1016 | "cell_type": "markdown",
1017 | "id": "62b9c13c",
1018 | "metadata": {},
1019 | "source": [
1020 | "The dictionary object above shows you all the possible measurement outcomes and what the probability is of getting them. The actual act of measuring forces the state to collapse into either the 0 or 1 state:"
1021 | ]
1022 | },
1023 | {
1024 | "cell_type": "code",
1025 | "execution_count": null,
1026 | "id": "546166ed",
1027 | "metadata": {},
1028 | "outputs": [],
1029 | "source": [
1030 | "# run this cell multiple times to show collapsing into one state or the other\n",
1031 | "res = plus_state.measure()\n",
1032 | "\n",
1033 | "res"
1034 | ]
1035 | },
1036 | {
1037 | "cell_type": "markdown",
1038 | "id": "cbf9efbb",
1039 | "metadata": {},
1040 | "source": [
1041 | "We can implement the same $|+\\rangle$ state with measurement using a quantum circuit:"
1042 | ]
1043 | },
1044 | {
1045 | "cell_type": "code",
1046 | "execution_count": null,
1047 | "id": "6af8a51d",
1048 | "metadata": {},
1049 | "outputs": [],
1050 | "source": [
1051 | "qc = QuantumCircuit(1,1)\n",
1052 | "qc.h(0)\n",
1053 | "qc.measure(0, 0)\n",
1054 | "\n",
1055 | "qc.draw(output=\"mpl\")"
1056 | ]
1057 | },
1058 | {
1059 | "cell_type": "markdown",
1060 | "id": "84e3a739",
1061 | "metadata": {},
1062 | "source": [
1063 | "If we ran this circuit using a simulator we would get the same results as we did with the statevector class."
1064 | ]
1065 | },
1066 | {
1067 | "cell_type": "markdown",
1068 | "id": "f3abea6e",
1069 | "metadata": {},
1070 | "source": [
1071 | "In the next example, let's use the `Statevector` class to find the measurement outcomes for a dependent, probabilistic state. We'll find the measurement probilities for the 2-qubit Bell State $|\\phi^+\\rangle$ :"
1072 | ]
1073 | },
1074 | {
1075 | "cell_type": "code",
1076 | "execution_count": null,
1077 | "id": "f0c6e31d",
1078 | "metadata": {},
1079 | "outputs": [],
1080 | "source": [
1081 | "sv_bell = Statevector([np.sqrt(1/2), 0, 0, np.sqrt(1/2)])\n",
1082 | "\n",
1083 | "sv_bell.draw('latex')"
1084 | ]
1085 | },
1086 | {
1087 | "cell_type": "code",
1088 | "execution_count": null,
1089 | "id": "60aca301",
1090 | "metadata": {},
1091 | "outputs": [],
1092 | "source": [
1093 | "sv_bell.probabilities_dict()"
1094 | ]
1095 | },
1096 | {
1097 | "cell_type": "markdown",
1098 | "id": "734489ba",
1099 | "metadata": {},
1100 | "source": [
1101 | " Ex 9 - Using the Statevector class find the probabilities for the other 3 states in the Bell Basis: $|\\psi^+\\rangle$, $|\\psi^-\\rangle$, $|\\phi^-\\rangle$. Hint: check out lesson 2 to refresh your memory on the equations of the Bell states
"
1102 | ]
1103 | },
1104 | {
1105 | "cell_type": "code",
1106 | "execution_count": null,
1107 | "id": "119714dd",
1108 | "metadata": {},
1109 | "outputs": [],
1110 | "source": [
1111 | "sv_psi_plus = # create a statevector for |𝜓+⟩ here\n",
1112 | "prob_psi_plus = # find the measurement probabilities for |𝜓+⟩ here\n",
1113 | "\n",
1114 | "sv_psi_minus = # create a statevector for |𝜓−⟩ here\n",
1115 | "prob_psi_minus = # find the measurement probabilities for |𝜓−⟩ here\n",
1116 | "\n",
1117 | "sv_phi_minus = # create a statevector for |𝜙−⟩ here\n",
1118 | "prob_phi_minus = # find the measurement probabilities for |𝜙−⟩ here"
1119 | ]
1120 | },
1121 | {
1122 | "cell_type": "code",
1123 | "execution_count": null,
1124 | "id": "72585681",
1125 | "metadata": {},
1126 | "outputs": [],
1127 | "source": [
1128 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex9\n",
1129 | "\n",
1130 | "grade_lab1_ex9([prob_psi_plus, prob_psi_minus, prob_phi_minus])"
1131 | ]
1132 | },
1133 | {
1134 | "cell_type": "markdown",
1135 | "id": "0faf6184",
1136 | "metadata": {},
1137 | "source": [
1138 | "# Final Challenge - generate a QFT circuit\n",
1139 | "\n",
1140 | "[The Fourier transform](https://en.wikipedia.org/wiki/Fourier_transform) occurs in many different formats throughout classical computing, in areas ranging from signal processing to data compression to complexity theory. The quantum Fourier transform (QFT) is the quantum implementation of the discrete Fourier transform over the amplitudes of a wavefunction. It is part of many quantum algorithms, most notably Shor's factoring algorithm and quantum phase estimation. You'll learn more about this important implementation later on during the Summer School, but for this final challenge of Lab 1 we would like you to use Qiskit to create the following QFT circuit on 2 qubits:\n",
1141 | "\n",
1142 | ""
1143 | ]
1144 | },
1145 | {
1146 | "cell_type": "markdown",
1147 | "id": "a06d2b68",
1148 | "metadata": {},
1149 | "source": [
1150 | " Ex 10 - create a 2 qubit QFT circuit using qiskit
"
1151 | ]
1152 | },
1153 | {
1154 | "cell_type": "code",
1155 | "execution_count": null,
1156 | "id": "e51049ac",
1157 | "metadata": {},
1158 | "outputs": [],
1159 | "source": [
1160 | "qft = QuantumCircuit(2)\n",
1161 | "\n",
1162 | "##############################\n",
1163 | "# add gates to your circuit here\n",
1164 | "\n",
1165 | "\n",
1166 | "\n",
1167 | "##############################\n",
1168 | "\n",
1169 | "qft.draw(output='mpl')"
1170 | ]
1171 | },
1172 | {
1173 | "cell_type": "code",
1174 | "execution_count": null,
1175 | "id": "9ad87edc",
1176 | "metadata": {},
1177 | "outputs": [],
1178 | "source": [
1179 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex10\n",
1180 | "\n",
1181 | "grade_lab1_ex10(qft)"
1182 | ]
1183 | },
1184 | {
1185 | "cell_type": "markdown",
1186 | "id": "94b90a28",
1187 | "metadata": {},
1188 | "source": [
1189 | "To see the matrix that describes the action of this circuit, we can plug the circuit into the `Operator` function like this:"
1190 | ]
1191 | },
1192 | {
1193 | "cell_type": "code",
1194 | "execution_count": null,
1195 | "id": "d83e5f5b",
1196 | "metadata": {},
1197 | "outputs": [],
1198 | "source": [
1199 | "U = Operator(qft)\n",
1200 | "\n",
1201 | "array_to_latex(U)"
1202 | ]
1203 | },
1204 | {
1205 | "cell_type": "markdown",
1206 | "id": "9c1ec931",
1207 | "metadata": {},
1208 | "source": [
1209 | "Congratulations! You finished Lab 1 of the Qiskit Global Summer School 2023! 🎉 🎉 🎉"
1210 | ]
1211 | }
1212 | ],
1213 | "metadata": {
1214 | "kernelspec": {
1215 | "display_name": "Python 3 (ipykernel)",
1216 | "language": "python",
1217 | "name": "python3"
1218 | },
1219 | "language_info": {
1220 | "codemirror_mode": {
1221 | "name": "ipython",
1222 | "version": 3
1223 | },
1224 | "file_extension": ".py",
1225 | "mimetype": "text/x-python",
1226 | "name": "python",
1227 | "nbconvert_exporter": "python",
1228 | "pygments_lexer": "ipython3",
1229 | "version": "3.10.4"
1230 | }
1231 | },
1232 | "nbformat": 4,
1233 | "nbformat_minor": 5
1234 | }
1235 |
--------------------------------------------------------------------------------
/content/lab1/resources/func_table.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab1/resources/func_table.png
--------------------------------------------------------------------------------
/content/lab1/resources/qft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab1/resources/qft.png
--------------------------------------------------------------------------------
/content/lab2/lab2.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Lab 2 - Creating Entanglement with Qiskit\n",
9 | "\n",
10 | "This lab demonstrates interesting properties of *entangled* qubits. In particular, we will consider two experiments:\n",
11 | "- **CHSH Inequality Violation** - this shows that quantum mechanics *cannot* be explained by a local hidden variable theory\n",
12 | "- **Teleportation** - teleport an arbitrary quantum state using an entangled qubit pair as a resource\n",
13 | "\n",
14 | "In particular, this lab demonstrates how to use new features from IBM Quantum \n",
15 | "- **Primitives** - abstract measurement and error mitigation for scalable quantum computing\n",
16 | "- **Dynamic Circuits** - mid-circuit measurement and feed-forward within the qubits' coherence time"
17 | ]
18 | },
19 | {
20 | "attachments": {},
21 | "cell_type": "markdown",
22 | "metadata": {},
23 | "source": [
24 | "## Getting Started\n",
25 | "\n",
26 | "Start by importing some libraries we need, including the `Sampler` and `Estimator` primitives from Qiskit. While the primitives from `qiskit.providers` use a local statevector simulator by default, the syntax within this lab is easily generalizable to running experiments on real systems.\n",
27 | "\n",
28 | "To run on real hearware requires a Qiskit Runtime service instance. If you haven't done so already, follow the instructions in the Qiskit [Getting started guide](https://qiskit.org/documentation/partners/qiskit_ibm_runtime/getting_started.html) to set one up. TODO: include video links and such. After setup, import the `Sampler` and `Estimator` primitives from `qiskit_ibm_runtime` instead. Additionally we will need `QiskitRuntimeService` and `Session`, which form the interface between Qiskit and Qiskit IBM Runtime. Then the below exercises can be run on real systems by instantiating the primitives in this way (as opposed to from `qiskit.primitives`):\n",
29 | "\n",
30 | "```\n",
31 | "from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Estimator\n",
32 | "\n",
33 | "service = QiskitRuntimeService()\n",
34 | "backend = service.get_backend('...')\n",
35 | "session = Session(service=service, backend=backend)\n",
36 | "sampler = Sampler(session=session)\n",
37 | "estimator = Estimator(session=session)\n",
38 | "```\n",
39 | "where additional options can be specified in the `Sampler` and `Estimator` with the `Options` class. See this [how-to](https://qiskit.org/ecosystem/ibm-runtime/how_to/run_session.html) for using Primitives with Runtime Sessions.\n"
40 | ]
41 | },
42 | {
43 | "cell_type": "code",
44 | "execution_count": null,
45 | "metadata": {},
46 | "outputs": [],
47 | "source": [
48 | "from qiskit.circuit import QuantumCircuit\n",
49 | "from qiskit.primitives import Estimator, Sampler\n",
50 | "from qiskit.quantum_info import SparsePauliOp\n",
51 | "from qiskit.visualization import plot_histogram\n",
52 | "\n",
53 | "import numpy as np\n",
54 | "import matplotlib.pyplot as plt\n",
55 | "plt.style.use('dark_background') # optional"
56 | ]
57 | },
58 | {
59 | "attachments": {},
60 | "cell_type": "markdown",
61 | "metadata": {},
62 | "source": [
63 | "## CHSH Inequality Violation\n",
64 | "\n",
65 | "### Warm Up\n",
66 | "\n",
67 | "Create circuits that put the qubit in the excited $|1\\rangle$ and superposition $|+\\rangle$ states, respectivly, and measure them in different bases. This is done first with the `Sampler` primitive (which is most similar to the `backend.run()` used in the previous lab), and then with the `Estimator` primitive to show how measurement is abstracted in that we do not need to worry about rotating the qubit into the appropriate measurement basis. The primitives will be executed withing the `Session` context which allows efficiency to optimize workloads."
68 | ]
69 | },
70 | {
71 | "cell_type": "code",
72 | "execution_count": null,
73 | "metadata": {},
74 | "outputs": [],
75 | "source": [
76 | "# create excited |1> state\n",
77 | "qc_1 = QuantumCircuit(1)\n",
78 | "qc_1.x(0)\n",
79 | "qc_1.draw('mpl')\n"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": null,
85 | "metadata": {},
86 | "outputs": [],
87 | "source": []
88 | },
89 | {
90 | "cell_type": "code",
91 | "execution_count": null,
92 | "metadata": {},
93 | "outputs": [],
94 | "source": [
95 | "# create superposition |+> state\n",
96 | "qc_plus = QuantumCircuit(1)\n",
97 | "qc_plus.h(0)\n",
98 | "qc_plus.draw('mpl')"
99 | ]
100 | },
101 | {
102 | "attachments": {},
103 | "cell_type": "markdown",
104 | "metadata": {},
105 | "source": [
106 | "### Sampler Primitive\n",
107 | "\n",
108 | "First use the `Sampler` to measure qubits in the $Z$-basis (the physical basis in which qubits are measured). The `Sampler` will count the number of outcomes of the $|0\\rangle$ state and $|1\\rangle$ state, normalized by the number of shots (experiments performed). The `Sampler` also offers the ability to easily perform error mitigation (which is covered in Lab 5), which modifies this calculation, and hence the outcomes are refered to as *quasi-probabilities*.\n",
109 | "\n",
110 | "Measurments must be present in the circuit when using the `Sampler` primitive. Then the `Session` context is opened, the `Sampler` is instantiated, and `sampler.run()` is used to send the circuits to the backend, similar to the `backend.run()` syntax you may already be familiar with."
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "metadata": {},
117 | "outputs": [],
118 | "source": [
119 | "qc_1.measure_all()\n",
120 | "qc_plus.measure_all()\n",
121 | "\n",
122 | "sampler = Sampler()\n",
123 | "job_1 = sampler.run(qc_1)\n",
124 | "job_plus = sampler.run(qc_plus)"
125 | ]
126 | },
127 | {
128 | "cell_type": "code",
129 | "execution_count": null,
130 | "metadata": {},
131 | "outputs": [],
132 | "source": [
133 | "job_1.result().quasi_dists"
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": null,
139 | "metadata": {},
140 | "outputs": [],
141 | "source": [
142 | "job_plus.result().quasi_dists"
143 | ]
144 | },
145 | {
146 | "cell_type": "code",
147 | "execution_count": null,
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "legend = [\"Excited State\", \"Plus State\"] # TODO: Excited State does not appear\n",
152 | "plot_histogram([job_1.result().quasi_dists[0], job_plus.result().quasi_dists[0]], legend=legend)"
153 | ]
154 | },
155 | {
156 | "attachments": {},
157 | "cell_type": "markdown",
158 | "metadata": {},
159 | "source": [
160 | "The result for the excited state is always $|1\\rangle$ wheres it is roughly half $|0\\rangle$ and half $|1\\rangle$ for the plus superposition state. This is because the $|0\\rangle$ and $|1\\rangle$ states are *eigenstates* of the $Z$ operator (with $+1$ and $-1$ eigenvalues, respectively).\n",
161 | "\n",
162 | "Let's switch and measure in the $X$ basis. Using the `Sampler` we must rotate the qubit from the $X$-basis to the $Z$-basis for measurement (because that is the only basis we can actually perform measurement in)."
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "metadata": {},
169 | "outputs": [],
170 | "source": [
171 | "qc_1.remove_final_measurements()\n",
172 | "qc_plus.remove_final_measurements()\n",
173 | "\n",
174 | "# rotate into the X-basis\n",
175 | "qc_1.h(0)\n",
176 | "qc_plus.h(0)\n",
177 | "\n",
178 | "qc_1.measure_all()\n",
179 | "qc_plus.measure_all()"
180 | ]
181 | },
182 | {
183 | "cell_type": "code",
184 | "execution_count": null,
185 | "metadata": {},
186 | "outputs": [],
187 | "source": [
188 | "sampler = Sampler()\n",
189 | "job_1 = sampler.run(qc_1)\n",
190 | "job_plus = sampler.run(qc_plus)"
191 | ]
192 | },
193 | {
194 | "cell_type": "code",
195 | "execution_count": null,
196 | "metadata": {},
197 | "outputs": [],
198 | "source": [
199 | "plot_histogram([job_1.result().quasi_dists[0], job_plus.result().quasi_dists[0]], legend=legend)"
200 | ]
201 | },
202 | {
203 | "attachments": {},
204 | "cell_type": "markdown",
205 | "metadata": {},
206 | "source": [
207 | "Now we see the opposite: the plus superposition always give the 1 result, hence an eigenstate of the $X$ operator, whereas the excited $|1\\rangle$ yields a roughtly fifty-fifty split. The $|+\\rangle$ and $|-\\rangle$ states are eigenstates of the $X$ operator, with eigenvalues $+1$ and $-1$, respectively. This is good to remember when considering how the `Estimator` works in the next subsection. "
208 | ]
209 | },
210 | {
211 | "attachments": {},
212 | "cell_type": "markdown",
213 | "metadata": {},
214 | "source": [
215 | "### Estimator Primitive\n",
216 | "\n",
217 | "The Qiskit Runtime Primitives allow us to abstract measurement into the `Estimator` primitive, where it is specified as an *observable*. In particular, we can construct the same circuits, the excited $|1\\rangle$ and superposition $|+\\rangle$ as before. However, in the case of the `Estimator`, we *do not* add measurements to the circuit. Instead, specify a list of observables which take the form of Pauli strings. In our case for a measurement of a single qubit, we specify `'Z'` for the $Z$-basis and `'X'` for the $X$-basis."
218 | ]
219 | },
220 | {
221 | "cell_type": "code",
222 | "execution_count": null,
223 | "metadata": {},
224 | "outputs": [],
225 | "source": [
226 | "qc2_1 = QuantumCircuit(1)\n",
227 | "qc2_1.x(0)\n",
228 | "\n",
229 | "qc2_plus = QuantumCircuit(1)\n",
230 | "qc2_plus.h(0)\n",
231 | "\n",
232 | "obsvs = list(SparsePauliOp(['Z', 'X']))"
233 | ]
234 | },
235 | {
236 | "cell_type": "code",
237 | "execution_count": null,
238 | "metadata": {},
239 | "outputs": [],
240 | "source": [
241 | "estimator = Estimator()\n",
242 | "job2_1 = estimator.run([qc2_1]*len(obsvs), observables=obsvs)\n",
243 | "job2_plus = estimator.run([qc2_plus]*len(obsvs), observables=obsvs)"
244 | ]
245 | },
246 | {
247 | "cell_type": "code",
248 | "execution_count": null,
249 | "metadata": {},
250 | "outputs": [],
251 | "source": [
252 | "job2_1.result()"
253 | ]
254 | },
255 | {
256 | "cell_type": "code",
257 | "execution_count": null,
258 | "metadata": {},
259 | "outputs": [],
260 | "source": [
261 | "# TODO: make this into module that outputs a nice table\n",
262 | "print(f' | | ')\n",
263 | "print(f'----|------------------')\n",
264 | "print(f'|1> | {job2_1.result().values[0]} | {job2_1.result().values[1]}')\n",
265 | "print(f'|+> | {job2_plus.result().values[0]} | {job2_plus.result().values[1]}')"
266 | ]
267 | },
268 | {
269 | "attachments": {},
270 | "cell_type": "markdown",
271 | "metadata": {},
272 | "source": [
273 | "Just as before, we see the $|1\\rangle$ state expectation in the $Z$-basis is $-1$ (corresponding to its eigenvalue) and around zero in the $X$-basis (average over $+1$ and $-1$ eigenvalues), and vice-versa for the $|+\\rangle$ state (although its eigenvalue of the $X$ operators is $+1$)."
274 | ]
275 | },
276 | {
277 | "attachments": {},
278 | "cell_type": "markdown",
279 | "metadata": {},
280 | "source": [
281 | "## CHSH Inequality\n",
282 | "\n",
283 | "Imagine Alice and Bob are given each one part of a bipartite entangled system. Each of them then performs two measurements on their part in two different bases. Let's call Alice's bases A and a and Bob's B and b. What is the expectation value of the quantity \n",
284 | "\n",
285 | "$$\n",
286 | "\\langle CHSH \\rangle = \\langle AB \\rangle - \\langle Ab \\rangle + \\langle aB \\rangle + \\langle ab \\rangle ?\n",
287 | "$$\n",
288 | "\n",
289 | "Now, Alice and Bob have one qubit each, so any measurement they perform on their system (qubit) can only yield one of two possible outcomes: +1 or -1. Note that whereas we typically refer to the two qubit states as $|0\\rangle$ and $|1\\rangle$, these are eigenstates, and a projective measurement will yield their eigenvalues, +1 and -1, respectively.\n",
290 | "\n",
291 | "Therefore, if any measurement of A, a, B, and b can only yield $\\pm 1$, the quantities $(B-b)$ and $(B+b)$ can only be 0 or $\\pm 2$. And thus, the quantity $A(B-b) + a(B+b)$ can only be either +2 or -2, which means that there should be a bound for the expectation value of the quantity we have called\n",
292 | "\n",
293 | "$$\n",
294 | "|\\langle CHSH \\rangle| = |\\langle AB \\rangle - \\langle Ab \\rangle + \\langle aB \\rangle + \\langle ab \\rangle| \\le 2.\n",
295 | "$$\n",
296 | "\n",
297 | "Now, the above discussion is oversimplified, because we could consider that the outcome on any set of measurements from Alice and Bob could depend on a set of local hidden variables, but it can be shown with some math that, even when that is the case, the expectation value of the quantity $CHSH$ should be bounded by 2 if local realism held.\n",
298 | "\n",
299 | "But what happens when we do these experiments with an entangled system? Let's try it!"
300 | ]
301 | },
302 | {
303 | "attachments": {},
304 | "cell_type": "markdown",
305 | "metadata": {},
306 | "source": [
307 | "The first step is to build the observable\n",
308 | "$$\n",
309 | "CHSH = A(B-b) + a(B+b) = AB - Ab + aB +ab\n",
310 | "$$\n",
311 | "where $A, a$ are each one of $\\{IX, IZ\\}$ for qubit 0 and $B, b$ are each one of $\\{XI, ZI\\}$ for qubit 1 (corresponding to little-endian notation). Paulis on different qubits can be composed by specifying order with a Pauli string, for example instantiating a `SparsePauliOp` with the `'ZX'` argument implies a measurement of $\\langle X \\rangle$ on `q0` and $\\langle Z \\rangle$ on `q1` . This *tensor* product (combining operations on *different* qubits) can be explicitly stated using the `.tensor()` method. Additionally, combining operations on the *same* qubit(s) uses the *compositional* product with the `.compose()` method. For example, all these statements create the same Pauli operator:\n",
312 | "\n",
313 | "```\n",
314 | "from qiskit.quantum_info import SparsePauliOp\n",
315 | "\n",
316 | "ZX = SparsePauliOp('ZX')\n",
317 | "ZX = SparsePauliOp(['ZX'], coeffs=[1.]) # extendable to a sum of Paulis\n",
318 | "ZX = SparsePauliOp('Z').tensor(SparsePauliOp('X')) # extendable to a tensor product of Paulis\n",
319 | "ZX = SparsePauliOp('XZ').compose(SparsePauliOp('YY')) # extendable to a compositional product of Paulis\n",
320 | "```\n"
321 | ]
322 | },
323 | {
324 | "attachments": {},
325 | "cell_type": "markdown",
326 | "metadata": {},
327 | "source": [
328 | "Ex 1 - create an operator for CHSH witness"
329 | ]
330 | },
331 | {
332 | "cell_type": "code",
333 | "execution_count": null,
334 | "metadata": {},
335 | "outputs": [],
336 | "source": [
337 | "obsv = # create operator for chsh witness"
338 | ]
339 | },
340 | {
341 | "cell_type": "code",
342 | "execution_count": null,
343 | "metadata": {},
344 | "outputs": [],
345 | "source": [
346 | "from qc_grader.challenges.qgss_2023 import grade_lab2_ex1\n",
347 | "\n",
348 | "grade_lab2_ex1(obsv)"
349 | ]
350 | },
351 | {
352 | "attachments": {},
353 | "cell_type": "markdown",
354 | "metadata": {},
355 | "source": [
356 | "### Create Entangled Qubit Pair\n",
357 | "\n",
358 | "Next we want to test the $CHSH$ observable on an entangled pair, for example the maximally-entangled Bell state\n",
359 | "$$\n",
360 | "|\\Phi\\rangle = \\frac{1}{\\sqrt{2}} \\left(|00\\rangle + |11\\rangle \\right)\n",
361 | "$$\n",
362 | "which is created with a Hadamard gate followed by a CNOT with the target on the same qubit as the Hadamard. Due to the simplifaction of measuring in just the $X$- and $Z$-bases as discussed above, we will *rotate* the Bell state around the Bloch sphere which is equivalant to changing the measurement basis as demonstrated in the Warmup section. This can be done by applying an $R_y(\\theta)$ gate where $\\theta$ is a `Parameter` to be specified at the `Estimator` API call. This produces the state\n",
363 | "$$\n",
364 | "|\\psi\\rangle = \\frac{1}{\\sqrt{2}} \\left(\\cos(\\theta/2) |00\\rangle + \\sin(\\theta/2)|11\\rangle \\right)\n",
365 | "$$"
366 | ]
367 | },
368 | {
369 | "cell_type": "code",
370 | "execution_count": null,
371 | "metadata": {},
372 | "outputs": [],
373 | "source": [
374 | "from qiskit.circuit import Parameter\n",
375 | "\n",
376 | "theta = Parameter('θ')\n",
377 | "\n",
378 | "qc = QuantumCircuit(2)\n",
379 | "qc.h(0)\n",
380 | "qc.cx(0, 1)\n",
381 | "qc.ry(theta, 0)\n",
382 | "\n",
383 | "qc.draw('mpl')"
384 | ]
385 | },
386 | {
387 | "attachments": {},
388 | "cell_type": "markdown",
389 | "metadata": {},
390 | "source": [
391 | "Next we need to specify a `Sequence` of `Parameter`s that show a clear violation of the CHSH Inequality, namely \n",
392 | "$$\n",
393 | "|\\langle CHSH \\rangle| > 2.\n",
394 | "$$\n",
395 | "Let's make sure we have at least three points in violation."
396 | ]
397 | },
398 | {
399 | "attachments": {},
400 | "cell_type": "markdown",
401 | "metadata": {},
402 | "source": [
403 | "Ex 2 - Create a Parameterization (i.e., list, array) of the angle in the above circuit (in radians)\n",
404 | "\n",
405 | "Hint: Note the `type` for the `parameter_values` argument is `Sequence[Sequence[float]]`."
406 | ]
407 | },
408 | {
409 | "cell_type": "code",
410 | "execution_count": null,
411 | "metadata": {},
412 | "outputs": [],
413 | "source": [
414 | "angles = # create a parameterization of angles that will violate the inequality"
415 | ]
416 | },
417 | {
418 | "attachments": {},
419 | "cell_type": "markdown",
420 | "metadata": {},
421 | "source": [
422 | "Test your angles and observable by running with the `Estimator` before submitting to the grader."
423 | ]
424 | },
425 | {
426 | "cell_type": "code",
427 | "execution_count": null,
428 | "metadata": {},
429 | "outputs": [],
430 | "source": [
431 | "estimator = Estimator()\n",
432 | "job = estimator.run([qc]*len(angles), observables=[obsv]*len(angles), parameter_values=angles)\n",
433 | "exps = job.result().values\n",
434 | "\n",
435 | "plt.plot(angles, exps, marker='x', ls='-', color='green')\n",
436 | "plt.plot(angles, [2]*len(angles), ls='--', color='red', label='Classical Bound')\n",
437 | "plt.plot(angles, [-2]*len(angles), ls='--', color='red')\n",
438 | "plt.xlabel('angle (rad)')\n",
439 | "plt.ylabel('CHSH Witness')\n",
440 | "plt.legend(loc=4)"
441 | ]
442 | },
443 | {
444 | "attachments": {},
445 | "cell_type": "markdown",
446 | "metadata": {},
447 | "source": [
448 | "Did you see at least 3 points outside the red dashed lines? If so, you are ready to send to the grader!"
449 | ]
450 | },
451 | {
452 | "cell_type": "code",
453 | "execution_count": null,
454 | "metadata": {},
455 | "outputs": [],
456 | "source": [
457 | "from qc_grader.challenges.qgss_2023 import grade_lab2_ex2\n",
458 | "\n",
459 | "grade_lab2_ex2(obsv, angles)"
460 | ]
461 | },
462 | {
463 | "attachments": {},
464 | "cell_type": "markdown",
465 | "metadata": {},
466 | "source": [
467 | "# Teleportation\n",
468 | "\n",
469 | "Quantum information cannot be copied due to the *No Cloning Theorem*, however it can be \"teleported\" in the sense that a qubit can be entangled with a quantum resource, and via a protocol of measurements and *classical communication* of their results, the original quantum state can be reconstructed on a different qubit. This process destroys the information in the original qubit via measurement.\n",
470 | "\n",
471 | "In this exercise, we will construct a particular qubit state and then transfer that state to another qubit using the teleportation protocol. Here we will be looking at specific classical and quantum registers, so we need to import those."
472 | ]
473 | },
474 | {
475 | "cell_type": "code",
476 | "execution_count": null,
477 | "metadata": {},
478 | "outputs": [],
479 | "source": [
480 | "from qiskit.circuit import ClassicalRegister, QuantumRegister"
481 | ]
482 | },
483 | {
484 | "attachments": {},
485 | "cell_type": "markdown",
486 | "metadata": {},
487 | "source": [
488 | "### Create the circuit\n",
489 | "\n",
490 | "Define an angle $\\theta$ to rotate our qubit by. This will allow us to easily make comparisons for the original state and the teleported state."
491 | ]
492 | },
493 | {
494 | "cell_type": "code",
495 | "execution_count": null,
496 | "metadata": {},
497 | "outputs": [],
498 | "source": [
499 | "theta = Parameter('θ')\n",
500 | "\n",
501 | "qr = QuantumRegister(1, 'q')\n",
502 | "qc = QuantumCircuit(qr)\n",
503 | "qc.ry(theta, 0)\n",
504 | "qc.draw('mpl')"
505 | ]
506 | },
507 | {
508 | "attachments": {},
509 | "cell_type": "markdown",
510 | "metadata": {},
511 | "source": [
512 | "Alice possesses the quantum information $|\\psi\\rangle$ in the state of $q$ and wishes to transfer it to Bob. The resource they share is a special entangled state called a Bell state\n",
513 | "$$\n",
514 | "|\\Phi^+\\rangle = \\frac{1}{2} \\left( |00\\rangle + |11\\rangle \\right)\n",
515 | "$$\n",
516 | "with the first of the pair going to Alice and the second to Bob. Hence Alice has a 2-qubit register ($q$ and $Bell_0$) and Bob has a single-qubit register ($Bell_1$). We will construct the circuit by copying the original `qc` and adding the appropriate registers."
517 | ]
518 | },
519 | {
520 | "cell_type": "code",
521 | "execution_count": null,
522 | "metadata": {},
523 | "outputs": [],
524 | "source": [
525 | "tele_qc = qc.copy()\n",
526 | "bell = QuantumRegister(2, 'Bell')\n",
527 | "alice = ClassicalRegister(2, 'Alice')\n",
528 | "bob = ClassicalRegister(1, 'Bob')\n",
529 | "tele_qc.add_register(bell, alice, bob)\n",
530 | "tele_qc.draw('mpl')"
531 | ]
532 | },
533 | {
534 | "attachments": {},
535 | "cell_type": "markdown",
536 | "metadata": {},
537 | "source": [
538 | "Now create the Bell pair with $Bell_0$ going to Alice and $Bell_1$ going to Bob. This is done by using a Hadamard gate to put $Bell_0$ in the $|+\\rangle$ state and then performing a CNOT with the same qubit as the control. After they receive their respective qubit, they part ways."
539 | ]
540 | },
541 | {
542 | "cell_type": "code",
543 | "execution_count": null,
544 | "metadata": {},
545 | "outputs": [],
546 | "source": [
547 | "# create Bell state with other two qubits\n",
548 | "tele_qc.barrier()\n",
549 | "tele_qc.h(1)\n",
550 | "tele_qc.cx(1, 2)\n",
551 | "tele_qc.barrier()\n",
552 | "tele_qc.draw('mpl')"
553 | ]
554 | },
555 | {
556 | "attachments": {},
557 | "cell_type": "markdown",
558 | "metadata": {},
559 | "source": [
560 | "Next, Alice performs a CNOT controlled by $q$ on $Bell_0$, which maps information about the state onto it. She then applies a Hadamard gate on $q$."
561 | ]
562 | },
563 | {
564 | "cell_type": "code",
565 | "execution_count": null,
566 | "metadata": {},
567 | "outputs": [],
568 | "source": [
569 | "# alice operates on her qubits\n",
570 | "tele_qc.cx(0, 1)\n",
571 | "tele_qc.h(0)\n",
572 | "tele_qc.barrier()\n",
573 | "tele_qc.draw('mpl')"
574 | ]
575 | },
576 | {
577 | "attachments": {},
578 | "cell_type": "markdown",
579 | "metadata": {},
580 | "source": [
581 | "Now Alice measures her qubits and saves the results to her register."
582 | ]
583 | },
584 | {
585 | "cell_type": "code",
586 | "execution_count": null,
587 | "metadata": {},
588 | "outputs": [],
589 | "source": [
590 | "tele_qc.measure([qr[0], bell[0]], alice)\n",
591 | "tele_qc.draw('mpl')"
592 | ]
593 | },
594 | {
595 | "attachments": {},
596 | "cell_type": "markdown",
597 | "metadata": {},
598 | "source": [
599 | "Bob's qubit now has the information $|\\psi\\rangle$ from Alice's qubit $q$ encoded in $Bell_1$, but he does not know what basis to measure in to extract it. Accordingly, Alice must share the information in her register over a *classical* communication channel (this is why teleportation does not violate special relativity, no matter how far Alice and Bob are apart). She instructs Bob to perform an X gate on his qubit if her measurement of $Bell_0$ yields a 1 outcome, followed by a Z gate if her measurement of $q$ yields a 1.\n",
600 | "\n",
601 | "The applications of these gates can be conditioned on the measurement outcomes in two ways:\n",
602 | "- the `.c_if()` [instruction](https://qiskit.org/documentation/stubs/qiskit.circuit.Instruction.c_if.html), which applies the gate it modifies if the value of the `ClassicalRegister` index is equal to the value specified. Note that this works **only** on simulators.\n",
603 | "- the `.if_test()` [context](https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.if_test.html) which operates similarly, but generalizes the syntax to allow for nested conditionals. This works on both simulators and actual hardware."
604 | ]
605 | },
606 | {
607 | "attachments": {},
608 | "cell_type": "markdown",
609 | "metadata": {},
610 | "source": [
611 | "Ex 3 - Add approriate conditional gates to transform Bob's qubit into the $Z$-basis."
612 | ]
613 | },
614 | {
615 | "cell_type": "code",
616 | "execution_count": null,
617 | "metadata": {},
618 | "outputs": [],
619 | "source": [
620 | "graded_qc = tele_qc.copy()\n",
621 | "\n",
622 | "##############################\n",
623 | "# add gates to graded_qc here\n",
624 | "\n",
625 | "\n",
626 | "\n",
627 | "##############################\n",
628 | "\n",
629 | "graded_qc.draw('mpl')"
630 | ]
631 | },
632 | {
633 | "attachments": {},
634 | "cell_type": "markdown",
635 | "metadata": {},
636 | "source": [
637 | "Finally, Bob can measure his qubit, which would yield results with the same probabilities as had Alice measured it originally."
638 | ]
639 | },
640 | {
641 | "cell_type": "code",
642 | "execution_count": null,
643 | "metadata": {},
644 | "outputs": [],
645 | "source": [
646 | "graded_qc.barrier()\n",
647 | "graded_qc.measure(bell[1], bob)\n",
648 | "graded_qc.draw('mpl')"
649 | ]
650 | },
651 | {
652 | "cell_type": "code",
653 | "execution_count": null,
654 | "metadata": {},
655 | "outputs": [],
656 | "source": [
657 | "from qc_grader.challenges.qgss_2023 import grade_lab2_ex3\n",
658 | "\n",
659 | "grade_lab2_ex3(graded_qc, theta, 5*np.pi/7)"
660 | ]
661 | },
662 | {
663 | "attachments": {},
664 | "cell_type": "markdown",
665 | "metadata": {},
666 | "source": [
667 | "The statevector simulator cannot work with dynamic circuits because measurement is not a unitary operation. Therefore we import the `Sampler` primitive from `qiskit_aer` to use the `AerSimulator`. We choose our angle to be $5\\pi/7$, which will yield a 1 result about 80\\% of the time and 0 result about 20\\% of the time. Then we run both circuits: the original one Alice had and the teleported one Bob receives. "
668 | ]
669 | },
670 | {
671 | "cell_type": "code",
672 | "execution_count": null,
673 | "metadata": {},
674 | "outputs": [],
675 | "source": [
676 | "from qiskit_aer.primitives import Sampler\n",
677 | "\n",
678 | "angle = 5*np.pi/7\n",
679 | "\n",
680 | "sampler = Sampler()\n",
681 | "qc.measure_all()\n",
682 | "job_static = sampler.run(qc.bind_parameters({theta: angle}))\n",
683 | "job_dynamic = sampler.run(graded_qc.bind_parameters({theta: angle}))\n",
684 | "\n",
685 | "print(f\"Original Dists: {job_static.result().quasi_dists[0].binary_probabilities()}\")\n",
686 | "print(f\"Teleported Dists: {job_dynamic.result().quasi_dists[0].binary_probabilities()}\")"
687 | ]
688 | },
689 | {
690 | "attachments": {},
691 | "cell_type": "markdown",
692 | "metadata": {},
693 | "source": [
694 | "Wait, we see different results! While measuring Alice's original $q$ yields the expected ratio of outcomes, the teleported distributions have many more values. This is because the teleported circuit includes Alice's measurements of $q$ and $Bell_0$, whereas we only wish to see Bob's measurements of $Bell_1$ yield the same distribution. \n",
695 | "\n",
696 | "In order to rectify this, we must take the *marginal* counts, meaning we combine results in which Bob measures a 0 and all the results in which Bob measures a 1 over all the possible combinations. This is done with the `marginal_counts` [method](https://qiskit.org/documentation/stubs/qiskit.result.marginal_counts.html) from `qiskit.result`, which combines results over measurement indices."
697 | ]
698 | },
699 | {
700 | "attachments": {},
701 | "cell_type": "markdown",
702 | "metadata": {},
703 | "source": [
704 | "Ex 4 - Marginalize the teleported counts\n",
705 | "\n",
706 | "Hint: Remember that bit strings are reported in the little-endian convention."
707 | ]
708 | },
709 | {
710 | "cell_type": "code",
711 | "execution_count": null,
712 | "metadata": {},
713 | "outputs": [],
714 | "source": [
715 | "from qiskit.result import marginal_counts\n",
716 | "\n",
717 | "tele_counts = # marginalize counts"
718 | ]
719 | },
720 | {
721 | "attachments": {},
722 | "cell_type": "markdown",
723 | "metadata": {},
724 | "source": [
725 | "If we marginalized correctly, we will see that the quasi-distributions from Alice's measurement and Bob's measurement are nearly identical, demonstrating that teleportation was successful!"
726 | ]
727 | },
728 | {
729 | "cell_type": "code",
730 | "execution_count": null,
731 | "metadata": {},
732 | "outputs": [],
733 | "source": [
734 | "legend = ['Original State', 'Teleported State']\n",
735 | "plot_histogram([job_static.result().quasi_dists[0].binary_probabilities(), tele_counts], legend=legend)"
736 | ]
737 | },
738 | {
739 | "cell_type": "code",
740 | "execution_count": null,
741 | "metadata": {},
742 | "outputs": [],
743 | "source": [
744 | "from qc_grader.challenges.qgss_2023 import grade_lab2_ex4\n",
745 | "\n",
746 | "grade_lab2_ex4(tele_counts, job_dynamic.result())"
747 | ]
748 | },
749 | {
750 | "cell_type": "code",
751 | "execution_count": null,
752 | "metadata": {},
753 | "outputs": [],
754 | "source": [
755 | "import qiskit.tools.jupyter\n",
756 | "%qiskit_version_table"
757 | ]
758 | },
759 | {
760 | "cell_type": "code",
761 | "execution_count": null,
762 | "metadata": {},
763 | "outputs": [],
764 | "source": []
765 | }
766 | ],
767 | "metadata": {
768 | "kernelspec": {
769 | "display_name": "qiskit-runtime",
770 | "language": "python",
771 | "name": "python3"
772 | },
773 | "language_info": {
774 | "codemirror_mode": {
775 | "name": "ipython",
776 | "version": 3
777 | },
778 | "file_extension": ".py",
779 | "mimetype": "text/x-python",
780 | "name": "python",
781 | "nbconvert_exporter": "python",
782 | "pygments_lexer": "ipython3",
783 | "version": "3.9.7"
784 | },
785 | "orig_nbformat": 4
786 | },
787 | "nbformat": 4,
788 | "nbformat_minor": 2
789 | }
790 |
--------------------------------------------------------------------------------
/content/lab3/lab3.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "id": "abdd2993",
7 | "metadata": {},
8 | "source": [
9 | "# Lab 3 : Diving Into Quantum Algorithms\n",
10 | "***"
11 | ]
12 | },
13 | {
14 | "attachments": {},
15 | "cell_type": "markdown",
16 | "id": "b82a124a",
17 | "metadata": {},
18 | "source": [
19 | "## Section 1: QPE\n",
20 | "\n",
21 | "\n",
22 | "
\n",
23 | "\n",
24 | "\n",
25 | "As a reminder to you, the above figure outlines the Quantum Phase Estimation (QPE) circuit. Below, we'll provide a few import statements and functions that you'll use throughout the lab."
26 | ]
27 | },
28 | {
29 | "cell_type": "code",
30 | "execution_count": null,
31 | "id": "01b08df9",
32 | "metadata": {},
33 | "outputs": [],
34 | "source": [
35 | "from qiskit import QuantumCircuit, Aer, execute\n",
36 | "import numpy as np\n",
37 | "from qiskit.visualization import plot_histogram\n",
38 | "import matplotlib.pyplot as plt\n",
39 | "from math import gcd\n",
40 | "\n"
41 | ]
42 | },
43 | {
44 | "cell_type": "code",
45 | "execution_count": null,
46 | "id": "3c62e5a7",
47 | "metadata": {},
48 | "outputs": [],
49 | "source": [
50 | "#QFT Circuit\n",
51 | "def qft(n):\n",
52 | " \"\"\"Creates an n-qubit QFT circuit\"\"\"\n",
53 | " circuit = QuantumCircuit(n)\n",
54 | " def swap_registers(circuit, n):\n",
55 | " for qubit in range(n//2):\n",
56 | " circuit.swap(qubit, n-qubit-1)\n",
57 | " return circuit\n",
58 | " def qft_rotations(circuit, n):\n",
59 | " \"\"\"Performs qft on the first n qubits in circuit (without swaps)\"\"\"\n",
60 | " if n == 0:\n",
61 | " return circuit\n",
62 | " n -= 1\n",
63 | " circuit.h(n)\n",
64 | " for qubit in range(n):\n",
65 | " circuit.cp(np.pi/2**(n-qubit), qubit, n)\n",
66 | " qft_rotations(circuit, n)\n",
67 | " \n",
68 | " qft_rotations(circuit, n)\n",
69 | " swap_registers(circuit, n)\n",
70 | " return circuit\n",
71 | "\n",
72 | "#Inverse Quantum Fourier Transform\n",
73 | "def qft_dagger(qc, n):\n",
74 | " \"\"\"n-qubit QFTdagger the first n qubits in circ\"\"\"\n",
75 | " # Don't forget the Swaps!\n",
76 | " for qubit in range(n//2):\n",
77 | " qc.swap(qubit, n-qubit-1)\n",
78 | " for j in range(n):\n",
79 | " for m in range(j):\n",
80 | " qc.cp(-np.pi/float(2**(j-m)), m, j)\n",
81 | " qc.h(j)\n",
82 | " return qc"
83 | ]
84 | },
85 | {
86 | "attachments": {},
87 | "cell_type": "markdown",
88 | "id": "206bb6b0",
89 | "metadata": {},
90 | "source": [
91 | "### Step 1: Set up a QPE Circuit with four counting qubits\n",
92 | "\n",
93 | "\n",
94 | "\n",
95 | "Let's pick a phase gate with $\\theta = \\frac{1}{3}$ as a simple example unitary to test creating a QPE circuit. Here we'll use Qiskit's `PhaseGate` which applies $P|1\\rangle{}=e^{i\\lambda}|1\\rangle{}$. Since we want to examine QPE under a unitary with the form $U|1\\rangle{}=e^{i2\\pi \\theta}$, we should set $\\lambda=\\frac{2\\pi}{3}$.\n",
96 | "\n",
97 | "Create a QPE circuit with four counting qubits and name the circuit `qpe4`. It may be helpful to define two `QuantumRegister` objects, one for the \"system\" where the unitary will be applied and one for where the phase information will be stored. Feel free to reference the Qiskit Textbook's chapter on [Quantum Phase Estimation](https://learn.qiskit.org/course/ch-algorithms/quantum-phase-estimation#getting_more_precision)."
98 | ]
99 | },
100 | {
101 | "attachments": {},
102 | "cell_type": "markdown",
103 | "id": "67a189c9",
104 | "metadata": {},
105 | "source": [
106 | "It should look something like this (note that because Qiskit uses little endian notation, the ordering of the controlled-$U$ gates are different):\n",
107 | "\n",
108 | "
\n"
109 | ]
110 | },
111 | {
112 | "cell_type": "code",
113 | "execution_count": null,
114 | "id": "cdf021ec",
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "\n",
119 | "phase_register_size = 4\n",
120 | "qpe4 = QuantumCircuit(phase_register_size+1, phase_register_size)\n",
121 | "\n",
122 | "### Insert your code here\n",
123 | "\n"
124 | ]
125 | },
126 | {
127 | "attachments": {},
128 | "cell_type": "markdown",
129 | "id": "21259f16",
130 | "metadata": {},
131 | "source": [
132 | "Now use the `AerSimulator` to simulate this circuit and plot the histogram of the results. Use 2000 shots.\n"
133 | ]
134 | },
135 | {
136 | "cell_type": "code",
137 | "execution_count": null,
138 | "id": "f091e056",
139 | "metadata": {},
140 | "outputs": [],
141 | "source": [
142 | "## Run this cell to simulate 'qpe4' and to plot the histogram of the result\n",
143 | "sim = Aer.get_backend('aer_simulator')\n",
144 | "shots = 2000\n",
145 | "count_qpe4 = execute(qpe4, sim, shots=shots).result().get_counts()\n",
146 | "plot_histogram(count_qpe4, figsize=(9,5))"
147 | ]
148 | },
149 | {
150 | "cell_type": "code",
151 | "execution_count": null,
152 | "id": "fb998b47",
153 | "metadata": {},
154 | "outputs": [],
155 | "source": [
156 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex1 \n",
157 | "\n",
158 | "grade_lab3_ex1(count_qpe4)"
159 | ]
160 | },
161 | {
162 | "attachments": {},
163 | "cell_type": "markdown",
164 | "id": "9ca1785a",
165 | "metadata": {},
166 | "source": [
167 | "Next write a function to process the bit strings into the estimate of $\\theta$. Recall that the phase estimate is written in the form:\n",
168 | "\n",
169 | "$$ \\theta = 0.\\theta_1\\theta_2\\theta_3...\\theta_t = \\frac{\\theta_1}{2^1} + \\frac{\\theta_2}{2^2} + \\frac{\\theta_3}{2^3} + ... + \\frac{\\theta_t}{2^t} $$\n",
170 | "\n",
171 | "where $\\theta_i = \\{0,1\\}$. What is the estimated phase? What is the highest power of 2 this circuit can be accurate up to given your choice of the number of counting qubits (e.g. $2^{-2}$, $2^{-3}$, $2^{-4}$, etc.)?\n",
172 | "\n"
173 | ]
174 | },
175 | {
176 | "cell_type": "code",
177 | "execution_count": null,
178 | "id": "0d427100",
179 | "metadata": {},
180 | "outputs": [],
181 | "source": [
182 | "\n",
183 | "#Grab the highest probability measurement\n",
184 | "max_binary_counts = 0\n",
185 | "max_binary_val = ''\n",
186 | "for key, item in count_qpe4.items():\n",
187 | " if item > max_binary_counts:\n",
188 | " max_binary_counts = item\n",
189 | " max_binary_val = key\n",
190 | "\n",
191 | "## Your function to convert a binary string to decimal goes here\n",
192 | "\n",
193 | "\n",
194 | "\n",
195 | "estimated_phase = # calculate the estimated phase\n",
196 | "phase_accuracy_window = # highest power of 2 (i.e. smallest decimal) this circuit can estimate\n",
197 | "\n"
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "id": "732d9625",
204 | "metadata": {},
205 | "outputs": [],
206 | "source": [
207 | "\n",
208 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex2 \n",
209 | "\n",
210 | "grade_lab3_ex2([estimated_phase, phase_accuracy_window])"
211 | ]
212 | },
213 | {
214 | "attachments": {},
215 | "cell_type": "markdown",
216 | "id": "3820b0e3",
217 | "metadata": {},
218 | "source": [
219 | "## Step 2: Run on Noisy Hardware\n",
220 | "\n",
221 | "Now run this circuit using your favorite backend! Transpile this circuit a number of times (you pick how many) and pick the one with the lowest and highest circuit depth. \n",
222 | "\n",
223 | "Transpile the circuit with the parameter optimization_level = 3 to reduce the error in the result. Qiskit by default uses a stochastic swap mapper to place the needed SWAP gates, which varies the transpiled circuit results even under the same runtime settings. Therefore, to achieve shorter depth transpiled circuit for smaller error in the outcome, transpile qpe4 multiple times and choose one with the minimum circuit depth. Select the maximum circuit depth one as well to compare against, name them `min_depth_qpe` and `max_depth_qpe`.\n",
224 | "\n"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "id": "8297c6da",
231 | "metadata": {},
232 | "outputs": [],
233 | "source": [
234 | "from qiskit_ibm_provider import IBMProvider\n",
235 | "from qiskit.compiler import transpile\n",
236 | "\n",
237 | "\n",
238 | "provider = IBMProvider()\n",
239 | "hub = \"YOUR_HUB\"\n",
240 | "group = \"YOUR_GROUP\"\n",
241 | "project = \"YOUR_PROJECT\"\n",
242 | "backend_name = \"YOUR_BACKEND\"\n",
243 | "\n",
244 | "backend = provider.get_backend(backend_name, instance=f\"{hub}/{group}/{project}\")\n",
245 | "\n",
246 | "# your code goes here\n",
247 | "\n"
248 | ]
249 | },
250 | {
251 | "cell_type": "code",
252 | "execution_count": null,
253 | "id": "7b389bc6",
254 | "metadata": {},
255 | "outputs": [],
256 | "source": [
257 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex3\n",
258 | "\n",
259 | "grade_lab3_ex3([max_depth_qpe, min_depth_qpe])"
260 | ]
261 | },
262 | {
263 | "cell_type": "code",
264 | "execution_count": null,
265 | "id": "5adcd092",
266 | "metadata": {},
267 | "outputs": [],
268 | "source": [
269 | "shots = 2000\n",
270 | "\n",
271 | "#OPTIONAL: Run the minimum depth qpe circuit\n",
272 | "job_min_qpe4 = backend.run(min_depth_qpe, sim, shots=shots)\n",
273 | "print(job_min_qpe4.job_id())\n",
274 | "\n",
275 | "#Gather the count data\n",
276 | "count_min_qpe4 = job_min_qpe4.result().get_counts()\n",
277 | "plot_histogram(count_min_qpe4, figsize=(9,5))"
278 | ]
279 | },
280 | {
281 | "cell_type": "code",
282 | "execution_count": null,
283 | "id": "a05d8270",
284 | "metadata": {},
285 | "outputs": [],
286 | "source": [
287 | "\n",
288 | "#OPTIONAL: Run the maximum depth qpe circuit\n",
289 | "job_max_qpe4 = backend.run(max_depth_qpe, sim, shots=shots)\n",
290 | "print(job_max_qpe4.job_id())\n",
291 | "\n",
292 | "#Gather the count data\n",
293 | "count_max_qpe4 = job_max_qpe4.result().get_counts()\n",
294 | "plot_histogram(count_max_qpe4, figsize=(9,5))"
295 | ]
296 | },
297 | {
298 | "attachments": {},
299 | "cell_type": "markdown",
300 | "id": "3b2b49a6",
301 | "metadata": {},
302 | "source": [
303 | "## Step 3: Try with a different $\\theta$\n",
304 | "\n",
305 | "Now try the same procedure with $\\theta = \\frac{1}{7}$. Rewrite your code written above to create a function which generates a QPE circuit with $n$ register qubits. How many register qubits storing the phase information are needed for the estimate to be accurate to within $2^{-6}$? \n",
306 | "\n",
307 | "*Hint: It may be easier to iterate over different phase register sizes by creating a callable function. Perhaps call it* `qpe_circuit`"
308 | ]
309 | },
310 | {
311 | "cell_type": "code",
312 | "execution_count": null,
313 | "id": "b121893f",
314 | "metadata": {},
315 | "outputs": [],
316 | "source": [
317 | "\n",
318 | "\n",
319 | "def qpe_circuit(register_size):\n",
320 | " # Your code goes here\n",
321 | " \n",
322 | " return qpe\n",
323 | " \n",
324 | "\n"
325 | ]
326 | },
327 | {
328 | "cell_type": "code",
329 | "execution_count": null,
330 | "id": "42ec48b0",
331 | "metadata": {},
332 | "outputs": [],
333 | "source": [
334 | "## Run this cell to simulate 'qpe4' and to plot the histogram of the result\n",
335 | "reg_size = # Vary the register sizes\n",
336 | "\n",
337 | "qpe_check = qpe_circuit(reg_size)\n",
338 | "sim = Aer.get_backend('aer_simulator')\n",
339 | "shots = 10000\n",
340 | "count_qpe4 = execute(qpe_check, sim, shots=shots).result().get_counts()\n",
341 | "plot_histogram(count_qpe4, figsize=(9,5))"
342 | ]
343 | },
344 | {
345 | "cell_type": "code",
346 | "execution_count": null,
347 | "id": "d1cfe002",
348 | "metadata": {},
349 | "outputs": [],
350 | "source": [
351 | "\n",
352 | "\n",
353 | "# Process the count data to determine accuracy of the estimated phase\n",
354 | "\n",
355 | "\n",
356 | "\n"
357 | ]
358 | },
359 | {
360 | "cell_type": "code",
361 | "execution_count": null,
362 | "id": "190454a7",
363 | "metadata": {},
364 | "outputs": [],
365 | "source": [
366 | "required_register_size = #your answer here"
367 | ]
368 | },
369 | {
370 | "cell_type": "code",
371 | "execution_count": null,
372 | "id": "c08ac980",
373 | "metadata": {},
374 | "outputs": [],
375 | "source": [
376 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex4\n",
377 | "\n",
378 | "grade_lab3_ex4(required_register_size)"
379 | ]
380 | },
381 | {
382 | "attachments": {},
383 | "cell_type": "markdown",
384 | "id": "0a450574",
385 | "metadata": {},
386 | "source": [
387 | "## Section 2: Shor's Algorithm\n",
388 | "***\n",
389 | "\n",
390 | "Here we will construct a set of functions to implement Shor's algorithm. Remember that the goal of this algorithm is to find the prime factors of some large number $N$ and the key speedup this algorithm provides is by executing the period-finding part using a quantum computer. This is where this section of the lab will focus.\n",
391 | "\n",
392 | "\n",
393 | "Shor's algorithm is composed of the following steps:\n",
394 | "1. Choose a co-prime $a$, where $a\\in [2,N-1]$ and the greatest common divisor of $a$ and $N$ is 1.\n",
395 | "1. Find the order (periodicity) of $a$ modulo $N$, i.e. the smallest integer $r$ such that $a^r\\text{mod} N=1$\n",
396 | "1. Obtain the factor of $N$ by computing the greatest common divisor of $a^{r/2} \\pm 1$ and $N$."
397 | ]
398 | },
399 | {
400 | "attachments": {},
401 | "cell_type": "markdown",
402 | "id": "818edadf",
403 | "metadata": {},
404 | "source": [
405 | "## Step 1. Period Finding\n",
406 | "\n",
407 | "To begin, we'll use the unitary operator: $$ U|y\\rangle{} \\equiv |ay\\ \\text{mod} N\\rangle{} $$\n",
408 | "\n",
409 | "\n",
410 | "and explore the superposition state: \n",
411 | "$$\n",
412 | "|u\\rangle{} = \\frac{1}{\\sqrt{r}}\\sum_{k=0}^{r-1} e^{-\\frac{2\\pi ik}{r}}|a^k \\text{mod}N\\rangle{}\n",
413 | "$$\n",
414 | "\n",
415 | "Let's pick $a=3$ and $N=35$ as an example and investigate what the action of $U$ is on $|u\\rangle{}$\n",
416 | "\\begin{align}\n",
417 | " U|u\\rangle{} &= U\\frac{1}{\\sqrt{r}}\\left( |1\\rangle{} + e^{-\\frac{2\\pi i}{r}}|3\\rangle{} + e^{\\frac{-4\\pi i}{r}}|9\\rangle{} + ... + e^{\\frac{-20\\pi i}{r}}|4\\rangle{} + e^{\\frac{-22\\pi i}{r}}|12\\rangle{} \\right) \\\\\n",
418 | " & =\\frac{1}{\\sqrt{r}}\\left( U|1\\rangle{} + e^{-\\frac{2\\pi i}{r}}U|3\\rangle{} + e^{\\frac{-4\\pi i}{r}}U|9\\rangle{} + ... + e^{\\frac{-20\\pi i}{r}}U|4\\rangle{} + e^{\\frac{-22\\pi i}{r}}U|12\\rangle{} \\right) \\\\\n",
419 | " &= \\frac{1}{\\sqrt{r}}\\left( |3\\rangle{} + e^{-\\frac{2\\pi i}{r}}|9\\rangle{} + e^{\\frac{-4\\pi i}{r}}|27\\rangle{} + ... + e^{\\frac{-20\\pi i}{r}}|12\\rangle{} + e^{\\frac{-22\\pi i}{r}}|1\\rangle{} \\right) \\\\\n",
420 | " &= \\frac{e^{\\frac{2\\pi i}{r}}}{\\sqrt{r}}\\left( e^{-\\frac{2\\pi i}{r}}|3\\rangle{} + e^{\\frac{-4\\pi i}{r}}|9\\rangle{} + ... + e^{\\frac{-20\\pi i}{r}}|4\\rangle{} + e^{\\frac{-22\\pi i}{r}}|12\\rangle{} + |1\\rangle{} \\right) \\\\\n",
421 | " &= \\frac{e^{\\frac{2\\pi i}{r}}}{\\sqrt{r}} |u\\rangle{}.\n",
422 | "\\end{align}\n",
423 | "\n",
424 | "\n",
425 | "This is a particularly helpful eigenvalue as it contains $r$. In fact, it needs to be included in order to ensure the phase differences between the basis states are equal. This is also not the only eigenstate of $U$. For us to generalize further, we can multiply an integer $s$ to each of these phases, which will then show up in our eigenvalue\n",
426 | "\n",
427 | "\\begin{align}\n",
428 | " |u_s\\rangle{} &= \\frac{1}{\\sqrt{r}}\\sum_{k=0}^{r-1} e^{\\frac{-2\\pi isk}{r}|a^k\\text{mod} N\\rangle{}} \\\\\n",
429 | " U|u_s\\rangle{} &= e^{\\frac{2\\pi is}{r}}|u_s\\rangle{}.\n",
430 | "\\end{align}\n",
431 | "\n",
432 | "\n",
433 | "Now we have an eigenstate for each integer $0 \\leq s \\leq r$. Notably, if we add up all of these eigenstates, the phases cancel all other basis states except $|1\\rangle{}$ $$ \\frac{1}{\\sqrt{r}} \\sum_{s=0}^{r-1}|u_s\\rangle{} = |1\\rangle{} $$.\n",
434 | "\n",
435 | "\n",
436 | "Since any state in the computational basis can be written as a linear combination of these eigenstates, if we do QPE on $U$ using the state $|1\\rangle{}$, we will measure a phase \n",
437 | "\n",
438 | "$$ \\phi = \\frac{s}{r} $$\n",
439 | "where $s$ is a random integer between $0$ and $r-1$. Finally, we can use a method called the continued fraction algorithm on $\\phi$ in order to find r. The final circuit will look something like this\n"
440 | ]
441 | },
442 | {
443 | "attachments": {},
444 | "cell_type": "markdown",
445 | "id": "03df2e95",
446 | "metadata": {},
447 | "source": [
448 | "\n",
449 | "
\n",
450 | "\n",
451 | "\n",
452 | "***\n",
453 | "\n",
454 | "Below we'll provide the unitary $U$ needed for solving this period finding problem with $a=7$ and $N=15$\n",
455 | "\n",
456 | "$$ \n",
457 | " U|y\\rangle{} = |7y\\text{mod}15\\rangle{}.\n",
458 | "$$\n",
459 | "\n",
460 | "To create $U^x$ we will simply repeat the circuit $x$ times. The cell below will construct this unitary"
461 | ]
462 | },
463 | {
464 | "cell_type": "code",
465 | "execution_count": null,
466 | "id": "f44d5c01",
467 | "metadata": {},
468 | "outputs": [],
469 | "source": [
470 | "## Create 7mod15 gate\n",
471 | "N = 15\n",
472 | "m = int(np.ceil(np.log2(N)))\n",
473 | "\n",
474 | "U_qc = QuantumCircuit(m)\n",
475 | "U_qc.x(range(m))\n",
476 | "U_qc.swap(1, 2)\n",
477 | "U_qc.swap(2, 3)\n",
478 | "U_qc.swap(0, 3)\n",
479 | "\n",
480 | "U = U_qc.to_gate()\n",
481 | "U.name ='{}Mod{}'.format(7, N)"
482 | ]
483 | },
484 | {
485 | "attachments": {},
486 | "cell_type": "markdown",
487 | "id": "755e83e3",
488 | "metadata": {},
489 | "source": [
490 | "Confirm if the operator $U$ works as intended by creating a quantum circuit with $m=4$ qubits. Prepare the inpute state $|y\\rangle{}$ representing any integer between $0$ and $15$ (remembering that Qiskit uses little endian notation) such as $|1\\rangle{} = |0001\\rangle{}$, $|5\\rangle{} = |0101\\rangle{}$, etc. and apply $U|y\\rangle{}$. Check if the circuit produces the expected outcomes for several inputs: $|1\\rangle{}$, $|2\\rangle{}$, and $|5\\rangle{}$. (For example, the outcome for input state $|2\\rangle{}$ should be $|14\\rangle{}=|1110\\rangle{}$) \n",
491 | "\n",
492 | "Run these circuits through the `aer_simulator` backend with $20000$ shots, save the count data as `input_1`, `input_2`, and `input_5`."
493 | ]
494 | },
495 | {
496 | "cell_type": "code",
497 | "execution_count": null,
498 | "id": "2413af6c",
499 | "metadata": {},
500 | "outputs": [],
501 | "source": [
502 | "### your code goes here\n",
503 | "\n",
504 | "qcirc = QuantumCircuit(m)\n",
505 | "\n",
506 | "\n"
507 | ]
508 | },
509 | {
510 | "cell_type": "code",
511 | "execution_count": null,
512 | "id": "64292ddb",
513 | "metadata": {},
514 | "outputs": [],
515 | "source": [
516 | "## Run this cell to simulate 'qpe4' and to plot the histogram of the result\n",
517 | "sim = Aer.get_backend('aer_simulator')\n",
518 | "shots = 20000\n",
519 | "\n",
520 | "input_1 = execute(qcirc, sim, shots=shots).result().get_counts() # save the count data for input 1\n",
521 | "\n",
522 | "input_2 = # save the count data for input 2\n",
523 | "input_5 = # save the count data for input 5\n"
524 | ]
525 | },
526 | {
527 | "cell_type": "code",
528 | "execution_count": null,
529 | "id": "fc5f5437",
530 | "metadata": {},
531 | "outputs": [],
532 | "source": [
533 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex5\n",
534 | "\n",
535 | "grade_lab3_ex5([input_1, input_2, input_5])"
536 | ]
537 | },
538 | {
539 | "attachments": {},
540 | "cell_type": "markdown",
541 | "id": "c0b545ec",
542 | "metadata": {},
543 | "source": [
544 | "## Step 2. Implementing $U^{2^{m-1}}$\n",
545 | "\n",
546 | "Now we'll use this controlled-$U$ to estimate the phase $\\phi=\\frac{s}{r}$. But first, a quick point to note here. It turns out a sequence of `7Mod15` gates produce the identity when executed by a multiple of 4 times. To test this, create a quantum circuit implementing the `7mod15` gate $2^2$ times and run it on the `unitary_simulator` backend to obtain the matrix represenation of the gates in the circuit. Verify $U^{2^2}=I$."
547 | ]
548 | },
549 | {
550 | "cell_type": "code",
551 | "execution_count": null,
552 | "id": "28ef3fd2",
553 | "metadata": {},
554 | "outputs": [],
555 | "source": [
556 | "\n",
557 | "unitary_circ = QuantumCircuit(m)\n",
558 | "\n",
559 | "# Your code goes here\n",
560 | "\n"
561 | ]
562 | },
563 | {
564 | "cell_type": "code",
565 | "execution_count": null,
566 | "id": "5ba2ce23",
567 | "metadata": {},
568 | "outputs": [],
569 | "source": [
570 | "sim = Aer.get_backend('unitary_simulator')\n",
571 | "unitary = execute(unitary_circ, sim).result().get_unitary()"
572 | ]
573 | },
574 | {
575 | "cell_type": "code",
576 | "execution_count": null,
577 | "id": "fef158df",
578 | "metadata": {},
579 | "outputs": [],
580 | "source": [
581 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex6\n",
582 | "\n",
583 | "grade_lab3_ex6(unitary, unitary_circ)"
584 | ]
585 | },
586 | {
587 | "attachments": {},
588 | "cell_type": "markdown",
589 | "id": "b80f494e",
590 | "metadata": {},
591 | "source": [
592 | "## Step 3. Finding $\\phi$ and Continued Fractions\n",
593 | "\n",
594 | "\n",
595 | "Now armed with a way to execute $U^{2^{m-1}}$, let's use it in the QPE circuit you created earlier. Below is a function to construct a controlled-$U$ gate. Use $8$ qubits for the phase register and 4 qubits for the register which $U$ will act on, using the `aer_simulator` again, estimate the phase $\\phi$ given an input state of $|1\\rangle{}$.\n",
596 | "\n",
597 | "*Hint: at each step in the QPE circuit, you'll need to construct a new `cU_multi` circuit and append it to the QPE circuit. There will be several estimates that have approximately equal probability.*"
598 | ]
599 | },
600 | {
601 | "cell_type": "code",
602 | "execution_count": null,
603 | "id": "71b9775b",
604 | "metadata": {},
605 | "outputs": [],
606 | "source": [
607 | "#This function will return a ControlledGate object which repeats the action\n",
608 | "# of U, 2^k times\n",
609 | "def cU_multi(k):\n",
610 | " sys_register_size = 4\n",
611 | " circ = QuantumCircuit(sys_register_size)\n",
612 | " for _ in range(2**k):\n",
613 | " circ.append(U, range(sys_register_size))\n",
614 | " \n",
615 | " U_multi = circ.to_gate()\n",
616 | " U_multi.name = '7Mod15_[2^{}]'.format(k)\n",
617 | " \n",
618 | " cU_multi = U_multi.control()\n",
619 | " return cU_multi"
620 | ]
621 | },
622 | {
623 | "cell_type": "code",
624 | "execution_count": null,
625 | "id": "8f0f482f",
626 | "metadata": {},
627 | "outputs": [],
628 | "source": [
629 | "# your code goes here\n",
630 | "\n",
631 | "shor_qpe = #Create the QuantumCircuit needed to run with 8 phase register qubits\n"
632 | ]
633 | },
634 | {
635 | "cell_type": "code",
636 | "execution_count": null,
637 | "id": "f25f9d0b",
638 | "metadata": {},
639 | "outputs": [],
640 | "source": [
641 | "## Run this cell to simulate 'shor_qpe' and to plot the histogram of the results\n",
642 | "sim = Aer.get_backend('aer_simulator')\n",
643 | "shots = 20000\n",
644 | "shor_qpe_counts = execute(shor_qpe, sim, shots=shots).result().get_counts()\n",
645 | "plot_histogram(shor_qpe_counts, figsize=(9,5))"
646 | ]
647 | },
648 | {
649 | "cell_type": "code",
650 | "execution_count": null,
651 | "id": "20888bbf",
652 | "metadata": {},
653 | "outputs": [],
654 | "source": [
655 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex7\n",
656 | "\n",
657 | "grade_lab3_ex7(shor_qpe_counts)"
658 | ]
659 | },
660 | {
661 | "attachments": {},
662 | "cell_type": "markdown",
663 | "id": "94c8b2c0",
664 | "metadata": {},
665 | "source": [
666 | "We can then find the integers $s$ and $r$ using the continued fractions algorithm. Luckily python has built-in functionality for this using the `Fraction` function, where we will limit the denominator to $r<15$. Use this to find the estimated $s$ and $r$ for each outcome you measured above."
667 | ]
668 | },
669 | {
670 | "cell_type": "code",
671 | "execution_count": null,
672 | "id": "fc619b09",
673 | "metadata": {},
674 | "outputs": [],
675 | "source": [
676 | "from fractions import Fraction\n",
677 | "print(Fraction(0.666), '\\n')\n",
678 | "print(Fraction(0.666).limit_denominator(15))"
679 | ]
680 | },
681 | {
682 | "cell_type": "code",
683 | "execution_count": null,
684 | "id": "0cd183a8",
685 | "metadata": {},
686 | "outputs": [],
687 | "source": [
688 | "\n",
689 | "shor_qpe_fractions = # create a list of Fraction objects for each measurement outcome\n"
690 | ]
691 | },
692 | {
693 | "cell_type": "code",
694 | "execution_count": null,
695 | "id": "64f3ab41",
696 | "metadata": {},
697 | "outputs": [],
698 | "source": [
699 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex8\n",
700 | "\n",
701 | "grade_lab3_ex8(shor_qpe_fractions)"
702 | ]
703 | },
704 | {
705 | "attachments": {},
706 | "cell_type": "markdown",
707 | "id": "7be59ad2",
708 | "metadata": {},
709 | "source": [
710 | "## Step 4. Putting it all together\n",
711 | "\n",
712 | "Now let's put all of these steps together in order to factor the (very simple) number, $N = 15$. We'll continue with our example of $a=7$, remember that the phase we measure $s/r$ where $s$ is a random integer between $0$ and $r-1$ and:\n",
713 | "\n",
714 | "$$\n",
715 | " a^r\\text{mod}N = 1\n",
716 | "$$\n",
717 | "\n",
718 | "Then, once we have $r$, we can find a factor of $N$ by:\n",
719 | "\n",
720 | "$$\n",
721 | " \\left(a^r-1\\right)\\text{mod} N = 0\n",
722 | "$$\n",
723 | "which requires that $N$ must divide by $a^r-1$. If $r$ is even, we can also write\n",
724 | "\n",
725 | "$$\n",
726 | " a^r-1 = \\left(a^{r/2}+1\\right)\\left(a^{r/2}-1\\right).\n",
727 | "$$\n",
728 | "\n",
729 | "Put together a function called `shor_qpe` which takes an argument for $k$ (the number of counting qubits) and composes, runs, and processes Shor's algorithm to guess the factors. Use an input state of $|y\\rangle{}=|1\\rangle{}$ for the phase estimation. (Note: The function `cU_multi()` only executes $|ay\\ \\text{mod}\\ 15\\rangle{}$ for $a=7$)"
730 | ]
731 | },
732 | {
733 | "cell_type": "code",
734 | "execution_count": null,
735 | "id": "5be23ac1",
736 | "metadata": {},
737 | "outputs": [],
738 | "source": [
739 | "def shor_qpe(k):\n",
740 | "\n",
741 | " a = 7\n",
742 | " #Step 1. Begin a while loop until a nontrivial guess is found\n",
743 | " ### Your code goes here ###\n",
744 | "\n",
745 | "\n",
746 | " #Step 2a. Construct a QPE circuit with m phase count qubits\n",
747 | " # to guess the phase phi = s/r using the function cU_multi()\n",
748 | " ### Your code goes here ###\n",
749 | "\n",
750 | "\n",
751 | " #Step 2b. Run the QPE circuit with a single shot, record the results\n",
752 | " # and convert the estimated phase bitstring to decimal\n",
753 | " ### Your code goes here ###\n",
754 | "\n",
755 | "\n",
756 | " #Step 3. Use the Fraction object to find the guess for r\n",
757 | " ### Your code goes here ###\n",
758 | "\n",
759 | "\n",
760 | " #Step 4. Now that r has been found, use the builtin greatest common deonominator\n",
761 | " # function to determine the guesses for a factor of N\n",
762 | " guesses = [gcd(a**(r//2)-1, N), gcd(a**(r//2)+1, N)]\n",
763 | "\n",
764 | " #Step 5. For each guess in guesses, check if at least one is a non-trivial factor\n",
765 | " # i.e. (guess != 1 or N) and (N % guess == 0)\n",
766 | " ### Your code goes here ###\n",
767 | " \n",
768 | " #Step 6. If a nontrivial factor is found return the list 'guesses', otherwise\n",
769 | " # continue the while loop\n",
770 | " ### Your code goes here ###\n",
771 | " \n",
772 | " return guesses"
773 | ]
774 | },
775 | {
776 | "cell_type": "code",
777 | "execution_count": null,
778 | "id": "03a369ba",
779 | "metadata": {},
780 | "outputs": [],
781 | "source": [
782 | "from qc_grader.challenges.qgss_2023 import grade_lab3_ex9\n",
783 | "\n",
784 | "grade_lab3_ex9(shor_qpe)"
785 | ]
786 | },
787 | {
788 | "attachments": {},
789 | "cell_type": "markdown",
790 | "id": "6a1e61fd",
791 | "metadata": {},
792 | "source": [
793 | "Congratulations! You've completed Lab 3 of the Global Summer School!! 🎉\n",
794 | "\n",
795 | "This lab was adapted from both the [Qiskit QPE Lab](https://learn.qiskit.org/course/ch-labs/lab-5-accuracy-of-quantum-phase-estimation#lab-3-0) as well as the [Qiskit Shor's Algorithm](https://learn.qiskit.org/course/ch-labs/lab-7-scalable-shors-algorithm) lab"
796 | ]
797 | }
798 | ],
799 | "metadata": {
800 | "kernelspec": {
801 | "display_name": "Python 3 (ipykernel)",
802 | "language": "python",
803 | "name": "python3"
804 | },
805 | "language_info": {
806 | "codemirror_mode": {
807 | "name": "ipython",
808 | "version": 3
809 | },
810 | "file_extension": ".py",
811 | "mimetype": "text/x-python",
812 | "name": "python",
813 | "nbconvert_exporter": "python",
814 | "pygments_lexer": "ipython3",
815 | "version": "3.11.3"
816 | },
817 | "toc": {
818 | "base_numbering": 1,
819 | "nav_menu": {},
820 | "number_sections": true,
821 | "sideBar": true,
822 | "skip_h1_title": false,
823 | "title_cell": "Table of Contents",
824 | "title_sidebar": "Contents",
825 | "toc_cell": false,
826 | "toc_position": {},
827 | "toc_section_display": true,
828 | "toc_window_display": false
829 | }
830 | },
831 | "nbformat": 4,
832 | "nbformat_minor": 5
833 | }
834 |
--------------------------------------------------------------------------------
/content/lab3/resources/Shor_circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab3/resources/Shor_circuit.png
--------------------------------------------------------------------------------
/content/lab3/resources/qpe4_circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab3/resources/qpe4_circuit.png
--------------------------------------------------------------------------------
/content/lab3/resources/qpe_tex_qz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab3/resources/qpe_tex_qz.png
--------------------------------------------------------------------------------
/content/lab4/lab4.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Lab 4 : Iterative phase estimation\n",
9 | "\n",
10 | "In this lab, you'll implement a simple version of the iterative phase estimation algorithm. Using the recently introduced dynamic circuits capabilities, you'll be able to run the algorithm on an IBM quantum processor! This lab was adapted from the IBM Quantum Spring Challenge 2023, so if you participated in that challenge, it may look familiar. To encourage you to review the lab nevertheless, we added an extra exercise at the end."
11 | ]
12 | },
13 | {
14 | "attachments": {},
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "## Background\n",
19 | "\n",
20 | "The quantum phase estimation (QPE) algorithm is one of the most important and famous quantum algorithms. It is a key subroutine of Shor's factoring algorithm, as well as algorithms for quantum simulation. The textbook version of the algorithm uses a number of auxiliary qubits which scales with the desired precision, leading to circuits that are challenging to execute on today's noisy devices with limited qubit number and connectivity.\n",
21 | "\n",
22 | "Iterative phase estimation (IPE) is a variant of QPE which requires only one auxiliary qubit. In IPE, the auxiliary qubit is repeatedly measured, with the measurement results used to guide future quantum operations. Such classical feed-forward was previously impossible to execute on IBM's quantum processors, but with the recently introduced dynamic circuits capabilities, it is now possible.\n",
23 | "\n",
24 | "Like any phase estimation algorithm, IPE is designed to solve the following problem:\n",
25 | "\n",
26 | "**Problem statement:** Given a unitary matrix $U$ and an eigenstate $|\\Psi\\rangle$ of $U$ with an unknown eigenvalue $e^{i 2\\pi \\varphi}$, estimate the value of $\\varphi$.\n",
27 | "\n",
28 | "A few important details need to be clarified in this problem statement, namely, how $U$ and $|\\Psi\\rangle$ are specified. We assume that $U$ is given as a quantum circuit implementing $U$, and in fact, we assume we have the ability to efficiently implement the operations *controlled*-$U^{2^t}$ for positive integers $t$.\n",
29 | "This is the same assumption used in the original QPE algorithm.\n",
30 | "The eigenstate is also given as a quantum circuit: we assume we have the ability to efficiently prepare $|\\Psi\\rangle$.\n",
31 | "\n",
32 | "Let's first assume for simplicity that $\\varphi$ can have an exact binary expansion, that is, it can be written as\n",
33 | "$$\n",
34 | "\\varphi = \\varphi_1/2 + \\varphi_2/4 + \\cdots + \\varphi_m/2^m = 0.\\varphi_1 \\varphi_2 \\cdots \\varphi_m\n",
35 | "$$\n",
36 | "where in the final equality we are using \"decimal\" point notation in base 2.\n",
37 | "For simplicity, suppose $U$ is a unitary operator acting on one qubit (everything we say here also applies to the case where $U$ acts on multiple qubits). Since IPE requires an auxiliary qubit, we need a system of two qubits, $q_0$ and $q_1$, where $q_0$ is the auxiliary qubit and $q_1$ represents the physical system on which $U$ operates.\n",
38 | "\n",
39 | "Now, suppose that we initialize $q_0$ in the state $|+\\rangle = \\frac{|0\\rangle + |1\\rangle}{\\sqrt{2}}$ and $q_1$ in the state $|\\Psi \\rangle$.\n",
40 | "What happens if we apply the *controlled*-$U^{2^t}$ gate, with $q_0$ being the control and $q_1$ being the target?\n",
41 | "Since $|\\Psi \\rangle$ is an eigenstate of $U$ with eigenvalue $e^{i 2\\pi \\varphi}$, we have\n",
42 | "$$\n",
43 | "\\begin{align}\n",
44 | "|+\\rangle |\\Psi \\rangle &= \\left(\\frac{|0\\rangle + |1\\rangle}{\\sqrt{2}}\\right) |\\Psi \\rangle \\\\\n",
45 | "&= \\frac{|0\\rangle |\\Psi \\rangle + |1\\rangle |\\Psi \\rangle}{\\sqrt{2}} \\\\\n",
46 | "&\\xrightarrow{\\text{controlled-}U^{2^t}} \\frac{|0\\rangle |\\Psi \\rangle + e^{i 2 \\pi 2^{t} \\varphi} |1\\rangle |\\Psi \\rangle}{\\sqrt{2}} \\\\\n",
47 | "&= \\left(\\frac{|0\\rangle + e^{i 2 \\pi 2^{t} \\varphi} |1\\rangle}{\\sqrt{2}}\\right) |\\Psi \\rangle.\n",
48 | "\\end{align}\n",
49 | "$$\n",
50 | "That is, the state of the system qubit remains unchanged, while a phase of $e^{i 2 \\pi 2^{t} \\varphi}$ has been \"kicked back\" into the state of the auxiliary qubit.\n",
51 | "\n",
52 | "Now, note that\n",
53 | "$$\n",
54 | "e^{i 2 \\pi 2^{t} \\varphi} = e^{i 2 \\pi 2^{t} (0.\\varphi_1 \\varphi_2 \\cdots \\varphi_m)}\n",
55 | "= e^{i 2 \\pi (\\varphi_1 \\cdots \\varphi_t . \\varphi_{t + 1} \\cdots \\varphi_m)}\n",
56 | "= e^{i 2 \\pi (0. \\varphi_{t + 1} \\cdots \\varphi_m)},\n",
57 | "$$\n",
58 | "where in the last equality, the whole number part of the \"decimal\" representation of the phase has disappeared because $e^{i 2\\pi n} = 1$ for any integer $n$.\n",
59 | "For example:\n",
60 | "- for $t=0$, the phase would be $e^{i 2 \\pi 2^{0} \\varphi} = e^{i 2 \\pi \\varphi} = e^{i 2 \\pi 0.\\varphi_1 \\varphi_2 ... \\varphi_m}$\n",
61 | "- for $t=1$, the phase would be $e^{i 2 \\pi 2^{1} \\varphi}= e^{i 2 \\pi \\varphi_1} e^{i 2 \\pi 0.\\varphi_2 \\varphi_3 ... \\varphi_m} = e^{i 2 \\pi 0.\\varphi_2 \\varphi_3 ... \\varphi_m}$\n",
62 | "- for $t=2$, the phase would be $e^{i 2 \\pi 2^{2} \\varphi} = e^{i 2 \\pi 0.\\varphi_3 \\varphi_4 ... \\varphi_m}$\n",
63 | "- for $t=m-1$, the phase would be $e^{i 2 \\pi 2^{m-1} \\varphi} = e^{i 2 \\pi 0.\\varphi_m}$.\n",
64 | "\n",
65 | "In the last case where $t = m - 1$, the phase is $e^{i 2 \\pi 0.\\varphi_m}$, which is equal to $1$ if $\\varphi_m = 0$ and $-1$ if $\\varphi_m = 1$.\n",
66 | "In the first case, the auxiliary qubit $q_0$ would be in the state $|+\\rangle = \\frac{|0\\rangle + |1\\rangle}{\\sqrt{2}}$, and in the second case it would be\n",
67 | "in the state $|-\\rangle = \\frac{|0\\rangle - |1\\rangle}{\\sqrt{2}}$. Therefore, measuring the qubit in the Pauli $X$ basis would distinguish these cases with a 100\\% success rate.\n",
68 | "This is done by performing a Hadamard gate on the qubit before measuring it. In the first case we would measure 0 and in the second case we would measure 1;\n",
69 | "in other words, the measured bit would be equal to $\\varphi_m$.\n",
70 | "\n",
71 | "### The algorithm\n",
72 | "\n",
73 | "In the first step of the IPE algorithm, we directly measure the least significant bit of the phase $\\varphi$, $\\varphi_m$, by initializing the 2-qubit registers as described above ( $q_0 \\rightarrow |+\\rangle$ and $q_1 \\rightarrow |\\Psi \\rangle$ ), performing a *controlled*-$U^{2^{m-1}}$ operation, and measuring $q_0$ in the Pauli $X$ basis.\n",
74 | "\n",
75 | "in the second step, we initialize the systems in the same way and apply a *controlled*-$U^{2^{m-2}}$ operation. The relative phase in $q_0$ after these operations is now $e^{i 2 \\pi 0.\\varphi_{m-1}\\varphi_{m}}= e^{i 2 \\pi 0.\\varphi_{m-1}} e^{i 2 \\pi \\varphi_m/4}$. \n",
76 | "To extract the phase bit $\\varphi_{m-1}$, first perform a phase correction by rotating around the $Z$-axis by an angle $-2 \\pi \\varphi_m/4=-\\pi \\varphi_m/2$, which results in the state of $q_0$ to be $|0\\rangle + e^{i 2 \\pi 0.\\varphi_{m-1}} | 1 \\rangle$. Perform a measurement on $q_0$ in the Pauli $X$ basis to obtain the phase bit $\\varphi_{m-1}$. \n",
77 | "\n",
78 | "Therefore, the $k$-th step of the IPE, getting $\\varphi_{m-k+1}$, consists of the register initialization ($q_0$ in $|+\\rangle$, $q_1$ in $|\\Psi\\rangle$), the application of a *controlled*-$U^{2^{m-k}}$, a rotation around $Z$ of angle $\\omega_k = -2 \\pi 0.0\\varphi_{m-k+2} ... \\varphi_m$, and a measurement of $q_0$ in the Pauli $X$ basis: a Hadamard transform to $q_0$, and a measurement of $q_0$ in the computational basis. Note that $q_1$ remains in the state $|\\Psi\\rangle$ throughout the algorithm."
79 | ]
80 | },
81 | {
82 | "attachments": {},
83 | "cell_type": "markdown",
84 | "metadata": {},
85 | "source": [
86 | "## Implementation\n",
87 | "\n",
88 | "In this lab, we will perform IPE on the single-qubit $S$-gate. The $S$ gate is given by the matrix\n",
89 | "\n",
90 | "$$ S =\n",
91 | "\\begin{pmatrix}\n",
92 | "1 & 0\\\\\n",
93 | "0 & e^{i\\pi / 2}\n",
94 | "\\end{pmatrix}$$\n",
95 | "\n",
96 | "We will use the eigenstate $|\\Psi\\rangle = |1\\rangle$, which has eigenvalue $e^{i\\pi / 2}= e^{i2\\pi \\cdot 1/4}$. So we have $\\varphi = 1/4 = 0.01 = 0.\\varphi_1 \\varphi_2$. Since $\\varphi$ can be represented exactly with 2 bits, our quantum circuit implementation will use a classical register with two bits to store the result.\n",
97 | "\n",
98 | "The controlled-$S$ gate can be implemented using the controlled phase gate, available in Qiskit as `CPhaseGate`, which can also be applied by calling the `cp` method of a `QuantumCircuit`. The controlled phase gate is parameterized by an angle $\\theta$ and has the matrix\n",
99 | "$$\n",
100 | " \\text{CPhase}(\\theta) =\n",
101 | " \\begin{pmatrix}\n",
102 | " 1 & 0 & 0 & 0 \\\\\n",
103 | " 0 & 1 & 0 & 0 \\\\\n",
104 | " 0 & 0 & 1 & 0 \\\\\n",
105 | " 0 & 0 & 0 & e^{i\\theta}\n",
106 | " \\end{pmatrix}\n",
107 | "$$"
108 | ]
109 | },
110 | {
111 | "attachments": {},
112 | "cell_type": "markdown",
113 | "metadata": {},
114 | "source": [
115 | "\n",
116 | "### Step 1\n",
117 | "\n",
118 | "In the first step of the algorithm, we measure the least significant bit of $\\varphi$.\n",
119 | "\n",
120 | "#### Exercise 1\n",
121 | "\n",
122 | "Obtain the least significant bit of $\\varphi$ by performing the following steps:\n",
123 | "1. Initialize the qubits:\n",
124 | " - Apply a Hadamard on the auxiliary qubit.\n",
125 | " - Apply an X gate on the system qubit to put it in the $|1\\rangle$ state.\n",
126 | "2. Apply a *controlled*-$S^{2}$ gate by applying a `CPhaseGate` with the appropriate angle.\n",
127 | "3. Measure the auxiliary qubit in the $X$ basis:\n",
128 | " - Apply a Hadamard gate on the auxiliary qubit.\n",
129 | " - Measure it in the computational basis.\n",
130 | "\n",
131 | "The resulting circuit should look something like this:\n",
132 | "\n",
133 | ""
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": null,
139 | "metadata": {},
140 | "outputs": [],
141 | "source": [
142 | "from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister\n",
143 | "import numpy as np\n",
144 | "\n",
145 | "\n",
146 | "def step_1_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:\n",
147 | " # qr is a quantum register with 2 qubits\n",
148 | " # cr is a classical register with 2 bits\n",
149 | "\n",
150 | " qc = QuantumCircuit(qr, cr)\n",
151 | "\n",
152 | " ####### your code goes here #######\n",
153 | "\n",
154 | " return qc\n",
155 | "\n",
156 | "\n",
157 | "qr = QuantumRegister(2, \"q\")\n",
158 | "cr = ClassicalRegister(2, \"c\")\n",
159 | "qc = QuantumCircuit(qr, cr)\n",
160 | "qc = step_1_circuit(qr, cr)\n",
161 | "qc.draw(\"mpl\")"
162 | ]
163 | },
164 | {
165 | "cell_type": "code",
166 | "execution_count": null,
167 | "metadata": {},
168 | "outputs": [],
169 | "source": [
170 | "# Submit your circuit\n",
171 | "\n",
172 | "from qc_grader.challenges.qgss_2023 import grade_lab4_ex1\n",
173 | "\n",
174 | "grade_lab4_ex1(qc)"
175 | ]
176 | },
177 | {
178 | "attachments": {},
179 | "cell_type": "markdown",
180 | "metadata": {},
181 | "source": [
182 | "### Step 2\n",
183 | "\n",
184 | "In the first step, we measured the least significant bit $\\varphi_2$. In the second (and final) step, we extract the next bit $\\varphi_1$, which will involve applying a phase correction to cancel out the phase contribution from $\\varphi_2$. The phase correction depends on the value of the classical register holding $\\varphi_2$. We need dynamic circuits to perform this classical feedback! The phase correction can be applied using `PhaseGate` or by directly calling the `p` method of a QuantumCircuit.\n",
185 | "\n",
186 | "#### Exercise 2\n",
187 | "\n",
188 | "In this exercise, we begin with the circuit from Step 1, which you should have constructed in Exercise 1.\n",
189 | "\n",
190 | "Obtain the next bit of $\\varphi$ by performing the following steps:\n",
191 | "1. Reset and re-initialize the auxiliary qubit.\n",
192 | "2. Apply the controlled unitary gate.\n",
193 | "3. Measure the auxiliary qubit in the $X$ basis.\n",
194 | "\n",
195 | "Here and in the rest of the lab, please use the [`QuantumCircuit.if_test`](https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.if_test.html) method to apply operations controlled on classical measurement results.\n",
196 | "\n",
197 | "The resulting circuit should look something like the one in the drawing below. Note that in the drawing, the instructions inside the `if_else` instruction are not shown. Therefore, even if your circuit looks the same as the drawing, it may still be different.\n",
198 | "\n",
199 | ""
200 | ]
201 | },
202 | {
203 | "cell_type": "code",
204 | "execution_count": null,
205 | "metadata": {},
206 | "outputs": [],
207 | "source": [
208 | "def step_2_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:\n",
209 | " # qr is a quantum register with 2 qubits\n",
210 | " # cr is a classical register with 2 bits\n",
211 | "\n",
212 | " # begin with the circuit from Step 1\n",
213 | " qc = step_1_circuit(qr, cr)\n",
214 | "\n",
215 | " ####### your code goes here #######\n",
216 | "\n",
217 | " return qc\n",
218 | "\n",
219 | "\n",
220 | "qr = QuantumRegister(2, \"q\")\n",
221 | "cr = ClassicalRegister(2, \"c\")\n",
222 | "qc = QuantumCircuit(qr, cr)\n",
223 | "qc = step_2_circuit(qr, cr)\n",
224 | "qc.draw(\"mpl\")"
225 | ]
226 | },
227 | {
228 | "cell_type": "code",
229 | "execution_count": null,
230 | "metadata": {},
231 | "outputs": [],
232 | "source": [
233 | "# Submit your circuit\n",
234 | "\n",
235 | "from qc_grader.challenges.qgss_2023 import grade_lab4_ex2\n",
236 | "\n",
237 | "grade_lab4_ex2(qc)"
238 | ]
239 | },
240 | {
241 | "attachments": {},
242 | "cell_type": "markdown",
243 | "metadata": {},
244 | "source": [
245 | "## Run on simulator\n",
246 | "\n",
247 | "Now that we have the complete circuit, let's first run it on a local simulator."
248 | ]
249 | },
250 | {
251 | "cell_type": "code",
252 | "execution_count": null,
253 | "metadata": {},
254 | "outputs": [],
255 | "source": [
256 | "from qiskit_aer import AerSimulator\n",
257 | "\n",
258 | "sim = AerSimulator()\n",
259 | "job = sim.run(qc, shots=1000)\n",
260 | "result = job.result()\n",
261 | "counts = result.get_counts()\n",
262 | "counts"
263 | ]
264 | },
265 | {
266 | "attachments": {},
267 | "cell_type": "markdown",
268 | "metadata": {},
269 | "source": [
270 | "If your circuit is correct, you should have gotten the bitstring `01` with 100% probability. This value corresponds to the phase written in binary as $\\varphi = 0.01 = 1/4$. Indeed, this is the correct phase!"
271 | ]
272 | },
273 | {
274 | "attachments": {},
275 | "cell_type": "markdown",
276 | "metadata": {},
277 | "source": [
278 | "### Exercise 3\n",
279 | "\n",
280 | "Construct an IPE circuit to estimate the phase of the T gate, whose matrix is given by\n",
281 | "\n",
282 | "$$ T =\n",
283 | "\\begin{pmatrix}\n",
284 | "1 & 0\\\\\n",
285 | "0 & e^{i\\pi / 4}\n",
286 | "\\end{pmatrix}$$\n",
287 | "\n",
288 | "How many bits are needed to represent the phase in this case?"
289 | ]
290 | },
291 | {
292 | "cell_type": "code",
293 | "execution_count": null,
294 | "metadata": {},
295 | "outputs": [],
296 | "source": [
297 | "from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister\n",
298 | "import numpy as np\n",
299 | "\n",
300 | "\n",
301 | "def t_gate_ipe_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:\n",
302 | " # qr is a quantum register with 2 qubits\n",
303 | " # cr is a classical register with 3 bits\n",
304 | "\n",
305 | " qc = QuantumCircuit(qr, cr)\n",
306 | "\n",
307 | " ####### your code goes here #######\n",
308 | "\n",
309 | " return qc\n",
310 | "\n",
311 | "\n",
312 | "qr = QuantumRegister(2, \"q\")\n",
313 | "cr = ClassicalRegister(3, \"c\")\n",
314 | "qc = QuantumCircuit(qr, cr)\n",
315 | "qc = t_gate_ipe_circuit(qr, cr)\n",
316 | "qc.draw(\"mpl\")"
317 | ]
318 | },
319 | {
320 | "cell_type": "code",
321 | "execution_count": null,
322 | "metadata": {},
323 | "outputs": [],
324 | "source": [
325 | "from qiskit_aer import AerSimulator\n",
326 | "\n",
327 | "sim = AerSimulator()\n",
328 | "job = sim.run(qc, shots=1000)\n",
329 | "result = job.result()\n",
330 | "counts = result.get_counts()\n",
331 | "counts"
332 | ]
333 | },
334 | {
335 | "cell_type": "code",
336 | "execution_count": null,
337 | "metadata": {},
338 | "outputs": [],
339 | "source": [
340 | "# Submit your circuit\n",
341 | "\n",
342 | "from qc_grader.challenges.qgss_2023 import grade_lab4_ex3\n",
343 | "\n",
344 | "grade_lab4_ex3(qc)"
345 | ]
346 | },
347 | {
348 | "attachments": {},
349 | "cell_type": "markdown",
350 | "metadata": {},
351 | "source": [
352 | "### When the phase does not have an exact binary expansion\n",
353 | "\n",
354 | "Let's consider the case when the phase does not have an exact binary expansion, for example, $\\varphi = 1/3$.\n",
355 | "In this case, the single-qubit gate has the unitary\n",
356 | "\n",
357 | "$$ U =\n",
358 | "\\begin{pmatrix}\n",
359 | "1 & 0\\\\\n",
360 | "0 & e^{i2\\pi / 3}\n",
361 | "\\end{pmatrix}\n",
362 | "$$\n",
363 | "\n",
364 | "The angle $\\varphi = 1/3$ does not have an exact finite binary expansion. In contrast, it has the infinite binary expansion\n",
365 | "\n",
366 | "$$\n",
367 | "1/3 = 0.010101\\ldots\n",
368 | "$$\n",
369 | "\n",
370 | "In practice we work with a fixed number of bits of precision, so our goal is to obtain the closest value that can be represented with those bits. In the following example, we will use two bits of precision. In this case, the closest value is $0.01 = 1/4$. Because this value does not represent the exact phase, there is some probability that we will obtain a different, less precise result.\n",
371 | "\n",
372 | "In the following code cells, we construct and simulate an IPE circuit to measure the phase of this gate."
373 | ]
374 | },
375 | {
376 | "cell_type": "code",
377 | "execution_count": null,
378 | "metadata": {},
379 | "outputs": [],
380 | "source": [
381 | "from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister\n",
382 | "import numpy as np\n",
383 | "\n",
384 | "\n",
385 | "def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:\n",
386 | " # qr is a quantum register with 2 qubits\n",
387 | " # cr is a classical register with 2 bits\n",
388 | "\n",
389 | " qc = QuantumCircuit(qr, cr)\n",
390 | "\n",
391 | " # Initialization\n",
392 | " q0, q1 = qr\n",
393 | " qc.h(q0)\n",
394 | " qc.x(q1)\n",
395 | "\n",
396 | " # Apply control-U operator as many times as needed to get the least significant phase bit\n",
397 | " u_angle = 2 * np.pi / 3\n",
398 | " k = 1\n",
399 | " cphase_angle = u_angle * 2**k\n",
400 | " qc.cp(cphase_angle, q0, q1)\n",
401 | "\n",
402 | " # Measure the auxiliary qubit in x-basis into the first classical bit\n",
403 | " qc.h(q0)\n",
404 | " c0, c1 = cr\n",
405 | " qc.measure(q0, c0)\n",
406 | "\n",
407 | " # Reset and re-initialize the auxiliary qubit\n",
408 | " qc.reset(q0)\n",
409 | " qc.h(q0)\n",
410 | "\n",
411 | " # Apply phase correction conditioned on the first classical bit\n",
412 | " with qc.if_test((c0, 1)):\n",
413 | " qc.p(-np.pi / 2, q0)\n",
414 | "\n",
415 | " # Apply control-U operator as many times as needed to get the next phase bit\n",
416 | " k = 0\n",
417 | " cphase_angle = u_angle * 2**k\n",
418 | " qc.cp(cphase_angle, q0, q1)\n",
419 | "\n",
420 | " # Measure the auxiliary qubit in x-basis into the second classical bit\n",
421 | " qc.h(q0)\n",
422 | " qc.measure(q0, c1)\n",
423 | "\n",
424 | " return qc\n",
425 | "\n",
426 | "\n",
427 | "qr = QuantumRegister(2, \"q\")\n",
428 | "cr = ClassicalRegister(2, \"c\")\n",
429 | "qc = QuantumCircuit(qr, cr)\n",
430 | "qc = u_circuit(qr, cr)\n",
431 | "qc.draw(\"mpl\")"
432 | ]
433 | },
434 | {
435 | "cell_type": "code",
436 | "execution_count": null,
437 | "metadata": {},
438 | "outputs": [],
439 | "source": [
440 | "from qiskit_aer import AerSimulator\n",
441 | "\n",
442 | "sim = AerSimulator()\n",
443 | "job = sim.run(qc, shots=1000)\n",
444 | "result = job.result()\n",
445 | "counts = result.get_counts()\n",
446 | "print(counts)\n",
447 | "success_probability = counts[\"01\"] / counts.shots()\n",
448 | "print(f\"Success probability: {success_probability}\")"
449 | ]
450 | },
451 | {
452 | "attachments": {},
453 | "cell_type": "markdown",
454 | "metadata": {},
455 | "source": [
456 | "As you can see, this time, we are not guaranteed to obtain the desired result. A natural question to ask is: How can we boost the success probability?\n",
457 | "\n",
458 | "One way that the algorithm fails is that the first measured bit is incorrect. In this case, the phase correction applied before measuring the second bit is also incorrect, causing the rest of the bits to be likely incorrect as well. A simple way to mitigate this problem is to repeat the measurement of the first few bits several times and take a majority vote to increase the likelihood that we measure the bit correctly. Implementing this procedure within a single circuit requires performing arithmetic on the measured outcomes. Due to a temporary limitation in Qiskit, it is currently not possible to perform arithmetic on measured bits and condition future circuit operations on the results. So, here we will measure each bit using separate circuits.\n",
459 | "\n",
460 | "The following code cells construct and simulate an IPE circuit for measuring just the first bit of the phase."
461 | ]
462 | },
463 | {
464 | "cell_type": "code",
465 | "execution_count": null,
466 | "metadata": {},
467 | "outputs": [],
468 | "source": [
469 | "from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister\n",
470 | "import numpy as np\n",
471 | "\n",
472 | "\n",
473 | "def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:\n",
474 | " # qr is a quantum register with 2 qubits\n",
475 | " # cr is a classical register with 1 bits\n",
476 | "\n",
477 | " qc = QuantumCircuit(qr, cr)\n",
478 | "\n",
479 | " # Initialization\n",
480 | " q0, q1 = qr\n",
481 | " qc.h(q0)\n",
482 | " qc.x(q1)\n",
483 | "\n",
484 | " # Apply control-U operator as many times as needed to get the least significant phase bit\n",
485 | " u_angle = 2 * np.pi / 3\n",
486 | " k = 1\n",
487 | " cphase_angle = u_angle * 2**k\n",
488 | " qc.cp(cphase_angle, q0, q1)\n",
489 | "\n",
490 | " # Measure the auxiliary qubit in x-basis\n",
491 | " qc.h(q0)\n",
492 | " (c0,) = cr\n",
493 | " qc.measure(q0, c0)\n",
494 | "\n",
495 | " return qc\n",
496 | "\n",
497 | "\n",
498 | "qr = QuantumRegister(2, \"q\")\n",
499 | "cr = ClassicalRegister(1, \"c\")\n",
500 | "qc = QuantumCircuit(qr, cr)\n",
501 | "qc = u_circuit(qr, cr)\n",
502 | "qc.draw(\"mpl\")"
503 | ]
504 | },
505 | {
506 | "cell_type": "code",
507 | "execution_count": null,
508 | "metadata": {},
509 | "outputs": [],
510 | "source": [
511 | "job = sim.run(qc, shots=15)\n",
512 | "result = job.result()\n",
513 | "counts = result.get_counts()\n",
514 | "print(counts)"
515 | ]
516 | },
517 | {
518 | "attachments": {},
519 | "cell_type": "markdown",
520 | "metadata": {},
521 | "source": [
522 | "Hopefully, the correct bit was measured more often than not.\n",
523 | "\n",
524 | "### Exercise 4\n",
525 | "\n",
526 | "Examine the counts dictionary from the output of the last code cell. What is the correct value for the first bit? Was it measured more often than not? If not, rerun the last code cell until it is. Then, write some code in the code cell below that sets the variable `step1_bit` equal to the value of the bit that was measured the majority of the time."
527 | ]
528 | },
529 | {
530 | "cell_type": "code",
531 | "execution_count": null,
532 | "metadata": {},
533 | "outputs": [],
534 | "source": [
535 | "step1_bit: int\n",
536 | "\n",
537 | "####### your code goes here #######\n"
538 | ]
539 | },
540 | {
541 | "cell_type": "code",
542 | "execution_count": null,
543 | "metadata": {},
544 | "outputs": [],
545 | "source": [
546 | "# Submit your result\n",
547 | "\n",
548 | "from qc_grader.challenges.qgss_2023 import grade_lab4_ex4\n",
549 | "\n",
550 | "grade_lab4_ex4(step1_bit)"
551 | ]
552 | },
553 | {
554 | "attachments": {},
555 | "cell_type": "markdown",
556 | "metadata": {},
557 | "source": [
558 | "### Exercise 5\n",
559 | "\n",
560 | "Now construct the circuit to measure the second bit of the phase. Replace the first stage of the circuit with one which simply sets the auxiliary bit to the value we measured above, so that we always measure the correct value for the first bit of the phase."
561 | ]
562 | },
563 | {
564 | "cell_type": "code",
565 | "execution_count": null,
566 | "metadata": {},
567 | "outputs": [],
568 | "source": [
569 | "from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister\n",
570 | "import numpy as np\n",
571 | "\n",
572 | "\n",
573 | "def u_circuit(qr: QuantumRegister, cr: ClassicalRegister) -> QuantumCircuit:\n",
574 | " # qr is a quantum register with 2 qubits\n",
575 | " # cr is a classical register with 2 bits\n",
576 | "\n",
577 | " qc = QuantumCircuit(qr, cr)\n",
578 | "\n",
579 | " ####### your code goes here #######\n",
580 | "\n",
581 | " return qc\n",
582 | "\n",
583 | "\n",
584 | "qr = QuantumRegister(2, \"q\")\n",
585 | "cr = ClassicalRegister(2, \"c\")\n",
586 | "qc = QuantumCircuit(qr, cr)\n",
587 | "qc = u_circuit(qr, cr)\n",
588 | "qc.draw(\"mpl\")"
589 | ]
590 | },
591 | {
592 | "cell_type": "code",
593 | "execution_count": null,
594 | "metadata": {},
595 | "outputs": [],
596 | "source": [
597 | "# Submit your result\n",
598 | "\n",
599 | "from qc_grader.challenges.qgss_2023 import grade_lab4_ex5\n",
600 | "\n",
601 | "grade_lab4_ex5(qc)"
602 | ]
603 | },
604 | {
605 | "cell_type": "code",
606 | "execution_count": null,
607 | "metadata": {},
608 | "outputs": [],
609 | "source": [
610 | "from qiskit_aer import AerSimulator\n",
611 | "\n",
612 | "sim = AerSimulator()\n",
613 | "job = sim.run(qc, shots=1000)\n",
614 | "result = job.result()\n",
615 | "counts = result.get_counts()\n",
616 | "print(counts)\n",
617 | "success_probability = counts[\"01\"] / counts.shots()\n",
618 | "print(f\"Success probability: {success_probability}\")"
619 | ]
620 | },
621 | {
622 | "attachments": {},
623 | "cell_type": "markdown",
624 | "metadata": {},
625 | "source": [
626 | "Now, the success probability is much higher than before!"
627 | ]
628 | },
629 | {
630 | "attachments": {},
631 | "cell_type": "markdown",
632 | "metadata": {},
633 | "source": [
634 | "### Exercise 6 (ungraded)\n",
635 | "\n",
636 | "So far, the IPE circuits we constructed were designed for a specific gate and a specific number of bits of precision. Let's now generalize your code to implement a general IPE routine that can handle different gates and levels of precision.\n",
637 | "\n",
638 | "Complete the following function to implement a generalized IPE routine. It takes the following inputs:\n",
639 | "- `qr`: The quantum register. The first qubit is assumed to be the auxiliary qubit, and the rest of them the system qubits.\n",
640 | "- `cr`: The classical register. Its length indicates the desired number of bits of precision.\n",
641 | "- `controlled_unitaries`: A list of gates implementing *controlled*-$U^{2^t}$ for $t = 0, \\ldots, m-1$, where $m$ is the number of bits of precision.\n",
642 | "- `state_prep`: A gate used to initialize the state of the system qubits."
643 | ]
644 | },
645 | {
646 | "cell_type": "code",
647 | "execution_count": null,
648 | "metadata": {},
649 | "outputs": [],
650 | "source": [
651 | "from qiskit.circuit import Gate\n",
652 | "\n",
653 | "\n",
654 | "def iterative_phase_estimation(\n",
655 | " qr: QuantumRegister,\n",
656 | " cr: ClassicalRegister,\n",
657 | " controlled_unitaries: list[Gate],\n",
658 | " state_prep: Gate,\n",
659 | ") -> QuantumCircuit:\n",
660 | " qc = QuantumCircuit(qr, cr)\n",
661 | "\n",
662 | " ####### your code goes here #######\n",
663 | "\n",
664 | " return qc"
665 | ]
666 | },
667 | {
668 | "attachments": {},
669 | "cell_type": "markdown",
670 | "metadata": {},
671 | "source": [
672 | "The example below shows how one would use this function to generate the IPE circuit for the $S$ gate. The simulation results should match what you got above."
673 | ]
674 | },
675 | {
676 | "cell_type": "code",
677 | "execution_count": null,
678 | "metadata": {},
679 | "outputs": [],
680 | "source": [
681 | "from qiskit.circuit.library import CPhaseGate, XGate\n",
682 | "\n",
683 | "qr = QuantumRegister(2, \"q\")\n",
684 | "cr = ClassicalRegister(2, \"c\")\n",
685 | "\n",
686 | "s_angle = np.pi / 2\n",
687 | "controlled_unitaries = [CPhaseGate(s_angle * 2**k) for k in range(2)]\n",
688 | "qc = iterative_phase_estimation(qr, cr, controlled_unitaries, XGate())\n",
689 | "\n",
690 | "sim = AerSimulator()\n",
691 | "job = sim.run(qc, shots=1000)\n",
692 | "result = job.result()\n",
693 | "counts = result.get_counts()\n",
694 | "counts"
695 | ]
696 | },
697 | {
698 | "attachments": {},
699 | "cell_type": "markdown",
700 | "metadata": {},
701 | "source": [
702 | "## Run on hardware\n",
703 | "\n",
704 | "In the final part of this lab, we will run some circuits on real hardware! The code cells below initialize and run the circuit you created in Exercise 2 to measure the phase of the $S$ gate. Because current quantum hardware suffers from noise, the results will not be as good as what you got on the simulator. Feel free to try running the other circuits you created in this lab, though be aware that larger circuits, like the one from Exercise 3 for measuring the phase of the $T$ gate, will suffer from even more noise."
705 | ]
706 | },
707 | {
708 | "cell_type": "code",
709 | "execution_count": null,
710 | "metadata": {},
711 | "outputs": [],
712 | "source": [
713 | "from qiskit_ibm_provider import IBMProvider\n",
714 | "\n",
715 | "provider = IBMProvider()"
716 | ]
717 | },
718 | {
719 | "cell_type": "code",
720 | "execution_count": null,
721 | "metadata": {},
722 | "outputs": [],
723 | "source": [
724 | "hub = \"YOUR_HUB\"\n",
725 | "group = \"YOUR_GROUP\"\n",
726 | "project = \"YOUR_PROJECT\"\n",
727 | "\n",
728 | "backend_name = \"ibmq_manila\"\n",
729 | "backend = provider.get_backend(backend_name, instance=f\"{hub}/{group}/{project}\")"
730 | ]
731 | },
732 | {
733 | "cell_type": "code",
734 | "execution_count": null,
735 | "metadata": {},
736 | "outputs": [],
737 | "source": [
738 | "from qiskit import transpile\n",
739 | "\n",
740 | "qr = QuantumRegister(2, \"q\")\n",
741 | "cr = ClassicalRegister(2, \"c\")\n",
742 | "qc = QuantumCircuit(qr, cr)\n",
743 | "qc = step_2_circuit(qr, cr)\n",
744 | "qc_transpiled = transpile(qc, backend)"
745 | ]
746 | },
747 | {
748 | "cell_type": "code",
749 | "execution_count": null,
750 | "metadata": {},
751 | "outputs": [],
752 | "source": [
753 | "job = backend.run(qc_transpiled, shots=1000, dynamic=True)\n",
754 | "job_id = job.job_id()\n",
755 | "print(job_id)"
756 | ]
757 | },
758 | {
759 | "cell_type": "code",
760 | "execution_count": null,
761 | "metadata": {},
762 | "outputs": [],
763 | "source": [
764 | "retrieve_job = provider.retrieve_job(job_id)\n",
765 | "retrieve_job.status()"
766 | ]
767 | },
768 | {
769 | "cell_type": "code",
770 | "execution_count": null,
771 | "metadata": {},
772 | "outputs": [],
773 | "source": [
774 | "from qiskit.tools.visualization import plot_histogram\n",
775 | "\n",
776 | "counts = retrieve_job.result().get_counts()\n",
777 | "plot_histogram(counts)"
778 | ]
779 | }
780 | ],
781 | "metadata": {
782 | "kernelspec": {
783 | "display_name": "Python 3 (ipykernel)",
784 | "language": "python",
785 | "name": "python3"
786 | },
787 | "language_info": {
788 | "codemirror_mode": {
789 | "name": "ipython",
790 | "version": 3
791 | },
792 | "file_extension": ".py",
793 | "mimetype": "text/x-python",
794 | "name": "python",
795 | "nbconvert_exporter": "python",
796 | "pygments_lexer": "ipython3",
797 | "version": "3.10.11"
798 | },
799 | "vscode": {
800 | "interpreter": {
801 | "hash": "c2040b9df22fb8e6f552d9b589c97ff536ffe03a0da1ea2949f78b5a0e303bb6"
802 | }
803 | },
804 | "widgets": {
805 | "application/vnd.jupyter.widget-state+json": {
806 | "state": {},
807 | "version_major": 2,
808 | "version_minor": 0
809 | }
810 | }
811 | },
812 | "nbformat": 4,
813 | "nbformat_minor": 4
814 | }
815 |
--------------------------------------------------------------------------------
/content/lab4/resources/step1-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab4/resources/step1-circuit.png
--------------------------------------------------------------------------------
/content/lab4/resources/step2-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qiskit-community/qgss-2023/f628b80b2a227a2bc8bffdd41b877a107eea8f9a/content/lab4/resources/step2-circuit.png
--------------------------------------------------------------------------------
/content/lab5/lab5.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Lab 5: Error mitigation with Qiskit Runtime\n",
9 | "\n",
10 | "In this lab, we'll explore a few of the error mitigation options available through Qiskit Runtime. Specifically, we'll define a simple observable and initial state and use the Estimator primitive to measure the expectation value. Using noisy simulations, we'll explore the effect of different error mitigation strategies."
11 | ]
12 | },
13 | {
14 | "attachments": {},
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "## Setup\n",
19 | "\n",
20 | "We'll define a simple Heisenberg Hamiltonian model to use as an example. We'll also construct a simple state preparation circuit."
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": null,
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "from qiskit import QuantumCircuit, QuantumRegister\n",
30 | "from qiskit.quantum_info import SparsePauliOp\n",
31 | "\n",
32 | "\n",
33 | "def heisenberg_hamiltonian(\n",
34 | " length: int, jx: float = 1.0, jy: float = 0.0, jz: float = 0.0\n",
35 | ") -> SparsePauliOp:\n",
36 | " terms = []\n",
37 | " for i in range(length - 1):\n",
38 | " if jx:\n",
39 | " terms.append((\"XX\", [i, i + 1], jx))\n",
40 | " if jy:\n",
41 | " terms.append((\"YY\", [i, i + 1], jy))\n",
42 | " if jz:\n",
43 | " terms.append((\"ZZ\", [i, i + 1], jz))\n",
44 | " return SparsePauliOp.from_sparse_list(terms, num_qubits=length)\n",
45 | "\n",
46 | "\n",
47 | "def state_prep_circuit(num_qubits: int, layers: int = 1) -> QuantumCircuit:\n",
48 | " qubits = QuantumRegister(num_qubits, name=\"q\")\n",
49 | " circuit = QuantumCircuit(qubits)\n",
50 | " circuit.h(qubits)\n",
51 | " for _ in range(layers):\n",
52 | " for i in range(0, num_qubits - 1, 2):\n",
53 | " circuit.cx(qubits[i], qubits[i + 1])\n",
54 | " circuit.ry(0.1, qubits)\n",
55 | " for i in range(1, num_qubits - 1, 2):\n",
56 | " circuit.cx(qubits[i], qubits[i + 1])\n",
57 | " circuit.ry(0.1, qubits)\n",
58 | " return circuit"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": null,
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "length = 5\n",
68 | "\n",
69 | "hamiltonian = heisenberg_hamiltonian(length, 1.0, 1.0)\n",
70 | "circuit = state_prep_circuit(length, layers=2)\n",
71 | "\n",
72 | "print(hamiltonian)\n",
73 | "circuit.draw(\"mpl\")"
74 | ]
75 | },
76 | {
77 | "attachments": {},
78 | "cell_type": "markdown",
79 | "metadata": {},
80 | "source": [
81 | "## Calculate exact expectation value (energy)\n",
82 | "\n",
83 | "First, we'll calculate the exact expectation value using a local simulator implementation of the Estimator primitive. The expectation value of a Hamiltonian is also referred to as \"energy.\""
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": null,
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "from qiskit_aer.primitives import Estimator\n",
93 | "\n",
94 | "estimator = Estimator(approximation=True)\n",
95 | "job = estimator.run(circuit, hamiltonian, shots=None)\n",
96 | "result = job.result()\n",
97 | "exact_value = result.values[0]\n",
98 | "\n",
99 | "print(f\"Exact energy: {exact_value}\")"
100 | ]
101 | },
102 | {
103 | "attachments": {},
104 | "cell_type": "markdown",
105 | "metadata": {},
106 | "source": [
107 | "## Run noisy simulation through Qiskit Runtime\n",
108 | "\n",
109 | "Next, we'll initialize the Qiskit Runtime service and switch to using its Estimator primitive, backed by a simulator that can handle noise. Even though our circuit acts on 5 qubits, we'll initialize a simulator with 6 qubits in order to later demonstrate the potential effects of qubit choice."
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": null,
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "from qiskit_ibm_runtime import QiskitRuntimeService\n",
119 | "\n",
120 | "hub = \"ibm-q-internal\"\n",
121 | "group = \"deployed\"\n",
122 | "project = \"default\"\n",
123 | "service = QiskitRuntimeService(instance=f\"{hub}/{group}/{project}\")"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": null,
129 | "metadata": {},
130 | "outputs": [],
131 | "source": [
132 | "from qiskit_ibm_runtime import Estimator, Options, Session\n",
133 | "from qiskit.transpiler import CouplingMap\n",
134 | "\n",
135 | "backend = service.get_backend(\"simulator_statevector\")\n",
136 | "# set simulation options\n",
137 | "simulator = {\n",
138 | " \"basis_gates\": [\"id\", \"rz\", \"sx\", \"cx\", \"reset\"],\n",
139 | " \"coupling_map\": list(CouplingMap.from_line(length + 1)),\n",
140 | "}\n",
141 | "shots = 10000"
142 | ]
143 | },
144 | {
145 | "attachments": {},
146 | "cell_type": "markdown",
147 | "metadata": {},
148 | "source": [
149 | "### No noise"
150 | ]
151 | },
152 | {
153 | "attachments": {},
154 | "cell_type": "markdown",
155 | "metadata": {},
156 | "source": [
157 | "First, we'll run the simulation with no noise."
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "import math\n",
167 | "\n",
168 | "options = Options(\n",
169 | " simulator=simulator,\n",
170 | " resilience_level=0,\n",
171 | ")\n",
172 | "\n",
173 | "with Session(service=service, backend=backend):\n",
174 | " estimator = Estimator(options=options)\n",
175 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
176 | "\n",
177 | "result = job.result()\n",
178 | "experiment_value = result.values[0]\n",
179 | "error = abs(experiment_value - exact_value)\n",
180 | "variance = result.metadata[0][\"variance\"]\n",
181 | "std = math.sqrt(variance / shots)\n",
182 | "\n",
183 | "print(f\"Estimated energy: {experiment_value}\")\n",
184 | "print(f\"Energy error: {error}\")\n",
185 | "print(f\"Variance: {variance}\")\n",
186 | "print(f\"Standard error: {std}\")"
187 | ]
188 | },
189 | {
190 | "attachments": {},
191 | "cell_type": "markdown",
192 | "metadata": {},
193 | "source": [
194 | "### Readout error\n",
195 | "\n",
196 | "Next, let's run a simulation with readout error."
197 | ]
198 | },
199 | {
200 | "attachments": {},
201 | "cell_type": "markdown",
202 | "metadata": {},
203 | "source": [
204 | "#### Exercise 1\n",
205 | "\n",
206 | "In this exercise, you'll construct a noise model that has modest readout error on all qubits except for the first qubit, which will have really bad readout error.\n",
207 | "\n",
208 | "Specifically, construct a noise model with the following properties:\n",
209 | "- For the first qubit (qubit 0):\n",
210 | " - A readout of 1 has a 50% probability of being erroneously read as 0.\n",
211 | " - A readout of 0 has a 20% probability of being erroneously read as 1.\n",
212 | "- For the rest of the qubits:\n",
213 | " - A readout of 1 has a 5% probability of being erroneously read as 0.\n",
214 | " - A readout of 0 has a 2% probability of being erroneously read as 1.\n",
215 | "\n",
216 | "You may find it helpful to consult the following resources:\n",
217 | " - https://qiskit.org/ecosystem/aer/apidocs/aer_noise.html\n",
218 | " - https://qiskit.org/ecosystem/aer/tutorials/3_building_noise_models.html\n",
219 | " "
220 | ]
221 | },
222 | {
223 | "cell_type": "code",
224 | "execution_count": null,
225 | "metadata": {},
226 | "outputs": [],
227 | "source": [
228 | "from qiskit_aer.noise import NoiseModel, ReadoutError\n",
229 | "\n",
230 | "noise_model = NoiseModel()\n",
231 | "\n",
232 | "##### your code here #####\n",
233 | "\n",
234 | "print(noise_model)"
235 | ]
236 | },
237 | {
238 | "cell_type": "code",
239 | "execution_count": null,
240 | "metadata": {},
241 | "outputs": [],
242 | "source": [
243 | "# Submit your answer\n",
244 | "\n",
245 | "from qc_grader.challenges.qgss_2023 import grade_lab5_ex1\n",
246 | "\n",
247 | "grade_lab5_ex1(noise_model)"
248 | ]
249 | },
250 | {
251 | "attachments": {},
252 | "cell_type": "markdown",
253 | "metadata": {},
254 | "source": [
255 | "First, let's try running the simulation without doing anything to mitigate the readout error. We'll explicitly set `resilience_level = 0` to ensure that no error mitigation is applied by the Runtime service. To illustrate the effect of a poor choice of qubits, we'll explicitly specify an initial layout that includes qubit 0."
256 | ]
257 | },
258 | {
259 | "cell_type": "code",
260 | "execution_count": null,
261 | "metadata": {},
262 | "outputs": [],
263 | "source": [
264 | "options = Options(\n",
265 | " simulator=dict(noise_model=noise_model, **simulator),\n",
266 | " resilience_level=0,\n",
267 | " transpilation=dict(initial_layout=list(range(length))),\n",
268 | ")\n",
269 | "\n",
270 | "with Session(service=service, backend=backend):\n",
271 | " estimator = Estimator(options=options)\n",
272 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
273 | "\n",
274 | "result = job.result()\n",
275 | "experiment_value = result.values[0]\n",
276 | "error = abs(experiment_value - exact_value)\n",
277 | "variance = result.metadata[0][\"variance\"]\n",
278 | "std = math.sqrt(variance / shots)\n",
279 | "\n",
280 | "print(f\"Estimated energy: {experiment_value}\")\n",
281 | "print(f\"Energy error: {error}\")\n",
282 | "print(f\"Variance: {variance}\")\n",
283 | "print(f\"Standard error: {std}\")"
284 | ]
285 | },
286 | {
287 | "attachments": {},
288 | "cell_type": "markdown",
289 | "metadata": {},
290 | "source": [
291 | "The error we get is pretty large. To improve things, let's pick a qubit layout that avoids qubit 0."
292 | ]
293 | },
294 | {
295 | "cell_type": "code",
296 | "execution_count": null,
297 | "metadata": {},
298 | "outputs": [],
299 | "source": [
300 | "options = Options(\n",
301 | " simulator=dict(noise_model=noise_model, **simulator),\n",
302 | " resilience_level=0,\n",
303 | " transpilation=dict(initial_layout=list(range(1, length + 1))),\n",
304 | ")\n",
305 | "\n",
306 | "with Session(service=service, backend=backend):\n",
307 | " estimator = Estimator(options=options)\n",
308 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
309 | "\n",
310 | "result = job.result()\n",
311 | "experiment_value = result.values[0]\n",
312 | "error = abs(experiment_value - exact_value)\n",
313 | "variance = result.metadata[0][\"variance\"]\n",
314 | "std = math.sqrt(variance / shots)\n",
315 | "\n",
316 | "print(f\"Estimated energy: {experiment_value}\")\n",
317 | "print(f\"Energy error: {error}\")\n",
318 | "print(f\"Variance: {variance}\")\n",
319 | "print(f\"Standard error: {std}\")"
320 | ]
321 | },
322 | {
323 | "attachments": {},
324 | "cell_type": "markdown",
325 | "metadata": {},
326 | "source": [
327 | "The error is smaller now, but still significant. Let's now enable readout error mitigation by setting `resilience_level = 1`."
328 | ]
329 | },
330 | {
331 | "cell_type": "code",
332 | "execution_count": null,
333 | "metadata": {},
334 | "outputs": [],
335 | "source": [
336 | "options = Options(\n",
337 | " simulator=dict(noise_model=noise_model, **simulator),\n",
338 | " resilience_level=1,\n",
339 | " transpilation=dict(initial_layout=list(range(1, length + 1))),\n",
340 | ")\n",
341 | "\n",
342 | "with Session(service=service, backend=backend):\n",
343 | " estimator = Estimator(options=options)\n",
344 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
345 | "\n",
346 | "result = job.result()\n",
347 | "experiment_value = result.values[0]\n",
348 | "error = abs(experiment_value - exact_value)\n",
349 | "variance = result.metadata[0][\"variance\"]\n",
350 | "std = math.sqrt(variance / shots)\n",
351 | "\n",
352 | "print(f\"Estimated energy: {experiment_value}\")\n",
353 | "print(f\"Energy error: {error}\")\n",
354 | "print(f\"Variance: {variance}\")\n",
355 | "print(f\"Standard error: {std}\")"
356 | ]
357 | },
358 | {
359 | "attachments": {},
360 | "cell_type": "markdown",
361 | "metadata": {},
362 | "source": [
363 | "Now, the effect of readout error has been almost completely mitigated! This mitigation did not come for free. In particular,\n",
364 | "- To perform readout error mitigation, the Runtime service has to run additional calibration circuits, so the overall running time may be longer.\n",
365 | "- The variance of the estimator has increased, leading to a larger standard error of the mean. As a consequence, a larger number of shots needs to be specified in order to achieve a given standard error.\n",
366 | "\n",
367 | "Typically, these costs are relatively small, so it is almost always worthwhile to enable readout error mitigation."
368 | ]
369 | },
370 | {
371 | "attachments": {},
372 | "cell_type": "markdown",
373 | "metadata": {},
374 | "source": [
375 | "#### Exercise 2\n",
376 | "\n",
377 | "Suppose that turning on readout error mitigation increases the variance of your estimator by a factor of 2. If you originally ran your experiment with 10,000 shots, how many shots should you now use to achieve the same standard error of the mean?"
378 | ]
379 | },
380 | {
381 | "cell_type": "code",
382 | "execution_count": null,
383 | "metadata": {},
384 | "outputs": [],
385 | "source": [
386 | "new_shots: int\n",
387 | "\n",
388 | "##### your code here #####\n"
389 | ]
390 | },
391 | {
392 | "cell_type": "code",
393 | "execution_count": null,
394 | "metadata": {},
395 | "outputs": [],
396 | "source": [
397 | "# Submit your answer\n",
398 | "\n",
399 | "from qc_grader.challenges.qgss_2023 import grade_lab5_ex2\n",
400 | "\n",
401 | "grade_lab5_ex2(new_shots)"
402 | ]
403 | },
404 | {
405 | "attachments": {},
406 | "cell_type": "markdown",
407 | "metadata": {},
408 | "source": [
409 | "### Depolarizing error and zero-noise extrapolation\n",
410 | "\n",
411 | "In this section, we will see how depolarizing error can be mitigated using zero-noise extrapolation. Because the zero-noise extrapolation feature of Qiskit Runtime is still in beta, it currently has a few limitations. In particular, as of the time of this writing, the zero-noise extrapolation feature does not mitigate readout error. Therefore, in the examples below, we will remove readout error from our noise model."
412 | ]
413 | },
414 | {
415 | "attachments": {},
416 | "cell_type": "markdown",
417 | "metadata": {},
418 | "source": [
419 | "#### Exercise 3\n",
420 | "\n",
421 | "Construct a noise model that adds two-qubit depolarizing error after each CNOT gate, such that the error channel maps the input quantum state to the completely mixed state with 1% probability."
422 | ]
423 | },
424 | {
425 | "cell_type": "code",
426 | "execution_count": null,
427 | "metadata": {},
428 | "outputs": [],
429 | "source": [
430 | "from qiskit_aer.noise import depolarizing_error\n",
431 | "\n",
432 | "noise_model = NoiseModel()\n",
433 | "\n",
434 | "##### your code here #####\n",
435 | "\n",
436 | "print(noise_model)"
437 | ]
438 | },
439 | {
440 | "cell_type": "code",
441 | "execution_count": null,
442 | "metadata": {},
443 | "outputs": [],
444 | "source": [
445 | "# Submit your answer\n",
446 | "\n",
447 | "from qc_grader.challenges.qgss_2023 import grade_lab5_ex3\n",
448 | "\n",
449 | "grade_lab5_ex3(noise_model)"
450 | ]
451 | },
452 | {
453 | "attachments": {},
454 | "cell_type": "markdown",
455 | "metadata": {},
456 | "source": [
457 | "Let's run the estimator with `resilience_level = 1`, which turns on readout error mitigation. Because our noise model doesn't include readout error mitigation, we don't expect this to help."
458 | ]
459 | },
460 | {
461 | "cell_type": "code",
462 | "execution_count": null,
463 | "metadata": {},
464 | "outputs": [],
465 | "source": [
466 | "options = Options(\n",
467 | " simulator=dict(noise_model=noise_model, **simulator),\n",
468 | " resilience_level=1,\n",
469 | ")\n",
470 | "\n",
471 | "with Session(service=service, backend=backend):\n",
472 | " estimator = Estimator(options=options)\n",
473 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
474 | "\n",
475 | "result = job.result()\n",
476 | "experiment_value = result.values[0]\n",
477 | "error = abs(experiment_value - exact_value)\n",
478 | "variance = result.metadata[0][\"variance\"]\n",
479 | "std = math.sqrt(variance / shots)\n",
480 | "\n",
481 | "print(f\"Estimated energy: {experiment_value}\")\n",
482 | "print(f\"Energy error: {error}\")\n",
483 | "print(f\"Variance: {variance}\")\n",
484 | "print(f\"Standard error: {std}\")"
485 | ]
486 | },
487 | {
488 | "attachments": {},
489 | "cell_type": "markdown",
490 | "metadata": {},
491 | "source": [
492 | "As expected, the error we get is pretty significant.\n",
493 | "\n",
494 | "Now, let's turn on zero-noise extrapolation by setting `resilience_level = 2`."
495 | ]
496 | },
497 | {
498 | "cell_type": "code",
499 | "execution_count": null,
500 | "metadata": {},
501 | "outputs": [],
502 | "source": [
503 | "options = Options(\n",
504 | " simulator=dict(noise_model=noise_model, **simulator),\n",
505 | " resilience_level=2,\n",
506 | ")\n",
507 | "\n",
508 | "with Session(service=service, backend=backend):\n",
509 | " estimator = Estimator(options=options)\n",
510 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
511 | "\n",
512 | "result = job.result()\n",
513 | "experiment_value = result.values[0]\n",
514 | "error = abs(experiment_value - exact_value)\n",
515 | "variances = result.metadata[0][\"zne\"][\"noise_amplification\"][\"variance\"]\n",
516 | "\n",
517 | "print(f\"Estimated energy: {experiment_value}\")\n",
518 | "print(f\"Energy error: {error}\")\n",
519 | "print(f\"Variances: {variances}\")"
520 | ]
521 | },
522 | {
523 | "attachments": {},
524 | "cell_type": "markdown",
525 | "metadata": {},
526 | "source": [
527 | "Now, the effect of depolarizing noise has been almost completely mitigated! Note that instead of getting a single variance value for the estimator, we are now returned a list of variances, one for each data point measured for the extrapolation. In a future version of Qiskit Runtime, these variances will also be extrapolated to return a single variance for the final estimator."
528 | ]
529 | },
530 | {
531 | "attachments": {},
532 | "cell_type": "markdown",
533 | "metadata": {},
534 | "source": [
535 | "#### Exercise 4 (ungraded)\n",
536 | "\n",
537 | "Besides depolarizing error, what other kinds of noise can be mitigated by zero-noise extrapolation? Test your proposals by constructing other noise models, and then simulating them with and without zero-noise extrapolation."
538 | ]
539 | }
540 | ],
541 | "metadata": {
542 | "kernelspec": {
543 | "display_name": "qgss-2023-svnk7ds3",
544 | "language": "python",
545 | "name": "python3"
546 | },
547 | "language_info": {
548 | "codemirror_mode": {
549 | "name": "ipython",
550 | "version": 3
551 | },
552 | "file_extension": ".py",
553 | "mimetype": "text/x-python",
554 | "name": "python",
555 | "nbconvert_exporter": "python",
556 | "pygments_lexer": "ipython3",
557 | "version": "3.10.11"
558 | },
559 | "orig_nbformat": 4
560 | },
561 | "nbformat": 4,
562 | "nbformat_minor": 2
563 | }
564 |
--------------------------------------------------------------------------------
/solutions/lab1/lab1-solution.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "id": "d9bef481",
6 | "metadata": {},
7 | "source": [
8 | "# Qiskit Global Summer School 2023 - Lab 1\n",
9 | "\n",
10 | "This lab shows you how to use Qiskit to implement some of the key concepts you learned in the first 3 lectures of the Qiskit Global Summer School 2023."
11 | ]
12 | },
13 | {
14 | "cell_type": "code",
15 | "execution_count": null,
16 | "id": "d4749ad4",
17 | "metadata": {},
18 | "outputs": [],
19 | "source": [
20 | "# required imports:\n",
21 | "from qiskit.visualization import array_to_latex\n",
22 | "from qiskit.quantum_info import Statevector, random_statevector\n",
23 | "from qiskit.quantum_info.operators import Operator, Pauli\n",
24 | "from qiskit import QuantumCircuit\n",
25 | "from qiskit.circuit.library import HGate, CXGate\n",
26 | "import numpy as np"
27 | ]
28 | },
29 | {
30 | "cell_type": "markdown",
31 | "id": "742da035",
32 | "metadata": {},
33 | "source": [
34 | "## Vectors and Dirac Notation"
35 | ]
36 | },
37 | {
38 | "cell_type": "markdown",
39 | "id": "808ec86f",
40 | "metadata": {},
41 | "source": [
42 | "In the lectures you learned different ways of representing quantum states, including how to use bra-ket (Dirac) notation.\n",
43 | "\n",
44 | "Although bra-ket notation cannot be represented exactly in code, we can represent their vector and matrix equivalent with python.\n",
45 | "\n",
46 | "E.g. we can represent $|0\\rangle$ using a python list:"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "id": "b93989c8",
53 | "metadata": {},
54 | "outputs": [],
55 | "source": [
56 | "ket0 = [[1],[0]]"
57 | ]
58 | },
59 | {
60 | "cell_type": "markdown",
61 | "id": "34428069",
62 | "metadata": {},
63 | "source": [
64 | "And we can use one of Qiskit's visualisation tools to make our vectors nicer to look at:"
65 | ]
66 | },
67 | {
68 | "cell_type": "code",
69 | "execution_count": null,
70 | "id": "ee9b7eb0",
71 | "metadata": {},
72 | "outputs": [],
73 | "source": [
74 | "array_to_latex(ket0)"
75 | ]
76 | },
77 | {
78 | "cell_type": "markdown",
79 | "id": "a61be47b",
80 | "metadata": {},
81 | "source": [
82 | "We can do the same with $\\langle0|$:"
83 | ]
84 | },
85 | {
86 | "cell_type": "code",
87 | "execution_count": null,
88 | "id": "25f9ff7e",
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "bra0 = [1,0]\n",
93 | "array_to_latex(bra0)"
94 | ]
95 | },
96 | {
97 | "cell_type": "markdown",
98 | "id": "2cc359db",
99 | "metadata": {},
100 | "source": [
101 | " Ex 1 - create $|1\\rangle$ and $\\langle1|$ with python lists
"
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": null,
107 | "id": "59a7be22",
108 | "metadata": {},
109 | "outputs": [],
110 | "source": [
111 | "ket1 = [[0], [1]]\n",
112 | "bra1 = [0, 1]"
113 | ]
114 | },
115 | {
116 | "cell_type": "code",
117 | "execution_count": null,
118 | "id": "dcc474ad",
119 | "metadata": {},
120 | "outputs": [],
121 | "source": [
122 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex1 \n",
123 | "\n",
124 | "grade_lab1_ex1([ket1, bra1])"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "id": "21b44ec9",
130 | "metadata": {},
131 | "source": [
132 | "## Qiskit `Statevector` Class\n",
133 | "\n",
134 | "In the lectures you learned about using state vectors to represent quantum states. You can represent quantum state vectors in code using Qiskit's [`Statevector` class](https://qiskit.org/documentation/stubs/qiskit.quantum_info.Statevector.html).\n",
135 | "\n",
136 | "Qiskit's `Statevector` class can take different forms of input (e.g. python list, numpy array, another state vector) to construct a state vector.\n",
137 | "\n",
138 | "Let's take the `bra0` object we created earlier and convert it to a `Statevector` object:"
139 | ]
140 | },
141 | {
142 | "cell_type": "code",
143 | "execution_count": null,
144 | "id": "3ac7420a",
145 | "metadata": {},
146 | "outputs": [],
147 | "source": [
148 | "sv_bra0 = Statevector(bra0)\n",
149 | "\n",
150 | "sv_bra0"
151 | ]
152 | },
153 | {
154 | "cell_type": "markdown",
155 | "id": "9d3015ec",
156 | "metadata": {},
157 | "source": [
158 | "The `Statevector` class has its own `draw()` method:"
159 | ]
160 | },
161 | {
162 | "cell_type": "code",
163 | "execution_count": null,
164 | "id": "c7d0a57c",
165 | "metadata": {},
166 | "outputs": [],
167 | "source": [
168 | "sv_bra0.draw('latex')"
169 | ]
170 | },
171 | {
172 | "cell_type": "markdown",
173 | "id": "9a443d7b",
174 | "metadata": {},
175 | "source": [
176 | "We can create more complex statevectors with multiple qubits like this:"
177 | ]
178 | },
179 | {
180 | "cell_type": "code",
181 | "execution_count": null,
182 | "id": "dc70f9c7",
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "sv_eq = Statevector([1/2, 3/4, 4/5, 6/8])\n",
187 | "\n",
188 | "sv_eq.draw('latex')"
189 | ]
190 | },
191 | {
192 | "cell_type": "markdown",
193 | "id": "c7e00788",
194 | "metadata": {},
195 | "source": [
196 | "Note that the vector above is not a valid state vector as it is not normalised. \n",
197 | "We can check this with the `is_valid()` method:"
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "id": "8459bf73",
204 | "metadata": {},
205 | "outputs": [],
206 | "source": [
207 | "sv_eq.is_valid()"
208 | ]
209 | },
210 | {
211 | "cell_type": "markdown",
212 | "id": "a4b83945",
213 | "metadata": {},
214 | "source": [
215 | " Ex 2 - create your own valid statevector object using the `Statevector` class
"
216 | ]
217 | },
218 | {
219 | "cell_type": "code",
220 | "execution_count": null,
221 | "id": "02cfaf2f",
222 | "metadata": {},
223 | "outputs": [],
224 | "source": [
225 | "sv_valid = Statevector([1/2, 1/2, 1/2, 1/2])"
226 | ]
227 | },
228 | {
229 | "cell_type": "code",
230 | "execution_count": null,
231 | "id": "ed497a1a",
232 | "metadata": {},
233 | "outputs": [],
234 | "source": [
235 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex2 \n",
236 | "\n",
237 | "grade_lab1_ex2(sv_valid)"
238 | ]
239 | },
240 | {
241 | "cell_type": "markdown",
242 | "id": "3faad35b",
243 | "metadata": {},
244 | "source": [
245 | "## Qiskit `Operator` Class\n",
246 | "\n",
247 | "The [`Operator` class](https://qiskit.org/documentation/stubs/qiskit.quantum_info.Operator.html#qiskit.quantum_info.Operator) is used in Qiskit to represent matrix operators acting on a quantum system. It has several methods to build composite operators using tensor products of smaller operators, and to compose operators.\n",
248 | "\n",
249 | "One way we can initialise a Qiskit `Operator` is by using a python list, like the one we created earlier:"
250 | ]
251 | },
252 | {
253 | "cell_type": "code",
254 | "execution_count": null,
255 | "id": "180a544f",
256 | "metadata": {},
257 | "outputs": [],
258 | "source": [
259 | "op_bra0 = Operator(bra0)\n",
260 | "\n",
261 | "op_bra0"
262 | ]
263 | },
264 | {
265 | "cell_type": "markdown",
266 | "id": "dc578df9",
267 | "metadata": {},
268 | "source": [
269 | "The Operator class comes with some handy methods for working with operators, for example we can find the tensor product of 2 operators by using the `tensor()` method:"
270 | ]
271 | },
272 | {
273 | "cell_type": "code",
274 | "execution_count": null,
275 | "id": "7aed9441",
276 | "metadata": {},
277 | "outputs": [],
278 | "source": [
279 | "op_ket0 = Operator(ket0)\n",
280 | "op_bra0.tensor(op_ket0)"
281 | ]
282 | },
283 | {
284 | "cell_type": "markdown",
285 | "id": "e244f5a0",
286 | "metadata": {},
287 | "source": [
288 | "We'll use the `Operator` and `Statevector` classes more in the following exercises."
289 | ]
290 | },
291 | {
292 | "cell_type": "markdown",
293 | "id": "208cf2b6",
294 | "metadata": {},
295 | "source": [
296 | "## Inner & Outer Product\n",
297 | "\n",
298 | "In the lectures you covered the concepts of the inner and outer product. We can explore these concepts in code using numpy methods `.dot()` (the inner product is a generalised form of the dot product) and `.outer()`.\n",
299 | "\n",
300 | "For example, we can find the inner product $\\langle0|0\\rangle$ like this:"
301 | ]
302 | },
303 | {
304 | "cell_type": "code",
305 | "execution_count": null,
306 | "id": "cf3cb816",
307 | "metadata": {},
308 | "outputs": [],
309 | "source": [
310 | "braket = np.dot(op_bra0,op_ket0)\n",
311 | "array_to_latex(braket)"
312 | ]
313 | },
314 | {
315 | "cell_type": "markdown",
316 | "id": "e5d604d7",
317 | "metadata": {},
318 | "source": [
319 | "and the outer product $|0\\rangle\\langle0|$ like this:"
320 | ]
321 | },
322 | {
323 | "cell_type": "code",
324 | "execution_count": null,
325 | "id": "73232e3d",
326 | "metadata": {},
327 | "outputs": [],
328 | "source": [
329 | "ketbra = np.outer(ket0,bra0)\n",
330 | "array_to_latex(ketbra)"
331 | ]
332 | },
333 | {
334 | "cell_type": "code",
335 | "execution_count": null,
336 | "id": "98e2ba66",
337 | "metadata": {},
338 | "outputs": [],
339 | "source": [
340 | "braket = np.dot(op_bra0,op_ket0)\n",
341 | "array_to_latex(braket)"
342 | ]
343 | },
344 | {
345 | "cell_type": "markdown",
346 | "id": "cdca2a30",
347 | "metadata": {},
348 | "source": [
349 | "Note: the numpy methods we used above work with Qiskit Operators as well as regular python lists."
350 | ]
351 | },
352 | {
353 | "cell_type": "markdown",
354 | "id": "2dc9ed02",
355 | "metadata": {},
356 | "source": [
357 | " Ex 3 - use numpy to find the result of the following inner and outer products: $\\langle1|0\\rangle, \\langle0|1\\rangle, \\langle1|1\\rangle, |1\\rangle\\langle0|, |0\\rangle\\langle1|$ and $|1\\rangle\\langle1| $
"
358 | ]
359 | },
360 | {
361 | "cell_type": "code",
362 | "execution_count": null,
363 | "id": "72e1bb34",
364 | "metadata": {},
365 | "outputs": [],
366 | "source": [
367 | "bra1ket0 = [0]\n",
368 | "\n",
369 | "bra0ket1 = [0]\n",
370 | "\n",
371 | "bra1ket1 = [1]\n",
372 | "\n",
373 | "ket1bra0 = [[0, 0], [1, 0]]\n",
374 | "\n",
375 | "ket0bra1 = [[0, 1], [0, 0]]\n",
376 | "\n",
377 | "ket1bra1 = [[0, 0], [0, 1]]"
378 | ]
379 | },
380 | {
381 | "cell_type": "code",
382 | "execution_count": null,
383 | "id": "37b9cf70",
384 | "metadata": {},
385 | "outputs": [],
386 | "source": [
387 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex3 \n",
388 | "\n",
389 | "grade_lab1_ex3([bra1ket0, bra0ket1, bra1ket1, ket1bra0, ket0bra1, ket1bra1])"
390 | ]
391 | },
392 | {
393 | "cell_type": "markdown",
394 | "id": "7c03e06d",
395 | "metadata": {},
396 | "source": [
397 | " \n",
398 | "
Ex 4 - when the inner product of 2 quantum states is equal to 0, those states are orthogonal. Which of the following states are orthogonal?
\n",
399 | "
a) $\\vert 0\\rangle$ and $\\vert 1\\rangle$
\n",
400 | "
b) $\\vert 0\\rangle$ and $\\vert 0\\rangle$
\n",
401 | "
c) $\\vert 1\\rangle$ and $\\vert 1\\rangle$
\n",
402 | "
"
403 | ]
404 | },
405 | {
406 | "cell_type": "code",
407 | "execution_count": null,
408 | "id": "6e061263",
409 | "metadata": {},
410 | "outputs": [],
411 | "source": [
412 | "answer = ['a']"
413 | ]
414 | },
415 | {
416 | "cell_type": "code",
417 | "execution_count": null,
418 | "id": "bf314ab5",
419 | "metadata": {},
420 | "outputs": [],
421 | "source": [
422 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex4 \n",
423 | "\n",
424 | "grade_lab1_ex4(answer)"
425 | ]
426 | },
427 | {
428 | "cell_type": "markdown",
429 | "id": "e695b37a",
430 | "metadata": {},
431 | "source": [
432 | "## Deterministic operations\n",
433 | "\n",
434 | "As mentioned in the lectures, there are 4 single bit deterministic operations: \n",
435 | "f1 = constant-0 \n",
436 | "f2 = identity \n",
437 | "f3 = bit flip / not \n",
438 | "f4 = constant-1\n",
439 | "\n",
440 | "$$\n",
441 | "\\begin{array}{c|c}\n",
442 | " a & f_1(a)\\\\\n",
443 | " \\hline\n",
444 | " 0 & 0\\\\\n",
445 | " 1 & 0\n",
446 | "\\end{array}\n",
447 | "\\qquad\n",
448 | "\\begin{array}{c|c}\n",
449 | " a & f_2(a)\\\\\n",
450 | " \\hline\n",
451 | " 0 & 0\\\\\n",
452 | " 1 & 1\n",
453 | "\\end{array}\n",
454 | "\\qquad\n",
455 | "\\begin{array}{c|c}\n",
456 | " a & f_3(a)\\\\\n",
457 | " \\hline\n",
458 | " 0 & 1\\\\\n",
459 | " 1 & 0\n",
460 | "\\end{array}\n",
461 | "\\qquad\n",
462 | "\\begin{array}{c|c}\n",
463 | " a & f_4(a)\\\\\n",
464 | " \\hline\n",
465 | " 0 & 1\\\\\n",
466 | " 1 & 1\n",
467 | "\\end{array}\n",
468 | "$$\n",
469 | "\n",
470 | "We can create Qiskit Operators for these 4 operations, by passing their matrix representations as arguments to the `Operator` class.\n",
471 | "\n",
472 | "E.g. for constant-0 we can create the corresponding matrix m1 like so:"
473 | ]
474 | },
475 | {
476 | "cell_type": "code",
477 | "execution_count": null,
478 | "id": "8edc4262",
479 | "metadata": {},
480 | "outputs": [],
481 | "source": [
482 | "m1 = Operator([[1,1],[0,0]])\n",
483 | "array_to_latex(m1)"
484 | ]
485 | },
486 | {
487 | "cell_type": "markdown",
488 | "id": "2792a781",
489 | "metadata": {},
490 | "source": [
491 | "and similarly for m3:"
492 | ]
493 | },
494 | {
495 | "cell_type": "code",
496 | "execution_count": null,
497 | "id": "9cc2ff6c",
498 | "metadata": {},
499 | "outputs": [],
500 | "source": [
501 | "m3 = Operator([[0,1],[1,0]])\n",
502 | "array_to_latex(m3)"
503 | ]
504 | },
505 | {
506 | "cell_type": "markdown",
507 | "id": "765e8f0e",
508 | "metadata": {},
509 | "source": [
510 | "We can also use builtin python mutliplication operations (e.g. `@`, `.dot`, or `.matmul`) to check the following equation: $ M|a\\rangle = f|a\\rangle $\n",
511 | "\n",
512 | "e.g. $ M1|0\\rangle = f1|0\\rangle $ = 0"
513 | ]
514 | },
515 | {
516 | "cell_type": "code",
517 | "execution_count": null,
518 | "id": "a283a8bd",
519 | "metadata": {},
520 | "outputs": [],
521 | "source": [
522 | "array_to_latex(m1@ket0)"
523 | ]
524 | },
525 | {
526 | "cell_type": "markdown",
527 | "id": "f044b3f6",
528 | "metadata": {},
529 | "source": [
530 | " Ex 5 - create Qiskit Operators for m2 and m4 (hint: check out the lectures to find the appropriate matrices)
"
531 | ]
532 | },
533 | {
534 | "cell_type": "code",
535 | "execution_count": null,
536 | "id": "31927dfa",
537 | "metadata": {},
538 | "outputs": [],
539 | "source": [
540 | "m2 = Operator([[1, 0], [0, 1]])\n",
541 | "m4 = Operator([[0, 0], [1, 1]])"
542 | ]
543 | },
544 | {
545 | "cell_type": "code",
546 | "execution_count": null,
547 | "id": "62081ac6",
548 | "metadata": {},
549 | "outputs": [],
550 | "source": [
551 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex5\n",
552 | "\n",
553 | "grade_lab1_ex5([m2, m4])"
554 | ]
555 | },
556 | {
557 | "cell_type": "markdown",
558 | "id": "d611468d",
559 | "metadata": {},
560 | "source": [
561 | "## Probabilistic operations\n",
562 | "\n",
563 | "A Controlled-NOT (or CNOT) operation is a probabilistic operation you can apply on 2 qubits.\n",
564 | "\n",
565 | "Applying a CNOT on a state (X,Y) involves performing a NOT operation on Y when X is 1, otherwise do nothing.\n",
566 | "X is the control bit, Y is the target bit.\n",
567 | "\n",
568 | "We can implement a CNOT gate (and many other quantum gates) using a class from [Qiskit's circuit library](https://qiskit.org/documentation/apidoc/circuit_library.html):"
569 | ]
570 | },
571 | {
572 | "cell_type": "code",
573 | "execution_count": null,
574 | "id": "d6aec987",
575 | "metadata": {},
576 | "outputs": [],
577 | "source": [
578 | "cnot = CXGate()\n",
579 | "\n",
580 | "array_to_latex(cnot)"
581 | ]
582 | },
583 | {
584 | "cell_type": "markdown",
585 | "id": "49ee11bb",
586 | "metadata": {},
587 | "source": [
588 | "Note: this matrix is different from the one that appeared in the lesson because `CXGate()` takes the right qubit to be the control rather than the left qubit."
589 | ]
590 | },
591 | {
592 | "cell_type": "markdown",
593 | "id": "bd0665bc",
594 | "metadata": {},
595 | "source": [
596 | "## Unitary Operations\n",
597 | "\n",
598 | "An operator is unitary if: $ UU^{\\dagger} = \\mathbb{1} = U^{\\dagger} U$\n",
599 | "\n",
600 | "We can check if an operator is Unitary using Qiskit with the `is_unitary()` method:"
601 | ]
602 | },
603 | {
604 | "cell_type": "code",
605 | "execution_count": null,
606 | "id": "83e80fd5",
607 | "metadata": {},
608 | "outputs": [],
609 | "source": [
610 | "m3.is_unitary()"
611 | ]
612 | },
613 | {
614 | "cell_type": "markdown",
615 | "id": "ef61f742",
616 | "metadata": {},
617 | "source": [
618 | "With small operators like m3 we could probably figure this out easily by ourselves, but with more complex operators it becomes more convenient to use the Qiskit function:"
619 | ]
620 | },
621 | {
622 | "cell_type": "code",
623 | "execution_count": null,
624 | "id": "90fa7840",
625 | "metadata": {},
626 | "outputs": [],
627 | "source": [
628 | "random = Operator(np.array([[ 0.50778085-0.44607116j, -0.1523741 +0.14128434j, 0.44607116+0.50778085j,\n",
629 | " -0.14128434-0.1523741j ],\n",
630 | " [ 0.16855994+0.12151822j, 0.55868196+0.38038841j, -0.12151822+0.16855994j,\n",
631 | " -0.38038841+0.55868196j],\n",
632 | " [ 0.50778085-0.44607116j, -0.1523741 +0.14128434j, -0.44607116-0.50778085j,\n",
633 | " 0.14128434+0.1523741j ],\n",
634 | " [ 0.16855994+0.12151822j, 0.55868196+0.38038841j, 0.12151822-0.16855994j,\n",
635 | " 0.38038841-0.55868196j]]))\n",
636 | "\n",
637 | "random.is_unitary()"
638 | ]
639 | },
640 | {
641 | "cell_type": "markdown",
642 | "id": "e6778ed6",
643 | "metadata": {},
644 | "source": [
645 | " Ex 6 - create an operator using the `Operator` class that is not unitary
"
646 | ]
647 | },
648 | {
649 | "cell_type": "code",
650 | "execution_count": null,
651 | "id": "c43f5bbf",
652 | "metadata": {},
653 | "outputs": [],
654 | "source": [
655 | "non_unitary_op = Operator(np.array([0,1,2,3]))"
656 | ]
657 | },
658 | {
659 | "cell_type": "code",
660 | "execution_count": null,
661 | "id": "c159e466",
662 | "metadata": {},
663 | "outputs": [],
664 | "source": [
665 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex6\n",
666 | "\n",
667 | "grade_lab1_ex6(non_unitary_op)"
668 | ]
669 | },
670 | {
671 | "cell_type": "markdown",
672 | "id": "220179f8",
673 | "metadata": {},
674 | "source": [
675 | "### Qubit Unitary Operations - Pauli Operations\n",
676 | "\n",
677 | "Some of the most common unitary operations in quantum computing are the Pauli operations. Qiskit's `Pauli` classes make it easy to interact with Pauli operators in code:"
678 | ]
679 | },
680 | {
681 | "cell_type": "markdown",
682 | "id": "26c5fec2",
683 | "metadata": {},
684 | "source": [
685 | "E.g. Pauli X ($\\sigma_x$), the bit flip:"
686 | ]
687 | },
688 | {
689 | "cell_type": "code",
690 | "execution_count": null,
691 | "id": "290462f3",
692 | "metadata": {},
693 | "outputs": [],
694 | "source": [
695 | "pauli_x = Pauli('X')\n",
696 | "\n",
697 | "array_to_latex(pauli_x)"
698 | ]
699 | },
700 | {
701 | "cell_type": "markdown",
702 | "id": "ff2d5d30",
703 | "metadata": {},
704 | "source": [
705 | "Pauli Y ($\\sigma_y$):"
706 | ]
707 | },
708 | {
709 | "cell_type": "code",
710 | "execution_count": null,
711 | "id": "21b32f55",
712 | "metadata": {},
713 | "outputs": [],
714 | "source": [
715 | "pauli_y = Pauli('Y')\n",
716 | "\n",
717 | "array_to_latex(pauli_y)"
718 | ]
719 | },
720 | {
721 | "cell_type": "markdown",
722 | "id": "c9850043",
723 | "metadata": {},
724 | "source": [
725 | "Pauli Z ($\\sigma_z$), the phase flip:"
726 | ]
727 | },
728 | {
729 | "cell_type": "code",
730 | "execution_count": null,
731 | "id": "9df1dff2",
732 | "metadata": {},
733 | "outputs": [],
734 | "source": [
735 | "pauli_z = Pauli('Z')\n",
736 | "\n",
737 | "array_to_latex(pauli_z)"
738 | ]
739 | },
740 | {
741 | "cell_type": "markdown",
742 | "id": "bdfa41f0",
743 | "metadata": {},
744 | "source": [
745 | "We can use the `Operator` class with the `Pauli` class:"
746 | ]
747 | },
748 | {
749 | "cell_type": "code",
750 | "execution_count": null,
751 | "id": "71eb657f",
752 | "metadata": {},
753 | "outputs": [],
754 | "source": [
755 | "op_x = Operator(pauli_x)\n",
756 | "\n",
757 | "op_x"
758 | ]
759 | },
760 | {
761 | "cell_type": "markdown",
762 | "id": "22a1dc55",
763 | "metadata": {},
764 | "source": [
765 | "Let's use the `Operator` class and numpy to find the outcome of $\\sigma_x|0\\rangle$"
766 | ]
767 | },
768 | {
769 | "cell_type": "code",
770 | "execution_count": null,
771 | "id": "a3b46f65",
772 | "metadata": {},
773 | "outputs": [],
774 | "source": [
775 | "op_new = np.dot(op_x,ket0)\n",
776 | "\n",
777 | "array_to_latex(op_new)"
778 | ]
779 | },
780 | {
781 | "cell_type": "markdown",
782 | "id": "0434735a",
783 | "metadata": {},
784 | "source": [
785 | " Ex 7 - Apply the Pauli-Z operator on $|1\\rangle$
"
786 | ]
787 | },
788 | {
789 | "cell_type": "code",
790 | "execution_count": null,
791 | "id": "11ea5267",
792 | "metadata": {},
793 | "outputs": [],
794 | "source": [
795 | "result = [[0.+0.j], [-1.+0.j]]"
796 | ]
797 | },
798 | {
799 | "cell_type": "code",
800 | "execution_count": null,
801 | "id": "694825bd",
802 | "metadata": {},
803 | "outputs": [],
804 | "source": [
805 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex7\n",
806 | "\n",
807 | "grade_lab1_ex7(result)"
808 | ]
809 | },
810 | {
811 | "cell_type": "markdown",
812 | "id": "677ab1ec",
813 | "metadata": {},
814 | "source": [
815 | "### Qubit Unitary Operations - Hadamard\n"
816 | ]
817 | },
818 | {
819 | "cell_type": "markdown",
820 | "id": "1fa9f846",
821 | "metadata": {},
822 | "source": [
823 | "The Hadamard gate is one of the most important unitary operations in quantum computing. We can implement a Hadamard gate (and many other quantum gates) using a class from [Qiskit's circuit library](https://qiskit.org/documentation/apidoc/circuit_library.html):"
824 | ]
825 | },
826 | {
827 | "cell_type": "code",
828 | "execution_count": null,
829 | "id": "74251a81",
830 | "metadata": {},
831 | "outputs": [],
832 | "source": [
833 | "hadamard = HGate()\n",
834 | "\n",
835 | "array_to_latex(hadamard)"
836 | ]
837 | },
838 | {
839 | "cell_type": "markdown",
840 | "id": "08b41da3",
841 | "metadata": {},
842 | "source": [
843 | "You can convert many Qiskit classes to operators to make use of functions specific to the `Operator` class, such as `is_unitary`"
844 | ]
845 | },
846 | {
847 | "cell_type": "code",
848 | "execution_count": null,
849 | "id": "ebc2f8df",
850 | "metadata": {},
851 | "outputs": [],
852 | "source": [
853 | "hop = Operator(hadamard)\n",
854 | "hop.is_unitary()"
855 | ]
856 | },
857 | {
858 | "cell_type": "markdown",
859 | "id": "aa5207cc",
860 | "metadata": {},
861 | "source": [
862 | "## Quantum Circuits\n",
863 | "\n",
864 | "In the lectures you learned how to create a Quantum Circuit using a CNOT and a Hadamard gate. This circuit creates the Bell State $|\\phi^+\\rangle$. We can implement this using Qiskit's `QuantumCircuit` class:"
865 | ]
866 | },
867 | {
868 | "cell_type": "code",
869 | "execution_count": null,
870 | "id": "c1b3e5a9",
871 | "metadata": {},
872 | "outputs": [],
873 | "source": [
874 | "bell = QuantumCircuit(2)\n",
875 | "\n",
876 | "bell.h(0) # apply an H gate to the circuit\n",
877 | "bell.cx(0,1) # apply a CNOT gate to the circuit\n",
878 | "\n",
879 | "bell.draw(output=\"mpl\")"
880 | ]
881 | },
882 | {
883 | "cell_type": "markdown",
884 | "id": "1b2a93c8",
885 | "metadata": {},
886 | "source": [
887 | "If we want to check what the matrix representation is of this quantum state we can convert the circuit directly to an operator:"
888 | ]
889 | },
890 | {
891 | "cell_type": "code",
892 | "execution_count": null,
893 | "id": "ac8dab7c",
894 | "metadata": {},
895 | "outputs": [],
896 | "source": [
897 | "bell_op = Operator(bell)\n",
898 | "\n",
899 | "array_to_latex(bell_op)"
900 | ]
901 | },
902 | {
903 | "cell_type": "markdown",
904 | "id": "b2487769",
905 | "metadata": {},
906 | "source": [
907 | " Ex 8 - the GHZ state is similar to the Bell State but applied to 3 qubits. Create a quantum circuit outputting the GHZ state
"
908 | ]
909 | },
910 | {
911 | "cell_type": "code",
912 | "execution_count": null,
913 | "id": "f7469f9f",
914 | "metadata": {},
915 | "outputs": [],
916 | "source": [
917 | "ghz = QuantumCircuit(3)\n",
918 | "\n",
919 | "##############################\n",
920 | "# add gates to your circuit here\n",
921 | "\n",
922 | "ghz.h(0)\n",
923 | "ghz.cx(0, 1)\n",
924 | "ghz.cx(1, 2)\n",
925 | "\n",
926 | "##############################\n",
927 | "\n",
928 | "ghz.draw(output='mpl')"
929 | ]
930 | },
931 | {
932 | "cell_type": "code",
933 | "execution_count": null,
934 | "id": "ffb9113c",
935 | "metadata": {},
936 | "outputs": [],
937 | "source": [
938 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex8\n",
939 | "\n",
940 | "grade_lab1_ex8(ghz)"
941 | ]
942 | },
943 | {
944 | "cell_type": "markdown",
945 | "id": "dec5eb53",
946 | "metadata": {},
947 | "source": [
948 | "## Measuring Quantum states"
949 | ]
950 | },
951 | {
952 | "cell_type": "markdown",
953 | "id": "9d05a3cb",
954 | "metadata": {},
955 | "source": [
956 | "As explained in the lectures you can find the probability of measurement outcomes by taking the absolute value squared of the entries of a quantum state vector.\n",
957 | "\n",
958 | "For example, when measuring the + state: \n",
959 | "\n",
960 | "$ |+\\rangle = \\frac{1}{\\sqrt2}|0\\rangle + \\frac{1}{\\sqrt2}|1\\rangle $\n",
961 | "\n",
962 | "The probability of measuring 0 or 1 is given by the following:\n",
963 | "\n",
964 | "$ Pr(0) = |\\frac{1}{\\sqrt2}|^2 = \\frac{1}{2}$ \n",
965 | "$ Pr(1) = |\\frac{1}{\\sqrt2}|^2 = \\frac{1}{2}$"
966 | ]
967 | },
968 | {
969 | "cell_type": "markdown",
970 | "id": "09b17e63",
971 | "metadata": {},
972 | "source": [
973 | "Let's create a $|+\\rangle$ using the `Statevector` class:"
974 | ]
975 | },
976 | {
977 | "cell_type": "code",
978 | "execution_count": null,
979 | "id": "819fdc56",
980 | "metadata": {},
981 | "outputs": [],
982 | "source": [
983 | "plus_state = Statevector.from_label(\"+\")\n",
984 | "\n",
985 | "plus_state.draw('latex')"
986 | ]
987 | },
988 | {
989 | "cell_type": "code",
990 | "execution_count": null,
991 | "id": "4c7849e7",
992 | "metadata": {},
993 | "outputs": [],
994 | "source": [
995 | "plus_state"
996 | ]
997 | },
998 | {
999 | "cell_type": "markdown",
1000 | "id": "e9367460",
1001 | "metadata": {},
1002 | "source": [
1003 | "Now we can get the probability of measuring 0 or 1:"
1004 | ]
1005 | },
1006 | {
1007 | "cell_type": "code",
1008 | "execution_count": null,
1009 | "id": "4b954ae3",
1010 | "metadata": {},
1011 | "outputs": [],
1012 | "source": [
1013 | "plus_state.probabilities_dict()"
1014 | ]
1015 | },
1016 | {
1017 | "cell_type": "markdown",
1018 | "id": "62b9c13c",
1019 | "metadata": {},
1020 | "source": [
1021 | "The dictionary object above shows you all the possible measurement outcomes and what the probability is of getting them. The actual act of measuring forces the state to collapse into either the 0 or 1 state:"
1022 | ]
1023 | },
1024 | {
1025 | "cell_type": "code",
1026 | "execution_count": null,
1027 | "id": "546166ed",
1028 | "metadata": {},
1029 | "outputs": [],
1030 | "source": [
1031 | "# run this cell multiple times to show collapsing into one state or the other\n",
1032 | "res = plus_state.measure()\n",
1033 | "\n",
1034 | "res"
1035 | ]
1036 | },
1037 | {
1038 | "cell_type": "markdown",
1039 | "id": "cbf9efbb",
1040 | "metadata": {},
1041 | "source": [
1042 | "We can implement the same $|+\\rangle$ state with measurement using a quantum circuit:"
1043 | ]
1044 | },
1045 | {
1046 | "cell_type": "code",
1047 | "execution_count": null,
1048 | "id": "6af8a51d",
1049 | "metadata": {},
1050 | "outputs": [],
1051 | "source": [
1052 | "qc = QuantumCircuit(1,1)\n",
1053 | "qc.h(0)\n",
1054 | "qc.measure(0, 0)\n",
1055 | "\n",
1056 | "qc.draw(output=\"mpl\")"
1057 | ]
1058 | },
1059 | {
1060 | "cell_type": "markdown",
1061 | "id": "84e3a739",
1062 | "metadata": {},
1063 | "source": [
1064 | "If we ran this circuit using a simulator we would get the same results as we did with the statevector class."
1065 | ]
1066 | },
1067 | {
1068 | "cell_type": "markdown",
1069 | "id": "f3abea6e",
1070 | "metadata": {},
1071 | "source": [
1072 | "In the next example, let's use the `Statevector` class to find the measurement outcomes for a dependent, probabilistic state. We'll find the measurement probilities for the 2-qubit Bell State $|\\phi^+\\rangle$ :"
1073 | ]
1074 | },
1075 | {
1076 | "cell_type": "code",
1077 | "execution_count": null,
1078 | "id": "f0c6e31d",
1079 | "metadata": {},
1080 | "outputs": [],
1081 | "source": [
1082 | "sv_bell = Statevector([np.sqrt(1/2), 0, 0, np.sqrt(1/2)])\n",
1083 | "\n",
1084 | "sv_bell.draw('latex')"
1085 | ]
1086 | },
1087 | {
1088 | "cell_type": "code",
1089 | "execution_count": null,
1090 | "id": "60aca301",
1091 | "metadata": {},
1092 | "outputs": [],
1093 | "source": [
1094 | "sv_bell.probabilities_dict()"
1095 | ]
1096 | },
1097 | {
1098 | "cell_type": "markdown",
1099 | "id": "734489ba",
1100 | "metadata": {},
1101 | "source": [
1102 | " Ex 9 - Using the Statevector class find the probabilities for the other 3 states in the Bell Basis: $|\\psi^+\\rangle$, $|\\psi^-\\rangle$, $|\\phi^-\\rangle$. Hint: check out lesson 2 to refresh your memory on the equations of the Bell states
"
1103 | ]
1104 | },
1105 | {
1106 | "cell_type": "code",
1107 | "execution_count": null,
1108 | "id": "119714dd",
1109 | "metadata": {},
1110 | "outputs": [],
1111 | "source": [
1112 | "sv_psi_plus = Statevector([0, np.sqrt(1/2), np.sqrt(1/2), 0])\n",
1113 | "prob_psi_plus = {'01': 0.5000000000000001, '10': 0.5000000000000001}\n",
1114 | "\n",
1115 | "sv_psi_minus = Statevector([0, np.sqrt(1/2), -np.sqrt(1/2), 0])\n",
1116 | "prob_psi_minus = {'01': 0.5000000000000001, '10': 0.5000000000000001}\n",
1117 | "\n",
1118 | "sv_phi_minus = Statevector([np.sqrt(1/2), 0, 0, -np.sqrt(1/2)])\n",
1119 | "prob_phi_minus = {'00': 0.5000000000000001, '11': 0.5000000000000001}"
1120 | ]
1121 | },
1122 | {
1123 | "cell_type": "code",
1124 | "execution_count": null,
1125 | "id": "72585681",
1126 | "metadata": {},
1127 | "outputs": [],
1128 | "source": [
1129 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex9\n",
1130 | "\n",
1131 | "grade_lab1_ex9([prob_psi_plus, prob_psi_minus, prob_phi_minus])"
1132 | ]
1133 | },
1134 | {
1135 | "cell_type": "markdown",
1136 | "id": "0faf6184",
1137 | "metadata": {},
1138 | "source": [
1139 | "# Final Challenge - generate a QFT circuit\n",
1140 | "\n",
1141 | "[The Fourier transform](https://en.wikipedia.org/wiki/Fourier_transform) occurs in many different formats throughout classical computing, in areas ranging from signal processing to data compression to complexity theory. The quantum Fourier transform (QFT) is the quantum implementation of the discrete Fourier transform over the amplitudes of a wavefunction. It is part of many quantum algorithms, most notably Shor's factoring algorithm and quantum phase estimation. You'll learn more about this important implementation later on during the Summer School, but for this final challenge of Lab 1 we would like you to use Qiskit to create the following QFT circuit on 2 qubits:\n",
1142 | "\n",
1143 | ""
1144 | ]
1145 | },
1146 | {
1147 | "cell_type": "markdown",
1148 | "id": "a06d2b68",
1149 | "metadata": {},
1150 | "source": [
1151 | " Ex 10 - create a 2 qubit QFT circuit using qiskit
"
1152 | ]
1153 | },
1154 | {
1155 | "cell_type": "code",
1156 | "execution_count": null,
1157 | "id": "e51049ac",
1158 | "metadata": {},
1159 | "outputs": [],
1160 | "source": [
1161 | "qft = QuantumCircuit(2)\n",
1162 | "\n",
1163 | "##############################\n",
1164 | "# add gates to your circuit here\n",
1165 | "\n",
1166 | "qft.h(1)\n",
1167 | "qft.cp(np.pi/2, 0, 1)\n",
1168 | "qft.h(0)\n",
1169 | "qft.swap(0, 1)\n",
1170 | "\n",
1171 | "##############################\n",
1172 | "\n",
1173 | "qft.draw(output='mpl')"
1174 | ]
1175 | },
1176 | {
1177 | "cell_type": "code",
1178 | "execution_count": null,
1179 | "id": "9ad87edc",
1180 | "metadata": {},
1181 | "outputs": [],
1182 | "source": [
1183 | "from qc_grader.challenges.qgss_2023 import grade_lab1_ex10\n",
1184 | "\n",
1185 | "grade_lab1_ex10(qft)"
1186 | ]
1187 | },
1188 | {
1189 | "cell_type": "markdown",
1190 | "id": "94b90a28",
1191 | "metadata": {},
1192 | "source": [
1193 | "To see the matrix that describes the action of this circuit, we can plug the circuit into the `Operator` function like this:"
1194 | ]
1195 | },
1196 | {
1197 | "cell_type": "code",
1198 | "execution_count": null,
1199 | "id": "d83e5f5b",
1200 | "metadata": {},
1201 | "outputs": [],
1202 | "source": [
1203 | "U = Operator(qft)\n",
1204 | "\n",
1205 | "array_to_latex(U)"
1206 | ]
1207 | },
1208 | {
1209 | "cell_type": "markdown",
1210 | "id": "9c1ec931",
1211 | "metadata": {},
1212 | "source": [
1213 | "Congratulations! You finished Lab 1 of the Qiskit Global Summer School 2023! 🎉 🎉 🎉"
1214 | ]
1215 | }
1216 | ],
1217 | "metadata": {
1218 | "kernelspec": {
1219 | "display_name": "Python 3 (ipykernel)",
1220 | "language": "python",
1221 | "name": "python3"
1222 | },
1223 | "language_info": {
1224 | "codemirror_mode": {
1225 | "name": "ipython",
1226 | "version": 3
1227 | },
1228 | "file_extension": ".py",
1229 | "mimetype": "text/x-python",
1230 | "name": "python",
1231 | "nbconvert_exporter": "python",
1232 | "pygments_lexer": "ipython3",
1233 | "version": "3.10.4"
1234 | }
1235 | },
1236 | "nbformat": 4,
1237 | "nbformat_minor": 5
1238 | }
1239 |
--------------------------------------------------------------------------------
/solutions/lab5/lab5-solution.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "attachments": {},
5 | "cell_type": "markdown",
6 | "metadata": {},
7 | "source": [
8 | "# Lab 5: Error mitigation with Qiskit Runtime\n",
9 | "\n",
10 | "In this lab, we'll explore a few of the error mitigation options available through Qiskit Runtime. Specifically, we'll define a simple observable and initial state and use the Estimator primitive to measure the expectation value. Using noisy simulations, we'll explore the effect of different error mitigation strategies."
11 | ]
12 | },
13 | {
14 | "attachments": {},
15 | "cell_type": "markdown",
16 | "metadata": {},
17 | "source": [
18 | "## Setup\n",
19 | "\n",
20 | "We'll define a simple Heisenberg Hamiltonian model to use as an example. We'll also construct a simple state preparation circuit."
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": null,
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "from qiskit import QuantumCircuit, QuantumRegister\n",
30 | "from qiskit.quantum_info import SparsePauliOp\n",
31 | "\n",
32 | "\n",
33 | "def heisenberg_hamiltonian(\n",
34 | " length: int, jx: float = 1.0, jy: float = 0.0, jz: float = 0.0\n",
35 | ") -> SparsePauliOp:\n",
36 | " terms = []\n",
37 | " for i in range(length - 1):\n",
38 | " if jx:\n",
39 | " terms.append((\"XX\", [i, i + 1], jx))\n",
40 | " if jy:\n",
41 | " terms.append((\"YY\", [i, i + 1], jy))\n",
42 | " if jz:\n",
43 | " terms.append((\"ZZ\", [i, i + 1], jz))\n",
44 | " return SparsePauliOp.from_sparse_list(terms, num_qubits=length)\n",
45 | "\n",
46 | "\n",
47 | "def state_prep_circuit(num_qubits: int, layers: int = 1) -> QuantumCircuit:\n",
48 | " qubits = QuantumRegister(num_qubits, name=\"q\")\n",
49 | " circuit = QuantumCircuit(qubits)\n",
50 | " circuit.h(qubits)\n",
51 | " for _ in range(layers):\n",
52 | " for i in range(0, num_qubits - 1, 2):\n",
53 | " circuit.cx(qubits[i], qubits[i + 1])\n",
54 | " circuit.ry(0.1, qubits)\n",
55 | " for i in range(1, num_qubits - 1, 2):\n",
56 | " circuit.cx(qubits[i], qubits[i + 1])\n",
57 | " circuit.ry(0.1, qubits)\n",
58 | " return circuit"
59 | ]
60 | },
61 | {
62 | "cell_type": "code",
63 | "execution_count": null,
64 | "metadata": {},
65 | "outputs": [],
66 | "source": [
67 | "length = 5\n",
68 | "\n",
69 | "hamiltonian = heisenberg_hamiltonian(length, 1.0, 1.0)\n",
70 | "circuit = state_prep_circuit(length, layers=2)\n",
71 | "\n",
72 | "print(hamiltonian)\n",
73 | "circuit.draw(\"mpl\")"
74 | ]
75 | },
76 | {
77 | "attachments": {},
78 | "cell_type": "markdown",
79 | "metadata": {},
80 | "source": [
81 | "## Calculate exact expectation value (energy)\n",
82 | "\n",
83 | "First, we'll calculate the exact expectation value using a local simulator implementation of the Estimator primitive. The expectation value of a Hamiltonian is also referred to as \"energy.\""
84 | ]
85 | },
86 | {
87 | "cell_type": "code",
88 | "execution_count": null,
89 | "metadata": {},
90 | "outputs": [],
91 | "source": [
92 | "from qiskit_aer.primitives import Estimator\n",
93 | "\n",
94 | "estimator = Estimator(approximation=True)\n",
95 | "job = estimator.run(circuit, hamiltonian, shots=None)\n",
96 | "result = job.result()\n",
97 | "exact_value = result.values[0]\n",
98 | "\n",
99 | "print(f\"Exact energy: {exact_value}\")"
100 | ]
101 | },
102 | {
103 | "attachments": {},
104 | "cell_type": "markdown",
105 | "metadata": {},
106 | "source": [
107 | "## Run noisy simulation through Qiskit Runtime\n",
108 | "\n",
109 | "Next, we'll initialize the Qiskit Runtime service and switch to using its Estimator primitive, backed by a simulator that can handle noise."
110 | ]
111 | },
112 | {
113 | "cell_type": "code",
114 | "execution_count": null,
115 | "metadata": {},
116 | "outputs": [],
117 | "source": [
118 | "from qiskit_ibm_runtime import QiskitRuntimeService\n",
119 | "\n",
120 | "hub = \"ibm-q-internal\"\n",
121 | "group = \"deployed\"\n",
122 | "project = \"default\"\n",
123 | "service = QiskitRuntimeService(instance=f\"{hub}/{group}/{project}\")"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": null,
129 | "metadata": {},
130 | "outputs": [],
131 | "source": [
132 | "from qiskit_ibm_runtime import Estimator, Options, Session\n",
133 | "from qiskit.transpiler import CouplingMap\n",
134 | "\n",
135 | "backend = service.get_backend(\"simulator_statevector\")\n",
136 | "# set simulation options\n",
137 | "simulator = {\n",
138 | " \"basis_gates\": [\"id\", \"rz\", \"sx\", \"cx\", \"reset\"],\n",
139 | " \"coupling_map\": list(CouplingMap.from_line(length + 1)),\n",
140 | "}\n",
141 | "shots = 10000"
142 | ]
143 | },
144 | {
145 | "attachments": {},
146 | "cell_type": "markdown",
147 | "metadata": {},
148 | "source": [
149 | "### No noise"
150 | ]
151 | },
152 | {
153 | "attachments": {},
154 | "cell_type": "markdown",
155 | "metadata": {},
156 | "source": [
157 | "First, we'll run the simulation with no noise."
158 | ]
159 | },
160 | {
161 | "cell_type": "code",
162 | "execution_count": null,
163 | "metadata": {},
164 | "outputs": [],
165 | "source": [
166 | "import math\n",
167 | "\n",
168 | "options = Options(\n",
169 | " simulator=simulator,\n",
170 | " resilience_level=0,\n",
171 | ")\n",
172 | "\n",
173 | "with Session(service=service, backend=backend):\n",
174 | " estimator = Estimator(options=options)\n",
175 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
176 | "\n",
177 | "result = job.result()\n",
178 | "experiment_value = result.values[0]\n",
179 | "error = abs(experiment_value - exact_value)\n",
180 | "variance = result.metadata[0][\"variance\"]\n",
181 | "std = math.sqrt(variance / shots)\n",
182 | "\n",
183 | "print(f\"Estimated energy: {experiment_value}\")\n",
184 | "print(f\"Energy error: {error}\")\n",
185 | "print(f\"Variance: {variance}\")\n",
186 | "print(f\"Standard error: {std}\")"
187 | ]
188 | },
189 | {
190 | "attachments": {},
191 | "cell_type": "markdown",
192 | "metadata": {},
193 | "source": [
194 | "### Readout error\n",
195 | "\n",
196 | "Next, let's run a simulation with readout error."
197 | ]
198 | },
199 | {
200 | "attachments": {},
201 | "cell_type": "markdown",
202 | "metadata": {},
203 | "source": [
204 | "#### Exercise 1\n",
205 | "\n",
206 | "In this exercise, you'll construct a noise model that has modest readout error on all qubits except for the first qubit, which will have really bad readout error.\n",
207 | "\n",
208 | "Specifically, construct a noise model with the following properties:\n",
209 | "- For the first qubit (qubit 0):\n",
210 | " - A readout of 1 has a 50% probability of being erroneously read as 0.\n",
211 | " - A readout of 0 has a 20% probability of being erroneously read as 1.\n",
212 | "- For the rest of the qubits:\n",
213 | " - A readout of 1 has a 5% probability of being erroneously read as 0.\n",
214 | " - A readout of 0 has a 2% probability of being erroneously read as 1.\n",
215 | "\n",
216 | "You may find it helpful to consult the following resources:\n",
217 | " - https://qiskit.org/ecosystem/aer/apidocs/aer_noise.html\n",
218 | " - https://qiskit.org/documentation/tutorials/simulators/3_building_noise_models.html"
219 | ]
220 | },
221 | {
222 | "cell_type": "code",
223 | "execution_count": null,
224 | "metadata": {},
225 | "outputs": [],
226 | "source": [
227 | "from qiskit_aer.noise import NoiseModel, ReadoutError\n",
228 | "\n",
229 | "noise_model = NoiseModel()\n",
230 | "\n",
231 | "##### your code here #####\n",
232 | "\n",
233 | "# add modest readout error on all qubits\n",
234 | "p0given1 = 0.05\n",
235 | "p1given0 = 0.02\n",
236 | "readout_error = ReadoutError(\n",
237 | " [\n",
238 | " [1 - p1given0, p1given0],\n",
239 | " [p0given1, 1 - p0given1],\n",
240 | " ]\n",
241 | ")\n",
242 | "noise_model.add_all_qubit_readout_error(readout_error)\n",
243 | "\n",
244 | "# add really bad readout error on qubit 0\n",
245 | "p0given1 = 0.5\n",
246 | "p1given0 = 0.2\n",
247 | "readout_error = ReadoutError(\n",
248 | " [\n",
249 | " [1 - p1given0, p1given0],\n",
250 | " [p0given1, 1 - p0given1],\n",
251 | " ]\n",
252 | ")\n",
253 | "noise_model.add_readout_error(readout_error, [0])\n",
254 | "\n",
255 | "print(noise_model)"
256 | ]
257 | },
258 | {
259 | "cell_type": "code",
260 | "execution_count": null,
261 | "metadata": {},
262 | "outputs": [],
263 | "source": [
264 | "# Submit your answer\n",
265 | "\n",
266 | "from qc_grader.challenges.qgss_2023 import grade_lab5_ex1\n",
267 | "\n",
268 | "grade_lab5_ex1(noise_model)"
269 | ]
270 | },
271 | {
272 | "attachments": {},
273 | "cell_type": "markdown",
274 | "metadata": {},
275 | "source": [
276 | "First, let's try running the simulation without doing anything to mitigate the readout error. We'll explicitly set `resilience_level = 0` to ensure that no error mitigation is applied by the Runtime service. To illustrate the effect of a poor choice of qubits, we'll explicitly specify an initial layout that includes qubit 0."
277 | ]
278 | },
279 | {
280 | "cell_type": "code",
281 | "execution_count": null,
282 | "metadata": {},
283 | "outputs": [],
284 | "source": [
285 | "options = Options(\n",
286 | " simulator=dict(noise_model=noise_model, **simulator),\n",
287 | " resilience_level=0,\n",
288 | " transpilation=dict(initial_layout=list(range(length))),\n",
289 | ")\n",
290 | "\n",
291 | "with Session(service=service, backend=backend):\n",
292 | " estimator = Estimator(options=options)\n",
293 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
294 | "\n",
295 | "result = job.result()\n",
296 | "experiment_value = result.values[0]\n",
297 | "error = abs(experiment_value - exact_value)\n",
298 | "variance = result.metadata[0][\"variance\"]\n",
299 | "std = math.sqrt(variance / shots)\n",
300 | "\n",
301 | "print(f\"Estimated energy: {experiment_value}\")\n",
302 | "print(f\"Energy error: {error}\")\n",
303 | "print(f\"Variance: {variance}\")\n",
304 | "print(f\"Standard error: {std}\")"
305 | ]
306 | },
307 | {
308 | "attachments": {},
309 | "cell_type": "markdown",
310 | "metadata": {},
311 | "source": [
312 | "The error we get is pretty large. To improve things, let's pick a qubit layout that avoids qubit 0."
313 | ]
314 | },
315 | {
316 | "cell_type": "code",
317 | "execution_count": null,
318 | "metadata": {},
319 | "outputs": [],
320 | "source": [
321 | "options = Options(\n",
322 | " simulator=dict(noise_model=noise_model, **simulator),\n",
323 | " resilience_level=0,\n",
324 | " transpilation=dict(initial_layout=list(range(1, length + 1))),\n",
325 | ")\n",
326 | "\n",
327 | "with Session(service=service, backend=backend):\n",
328 | " estimator = Estimator(options=options)\n",
329 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
330 | "\n",
331 | "result = job.result()\n",
332 | "experiment_value = result.values[0]\n",
333 | "error = abs(experiment_value - exact_value)\n",
334 | "variance = result.metadata[0][\"variance\"]\n",
335 | "std = math.sqrt(variance / shots)\n",
336 | "\n",
337 | "print(f\"Estimated energy: {experiment_value}\")\n",
338 | "print(f\"Energy error: {error}\")\n",
339 | "print(f\"Variance: {variance}\")\n",
340 | "print(f\"Standard error: {std}\")"
341 | ]
342 | },
343 | {
344 | "attachments": {},
345 | "cell_type": "markdown",
346 | "metadata": {},
347 | "source": [
348 | "The error is smaller now, but still significant. Let's now enable readout error mitigation by setting `resilience_level = 1`."
349 | ]
350 | },
351 | {
352 | "cell_type": "code",
353 | "execution_count": null,
354 | "metadata": {},
355 | "outputs": [],
356 | "source": [
357 | "options = Options(\n",
358 | " simulator=dict(noise_model=noise_model, **simulator),\n",
359 | " resilience_level=1,\n",
360 | " transpilation=dict(initial_layout=list(range(1, length + 1))),\n",
361 | ")\n",
362 | "\n",
363 | "with Session(service=service, backend=backend):\n",
364 | " estimator = Estimator(options=options)\n",
365 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
366 | "\n",
367 | "result = job.result()\n",
368 | "experiment_value = result.values[0]\n",
369 | "error = abs(experiment_value - exact_value)\n",
370 | "variance = result.metadata[0][\"variance\"]\n",
371 | "std = math.sqrt(variance / shots)\n",
372 | "\n",
373 | "print(f\"Estimated energy: {experiment_value}\")\n",
374 | "print(f\"Energy error: {error}\")\n",
375 | "print(f\"Variance: {variance}\")\n",
376 | "print(f\"Standard error: {std}\")"
377 | ]
378 | },
379 | {
380 | "attachments": {},
381 | "cell_type": "markdown",
382 | "metadata": {},
383 | "source": [
384 | "Now, the effect of readout error has been almost completely mitigated! This mitigation did not come for free. In particular,\n",
385 | "- To perform readout error mitigation, the Runtime service has to run additional calibration circuits, so the overall running time may be longer.\n",
386 | "- The variance of the estimator has increased, leading to a larger standard error of the mean. As a consequence, a larger number of shots needs to be specified in order to achieve a given standard error.\n",
387 | "\n",
388 | "Typically, these costs are relatively small, so it is almost always worthwhile to enable readout error mitigation."
389 | ]
390 | },
391 | {
392 | "attachments": {},
393 | "cell_type": "markdown",
394 | "metadata": {},
395 | "source": [
396 | "#### Exercise 2\n",
397 | "\n",
398 | "Suppose that turning on readout error mitigation increases the variance of your estimator by a factor of 2. If you originally ran your experiment with 10,000 shots, how many shots should you now use to achieve the same standard error of the mean?"
399 | ]
400 | },
401 | {
402 | "cell_type": "code",
403 | "execution_count": null,
404 | "metadata": {},
405 | "outputs": [],
406 | "source": [
407 | "new_shots: int\n",
408 | "\n",
409 | "##### your code here #####\n",
410 | "\n",
411 | "new_shots = 20000"
412 | ]
413 | },
414 | {
415 | "cell_type": "code",
416 | "execution_count": null,
417 | "metadata": {},
418 | "outputs": [],
419 | "source": [
420 | "# Submit your answer\n",
421 | "\n",
422 | "from qc_grader.challenges.qgss_2023 import grade_lab5_ex2\n",
423 | "\n",
424 | "grade_lab5_ex2(new_shots)"
425 | ]
426 | },
427 | {
428 | "attachments": {},
429 | "cell_type": "markdown",
430 | "metadata": {},
431 | "source": [
432 | "### Depolarizing error and zero-noise extrapolation\n",
433 | "\n",
434 | "In this section, we will see how depolarizing error can be mitigated using zero-noise extrapolation. Because the zero-noise extrapolation feature of Qiskit Runtime is still in beta, it currently has a few limitations. In particular, as of the time of this writing, the zero-noise extrapolation feature does not mitigate readout error. Therefore, in the examples below, we will remove readout error from our noise model."
435 | ]
436 | },
437 | {
438 | "attachments": {},
439 | "cell_type": "markdown",
440 | "metadata": {},
441 | "source": [
442 | "#### Exercise 3\n",
443 | "\n",
444 | "Construct a noise model that adds two-qubit depolarizing error after each CNOT gate, such that the error channel maps the input quantum state to the completely mixed state with 1% probability."
445 | ]
446 | },
447 | {
448 | "cell_type": "code",
449 | "execution_count": null,
450 | "metadata": {},
451 | "outputs": [],
452 | "source": [
453 | "from qiskit_aer.noise import depolarizing_error\n",
454 | "\n",
455 | "noise_model = NoiseModel()\n",
456 | "\n",
457 | "##### your code here #####\n",
458 | "\n",
459 | "noise_model.add_all_qubit_quantum_error(depolarizing_error(0.01, 2), [\"cx\"])\n",
460 | "\n",
461 | "print(noise_model)"
462 | ]
463 | },
464 | {
465 | "cell_type": "code",
466 | "execution_count": null,
467 | "metadata": {},
468 | "outputs": [],
469 | "source": [
470 | "# Submit your answer\n",
471 | "\n",
472 | "from qc_grader.challenges.qgss_2023 import grade_lab5_ex3\n",
473 | "\n",
474 | "grade_lab5_ex3(noise_model)"
475 | ]
476 | },
477 | {
478 | "attachments": {},
479 | "cell_type": "markdown",
480 | "metadata": {},
481 | "source": [
482 | "Let's run the estimator with `resilience_level = 1`, which turns on readout error mitigation. Because our noise model doesn't include readout error mitigation, we don't expect this to help."
483 | ]
484 | },
485 | {
486 | "cell_type": "code",
487 | "execution_count": null,
488 | "metadata": {},
489 | "outputs": [],
490 | "source": [
491 | "options = Options(\n",
492 | " simulator=dict(noise_model=noise_model, **simulator),\n",
493 | " resilience_level=1,\n",
494 | ")\n",
495 | "\n",
496 | "with Session(service=service, backend=backend):\n",
497 | " estimator = Estimator(options=options)\n",
498 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
499 | "\n",
500 | "result = job.result()\n",
501 | "experiment_value = result.values[0]\n",
502 | "error = abs(experiment_value - exact_value)\n",
503 | "variance = result.metadata[0][\"variance\"]\n",
504 | "std = math.sqrt(variance / shots)\n",
505 | "\n",
506 | "print(f\"Estimated energy: {experiment_value}\")\n",
507 | "print(f\"Energy error: {error}\")\n",
508 | "print(f\"Variance: {variance}\")\n",
509 | "print(f\"Standard error: {std}\")"
510 | ]
511 | },
512 | {
513 | "attachments": {},
514 | "cell_type": "markdown",
515 | "metadata": {},
516 | "source": [
517 | "As expected, the error we get is pretty significant.\n",
518 | "\n",
519 | "Now, let's turn on zero-noise extrapolation by setting `resilience_level = 2`."
520 | ]
521 | },
522 | {
523 | "cell_type": "code",
524 | "execution_count": null,
525 | "metadata": {},
526 | "outputs": [],
527 | "source": [
528 | "options = Options(\n",
529 | " simulator=dict(noise_model=noise_model, **simulator),\n",
530 | " resilience_level=2,\n",
531 | ")\n",
532 | "\n",
533 | "with Session(service=service, backend=backend):\n",
534 | " estimator = Estimator(options=options)\n",
535 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
536 | "\n",
537 | "result = job.result()\n",
538 | "experiment_value = result.values[0]\n",
539 | "error = abs(experiment_value - exact_value)\n",
540 | "variances = result.metadata[0][\"zne\"][\"noise_amplification\"][\"variance\"]\n",
541 | "\n",
542 | "print(f\"Estimated energy: {experiment_value}\")\n",
543 | "print(f\"Energy error: {error}\")\n",
544 | "print(f\"Variances: {variances}\")"
545 | ]
546 | },
547 | {
548 | "attachments": {},
549 | "cell_type": "markdown",
550 | "metadata": {},
551 | "source": [
552 | "Now, the effect of depolarizing noise has been almost completely mitigated! Note that instead of getting a single variance value for the estimator, we are now returned a list of variances, one for each data point measured for the extrapolation. In a future version of Qiskit Runtime, these variances will also be extrapolated to return a single variance for the final estimator."
553 | ]
554 | },
555 | {
556 | "attachments": {},
557 | "cell_type": "markdown",
558 | "metadata": {},
559 | "source": [
560 | "#### Exercise 4 (ungraded)\n",
561 | "\n",
562 | "Besides depolarizing error, what other kinds of noise can be mitigated by zero-noise extrapolation? Test your proposals by constructing other noise models, and then simulating them with and without zero-noise extrapolation."
563 | ]
564 | },
565 | {
566 | "cell_type": "code",
567 | "execution_count": null,
568 | "metadata": {},
569 | "outputs": [],
570 | "source": [
571 | "from qiskit_aer.noise import depolarizing_error, amplitude_damping_error\n",
572 | "\n",
573 | "noise_model = NoiseModel()\n",
574 | "\n",
575 | "##### your code here #####\n",
576 | "\n",
577 | "noise_model.add_all_qubit_quantum_error(amplitude_damping_error(0.01).tensor(amplitude_damping_error(0.05)), [\"cx\"])\n",
578 | "\n",
579 | "print(noise_model)"
580 | ]
581 | },
582 | {
583 | "cell_type": "code",
584 | "execution_count": null,
585 | "metadata": {},
586 | "outputs": [],
587 | "source": [
588 | "options = Options(\n",
589 | " simulator=dict(noise_model=noise_model, **simulator),\n",
590 | " resilience_level=1,\n",
591 | ")\n",
592 | "\n",
593 | "with Session(service=service, backend=backend):\n",
594 | " estimator = Estimator(options=options)\n",
595 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
596 | "\n",
597 | "result = job.result()\n",
598 | "experiment_value = result.values[0]\n",
599 | "error = abs(experiment_value - exact_value)\n",
600 | "variance = result.metadata[0][\"variance\"]\n",
601 | "std = math.sqrt(variance / shots)\n",
602 | "\n",
603 | "print(f\"Estimated energy: {experiment_value}\")\n",
604 | "print(f\"Energy error: {error}\")\n",
605 | "print(f\"Variance: {variance}\")\n",
606 | "print(f\"Standard error: {std}\")"
607 | ]
608 | },
609 | {
610 | "cell_type": "code",
611 | "execution_count": null,
612 | "metadata": {},
613 | "outputs": [],
614 | "source": [
615 | "options = Options(\n",
616 | " simulator=dict(noise_model=noise_model, **simulator),\n",
617 | " resilience_level=2,\n",
618 | ")\n",
619 | "\n",
620 | "with Session(service=service, backend=backend):\n",
621 | " estimator = Estimator(options=options)\n",
622 | " job = estimator.run(circuit, hamiltonian, shots=shots)\n",
623 | "\n",
624 | "result = job.result()\n",
625 | "experiment_value = result.values[0]\n",
626 | "error = abs(experiment_value - exact_value)\n",
627 | "variances = result.metadata[0][\"zne\"][\"noise_amplification\"][\"variance\"]\n",
628 | "\n",
629 | "print(f\"Estimated energy: {experiment_value}\")\n",
630 | "print(f\"Energy error: {error}\")\n",
631 | "print(f\"Variances: {variances}\")"
632 | ]
633 | }
634 | ],
635 | "metadata": {
636 | "kernelspec": {
637 | "display_name": "qgss-2023-svnk7ds3",
638 | "language": "python",
639 | "name": "python3"
640 | },
641 | "language_info": {
642 | "codemirror_mode": {
643 | "name": "ipython",
644 | "version": 3
645 | },
646 | "file_extension": ".py",
647 | "mimetype": "text/x-python",
648 | "name": "python",
649 | "nbconvert_exporter": "python",
650 | "pygments_lexer": "ipython3",
651 | "version": "3.10.11"
652 | },
653 | "orig_nbformat": 4
654 | },
655 | "nbformat": 4,
656 | "nbformat_minor": 2
657 | }
658 |
--------------------------------------------------------------------------------