├── 01-gate-model-theory
├── annotated-gate-model-theory.pdf
├── blank-gate-model-theory.pdf
├── gate-model-bonus.pdf
├── gate-model-theory.pdf
└── notebooks
│ ├── Deutsch-Algorithm.ipynb
│ ├── Making-Entangled-States.ipynb
│ ├── Single-Qubit-Gates.ipynb
│ ├── Superdense-Coding.ipynb
│ ├── Teleportation.ipynb
│ └── bloch-sphere-movies
│ ├── hadamard_gate_animated.webm
│ ├── s_gate_animated.webm
│ ├── t_gate_animated.webm
│ ├── x_gate_animated.webm
│ ├── y_rotation_animated.webm
│ └── z_rotation_animated.webm
├── 02-gate-model-applications
├── annotated-gate-model-applications.pdf
├── blank-gate-model-applications.pdf
├── bonus-slides-noise.pdf
├── bonus-slides-qft.pdf
├── gate-model-applications.pdf
└── notebooks
│ ├── Neutrino-Oscillations.ipynb
│ ├── Quantum-Phase-Estimation.ipynb
│ ├── Solved-Neutrino-Oscillations.ipynb
│ ├── Solved-Quantum-Phase-Estimation.ipynb
│ └── qft_mini.png
├── 03-annealing-theory
├── annealing-theory.pdf
├── annotated-annealing-theory.pdf
└── blank-annealing-theory.pdf
├── 04-annealing-applications
├── Minor-Embeddings.ipynb
├── Vertex-Cover.ipynb
├── annealing-applications.pdf
├── annotated-annealing-applications.pdf
└── blank-annealing-applications.pdf
├── LICENSE
├── README.md
├── Resources.md
├── fall-students-2019
├── lecture-01-annotated.pdf
├── lecture-01-full.pdf
├── lecture-02-annotated.pdf
└── lecture-02-full.pdf
├── summer-students-2019
├── lecture-01-annotated.pdf
├── lecture-01-blank.pdf
├── lecture-01-unclear-topics.pdf
├── lecture-01.pdf
├── lecture-02-blank.pdf
└── lecture-02.pdf
├── syllabus.pdf
└── winter-students-2020
├── lecture-01-annotated.pdf
├── lecture-01.pdf
├── lecture-02-annotated.pdf
└── lecture-02.pdf
/01-gate-model-theory/annotated-gate-model-theory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/annotated-gate-model-theory.pdf
--------------------------------------------------------------------------------
/01-gate-model-theory/blank-gate-model-theory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/blank-gate-model-theory.pdf
--------------------------------------------------------------------------------
/01-gate-model-theory/gate-model-bonus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/gate-model-bonus.pdf
--------------------------------------------------------------------------------
/01-gate-model-theory/gate-model-theory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/gate-model-theory.pdf
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/Deutsch-Algorithm.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Deutsch's algorithm"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import numpy as np\n",
17 | "\n",
18 | "from qiskit import QuantumRegister, ClassicalRegister\n",
19 | "from qiskit import QuantumCircuit\n",
20 | "from qiskit import execute, BasicAer\n",
21 | "\n",
22 | "import qiskit.tools.visualization as qvis\n",
23 | "\n",
24 | "import warnings\n",
25 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {},
31 | "source": [
32 | "Deutsch's algorithm allows us to perform a task using fewer queries quantumly than are needed classically.\n",
33 | "\n",
34 | "Suppose we have some function $f$, that we know to be one of the 4 following functions:\n",
35 | "\n",
36 | "| Function ($f$) | $f$(0) | $f$(1) |\n",
37 | "| --- | -------- | -------- |\n",
38 | "| `constant_zero`| 0 | 0|\n",
39 | "| `constant_one` | 1| 1 |\n",
40 | "| `balanced_id` | 0| 1 |\n",
41 | "| `balanced_not`| 1 | 0 |\n",
42 | "\n",
43 | "The two \"constant\" functions output the same value no matter the input, whereas the balanced functions output different values for each input.\n"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "## Our task\n",
51 | "\n",
52 | "We are given a black box that we know implements one of: `constant_zero`, `constant_one`, `balanced_id`, `balanced_not`. We are allowed to query the box (i.e. ask for $f(x)$ for either $x= 0, 1$) as many times as we need. _How many queries are needed to determine with certainty which function the box implements_?\n",
53 | "\n",
54 | "Let's choose one of the four functions randomly."
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "metadata": {},
61 | "outputs": [],
62 | "source": [
63 | "function_names = ['constant_zero', 'constant_one', 'balanced_id', 'balanced_not']\n",
64 | "oracle = function_names[np.random.randint(4)]"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "metadata": {},
70 | "source": [
71 | "### Classical solution\n",
72 | "\n",
73 | "We _must_ query twice. For example, if we query $f(0)$ and receive $0$, we know we must have `constant_zero` or `balanced_id`, but we still can't pin down the exact function without querying a second time. "
74 | ]
75 | },
76 | {
77 | "cell_type": "markdown",
78 | "metadata": {},
79 | "source": [
80 | "### Quantum solution\n",
81 | "\n",
82 | "We can perform this task with only a _single_ query using two qubits."
83 | ]
84 | },
85 | {
86 | "cell_type": "markdown",
87 | "metadata": {},
88 | "source": [
89 | "Our oracle $U_f$ is going to implement the following transformation:\n",
90 | "\\begin{equation}\n",
91 | " U_f|x\\rangle|y\\rangle = |x\\rangle|y\\oplus f(x) \\rangle\n",
92 | "\\end{equation}\n",
93 | "where $f$ will be replaced with one of the four functions above, and $\\oplus$ indicates addition modulo 2.\n",
94 | "\n",
95 | "For example, if $f$ is `constant_one`,\n",
96 | "\\begin{eqnarray}\n",
97 | " U_f|00\\rangle &=& |01\\rangle \\\\\n",
98 | " U_f|01\\rangle &=& |00\\rangle \\\\\n",
99 | " U_f|10\\rangle &=& |11\\rangle \\\\\n",
100 | " U_f|11\\rangle &=& |10\\rangle\n",
101 | "\\end{eqnarray}\n",
102 | "\n",
103 | "From this you can see that each function will produce a different permutation of the computational basis states. \n",
104 | "\n",
105 | "**Exercise**: Compute the unitary matrix associated with each function, and write out the quantum circuit that implements it. (The answers are shown further down because we need them for the demo, but you can still compute them yourself to understand how they work.)"
106 | ]
107 | },
108 | {
109 | "cell_type": "markdown",
110 | "metadata": {},
111 | "source": [
112 | "To execute Deutsch's algorithm, we'll follow the steps as shown in class. We begin with the state \n",
113 | "\\begin{equation} |+-\\rangle =\n",
114 | " \\left(\\frac{|0\\rangle + |1\\rangle}{\\sqrt{2}}\\right) \\otimes \\left(\\frac{|0\\rangle - |1\\rangle}{\\sqrt{2}}\\right) = \\frac{1}{2}\\left(|00\\rangle - |01\\rangle + |10\\rangle - |11\\rangle \\right)\n",
115 | "\\end{equation}"
116 | ]
117 | },
118 | {
119 | "cell_type": "markdown",
120 | "metadata": {},
121 | "source": [
122 | "Let's set up the quantum and classical registers and prepare it:"
123 | ]
124 | },
125 | {
126 | "cell_type": "code",
127 | "execution_count": null,
128 | "metadata": {},
129 | "outputs": [],
130 | "source": [
131 | "q = QuantumRegister(2)\n",
132 | "c = ClassicalRegister(1)\n",
133 | "\n",
134 | "circ = QuantumCircuit(q, c)"
135 | ]
136 | },
137 | {
138 | "cell_type": "code",
139 | "execution_count": null,
140 | "metadata": {},
141 | "outputs": [],
142 | "source": [
143 | "# Prepares the state |+-> starting from |00>\n",
144 | "circ.h(q[0])\n",
145 | "circ.x(q[1])\n",
146 | "circ.h(q[1]);"
147 | ]
148 | },
149 | {
150 | "cell_type": "markdown",
151 | "metadata": {},
152 | "source": [
153 | "Let's now apply the oracle circuit:"
154 | ]
155 | },
156 | {
157 | "cell_type": "code",
158 | "execution_count": null,
159 | "metadata": {},
160 | "outputs": [],
161 | "source": [
162 | "# Here are the quantum circuits for each of our functions\n",
163 | "# If the circuit is 'constant_zero', we do nothing.\n",
164 | "if oracle == 'constant_one':\n",
165 | " circ.x(q[1])\n",
166 | "elif oracle == 'balanced_id':\n",
167 | " circ.cx(q[0], q[1])\n",
168 | "elif oracle == 'balanced_not':\n",
169 | " circ.x(q[0])\n",
170 | " circ.cx(q[0], q[1])"
171 | ]
172 | },
173 | {
174 | "cell_type": "markdown",
175 | "metadata": {},
176 | "source": [
177 | "We saw in the lectures that after calling the oracle, if the function is constant, we get a $|+\\rangle$ on the first qubit, but when it is balanced, we get $|-\\rangle$. We can combine all these results into a compact form:\n",
178 | "\n",
179 | "\\begin{equation}\n",
180 | " U_f|+\\rangle |- \\rangle = \\left( \\frac{|0\\rangle + (-1)^{f(0) \\oplus f(1)}|1\\rangle}{\\sqrt{2}} \\right) \\otimes \\left( \\frac{|0\\rangle - |1\\rangle}{\\sqrt{2}} \\right)\n",
181 | " \\end{equation}"
182 | ]
183 | },
184 | {
185 | "cell_type": "markdown",
186 | "metadata": {},
187 | "source": [
188 | "We can then determine the value of $f(0) \\oplus f(1)$, by applying a Hadamard to the first qubit to perform a basis change back to the computational basis, and then measuring. We should obtain:\n",
189 | "\\begin{equation}\n",
190 | " |f(0)\\oplus f(1) \\rangle \\otimes |-\\rangle\n",
191 | "\\end{equation}\n",
192 | "\n",
193 | "If the function is constant, $f(0) \\oplus f(1) = 0$ and so the first qubit will be in state $|0\\rangle$. Similarly, if it is balanced, the first qubit will be in state $|1\\rangle$. Does this work?"
194 | ]
195 | },
196 | {
197 | "cell_type": "code",
198 | "execution_count": null,
199 | "metadata": {},
200 | "outputs": [],
201 | "source": [
202 | "# Convert the first qubit back to the computational basis and measure\n",
203 | "circ.h(q[0])\n",
204 | "circ.measure(q[0], c[0])\n",
205 | "\n",
206 | "# Execute the circuit a single time to get the measurement outcome\n",
207 | "backend = BasicAer.get_backend('qasm_simulator')\n",
208 | "result = execute(circ, backend, shots = 1).result()\n",
209 | "counts = result.get_counts()\n",
210 | "\n",
211 | "if list(counts.keys())[0] == '0': # c[0] is a tuple of the register and the measurement value\n",
212 | " print(\"Measured first qubit in state |0>, function is constant.\")\n",
213 | "else:\n",
214 | " print(\"Measured first qubit in state |1>, function is balanced.\")"
215 | ]
216 | },
217 | {
218 | "cell_type": "markdown",
219 | "metadata": {},
220 | "source": [
221 | "Were we correct? Let's see which function the oracle had chosen to implement."
222 | ]
223 | },
224 | {
225 | "cell_type": "code",
226 | "execution_count": null,
227 | "metadata": {},
228 | "outputs": [],
229 | "source": [
230 | "print(f\"The function implemented by the oracle is '{oracle}'\")"
231 | ]
232 | },
233 | {
234 | "cell_type": "markdown",
235 | "metadata": {},
236 | "source": [
237 | "Deutsch's algorithm can also be generalized to the Deutsch-Josza algorithm when $f$ is a function on $n$ bits. No matter the value of $n$, it is still possible to determine whether $f$ is constant or balanced with a single query."
238 | ]
239 | }
240 | ],
241 | "metadata": {
242 | "kernelspec": {
243 | "display_name": "Python 3",
244 | "language": "python",
245 | "name": "python3"
246 | },
247 | "language_info": {
248 | "codemirror_mode": {
249 | "name": "ipython",
250 | "version": 3
251 | },
252 | "file_extension": ".py",
253 | "mimetype": "text/x-python",
254 | "name": "python",
255 | "nbconvert_exporter": "python",
256 | "pygments_lexer": "ipython3",
257 | "version": "3.6.5"
258 | }
259 | },
260 | "nbformat": 4,
261 | "nbformat_minor": 2
262 | }
263 |
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/Making-Entangled-States.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Circuits for constructing entangled states"
8 | ]
9 | },
10 | {
11 | "cell_type": "code",
12 | "execution_count": null,
13 | "metadata": {},
14 | "outputs": [],
15 | "source": [
16 | "import numpy as np\n",
17 | "\n",
18 | "from qiskit import QuantumRegister, ClassicalRegister\n",
19 | "from qiskit import QuantumCircuit\n",
20 | "from qiskit import execute, BasicAer\n",
21 | "\n",
22 | "import qiskit.tools.visualization as qvis\n",
23 | "\n",
24 | "import warnings\n",
25 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
26 | ]
27 | },
28 | {
29 | "cell_type": "markdown",
30 | "metadata": {},
31 | "source": [
32 | "In the lecture I showed you the entangled state\n",
33 | "\\begin{eqnarray}\n",
34 | " |\\Psi_\\rangle &=& \\frac{1}{\\sqrt{2}} \\left( |00\\rangle + |11 \\rangle \\right).\n",
35 | "\\end{eqnarray}\n",
36 | "\n",
37 | "But this isn't the only two-qubit entangled states. In fact, this state is just one of the four included in the so-called 'Bell basis', a set of 4 orthonormal 2-qubit entangled states. The Bell basis is especially important in the quantum teleporation protocol found in the other notebooks.\n",
38 | "\n",
39 | "Your job here is to write small quantum circuits to construct the four states in the Bell basis\n",
40 | "\\begin{eqnarray}\n",
41 | "|\\Psi_{0}\\rangle &=& \\frac{1}{\\sqrt{2}} \\left( |00\\rangle + |11 \\rangle \\right) \\\\\n",
42 | "|\\Psi_{1}\\rangle &=& \\frac{1}{\\sqrt{2}} \\left( |01\\rangle + |10 \\rangle \\right) \\\\\n",
43 | "|\\Psi_{2}\\rangle &=& \\frac{1}{\\sqrt{2}} \\left( |00\\rangle - |11 \\rangle \\right) \\\\\n",
44 | "|\\Psi_{3}\\rangle &=& \\frac{1}{\\sqrt{2}} \\left( |01\\rangle - |10 \\rangle \\right) \\\\\n",
45 | "\\end{eqnarray}\n",
46 | "Below is some boilerplate code for creating the circuits and outputting the state vector so you can be sure you got the right one."
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "q = QuantumRegister(2, name='q')\n",
56 | "bell_circuit = QuantumCircuit(q)\n",
57 | "\n",
58 | "# YOUR CODE GOES HERE \n",
59 | "# Construct the Bell states here by applying Qiskit gates\n",
60 | "# You can copy the code into 4 different cells or just modify in here\n",
61 | "# to get the different states\n",
62 | "bell_circuit.h(q[0])\n",
63 | "bell_circuit.cx(q[0], q[1])\n",
64 | "\n",
65 | "# Draw the circuit\n",
66 | "print(bell_circuit.draw())\n",
67 | "\n",
68 | "# Execute the quantum circuit and print the state vector\n",
69 | "backend = BasicAer.get_backend('statevector_simulator')\n",
70 | "result = execute(bell_circuit, backend).result()\n",
71 | "print(\"The state vector in the computational basis is:\")\n",
72 | "print(result.get_statevector(bell_circuit).reshape(4, 1))"
73 | ]
74 | },
75 | {
76 | "cell_type": "markdown",
77 | "metadata": {},
78 | "source": [
79 | "## Higher-dimensional entangled states"
80 | ]
81 | },
82 | {
83 | "cell_type": "markdown",
84 | "metadata": {},
85 | "source": [
86 | "Entanglement is not limited to two qubits, but can become much more complicated as there are many different 'separability structures' that a multi-qubit entangled state can take. For example, a 3-qubit state might be a tensor product of 3 single qubit states, a tensor product of a two-qubit entangled state with a single-qubit state, or even a purely entangled 3-qubit state (i.e. 'genuine multipartite entanglement').\n",
87 | "\n",
88 | "One such state is the GHZ state,\n",
89 | "\\begin{equation}\n",
90 | " |GHZ\\rangle = \\frac{1}{\\sqrt{2}} \\left( |000\\rangle + |111\\rangle \\right)\n",
91 | "\\end{equation}"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": null,
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "# YOUR CODE HERE\n",
101 | "# Create a circuit and construct the GHZ state. \n",
102 | "# You can copy the boilerplate code from the 2-qubit case but will of course have to\n",
103 | "# alter the register size and the gate sequence"
104 | ]
105 | }
106 | ],
107 | "metadata": {
108 | "kernelspec": {
109 | "display_name": "Python 3",
110 | "language": "python",
111 | "name": "python3"
112 | },
113 | "language_info": {
114 | "codemirror_mode": {
115 | "name": "ipython",
116 | "version": 3
117 | },
118 | "file_extension": ".py",
119 | "mimetype": "text/x-python",
120 | "name": "python",
121 | "nbconvert_exporter": "python",
122 | "pygments_lexer": "ipython3",
123 | "version": "3.6.5"
124 | }
125 | },
126 | "nbformat": 4,
127 | "nbformat_minor": 2
128 | }
129 |
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/Single-Qubit-Gates.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Playing with single-qubit gates"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "In this file we will visualize the behaviour of single-qubit rotation gates. I've written out some video-making functions to make it more exciting!\n",
15 | "\n",
16 | "**Important note**: The movies created here are stored on-disk and then read into the notebook player, so this script will be creating new files and directories on your HD."
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "metadata": {},
23 | "outputs": [],
24 | "source": [
25 | "import numpy as np\n",
26 | "\n",
27 | "from qiskit import QuantumRegister, ClassicalRegister\n",
28 | "from qiskit import QuantumCircuit\n",
29 | "from qiskit import execute, BasicAer\n",
30 | "\n",
31 | "import qiskit.tools.visualization as qvis\n",
32 | "\n",
33 | "import warnings\n",
34 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) \n",
35 | "\n",
36 | "import matplotlib.pyplot as plt\n",
37 | "# We will run a shell command to knit the image files together\n",
38 | "import os\n",
39 | "\n",
40 | "# For in-notebook video display\n",
41 | "import io\n",
42 | "import base64\n",
43 | "from IPython.display import HTML"
44 | ]
45 | },
46 | {
47 | "cell_type": "markdown",
48 | "metadata": {},
49 | "source": [
50 | "This function creates a movie given a name and a sequence of angles to rotate through. You can ignore this for now, but run it so you have the function for later."
51 | ]
52 | },
53 | {
54 | "cell_type": "code",
55 | "execution_count": null,
56 | "metadata": {},
57 | "outputs": [],
58 | "source": [
59 | "def create_movie(name, rotation_sequence, start_from_plus = False):\n",
60 | " # If start_from_plus is set to true, we'll apply a Hadamard before doing anything\n",
61 | " # else so that we initialize the qubit in the |+> state \n",
62 | " \n",
63 | " # Create the movie directory; overwrite if one of the same name is already present\n",
64 | " os.system(f\"rm -r {name}\")\n",
65 | " os.system(f\"mkdir {name}\")\n",
66 | "\n",
67 | " # Apply the rotations and save the images\n",
68 | " for idx_frame, rotation in enumerate(rotation_sequence):\n",
69 | " q = QuantumRegister(1)\n",
70 | " circ = QuantumCircuit(q)\n",
71 | "\n",
72 | " if start_from_plus:\n",
73 | " circ.h(q)\n",
74 | " \n",
75 | " circ.u3(*rotation, q)\n",
76 | "\n",
77 | " backend = BasicAer.get_backend('statevector_simulator') # the device to run on\n",
78 | " result = execute(circ, backend).result()\n",
79 | " psi = result.get_statevector(circ)\n",
80 | " img = qvis.plot_bloch_multivector(psi, title=f\"{name}\")\n",
81 | " img.savefig(f\"{name}/{name}_{str(idx_frame)}.png\")\n",
82 | " plt.show()\n",
83 | "\n",
84 | " # Create the movie\n",
85 | " os.system(f'ffmpeg -r 20 -i {name}/{name}_%01d.png {name}/{name}_animated.webm')\n",
86 | " \n",
87 | " # Return the movie for display - thank you stackoverflow!\n",
88 | " video = io.open(f'{os.getcwd()}/{name}/{name}_animated.webm', 'r+b').read()\n",
89 | " encoded = base64.b64encode(video)\n",
90 | " return HTML(data='''\n",
91 | " \n",
92 | " '''.format(encoded.decode('ascii')))"
93 | ]
94 | },
95 | {
96 | "cell_type": "markdown",
97 | "metadata": {},
98 | "source": [
99 | "In the cell below, we will choose form of our rotation and set up a series of angles to plot at. We will be using the u3 operator provided by Qiskit. u3 allows us to specify a 3 parameter rotation of the form\n",
100 | "\n",
101 | "\\begin{equation}\n",
102 | " u3(\\theta, \\phi, \\lambda) = \\begin{pmatrix}\n",
103 | " \\cos(\\theta/2) & -e^{i\\lambda}\\sin(\\theta/2) \\\\\n",
104 | " e^{i\\phi}\\sin(\\theta/2) & e^{i\\lambda + i\\phi} \\cos(\\theta/2)\n",
105 | " \\end{pmatrix}\n",
106 | "\\end{equation}\n",
107 | "\n",
108 | "Here are the parameterizations of u3 for the Pauli rotation gates:\n",
109 | "\n",
110 | "\\begin{eqnarray}\n",
111 | " R_x (\\theta) &=& u3 (\\theta, -\\pi/2, \\pi/2) \\\\\n",
112 | " R_y (\\theta) &=& u3 (\\theta, 0, 0) \\\\\n",
113 | " R_z (\\theta) &=& u3 (0, 0, \\theta)\n",
114 | "\\end{eqnarray}\n",
115 | "\n",
116 | "We can use these three alone to produce most of our universal gate set. For the Hadamard, though, we need something extra because it is not a rotation around one of the Cartesian axes. (It is in fact a rotation around $\\hat{x} + \\hat{z}$).\n",
117 | "\n",
118 | "\\begin{equation}\n",
119 | " H = u3(\\pi/2, 0, \\pi)\n",
120 | "\\end{equation}"
121 | ]
122 | },
123 | {
124 | "cell_type": "code",
125 | "execution_count": null,
126 | "metadata": {},
127 | "outputs": [],
128 | "source": [
129 | "# An X rotation\n",
130 | "rotation_angle = np.pi\n",
131 | "name = \"x_gate\" \n",
132 | "num_frames = 40 \n",
133 | "intermediate_angles = np.linspace(0, rotation_angle, num_frames)\n",
134 | "rotation_sequence = [(theta, -np.pi/2, np.pi/2) for theta in intermediate_angles] # The form of the tuple here specifies which gate you perform\n",
135 | "\n",
136 | "create_movie(name, rotation_sequence)"
137 | ]
138 | },
139 | {
140 | "cell_type": "code",
141 | "execution_count": null,
142 | "metadata": {},
143 | "outputs": [],
144 | "source": [
145 | "# A Y rotation\n",
146 | "rotation_angle = np.pi\n",
147 | "name = \"y_rotation\" \n",
148 | "num_frames = 40 \n",
149 | "intermediate_angles = np.linspace(0, rotation_angle, num_frames)\n",
150 | "rotation_sequence = [(theta, 0, 0) for theta in intermediate_angles] # The form of the tuple here specifies which gate you perform\n",
151 | "\n",
152 | "create_movie(name, rotation_sequence)"
153 | ]
154 | },
155 | {
156 | "cell_type": "code",
157 | "execution_count": null,
158 | "metadata": {},
159 | "outputs": [],
160 | "source": [
161 | "# A Z rotation\n",
162 | "rotation_angle = np.pi\n",
163 | "name = \"z_rotation\" \n",
164 | "num_frames = 40 \n",
165 | "intermediate_angles = np.linspace(0, rotation_angle, num_frames)\n",
166 | "rotation_sequence = [(0, 0, theta) for theta in intermediate_angles] # The form of the tuple here specifies which gate you perform\n",
167 | "\n",
168 | "create_movie(name, rotation_sequence, start_from_plus=True)"
169 | ]
170 | },
171 | {
172 | "cell_type": "code",
173 | "execution_count": null,
174 | "metadata": {},
175 | "outputs": [],
176 | "source": [
177 | "# S gate\n",
178 | "rotation_angle = np.pi/2\n",
179 | "name = \"s_gate\" \n",
180 | "num_frames = 40 \n",
181 | "intermediate_angles = np.linspace(0, rotation_angle, num_frames)\n",
182 | "rotation_sequence = [(0, 0, theta) for theta in intermediate_angles] # The form of the tuple here specifies which gate you perform\n",
183 | "\n",
184 | "create_movie(name, rotation_sequence, start_from_plus=True)"
185 | ]
186 | },
187 | {
188 | "cell_type": "code",
189 | "execution_count": null,
190 | "metadata": {},
191 | "outputs": [],
192 | "source": [
193 | "# T gate\n",
194 | "rotation_angle = np.pi/4\n",
195 | "name = \"t_gate\" \n",
196 | "num_frames = 40 \n",
197 | "intermediate_angles = np.linspace(0, rotation_angle, num_frames)\n",
198 | "rotation_sequence = [(0, 0, theta) for theta in intermediate_angles] # The form of the tuple here specifies which gate you perform\n",
199 | "\n",
200 | "create_movie(name, rotation_sequence, start_from_plus=True)"
201 | ]
202 | },
203 | {
204 | "cell_type": "markdown",
205 | "metadata": {},
206 | "source": [
207 | "To animate the Hadamard matrix, which is not an $x$, $y$, or $z$ rotation alone, we will need to use the general form of a unitary that creates a superposition. From the Qiskit documentation, that is $u3(\\pi/2, \\phi, \\lambda)$:\n",
208 | "\\begin{equation}\n",
209 | " u3(\\pi/2, \\phi, \\lambda) = \\frac{1}{\\sqrt{2}} \\begin{pmatrix}\n",
210 | " 1 & -e^{-i\\lambda} \\\\\n",
211 | " e^{i\\phi} & e^{i(\\phi+\\lambda)}\n",
212 | " \\end{pmatrix}\n",
213 | "\\end{equation}\n",
214 | "To get the Hadamard, we will need to play with the second *and* third parameters of the tuple going from 0 to $\\pi$."
215 | ]
216 | },
217 | {
218 | "cell_type": "code",
219 | "execution_count": null,
220 | "metadata": {},
221 | "outputs": [],
222 | "source": [
223 | "# Hadamard rotation\n",
224 | "name = \"hadamard_gate\" \n",
225 | "num_frames = 40 \n",
226 | "intermediate_angles_x = np.linspace(0, np.pi/2, num_frames)\n",
227 | "intermediate_angles_y = np.linspace(-np.pi/2, 0, num_frames)\n",
228 | "intermediate_angles_z = np.linspace(np.pi/2, np.pi, num_frames)\n",
229 | "rotation_sequence = [(intermediate_angles_x[i], intermediate_angles_y[i], intermediate_angles_z[i]) for i in range(num_frames)] \n",
230 | "create_movie(name, rotation_sequence)"
231 | ]
232 | },
233 | {
234 | "cell_type": "code",
235 | "execution_count": null,
236 | "metadata": {},
237 | "outputs": [],
238 | "source": []
239 | }
240 | ],
241 | "metadata": {
242 | "kernelspec": {
243 | "display_name": "Python 3",
244 | "language": "python",
245 | "name": "python3"
246 | },
247 | "language_info": {
248 | "codemirror_mode": {
249 | "name": "ipython",
250 | "version": 3
251 | },
252 | "file_extension": ".py",
253 | "mimetype": "text/x-python",
254 | "name": "python",
255 | "nbconvert_exporter": "python",
256 | "pygments_lexer": "ipython3",
257 | "version": "3.6.5"
258 | }
259 | },
260 | "nbformat": 4,
261 | "nbformat_minor": 2
262 | }
263 |
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/Superdense-Coding.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Superdense coding"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "Alice has 2 classical bits she wants to send to Bob. Without quantum computing, Alice would have to send Bob 2 bits, one per piece information. But in fact, it is possible to send both bits using just 1 qubit!\n",
15 | "\n",
16 | "Like many protocols in quantum computing, the key is making use of the entangled state\n",
17 | "\\begin{equation}\n",
18 | " |\\Psi\\rangle = \\frac{1}{\\sqrt{2}} \\left( |00\\rangle + |11 \\rangle \\right)\n",
19 | "\\end{equation}\n",
20 | "\n",
21 | "We will see that if Alice and Bob each have a qubit of this entangled state, Alice can manipulate her qubit such that Bob is able to obtain both bits."
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": 1,
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "import numpy as np\n",
31 | "\n",
32 | "from qiskit import QuantumRegister, ClassicalRegister\n",
33 | "from qiskit import QuantumCircuit\n",
34 | "from qiskit import execute, BasicAer\n",
35 | "\n",
36 | "import qiskit.tools.visualization as qvis\n",
37 | "\n",
38 | "import warnings\n",
39 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "Let's give Alice two bits at random."
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": 2,
52 | "metadata": {},
53 | "outputs": [
54 | {
55 | "name": "stdout",
56 | "output_type": "stream",
57 | "text": [
58 | "Alice wants to send Bob the bits 0 and 1.\n"
59 | ]
60 | }
61 | ],
62 | "source": [
63 | "alice_bits = [int(x) for x in np.round(np.random.rand(2))]\n",
64 | "print(f\"Alice wants to send Bob the bits {alice_bits[0]} and {alice_bits[1]}.\")"
65 | ]
66 | },
67 | {
68 | "cell_type": "markdown",
69 | "metadata": {},
70 | "source": [
71 | "First, let's set up the shared entangled state."
72 | ]
73 | },
74 | {
75 | "cell_type": "code",
76 | "execution_count": 3,
77 | "metadata": {},
78 | "outputs": [
79 | {
80 | "data": {
81 | "text/html": [
82 | "
┌───┐ \n",
83 | "a_0: |0>┤ H ├──■──\n",
84 | " └───┘┌─┴─┐\n",
85 | "b_0: |0>─────┤ X ├\n",
86 | " └───┘\n",
87 | " m_0: 0 ══════════\n",
88 | " \n",
89 | " m_1: 0 ══════════\n",
90 | " "
91 | ],
92 | "text/plain": [
93 | ""
94 | ]
95 | },
96 | "execution_count": 3,
97 | "metadata": {},
98 | "output_type": "execute_result"
99 | }
100 | ],
101 | "source": [
102 | "# One qubit each for Alice and Bob\n",
103 | "alice = QuantumRegister(1, 'a')\n",
104 | "bob = QuantumRegister(1, 'b')\n",
105 | "\n",
106 | "# Bob needs to measure his qubits, so this is a classical register to store results\n",
107 | "bob_outputs = ClassicalRegister(2, 'm')\n",
108 | "\n",
109 | "# Initialize a quantum circuit\n",
110 | "superdense_circuit = QuantumCircuit(alice, bob, bob_outputs)\n",
111 | "\n",
112 | "# Create the entangled state\n",
113 | "superdense_circuit.h(alice[0])\n",
114 | "superdense_circuit.cx(alice[0], bob[0]);\n",
115 | "\n",
116 | "superdense_circuit.draw()"
117 | ]
118 | },
119 | {
120 | "cell_type": "markdown",
121 | "metadata": {},
122 | "source": [
123 | "Now, based on Alice's bits, she's going to perform one of the following operations to her qubit:\n",
124 | "\\begin{eqnarray}\n",
125 | " 00 &\\rightarrow& I \\\\\n",
126 | " 01 &\\rightarrow& X \\\\\n",
127 | " 10 &\\rightarrow& Z \\\\\n",
128 | " 11 &\\rightarrow& ZX \\\\\n",
129 | "\\end{eqnarray}"
130 | ]
131 | },
132 | {
133 | "cell_type": "code",
134 | "execution_count": 4,
135 | "metadata": {},
136 | "outputs": [
137 | {
138 | "name": "stdout",
139 | "output_type": "stream",
140 | "text": [
141 | "Alice applies the X gate.\n"
142 | ]
143 | },
144 | {
145 | "data": {
146 | "text/html": [
147 | " ┌───┐ ┌───┐\n",
148 | "a_0: |0>┤ H ├──■──┤ X ├\n",
149 | " └───┘┌─┴─┐└───┘\n",
150 | "b_0: |0>─────┤ X ├─────\n",
151 | " └───┘ \n",
152 | " m_0: 0 ═══════════════\n",
153 | " \n",
154 | " m_1: 0 ═══════════════\n",
155 | " "
156 | ],
157 | "text/plain": [
158 | ""
159 | ]
160 | },
161 | "execution_count": 4,
162 | "metadata": {},
163 | "output_type": "execute_result"
164 | }
165 | ],
166 | "source": [
167 | "if alice_bits[0] == 0 and alice_bits[1] == 1:\n",
168 | " superdense_circuit.x(alice[0])\n",
169 | " print(\"Alice applies the X gate.\")\n",
170 | "elif alice_bits[0] == 1 and alice_bits[1] == 0:\n",
171 | " superdense_circuit.z(alice[0])\n",
172 | " print(\"Alice applies the Z gate.\")\n",
173 | "elif alice_bits[0] == 1 and alice_bits[1] == 1:\n",
174 | " superdense_circuit.x(alice[0])\n",
175 | " superdense_circuit.z(alice[0])\n",
176 | " print(\"Alice applies the X gate followed by the Z gate.\")\n",
177 | "else:\n",
178 | " print(\"Alice does nothing else to her qubits.\")\n",
179 | " \n",
180 | "superdense_circuit.draw()"
181 | ]
182 | },
183 | {
184 | "cell_type": "markdown",
185 | "metadata": {},
186 | "source": [
187 | "You might notice that this looks suspiciously similar to the circuits we made in notebook 01-03, where we constructed all four Bell states - this is exactly what Alice has done! She has performed a basis change; based on her first two bits, the two qubits are now in the state\n",
188 | "\n",
189 | "\\begin{eqnarray}\n",
190 | " 00 &\\rightarrow& \\frac{1}{\\sqrt{2}} \\left( |00\\rangle + |11 \\rangle \\right) \\\\\n",
191 | " 01 &\\rightarrow& \\frac{1}{\\sqrt{2}} \\left( |10\\rangle + |01 \\rangle \\right) \\\\\n",
192 | " 10 &\\rightarrow& \\frac{1}{\\sqrt{2}} \\left( |00\\rangle - |11 \\rangle \\right) \\\\\n",
193 | " 11 &\\rightarrow& \\frac{1}{\\sqrt{2}} \\left( |01\\rangle - |10 \\rangle \\right) \n",
194 | "\\end{eqnarray}"
195 | ]
196 | },
197 | {
198 | "cell_type": "markdown",
199 | "metadata": {},
200 | "source": [
201 | "Alice proceeds to send her qubit to Bob. With both qubits in hand, Bob has to make a measurement in the Bell basis. Depending on which of the four states he gets, he will know exactly which bits Alice sent.\n",
202 | "\n",
203 | "Note: Qiskit doesn't have an implementation for measuring directly in the Bell basis, so we will need to do a basis change to get us back to the computational basis. This is just a CNOT from qubit 1 to 2 and then a Hadamard on qubit 1."
204 | ]
205 | },
206 | {
207 | "cell_type": "code",
208 | "execution_count": 5,
209 | "metadata": {},
210 | "outputs": [
211 | {
212 | "data": {
213 | "text/html": [
214 | " ┌───┐ ┌───┐ ┌───┐\n",
215 | "a_0: |0>┤ H ├──■──┤ X ├──■──┤ H ├\n",
216 | " └───┘┌─┴─┐└───┘┌─┴─┐└───┘\n",
217 | "b_0: |0>─────┤ X ├─────┤ X ├─────\n",
218 | " └───┘ └───┘ \n",
219 | " m_0: 0 ═════════════════════════\n",
220 | " \n",
221 | " m_1: 0 ═════════════════════════\n",
222 | " "
223 | ],
224 | "text/plain": [
225 | ""
226 | ]
227 | },
228 | "execution_count": 5,
229 | "metadata": {},
230 | "output_type": "execute_result"
231 | }
232 | ],
233 | "source": [
234 | "# Basis change from Bell basis to computational basis\n",
235 | "superdense_circuit.cx(alice[0], bob[0]) # Technically Bob now has both qubits\n",
236 | "superdense_circuit.h(alice[0]);\n",
237 | "\n",
238 | "superdense_circuit.draw()"
239 | ]
240 | },
241 | {
242 | "cell_type": "markdown",
243 | "metadata": {},
244 | "source": [
245 | "Now let's measure Bob's qubits. "
246 | ]
247 | },
248 | {
249 | "cell_type": "code",
250 | "execution_count": 6,
251 | "metadata": {},
252 | "outputs": [
253 | {
254 | "data": {
255 | "text/html": [
256 | " ┌───┐ ┌───┐ ┌───┐┌─┐\n",
257 | "a_0: |0>┤ H ├──■──┤ X ├──■──┤ H ├┤M├\n",
258 | " └───┘┌─┴─┐└───┘┌─┴─┐└┬─┬┘└╥┘\n",
259 | "b_0: |0>─────┤ X ├─────┤ X ├─┤M├──╫─\n",
260 | " └───┘ └───┘ └╥┘ ║ \n",
261 | " m_0: 0 ══════════════════════╬═══╩═\n",
262 | " ║ \n",
263 | " m_1: 0 ══════════════════════╩═════\n",
264 | " "
265 | ],
266 | "text/plain": [
267 | ""
268 | ]
269 | },
270 | "execution_count": 6,
271 | "metadata": {},
272 | "output_type": "execute_result"
273 | }
274 | ],
275 | "source": [
276 | "superdense_circuit.measure(alice[0], bob_outputs[0])\n",
277 | "superdense_circuit.measure(bob[0], bob_outputs[1]);\n",
278 | "\n",
279 | "superdense_circuit.draw()"
280 | ]
281 | },
282 | {
283 | "cell_type": "markdown",
284 | "metadata": {},
285 | "source": [
286 | "We'll run the circuit only a single time - we should get the answer right away...\n",
287 | "\n",
288 | "Note: registers in Qiskit circuits are ordered in 'reverse'. So when we measure, we are actually getting something like $... b_2 b_1 b_0$. To make sure the bits are ordered correctly with respect to the problem, then, we have to reverse the output of the result."
289 | ]
290 | },
291 | {
292 | "cell_type": "code",
293 | "execution_count": 7,
294 | "metadata": {},
295 | "outputs": [],
296 | "source": [
297 | "# Execute the quantum circuit\n",
298 | "backend = BasicAer.get_backend('qasm_simulator')\n",
299 | "result = execute(superdense_circuit, backend, shots = 1).result()\n",
300 | "counts = result.get_counts()\n",
301 | "\n",
302 | "# Counts is a dictionary and the keys are bit strings; take the first one and split\n",
303 | "bob_bits = list(list(counts.keys())[0])\n",
304 | "bob_bits.reverse()"
305 | ]
306 | },
307 | {
308 | "cell_type": "code",
309 | "execution_count": 8,
310 | "metadata": {},
311 | "outputs": [
312 | {
313 | "name": "stdout",
314 | "output_type": "stream",
315 | "text": [
316 | "Alice sent Bob bits 0 and 1.\n",
317 | "Bob received bits 0 and 1 from Alice\n"
318 | ]
319 | }
320 | ],
321 | "source": [
322 | "print(f\"Alice sent Bob bits {alice_bits[0]} and {alice_bits[1]}.\")\n",
323 | "print(f\"Bob received bits {bob_bits[0]} and {bob_bits[1]} from Alice\")"
324 | ]
325 | },
326 | {
327 | "cell_type": "code",
328 | "execution_count": null,
329 | "metadata": {},
330 | "outputs": [],
331 | "source": []
332 | }
333 | ],
334 | "metadata": {
335 | "kernelspec": {
336 | "display_name": "Python 3",
337 | "language": "python",
338 | "name": "python3"
339 | },
340 | "language_info": {
341 | "codemirror_mode": {
342 | "name": "ipython",
343 | "version": 3
344 | },
345 | "file_extension": ".py",
346 | "mimetype": "text/x-python",
347 | "name": "python",
348 | "nbconvert_exporter": "python",
349 | "pygments_lexer": "ipython3",
350 | "version": "3.6.5"
351 | }
352 | },
353 | "nbformat": 4,
354 | "nbformat_minor": 2
355 | }
356 |
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/Teleportation.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Quantum teleportation"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "We may not be able to clone arbitrary quantum states, but we can do something arguably cooler - we can teleport them! If Alice and Bob share an entangled pair of qubits in the state\n",
15 | "\\begin{equation}\n",
16 | " |\\Psi\\rangle = \\frac{1}{\\sqrt{2}} \\left( |00\\rangle + |11 \\rangle \\right),\n",
17 | "\\end{equation}\n",
18 | "then they can use it to transfer the state of a third qubit to Bob.\n",
19 | "\n",
20 | "**Important!** Teleportation does not mean that the physical qubit held by Alice is being shipped to Bob. Rather, the _state_ of Alice's qubit will be 'transferred' to the qubit held by Bob following the protocol."
21 | ]
22 | },
23 | {
24 | "cell_type": "code",
25 | "execution_count": null,
26 | "metadata": {},
27 | "outputs": [],
28 | "source": [
29 | "import numpy as np\n",
30 | "\n",
31 | "from qiskit import QuantumRegister, ClassicalRegister\n",
32 | "from qiskit import QuantumCircuit\n",
33 | "from qiskit import execute, BasicAer\n",
34 | "\n",
35 | "import qiskit.tools.visualization as qvis\n",
36 | "\n",
37 | "import warnings\n",
38 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
39 | ]
40 | },
41 | {
42 | "cell_type": "markdown",
43 | "metadata": {},
44 | "source": [
45 | "First we'll set up the qubit registers and the circuit."
46 | ]
47 | },
48 | {
49 | "cell_type": "code",
50 | "execution_count": null,
51 | "metadata": {},
52 | "outputs": [],
53 | "source": [
54 | "# Set up the quantum circuit; Alice holds qubits 0 and 1, and Bob has qubit 2\n",
55 | "# We need quantum register with 3 qubits, and classical register with 2 qubits\n",
56 | "alice = QuantumRegister(2, 'a')\n",
57 | "bob = QuantumRegister(1, 'b')\n",
58 | "measurement_0 = ClassicalRegister(1, 'm0')\n",
59 | "measurement_1 = ClassicalRegister(1, 'm1')\n",
60 | "\n",
61 | "teleportation = QuantumCircuit(alice, bob, measurement_0, measurement_1)"
62 | ]
63 | },
64 | {
65 | "cell_type": "markdown",
66 | "metadata": {},
67 | "source": [
68 | "Next we'll give Alice a random initial state to teleport. We'll do so by sending randomly chosen angles as parameters to a general unitary rotation gate."
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": null,
74 | "metadata": {},
75 | "outputs": [],
76 | "source": [
77 | "# Prepare a random single-qubit state that we would like to send\n",
78 | "random_angles = np.random.rand(3)\n",
79 | "teleportation.u3(*random_angles, alice[0])\n",
80 | "\n",
81 | "teleportation.draw()"
82 | ]
83 | },
84 | {
85 | "cell_type": "markdown",
86 | "metadata": {},
87 | "source": [
88 | "Let's see on the Bloch sphere where this state is..."
89 | ]
90 | },
91 | {
92 | "cell_type": "code",
93 | "execution_count": null,
94 | "metadata": {},
95 | "outputs": [],
96 | "source": [
97 | "# To plot the Bloch vectors we will have to extract the state from the \n",
98 | "# statevector simulator.\n",
99 | "backend = BasicAer.get_backend('statevector_simulator') # the device to run on\n",
100 | "result = execute(teleportation, backend).result()\n",
101 | "initial_psi = result.get_statevector(teleportation)\n",
102 | "\n",
103 | "qvis.plot_bloch_multivector(initial_psi)"
104 | ]
105 | },
106 | {
107 | "cell_type": "markdown",
108 | "metadata": {},
109 | "source": [
110 | "Note that qubit 1 and qubit 2 are still in state $|0\\rangle$; our next step is to prepare the shared entangled state. "
111 | ]
112 | },
113 | {
114 | "cell_type": "code",
115 | "execution_count": null,
116 | "metadata": {},
117 | "outputs": [],
118 | "source": [
119 | "# Prepare an entangled state for Alice and Bob to share\n",
120 | "teleportation.h(alice[1])\n",
121 | "teleportation.cx(alice[1], bob[0])\n",
122 | "\n",
123 | "teleportation.draw()"
124 | ]
125 | },
126 | {
127 | "cell_type": "markdown",
128 | "metadata": {},
129 | "source": [
130 | "Now that everything is set up, to teleport the qubit, Alice is going to entangle the qubit she wants to send with the qubit she holds from the entangled pair."
131 | ]
132 | },
133 | {
134 | "cell_type": "code",
135 | "execution_count": null,
136 | "metadata": {},
137 | "outputs": [],
138 | "source": [
139 | "# Now entangle Alice's first qubit with the first qubit of shared Bell state\n",
140 | "teleportation.cx(alice[0], alice[1])\n",
141 | "teleportation.h(alice[0])\n",
142 | "\n",
143 | "teleportation.draw()"
144 | ]
145 | },
146 | {
147 | "cell_type": "markdown",
148 | "metadata": {},
149 | "source": [
150 | "Next she will measure both her qubits, giving her two classical bits."
151 | ]
152 | },
153 | {
154 | "cell_type": "code",
155 | "execution_count": null,
156 | "metadata": {},
157 | "outputs": [],
158 | "source": [
159 | "# Execute circuit and get 2 measurement outcomes\n",
160 | "teleportation.measure(alice[0], measurement_0[0])\n",
161 | "teleportation.measure(alice[1], measurement_1[0])\n",
162 | "\n",
163 | "teleportation.draw()"
164 | ]
165 | },
166 | {
167 | "cell_type": "markdown",
168 | "metadata": {},
169 | "source": [
170 | "Alice phones Bob and tells him what bits she measured. Based on the outcomes, Bob will apply some Pauli gates to his half of the entangled state:\n",
171 | "\\begin{eqnarray}\n",
172 | " 00 &\\rightarrow& I \\\\\n",
173 | " 01 &\\rightarrow& X \\\\\n",
174 | " 10 &\\rightarrow& Z \\\\\n",
175 | " 11 &\\rightarrow& ZX\n",
176 | "\\end{eqnarray}\n",
177 | "In the circuit these can be expressed as controlled gates using the classical outcomes as controls rather than normal qubits."
178 | ]
179 | },
180 | {
181 | "cell_type": "code",
182 | "execution_count": null,
183 | "metadata": {},
184 | "outputs": [],
185 | "source": [
186 | "# Based on outcomes, apply X, Z, or XZ\n",
187 | "teleportation.x(bob[0]).c_if(measurement_1, 1)\n",
188 | "teleportation.z(bob[0]).c_if(measurement_0, 1)\n",
189 | "\n",
190 | "teleportation.draw()"
191 | ]
192 | },
193 | {
194 | "cell_type": "markdown",
195 | "metadata": {},
196 | "source": [
197 | "After Bob applies his Paulis, his qubit will be in exactly the same state as the one Alice prepared! Let's plot the Bloch vectors to make sure."
198 | ]
199 | },
200 | {
201 | "cell_type": "code",
202 | "execution_count": null,
203 | "metadata": {},
204 | "outputs": [],
205 | "source": [
206 | "# Execute the full circuit\n",
207 | "result = execute(teleportation, backend).result()\n",
208 | "final_psi = result.get_statevector(teleportation)"
209 | ]
210 | },
211 | {
212 | "cell_type": "code",
213 | "execution_count": null,
214 | "metadata": {},
215 | "outputs": [],
216 | "source": [
217 | "qvis.plot_bloch_multivector(initial_psi)"
218 | ]
219 | },
220 | {
221 | "cell_type": "code",
222 | "execution_count": null,
223 | "metadata": {},
224 | "outputs": [],
225 | "source": [
226 | "qvis.plot_bloch_multivector(final_psi)"
227 | ]
228 | },
229 | {
230 | "cell_type": "markdown",
231 | "metadata": {},
232 | "source": [
233 | "Qubit 2, Bob's qubit, matches the initial state of Alice's qubit 0. Note that after the protocol, Alice's two qubits will be sitting in whatever states correspond to her classical measurement outcomes. "
234 | ]
235 | },
236 | {
237 | "cell_type": "markdown",
238 | "metadata": {},
239 | "source": [
240 | "**Exercise**: starting from the state\n",
241 | "\\begin{equation}\n",
242 | " \\left( \\alpha |0\\rangle + \\beta |1 \\rangle \\right) \\otimes \\frac{1}{\\sqrt{2}} \\left( |00\\rangle + |11 \\rangle \\right),\n",
243 | "\\end{equation}\n",
244 | "work through the teleportation protocol to see how Alice's state gets transferred to Bob. You'll have to expand everything, then perform a basis change on Alice's two qubits to shift them into the Bell basis. After, Alice performs the CNOT and H before measuring; this changes back to the computational basis so we can interpret the measurements more easily. You'll see, after this transformation, each of the computational basis states will be attached to a slightly different version of the original state in Bob's qubit, which can easily be undone with the Pauli operators."
245 | ]
246 | }
247 | ],
248 | "metadata": {
249 | "kernelspec": {
250 | "display_name": "Python 3",
251 | "language": "python",
252 | "name": "python3"
253 | },
254 | "language_info": {
255 | "codemirror_mode": {
256 | "name": "ipython",
257 | "version": 3
258 | },
259 | "file_extension": ".py",
260 | "mimetype": "text/x-python",
261 | "name": "python",
262 | "nbconvert_exporter": "python",
263 | "pygments_lexer": "ipython3",
264 | "version": "3.6.5"
265 | }
266 | },
267 | "nbformat": 4,
268 | "nbformat_minor": 2
269 | }
270 |
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/bloch-sphere-movies/hadamard_gate_animated.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/notebooks/bloch-sphere-movies/hadamard_gate_animated.webm
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/bloch-sphere-movies/s_gate_animated.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/notebooks/bloch-sphere-movies/s_gate_animated.webm
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/bloch-sphere-movies/t_gate_animated.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/notebooks/bloch-sphere-movies/t_gate_animated.webm
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/bloch-sphere-movies/x_gate_animated.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/notebooks/bloch-sphere-movies/x_gate_animated.webm
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/bloch-sphere-movies/y_rotation_animated.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/notebooks/bloch-sphere-movies/y_rotation_animated.webm
--------------------------------------------------------------------------------
/01-gate-model-theory/notebooks/bloch-sphere-movies/z_rotation_animated.webm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/01-gate-model-theory/notebooks/bloch-sphere-movies/z_rotation_animated.webm
--------------------------------------------------------------------------------
/02-gate-model-applications/annotated-gate-model-applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/02-gate-model-applications/annotated-gate-model-applications.pdf
--------------------------------------------------------------------------------
/02-gate-model-applications/blank-gate-model-applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/02-gate-model-applications/blank-gate-model-applications.pdf
--------------------------------------------------------------------------------
/02-gate-model-applications/bonus-slides-noise.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/02-gate-model-applications/bonus-slides-noise.pdf
--------------------------------------------------------------------------------
/02-gate-model-applications/bonus-slides-qft.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/02-gate-model-applications/bonus-slides-qft.pdf
--------------------------------------------------------------------------------
/02-gate-model-applications/gate-model-applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/02-gate-model-applications/gate-model-applications.pdf
--------------------------------------------------------------------------------
/02-gate-model-applications/notebooks/Neutrino-Oscillations.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Challenge problem: neutrino oscillations"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "This notebook is meant to get you started towards implementing the neutrino oscillation procedure detailed in http://arxiv.org/abs/1904.10559. The paper shows the details for both 2- and 3-flavour oscillations, but we'll focus here on just two, $\\nu_e$ and $\\nu_\\mu$. Specifically, we will work on reproducing Figure 2, where we will plot the 'survival' probability of the electron neutrino."
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "### Derivations and important physical constants"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "Recall from the lectures that I showed an expression for the probability of $\\nu_e$ turning into a $\\nu_\\mu$:\n",
29 | "\\begin{equation}\n",
30 | "P_{\\nu_e \\rightarrow \\nu_\\mu} = |\\nu_\\mu(t)|^2 = \\left[ \\sin(2\\theta) \\sin \\left( \\frac{E_2 - E_1}{2\\hbar}t \\right) \\right]^2\n",
31 | "\\end{equation}\n",
32 | "\n",
33 | "Oftentimes this is expressed in terms of travel distance $L$ and a neutrino energy $E$, using the correspondence $E_i = |p|^2 c^2 + m_i^2 c^4$ and making some approximations, such as $L \\approx ct$ for particles travelling at close to the speed of light:\n",
34 | "\\begin{eqnarray}\n",
35 | "P_{\\nu_e \\rightarrow \\nu_\\mu} = |\\nu_\\mu(t)|^2 &=& \\left[ \\sin(2\\theta) \\sin \\left( \\frac{(m_{\\nu_\\mu}^2 - m^2_{\\nu_e})}{2\\hbar} \\frac{L}{E} \\right) \\right]^2 \\\\\n",
36 | "&=& \\left[ \\sin(2\\theta) \\sin \\left( \\frac{\\Delta m^2}{2\\hbar c} \\frac{L}{E} \\right) \\right]^2 \n",
37 | "\\end{eqnarray}\n",
38 | "where $\\Delta m^2$ is shorthand for $m_{\\nu_\\mu}^2 - m^2_{\\nu_e}$.\n",
39 | "For a full derivation of this, see, for examples, Griffith's book _Introduction to Elementary Particles_. I'm just going to take it as a given."
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "**Task 1.** Track down the relevant constants you need: $\\theta$, $\\Delta m^2$ for electron and muon neutrinos, and $L$. You'll have to be really careful with units here, making sure you don't pick up (or lose) any rogue factors of $c$, etc. The physical neutrino parameters can be found online; the value of $L$ they used was from the KamLAND experiment (https://arxiv.org/pdf/hep-ex/0212021.pdf).\n"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "# Your constants go here"
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "metadata": {},
61 | "source": [
62 | "**Task 2.** Set up the required registers."
63 | ]
64 | },
65 | {
66 | "cell_type": "code",
67 | "execution_count": null,
68 | "metadata": {},
69 | "outputs": [],
70 | "source": [
71 | "import numpy as np\n",
72 | "\n",
73 | "from qiskit import QuantumRegister, ClassicalRegister\n",
74 | "from qiskit import QuantumCircuit\n",
75 | "from qiskit import execute, BasicAer\n",
76 | "\n",
77 | "import matplotlib.pyplot as plt\n",
78 | "\n",
79 | "import warnings\n",
80 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
81 | ]
82 | },
83 | {
84 | "cell_type": "code",
85 | "execution_count": null,
86 | "metadata": {},
87 | "outputs": [],
88 | "source": [
89 | "# Set up your registers here."
90 | ]
91 | },
92 | {
93 | "cell_type": "markdown",
94 | "metadata": {},
95 | "source": [
96 | "**Task 3.** Write a function that will apply a circuit to your registers that (a) creates the initial superposition, (b) applies the evolution operator for a neutrino of specified energy, (c) measures the output.\n",
97 | "\n",
98 | "The way this is done in the paper is actually by using $|0\\rangle$ and $|1\\rangle$ to represent the flavour basis (instead of using them to represent the mass basis). This way our measurements are made in the flavour basis as well. So make sure you are applying the unitary basis transformations in the \"right\" direction."
99 | ]
100 | },
101 | {
102 | "cell_type": "code",
103 | "execution_count": null,
104 | "metadata": {},
105 | "outputs": [],
106 | "source": [
107 | "def oscillate(E, q, c):\n",
108 | " circuit = QuantumCircuit(q, c)\n",
109 | " \n",
110 | " # Your code here\n",
111 | " \n",
112 | " return circuit"
113 | ]
114 | },
115 | {
116 | "cell_type": "markdown",
117 | "metadata": {},
118 | "source": [
119 | "**Task 4.** Simulate your circuit for varying energies, and plot the results."
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": null,
125 | "metadata": {},
126 | "outputs": [],
127 | "source": [
128 | "# Here is some boilerplate code for running and measuring the circuit, so \n",
129 | "# you don't have to worry about these details\n",
130 | "# neutrino_circuit = oscillate(E, q, c) \n",
131 | "# backend = BasicAer.get_backend('qasm_simulator') # the device to run on\n",
132 | "# result = execute(neutrino_circuit, backend, shots=n_shots).result()\n",
133 | "# counts = result.get_counts(neutrino_circuit)"
134 | ]
135 | }
136 | ],
137 | "metadata": {
138 | "kernelspec": {
139 | "display_name": "Python 3",
140 | "language": "python",
141 | "name": "python3"
142 | },
143 | "language_info": {
144 | "codemirror_mode": {
145 | "name": "ipython",
146 | "version": 3
147 | },
148 | "file_extension": ".py",
149 | "mimetype": "text/x-python",
150 | "name": "python",
151 | "nbconvert_exporter": "python",
152 | "pygments_lexer": "ipython3",
153 | "version": "3.6.5"
154 | }
155 | },
156 | "nbformat": 4,
157 | "nbformat_minor": 2
158 | }
159 |
--------------------------------------------------------------------------------
/02-gate-model-applications/notebooks/Quantum-Phase-Estimation.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Quantum phase estimation"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "Your task in this notebook is to implement the quantum Fourier transform on a set of 3 qubits in Qiskit, and then use it to estimate the eigenvalue of a simple Hamiltonian."
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "### Part A: Implementing the QFT"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "import numpy as np\n",
31 | "\n",
32 | "from qiskit import QuantumRegister, ClassicalRegister\n",
33 | "from qiskit import QuantumCircuit\n",
34 | "from qiskit import execute, BasicAer\n",
35 | "\n",
36 | "import qiskit.tools.visualization as qvis\n",
37 | "\n",
38 | "import warnings\n",
39 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "**Task 1.** In the lecture slides I showed the QFT in general; using this template, write out the circuit for the 3-qubit case. Don't forget the SWAP at the end! "
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {},
52 | "source": [
53 | "**Task 2.** Recall that the rotations have the form\n",
54 | "\\begin{equation}\n",
55 | " R_k = \\begin{pmatrix}\n",
56 | " 1 & 0 \\\\ 0 & e^{2\\pi i / 2^k}\n",
57 | " \\end{pmatrix}\n",
58 | "\\end{equation}\n",
59 | "Evaluate the unitary operations for the rotations present in Task 1. Do these gates look familiar?"
60 | ]
61 | },
62 | {
63 | "cell_type": "markdown",
64 | "metadata": {},
65 | "source": [
66 | "** Task 3.** Now it's time to implement the circuit. I've set up a 3-qubit quantum register, so all you have to do is implement the gates. For reference, [this page](https://qiskit.org/documentation/terra/summary_of_quantum_operations.html#controlled-operations-on-qubits) contains information on the gates you can implement natively in Qiskit. Hint: look at the `cu1` gates."
67 | ]
68 | },
69 | {
70 | "cell_type": "code",
71 | "execution_count": null,
72 | "metadata": {},
73 | "outputs": [],
74 | "source": [
75 | "# Empty register\n",
76 | "q = QuantumRegister(3)\n",
77 | "\n",
78 | "# This object will be our circuit\n",
79 | "qft = QuantumCircuit(q)\n",
80 | "\n",
81 | "# Apply gates to the first qubit\n",
82 | "\n",
83 | "# Apply gates to the second qubit\n",
84 | "\n",
85 | "# Apply remaining gates"
86 | ]
87 | },
88 | {
89 | "cell_type": "markdown",
90 | "metadata": {},
91 | "source": [
92 | "### Part B: Eigenvalue estimation"
93 | ]
94 | },
95 | {
96 | "cell_type": "markdown",
97 | "metadata": {},
98 | "source": [
99 | "In this part, we're going to implement the phase estimation algorithm to get the eigenvalue of the following unitary:\n",
100 | "\\begin{equation}\n",
101 | " U = \\begin{pmatrix}\n",
102 | " 1 & 0 \\\\ \n",
103 | " 0 & e^{5\\pi i / 4} \\\\\n",
104 | " \\end{pmatrix}\n",
105 | "\\end{equation}"
106 | ]
107 | },
108 | {
109 | "cell_type": "markdown",
110 | "metadata": {},
111 | "source": [
112 | "** Task 4. ** What is the eigenvalue $\\varphi$ of this matrix as expressed in the phase estimation problem, i.e. $e^{2\\pi i \\varphi}$? What is its expansion in the notation $0.\\varphi_1 \\cdots \\varphi_n$? How many qubits do you need to represent this exactly?"
113 | ]
114 | },
115 | {
116 | "cell_type": "markdown",
117 | "metadata": {},
118 | "source": [
119 | "Now let's set up the two quantum registers: one for the qubits to represent phase, and 1 for the eigenvector since it is a 2-dimensional vector. We'll also need some classical bits to hold the measurement outcomes of the phase register."
120 | ]
121 | },
122 | {
123 | "cell_type": "code",
124 | "execution_count": null,
125 | "metadata": {},
126 | "outputs": [],
127 | "source": [
128 | "# Change this to the number of qubits needed in your upper register\n",
129 | "n_phase_qubits = -1 \n",
130 | "\n",
131 | "ph_reg = QuantumRegister(n_phase_qubits)\n",
132 | "eig_reg = QuantumRegister(1)\n",
133 | "meas_reg = ClassicalRegister(n_phase_qubits)\n",
134 | "\n",
135 | "qpe = QuantumCircuit(ph_reg, eig_reg, meas_reg)"
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "metadata": {},
141 | "source": [
142 | "** Task 5. ** Apply the phase estimation algorithm to your registers. Recall that you'll also need to initialize the eigenvector register into the proper eigenvector."
143 | ]
144 | },
145 | {
146 | "cell_type": "code",
147 | "execution_count": null,
148 | "metadata": {},
149 | "outputs": [],
150 | "source": [
151 | "# Initialize the eigenvector in the lower register\n",
152 | "# \n",
153 | "\n",
154 | "\n",
155 | "# Perform the phase estimation; we're applying a controlled U varying amounts of times\n",
156 | "# "
157 | ]
158 | },
159 | {
160 | "cell_type": "markdown",
161 | "metadata": {},
162 | "source": [
163 | "** Task 6. ** Apply the inverse QFT to your phase register.\n",
164 | "\n",
165 | "Note: unfortunately, Qiskit does not yet support a simple circuit reversal operation; so you'll have to manually list the inverses of each gate. Don't forget to take the adjoint!"
166 | ]
167 | },
168 | {
169 | "cell_type": "code",
170 | "execution_count": null,
171 | "metadata": {},
172 | "outputs": [],
173 | "source": [
174 | "# Apply the inverse QFT to the phase register of the qpe circuit "
175 | ]
176 | },
177 | {
178 | "cell_type": "markdown",
179 | "metadata": {},
180 | "source": [
181 | "** Final task. ** Let's simulate the circuit and measure to get the vector corresponding to our phase!"
182 | ]
183 | },
184 | {
185 | "cell_type": "code",
186 | "execution_count": null,
187 | "metadata": {},
188 | "outputs": [],
189 | "source": [
190 | "qpe.measure(ph_reg, meas_reg)\n",
191 | "\n",
192 | "backend = BasicAer.get_backend('qasm_simulator') # the device to run on\n",
193 | "result = execute(qpe, backend, shots=1000).result()\n",
194 | "counts = result.get_counts(qpe)\n",
195 | "\n",
196 | "eigenvalue = 0\n",
197 | "\n",
198 | "# Your code here: Extract the counts and compute the decimal value\n",
199 | "# Recall that Qiskit measurement results come out 'backwards', so make sure\n",
200 | "# to put the output computational basis state in the reverse order first\n",
201 | "\n",
202 | "print(f\"Phase estimation routine returned the eigenvalue {eigenvalue}.\")"
203 | ]
204 | }
205 | ],
206 | "metadata": {
207 | "kernelspec": {
208 | "display_name": "Python 3",
209 | "language": "python",
210 | "name": "python3"
211 | },
212 | "language_info": {
213 | "codemirror_mode": {
214 | "name": "ipython",
215 | "version": 3
216 | },
217 | "file_extension": ".py",
218 | "mimetype": "text/x-python",
219 | "name": "python",
220 | "nbconvert_exporter": "python",
221 | "pygments_lexer": "ipython3",
222 | "version": "3.6.5"
223 | }
224 | },
225 | "nbformat": 4,
226 | "nbformat_minor": 2
227 | }
228 |
--------------------------------------------------------------------------------
/02-gate-model-applications/notebooks/Solved-Neutrino-Oscillations.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Challenge problem: neutrino oscillations"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "This notebook is meant to get you started towards implementing the neutrino oscillation procedure detailed in http://arxiv.org/abs/1904.10559. The paper shows the details for both 2- and 3-flavour oscillations, but we'll focus here on just two, $\\nu_e$ and $\\nu_\\mu$. Specifically, we will work on reproducing Figure 2, where we will plot the 'survival' probability of the electron neutrino."
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "### Derivations and important physical constants"
22 | ]
23 | },
24 | {
25 | "cell_type": "markdown",
26 | "metadata": {},
27 | "source": [
28 | "Recall from the lectures that I showed an expression for the probability of $\\nu_e$ turning into a $\\nu_\\mu$:\n",
29 | "\\begin{equation}\n",
30 | "P_{\\nu_e \\rightarrow \\nu_\\mu} = |\\nu_\\mu(t)|^2 = \\left[ \\sin(2\\theta) \\sin \\left( \\frac{E_2 - E_1}{2\\hbar}t \\right) \\right]^2\n",
31 | "\\end{equation}\n",
32 | "\n",
33 | "Oftentimes this is expressed in terms of travel distance $L$ and a neutrino energy $E$, using the correspondence $E_i = |p|^2 c^2 + m_i^2 c^4$ and making some approximations, such as $L \\approx ct$ for particles travelling at close to the speed of light:\n",
34 | "\\begin{eqnarray}\n",
35 | "P_{\\nu_e \\rightarrow \\nu_\\mu} = |\\nu_\\mu(t)|^2 &=& \\left[ \\sin(2\\theta) \\sin \\left( \\frac{(m_{\\nu_\\mu}^2 - m^2_{\\nu_e})}{2\\hbar} \\frac{L}{E} \\right) \\right]^2 \\\\\n",
36 | "&=& \\left[ \\sin(2\\theta) \\sin \\left( \\frac{\\Delta m^2}{2\\hbar c} \\frac{L}{E} \\right) \\right]^2 \n",
37 | "\\end{eqnarray}\n",
38 | "where $\\Delta m^2$ is shorthand for $m_{\\nu_\\mu}^2 - m^2_{\\nu_e}$.\n",
39 | "For a full derivation of this, see, for examples, Griffith's book _Introduction to Elementary Particles_. I'm just going to take it as a given."
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "**Task 1.** Track down the relevant constants you need: $\\theta$, $\\Delta m^2$ for electron and muon neutrinos, and $L$. You'll have to be really careful with units here, making sure you don't pick up (or lose) any rogue factors of $c$, etc. The physical neutrino parameters can be found online; the value of $L$ they used was from the KamLAND experiment (https://arxiv.org/pdf/hep-ex/0212021.pdf).\n"
47 | ]
48 | },
49 | {
50 | "cell_type": "code",
51 | "execution_count": null,
52 | "metadata": {},
53 | "outputs": [],
54 | "source": [
55 | "θ = 0.5867 # rad, Found on Wikipedia discussion of neutrino oscillation and PMNS matrix\n",
56 | "\n",
57 | "# Thanks to the authors Carlos and Benjamin for helping me out with this part!\n",
58 | "Δm_sq = 7.37e-5 # expressed in (eV / c^2)^2\n",
59 | "hbar_c = 1.97e-10 # expressed in eV * km\n",
60 | "L = 180 # km, from the experimental paper\n",
61 | "\n",
62 | "gev_conversion_factor = (1e-9)**2 / 1e-9 \n",
63 | "\n",
64 | "# This gives us a prefactor with units such that we can input an energy in GeV\n",
65 | "prefactor = (Δm_sq * L / (2 * hbar_c)) * gev_conversion_factor"
66 | ]
67 | },
68 | {
69 | "cell_type": "markdown",
70 | "metadata": {},
71 | "source": [
72 | "**Task 2.** Set up the required registers.\n",
73 | "\n",
74 | "For two-flavour oscillation, we only need to use a single qubit. So, we just need one quantum register with one qubit, and a classical register with a classical output bit."
75 | ]
76 | },
77 | {
78 | "cell_type": "code",
79 | "execution_count": null,
80 | "metadata": {},
81 | "outputs": [],
82 | "source": [
83 | "import numpy as np\n",
84 | "\n",
85 | "from qiskit import QuantumRegister, ClassicalRegister\n",
86 | "from qiskit import QuantumCircuit\n",
87 | "from qiskit import execute, BasicAer\n",
88 | "\n",
89 | "import matplotlib.pyplot as plt\n",
90 | "\n",
91 | "import warnings\n",
92 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": null,
98 | "metadata": {},
99 | "outputs": [],
100 | "source": [
101 | "q = QuantumRegister(1)\n",
102 | "c = ClassicalRegister(1)"
103 | ]
104 | },
105 | {
106 | "cell_type": "markdown",
107 | "metadata": {},
108 | "source": [
109 | "**Task 3.** Write a function that will apply a circuit to your registers that (a) creates the initial superposition, (b) applies the evolution operator for a neutrino of specified energy, (c) measures the output.\n",
110 | "\n",
111 | "The way this is done in the paper is actually by using $|0\\rangle$ and $|1\\rangle$ to represent the flavour basis (instead of using them to represent the mass basis). This way our measurements are made in the flavour basis as well. So make sure you are applying the unitary basis transformations in the \"right\" direction."
112 | ]
113 | },
114 | {
115 | "cell_type": "code",
116 | "execution_count": null,
117 | "metadata": {},
118 | "outputs": [],
119 | "source": [
120 | "def oscillate(E, q, c):\n",
121 | " circuit = QuantumCircuit(q, c)\n",
122 | " \n",
123 | " # First we convert from flavour basis to mass basis\n",
124 | " circuit.u3(2*θ, 0, 0, q[0])\n",
125 | " \n",
126 | " # Next we apply the evolution operator\n",
127 | " circuit.u1(prefactor / E, q[0])\n",
128 | "\n",
129 | " # Then we undo the basis change\n",
130 | " circuit.u3(-2*θ, 0, 0, q[0])\n",
131 | " \n",
132 | " circuit.measure(q, c)\n",
133 | " \n",
134 | " return circuit"
135 | ]
136 | },
137 | {
138 | "cell_type": "markdown",
139 | "metadata": {},
140 | "source": [
141 | "**Task 4.** Simulate your circuit for varying energies, and plot the results."
142 | ]
143 | },
144 | {
145 | "cell_type": "code",
146 | "execution_count": null,
147 | "metadata": {},
148 | "outputs": [],
149 | "source": [
150 | "energy_range = list(np.linspace(2, 12, 200))\n",
151 | "probs = []\n",
152 | "n_shots = 10000\n",
153 | "\n",
154 | "for E in energy_range:\n",
155 | " neutrino_circuit = oscillate(E / 1000, q, c)\n",
156 | " \n",
157 | " backend = BasicAer.get_backend('qasm_simulator') # the device to run on\n",
158 | " result = execute(neutrino_circuit, backend, shots=n_shots).result()\n",
159 | " counts = result.get_counts(neutrino_circuit)\n",
160 | " \n",
161 | " # Extract the probabilities\n",
162 | " if '0' in counts.keys():\n",
163 | " p_0 = counts['0'] / n_shots\n",
164 | " p_1 = 1 - p_0\n",
165 | " else:\n",
166 | " p_1 = counts['1'] / n_shots\n",
167 | " p_0 = 1 - p_1\n",
168 | " \n",
169 | " probs.append(p_0) "
170 | ]
171 | }
172 | ],
173 | "metadata": {
174 | "kernelspec": {
175 | "display_name": "Python 3",
176 | "language": "python",
177 | "name": "python3"
178 | },
179 | "language_info": {
180 | "codemirror_mode": {
181 | "name": "ipython",
182 | "version": 3
183 | },
184 | "file_extension": ".py",
185 | "mimetype": "text/x-python",
186 | "name": "python",
187 | "nbconvert_exporter": "python",
188 | "pygments_lexer": "ipython3",
189 | "version": "3.6.5"
190 | }
191 | },
192 | "nbformat": 4,
193 | "nbformat_minor": 2
194 | }
195 |
--------------------------------------------------------------------------------
/02-gate-model-applications/notebooks/Solved-Quantum-Phase-Estimation.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Quantum phase estimation"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "Your task in this notebook is to implement the quantum Fourier transform on a set of 3 qubits in Qiskit, and then use it to estimate the eigenvalue of a simple Hamiltonian."
15 | ]
16 | },
17 | {
18 | "cell_type": "markdown",
19 | "metadata": {},
20 | "source": [
21 | "### Part A: Implementing the QFT"
22 | ]
23 | },
24 | {
25 | "cell_type": "code",
26 | "execution_count": null,
27 | "metadata": {},
28 | "outputs": [],
29 | "source": [
30 | "import numpy as np\n",
31 | "\n",
32 | "from qiskit import QuantumRegister, ClassicalRegister\n",
33 | "from qiskit import QuantumCircuit\n",
34 | "from qiskit import execute, BasicAer\n",
35 | "\n",
36 | "import qiskit.tools.visualization as qvis\n",
37 | "\n",
38 | "import warnings\n",
39 | "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) "
40 | ]
41 | },
42 | {
43 | "cell_type": "markdown",
44 | "metadata": {},
45 | "source": [
46 | "**Task 1.** In the lecture slides I showed the QFT in general; using this template, write out the circuit for the 3-qubit case. Don't forget the SWAP at the end! "
47 | ]
48 | },
49 | {
50 | "cell_type": "markdown",
51 | "metadata": {},
52 | "source": [
53 | "** A. ** _Here's a graphic of the circuit._\n",
54 | "\n",
55 | " "
56 | ]
57 | },
58 | {
59 | "cell_type": "markdown",
60 | "metadata": {},
61 | "source": [
62 | "**Task 2.** Recall that the rotations have the form\n",
63 | "\\begin{equation}\n",
64 | " R_k = \\begin{pmatrix}\n",
65 | " 1 & 0 \\\\ 0 & e^{2\\pi i / 2^k}\n",
66 | " \\end{pmatrix}\n",
67 | "\\end{equation}\n",
68 | "Evaluate the unitary operations for the rotations present in Task 1. Do these gates look familiar?\n",
69 | "\n",
70 | "** A. ** _These are the $S$ and $T$ gates that I showed in the slides!_"
71 | ]
72 | },
73 | {
74 | "cell_type": "markdown",
75 | "metadata": {},
76 | "source": [
77 | "** Task 3.** Now it's time to implement the circuit. I've set up a 3-qubit quantum register, so all you have to do is implement the gates. For reference, [this page](https://qiskit.org/documentation/terra/summary_of_quantum_operations.html#controlled-operations-on-qubits) contains information on the gates you can implement natively in Qiskit. Hint: look at the `cu1` gates."
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": null,
83 | "metadata": {},
84 | "outputs": [],
85 | "source": [
86 | "# Empty register\n",
87 | "q = QuantumRegister(3)\n",
88 | "\n",
89 | "# This object will be our circuit\n",
90 | "qft = QuantumCircuit(q)\n",
91 | "\n",
92 | "# Apply gates to the first qubit\n",
93 | "qft.h(q[0])\n",
94 | "qft.cu1(np.pi/2, q[1], q[0])\n",
95 | "qft.cu1(np.pi/4, q[2], q[0])\n",
96 | "\n",
97 | "# Apply gates to the second qubit\n",
98 | "qft.h(q[1])\n",
99 | "qft.cu1(np.pi/2, q[2], q[1])\n",
100 | "\n",
101 | "# Apply remaining gates\n",
102 | "qft.h(q[2])\n",
103 | "\n",
104 | "qft.swap(q[0], q[2])"
105 | ]
106 | },
107 | {
108 | "cell_type": "markdown",
109 | "metadata": {},
110 | "source": [
111 | "### Part B: Eigenvalue estimation"
112 | ]
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "metadata": {},
117 | "source": [
118 | "In this part, we're going to implement the phase estimation algorithm to get the eigenvalue of the following unitary:\n",
119 | "\\begin{equation}\n",
120 | " U = \\begin{pmatrix}\n",
121 | " 1 & 0 \\\\ \n",
122 | " 0 & e^{5\\pi i / 4} \\\\\n",
123 | " \\end{pmatrix}\n",
124 | "\\end{equation}"
125 | ]
126 | },
127 | {
128 | "cell_type": "markdown",
129 | "metadata": {},
130 | "source": [
131 | "** Task 4. ** What is the eigenvalue $\\varphi$ of this matrix as expressed in the phase estimation problem, i.e. $e^{2\\pi i \\varphi}$? What is its expansion in the notation $0.\\varphi_1 \\cdots \\varphi_n$?\n",
132 | "\n",
133 | "** A. ** The eigenvalue here is $\\varphi = 0.625$. In decimal notation this is $0.101$, i.e. $1 \\cdot (2^{-1}) + 0 \\cdot 2^{-2} + 1 \\cdot 2^{-3}$. This fits perfectly in a 3-qubit state, and we should expect to measure $|101\\rangle$ as our output state after running the algorithm. "
134 | ]
135 | },
136 | {
137 | "cell_type": "markdown",
138 | "metadata": {},
139 | "source": [
140 | "Now let's set up the two quantum registers: one for the 3 qubits to represent phase, and 2 for the eigenvector since it is a 4-dimensional vector. We'll also need 3 classical bits to hold the measurement outcomes of the phase register."
141 | ]
142 | },
143 | {
144 | "cell_type": "code",
145 | "execution_count": null,
146 | "metadata": {},
147 | "outputs": [],
148 | "source": [
149 | "ph_reg = QuantumRegister(3)\n",
150 | "eig_reg = QuantumRegister(1)\n",
151 | "meas_reg = ClassicalRegister(3)\n",
152 | "\n",
153 | "qpe = QuantumCircuit(ph_reg, eig_reg, meas_reg)"
154 | ]
155 | },
156 | {
157 | "cell_type": "markdown",
158 | "metadata": {},
159 | "source": [
160 | "** Task 5. ** Apply the phase estimation algorithm to your registers. Recall that you'll also need to initialize the eigenvector register into the proper eigenvector."
161 | ]
162 | },
163 | {
164 | "cell_type": "code",
165 | "execution_count": null,
166 | "metadata": {},
167 | "outputs": [],
168 | "source": [
169 | "# Initialize the eigenvector\n",
170 | "qpe.x(eig_reg)\n",
171 | "\n",
172 | "# Perform the phase estimation; we're applying a controlled U varying amounts of times\n",
173 | "qpe.h(ph_reg)\n",
174 | "\n",
175 | "U_phase = 2 * np.pi * 0.625 \n",
176 | "\n",
177 | "qpe.cu1(U_phase, ph_reg[2], eig_reg[0])\n",
178 | "\n",
179 | "qpe.cu1(U_phase, ph_reg[1], eig_reg[0])\n",
180 | "qpe.cu1(U_phase, ph_reg[1], eig_reg[0])\n",
181 | "\n",
182 | "qpe.cu1(U_phase, ph_reg[0], eig_reg[0])\n",
183 | "qpe.cu1(U_phase, ph_reg[0], eig_reg[0])\n",
184 | "qpe.cu1(U_phase, ph_reg[0], eig_reg[0])\n",
185 | "qpe.cu1(U_phase, ph_reg[0], eig_reg[0])"
186 | ]
187 | },
188 | {
189 | "cell_type": "markdown",
190 | "metadata": {},
191 | "source": [
192 | "** Task 6. ** Apply the inverse QFT to your phase register.\n",
193 | "\n",
194 | "Note: unfortunately, Qiskit does not yet support a simple circuit reversal operation; so you'll have to manually list the inverses of each gate. Don't forget to take the adjoint!"
195 | ]
196 | },
197 | {
198 | "cell_type": "code",
199 | "execution_count": null,
200 | "metadata": {},
201 | "outputs": [],
202 | "source": [
203 | "qpe.swap(ph_reg[0], ph_reg[2])\n",
204 | "qpe.h(ph_reg[2])\n",
205 | "qpe.cu1(-np.pi/2, ph_reg[2], ph_reg[1])\n",
206 | "qpe.h(ph_reg[1])\n",
207 | "qpe.cu1(-np.pi/4, ph_reg[2], ph_reg[0])\n",
208 | "qpe.cu1(-np.pi/2, ph_reg[1], ph_reg[0])\n",
209 | "qpe.h(ph_reg[0])"
210 | ]
211 | },
212 | {
213 | "cell_type": "markdown",
214 | "metadata": {},
215 | "source": [
216 | "** Final task. ** Let's simulate the circuit and measure to get the vector corresponding to our phase!"
217 | ]
218 | },
219 | {
220 | "cell_type": "code",
221 | "execution_count": null,
222 | "metadata": {},
223 | "outputs": [],
224 | "source": [
225 | "qpe.measure(ph_reg, meas_reg)\n",
226 | "\n",
227 | "backend = BasicAer.get_backend('qasm_simulator') # the device to run on\n",
228 | "result = execute(qpe, backend, shots=1000).result()\n",
229 | "counts = result.get_counts(qpe)\n",
230 | "\n",
231 | "# Extract the counts and compute the value\n",
232 | "eigenvalue = 0\n",
233 | "\n",
234 | "# Reverse - in this case it is symmetric, but in general you'll have to be careful\n",
235 | "binary_eigenvalue = list(counts.keys())[0] \n",
236 | "binary_eigenvalue = binary_eigenvalue[::-1]\n",
237 | "\n",
238 | "for idx, b in enumerate(list(binary_eigenvalue)):\n",
239 | " if b == '1':\n",
240 | " eigenvalue += 2 ** (-(idx + 1))\n",
241 | "\n",
242 | "print(f\"Phase estimation routine returned the eigenvalue {eigenvalue}.\")"
243 | ]
244 | }
245 | ],
246 | "metadata": {
247 | "kernelspec": {
248 | "display_name": "Python 3",
249 | "language": "python",
250 | "name": "python3"
251 | },
252 | "language_info": {
253 | "codemirror_mode": {
254 | "name": "ipython",
255 | "version": 3
256 | },
257 | "file_extension": ".py",
258 | "mimetype": "text/x-python",
259 | "name": "python",
260 | "nbconvert_exporter": "python",
261 | "pygments_lexer": "ipython3",
262 | "version": "3.6.5"
263 | }
264 | },
265 | "nbformat": 4,
266 | "nbformat_minor": 2
267 | }
268 |
--------------------------------------------------------------------------------
/02-gate-model-applications/notebooks/qft_mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/02-gate-model-applications/notebooks/qft_mini.png
--------------------------------------------------------------------------------
/03-annealing-theory/annealing-theory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/03-annealing-theory/annealing-theory.pdf
--------------------------------------------------------------------------------
/03-annealing-theory/annotated-annealing-theory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/03-annealing-theory/annotated-annealing-theory.pdf
--------------------------------------------------------------------------------
/03-annealing-theory/blank-annealing-theory.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/03-annealing-theory/blank-annealing-theory.pdf
--------------------------------------------------------------------------------
/04-annealing-applications/Minor-Embeddings.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "## Minor embeddings"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "The problem we just solved was quite small, and each node had very few connections. For more tightly connected graphs, however, we will run into problems. In the chimera graph, each qubit is connected to at most 6 others. What do we if our problem requires more?\n",
15 | "\n",
16 | "The solution here is to find something called a _minor embedding_. This involves embedding a smaller graph into a large one where clusters of nodes will couple together as one in order to allow for more external connections. This will be easier to understand with some pictures."
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": null,
22 | "metadata": {},
23 | "outputs": [],
24 | "source": [
25 | "from minorminer import find_embedding\n",
26 | "import networkx as nx\n",
27 | "import dwave_networkx as dnx\n",
28 | "import numpy as np\n",
29 | "import matplotlib.pyplot as plt"
30 | ]
31 | },
32 | {
33 | "cell_type": "markdown",
34 | "metadata": {},
35 | "source": [
36 | "First, let's look at our old graph and see how we can map it onto D-Wave's chimera hardware."
37 | ]
38 | },
39 | {
40 | "cell_type": "code",
41 | "execution_count": null,
42 | "metadata": {},
43 | "outputs": [],
44 | "source": [
45 | "n_vertices = 4\n",
46 | "n_edges = 3\n",
47 | "\n",
48 | "# We are going to colour the nodes to make it easier to see in the embedding which \n",
49 | "# embedded nodes belong to which original nodes\n",
50 | "colours = [tuple(np.random.rand(4, )) for x in range(n_vertices)]\n",
51 | "\n",
52 | "graph = nx.gnm_random_graph(n_vertices, n_edges)\n",
53 | "nx.draw(graph, with_labels=True, node_color=colours)"
54 | ]
55 | },
56 | {
57 | "cell_type": "markdown",
58 | "metadata": {},
59 | "source": [
60 | "Here is a single unit cell of a chimera graph"
61 | ]
62 | },
63 | {
64 | "cell_type": "code",
65 | "execution_count": null,
66 | "metadata": {},
67 | "outputs": [],
68 | "source": [
69 | "chimera_unit_cell = dnx.chimera_graph(1, 1, 4)\n",
70 | "dnx.draw_chimera(chimera_unit_cell, with_labels=True)"
71 | ]
72 | },
73 | {
74 | "cell_type": "markdown",
75 | "metadata": {},
76 | "source": [
77 | "Now let's find and display a minor embedding of our graph onto the unit cell; it should fit no problem."
78 | ]
79 | },
80 | {
81 | "cell_type": "code",
82 | "execution_count": null,
83 | "metadata": {},
84 | "outputs": [],
85 | "source": [
86 | "embedding = find_embedding(graph.edges(), chimera_unit_cell.edges())\n",
87 | "colour_dict = {}\n",
88 | "for node_idx in range(len(graph.nodes)):\n",
89 | " if node_idx in embedding.keys():\n",
90 | " colour_dict[node_idx] = colours[node_idx]\n",
91 | " else:\n",
92 | " colour_dict[node_idx] = None"
93 | ]
94 | },
95 | {
96 | "cell_type": "code",
97 | "execution_count": null,
98 | "metadata": {},
99 | "outputs": [],
100 | "source": [
101 | "nx.draw(graph, with_labels=True, node_color=colours)"
102 | ]
103 | },
104 | {
105 | "cell_type": "code",
106 | "execution_count": null,
107 | "metadata": {},
108 | "outputs": [],
109 | "source": [
110 | "#embedding = find_embedding(graph.edges(), chimera_unit_cell.edges())\n",
111 | "dnx.draw_chimera_embedding(chimera_unit_cell, embedding, with_labels=True, chain_color=colour_dict)"
112 | ]
113 | },
114 | {
115 | "cell_type": "markdown",
116 | "metadata": {},
117 | "source": [
118 | "Now let's try something larger - we'll need 4 unit cells for this."
119 | ]
120 | },
121 | {
122 | "cell_type": "code",
123 | "execution_count": null,
124 | "metadata": {},
125 | "outputs": [],
126 | "source": [
127 | "n_vertices = 10\n",
128 | "n_edges = 30\n",
129 | "\n",
130 | "colours = [tuple(np.random.rand(4, )) for x in range(n_vertices)]\n",
131 | "\n",
132 | "graph = nx.gnm_random_graph(n_vertices, n_edges)\n",
133 | "nx.draw(graph, with_labels=True, node_color=colours)"
134 | ]
135 | },
136 | {
137 | "cell_type": "code",
138 | "execution_count": null,
139 | "metadata": {},
140 | "outputs": [],
141 | "source": [
142 | "chimera_layout = dnx.chimera_graph(2, 2, 4)\n",
143 | "embedding = find_embedding(graph.edges(), chimera_layout.edges())\n",
144 | "colour_dict = {}\n",
145 | "for node_idx in range(len(graph.nodes)):\n",
146 | " if node_idx in embedding.keys():\n",
147 | " colour_dict[node_idx] = colours[node_idx]\n",
148 | " else:\n",
149 | " colour_dict[node_idx] = None\n",
150 | "dnx.draw_chimera_embedding(chimera_layout, embedding, with_labels=True, chain_color=colour_dict)"
151 | ]
152 | }
153 | ],
154 | "metadata": {
155 | "kernelspec": {
156 | "display_name": "Python 3",
157 | "language": "python",
158 | "name": "python3"
159 | },
160 | "language_info": {
161 | "codemirror_mode": {
162 | "name": "ipython",
163 | "version": 3
164 | },
165 | "file_extension": ".py",
166 | "mimetype": "text/x-python",
167 | "name": "python",
168 | "nbconvert_exporter": "python",
169 | "pygments_lexer": "ipython3",
170 | "version": "3.6.5"
171 | }
172 | },
173 | "nbformat": 4,
174 | "nbformat_minor": 2
175 | }
176 |
--------------------------------------------------------------------------------
/04-annealing-applications/Vertex-Cover.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Solving vertex cover with a quantum annealer"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {},
13 | "source": [
14 | "The problem of vertex cover is, given an undirected graph $G = (V, E)$, colour the smallest amount of vertices such that each edge $e \\in E$ is connected to a coloured vertex.\n",
15 | "\n",
16 | "This notebooks works through the process of creating a random graph, translating to an optimization problem, and eventually finding the ground state using a quantum annealer."
17 | ]
18 | },
19 | {
20 | "cell_type": "markdown",
21 | "metadata": {},
22 | "source": [
23 | "### Graph setup"
24 | ]
25 | },
26 | {
27 | "cell_type": "markdown",
28 | "metadata": {},
29 | "source": [
30 | "The first thing we will do is create an instance of the problem, by constructing a small, random undirected graph. We are going to use the `networkx` package, which should already be installed if you have installed if you are using Anaconda."
31 | ]
32 | },
33 | {
34 | "cell_type": "code",
35 | "execution_count": null,
36 | "metadata": {},
37 | "outputs": [],
38 | "source": [
39 | "import dimod\n",
40 | "import networkx as nx\n",
41 | "import matplotlib.pyplot as plt\n",
42 | "import numpy as np"
43 | ]
44 | },
45 | {
46 | "cell_type": "code",
47 | "execution_count": null,
48 | "metadata": {},
49 | "outputs": [],
50 | "source": [
51 | "n_vertices = 5\n",
52 | "n_edges = 6\n",
53 | "\n",
54 | "small_graph = nx.gnm_random_graph(n_vertices, n_edges)"
55 | ]
56 | },
57 | {
58 | "cell_type": "code",
59 | "execution_count": null,
60 | "metadata": {},
61 | "outputs": [],
62 | "source": [
63 | "nx.draw(small_graph, with_labels=True)"
64 | ]
65 | },
66 | {
67 | "cell_type": "markdown",
68 | "metadata": {},
69 | "source": [
70 | "### Constructing the Hamiltonian"
71 | ]
72 | },
73 | {
74 | "cell_type": "markdown",
75 | "metadata": {},
76 | "source": [
77 | "I showed in class that the objective function for vertex cover looks like this:\n",
78 | "\\begin{equation}\n",
79 | " \\sum_{(u,v) \\in E} (1 - x_u) (1 - x_v) + \\gamma \\sum_{v \\in V} x_v\n",
80 | "\\end{equation}\n",
81 | "We want to find an assignment of the $x_u$ of 1 (coloured) or 0 (uncoloured) that _minimizes_ this function. The first sum tries to force us to choose an assignment that makes sure every edge gets attached to a coloured vertex. The second sum is essentially just counting the number of coloured vertices.\n",
82 | "\n",
83 | "**Task**: Expand out the QUBO above to see how you can convert it to a more 'traditional' looking QUBO:\n",
84 | "\\begin{equation}\n",
85 | " \\sum_{(u,v) \\in E} x_u x_v + \\sum_{v \\in V} (\\gamma - \\text{deg}(x_v)) x_v\n",
86 | "\\end{equation}\n",
87 | "where deg($x_v$) indicates the degree of vertex $x_v$ in the graph."
88 | ]
89 | },
90 | {
91 | "cell_type": "code",
92 | "execution_count": null,
93 | "metadata": {},
94 | "outputs": [],
95 | "source": [
96 | "γ = 0.8\n",
97 | "Q = {x : 1 for x in small_graph.edges()}\n",
98 | "r = {x : (γ - small_graph.degree[x]) for x in small_graph.nodes}"
99 | ]
100 | },
101 | {
102 | "cell_type": "markdown",
103 | "metadata": {},
104 | "source": [
105 | "Let's convert it to the appropriate data structure, and solve using the exact solver. "
106 | ]
107 | },
108 | {
109 | "cell_type": "code",
110 | "execution_count": null,
111 | "metadata": {},
112 | "outputs": [],
113 | "source": [
114 | "bqm = dimod.BinaryQuadraticModel(r, Q, 0, dimod.BINARY)\n",
115 | "response = dimod.ExactSolver().sample(bqm)\n",
116 | "print(f\"Sample energy = {next(response.data(['energy']))[0]}\")"
117 | ]
118 | },
119 | {
120 | "cell_type": "markdown",
121 | "metadata": {},
122 | "source": [
123 | "Let's print the graph with proper colours included"
124 | ]
125 | },
126 | {
127 | "cell_type": "code",
128 | "execution_count": null,
129 | "metadata": {},
130 | "outputs": [],
131 | "source": [
132 | "colour_assignments = next(response.data(['sample']))[0]\n",
133 | "colours = ['grey' if colour_assignments[x] == 0 else 'red' for x in range(len(colour_assignments))]\n",
134 | "\n",
135 | "nx.draw(small_graph, with_labels=True, node_color=colours)"
136 | ]
137 | },
138 | {
139 | "cell_type": "markdown",
140 | "metadata": {},
141 | "source": [
142 | "### Scaling up..."
143 | ]
144 | },
145 | {
146 | "cell_type": "markdown",
147 | "metadata": {},
148 | "source": [
149 | "That one was easy enough to solve by hand. Let's try a much larger instance..."
150 | ]
151 | },
152 | {
153 | "cell_type": "code",
154 | "execution_count": null,
155 | "metadata": {},
156 | "outputs": [],
157 | "source": [
158 | "n_vertices = 20\n",
159 | "n_edges = 60\n",
160 | "\n",
161 | "large_graph = nx.gnm_random_graph(n_vertices, n_edges)\n",
162 | "nx.draw(large_graph, with_labels=True)"
163 | ]
164 | },
165 | {
166 | "cell_type": "code",
167 | "execution_count": null,
168 | "metadata": {},
169 | "outputs": [],
170 | "source": [
171 | "# Create h, J and put it into the exact solver\n",
172 | "γ = 0.8\n",
173 | "Q = {x : 1 for x in large_graph.edges()}\n",
174 | "r = {x : (γ - large_graph.degree[x]) for x in large_graph.nodes}\n",
175 | "\n",
176 | "bqm = dimod.BinaryQuadraticModel(r, Q, 0, dimod.BINARY)\n",
177 | "response = dimod.ExactSolver().sample(bqm)\n",
178 | "print(f\"Sample energy = {next(response.data(['energy']))[0]}\")\n",
179 | " \n",
180 | "colour_assignments = next(response.data(['sample']))[0]\n",
181 | "colours = ['grey' if colour_assignments[x] == 0 else 'red' for x in range(len(colour_assignments))]\n",
182 | "\n",
183 | "nx.draw(large_graph, with_labels=True, node_color=colours)\n",
184 | "print(f\"Coloured {list(colour_assignments.values()).count(1)}/{n_vertices} vertices.\")"
185 | ]
186 | },
187 | {
188 | "cell_type": "markdown",
189 | "metadata": {},
190 | "source": [
191 | "### Running on the D-Wave"
192 | ]
193 | },
194 | {
195 | "cell_type": "markdown",
196 | "metadata": {},
197 | "source": [
198 | "You'll only be able to run the next few cells if you have D-Wave access. We will send the same graph as before to the D-Wave QPU and see what kind of results we get back!"
199 | ]
200 | },
201 | {
202 | "cell_type": "code",
203 | "execution_count": null,
204 | "metadata": {},
205 | "outputs": [],
206 | "source": [
207 | "from dwave.system.samplers import DWaveSampler\n",
208 | "from dwave.system.composites import EmbeddingComposite\n",
209 | "\n",
210 | "sampler = EmbeddingComposite(DWaveSampler())"
211 | ]
212 | },
213 | {
214 | "cell_type": "code",
215 | "execution_count": null,
216 | "metadata": {},
217 | "outputs": [],
218 | "source": [
219 | "ising_conversion = bqm.to_ising()\n",
220 | "h, J = ising_conversion[0], ising_conversion[1]\n",
221 | "response = sampler.sample_ising(h, J, num_reads = 1000)"
222 | ]
223 | },
224 | {
225 | "cell_type": "code",
226 | "execution_count": null,
227 | "metadata": {},
228 | "outputs": [],
229 | "source": [
230 | "best_solution =np.sort(response.record, order='energy')[0]"
231 | ]
232 | },
233 | {
234 | "cell_type": "code",
235 | "execution_count": null,
236 | "metadata": {},
237 | "outputs": [],
238 | "source": [
239 | "print(f\"Sample energy = {best_solution['energy']}\")\n",
240 | " \n",
241 | "colour_assignments_qpu = {x : best_solution['sample'][x] for x in range(n_vertices)}\n",
242 | "for x in range(n_vertices):\n",
243 | " if colour_assignments_qpu[x] == -1:\n",
244 | " colour_assignments_qpu[x] = 0\n",
245 | "colours = ['grey' if colour_assignments_qpu[x] == 0 else 'red' for x in range(len(colour_assignments_qpu))]\n",
246 | "\n",
247 | "nx.draw(large_graph, with_labels=True, node_color=colours)\n",
248 | "print(f\"Coloured {list(colour_assignments_qpu.values()).count(1)}/{n_vertices} vertices.\")"
249 | ]
250 | },
251 | {
252 | "cell_type": "code",
253 | "execution_count": null,
254 | "metadata": {},
255 | "outputs": [],
256 | "source": [
257 | "print(\"Node\\tExact\\tQPU\")\n",
258 | "for x in range(n_vertices):\n",
259 | " print(f\"{x}\\t{colour_assignments[x]}\\t{colour_assignments_qpu[x]}\")"
260 | ]
261 | },
262 | {
263 | "cell_type": "markdown",
264 | "metadata": {},
265 | "source": [
266 | "Here is a scatter plot of all the different energies we got out, against the number of times each solution occurred. "
267 | ]
268 | },
269 | {
270 | "cell_type": "code",
271 | "execution_count": null,
272 | "metadata": {},
273 | "outputs": [],
274 | "source": [
275 | "plt.scatter(response.record['energy'], response.record['num_occurrences'])"
276 | ]
277 | },
278 | {
279 | "cell_type": "code",
280 | "execution_count": null,
281 | "metadata": {},
282 | "outputs": [],
283 | "source": [
284 | "response.record['num_occurrences']"
285 | ]
286 | }
287 | ],
288 | "metadata": {
289 | "kernelspec": {
290 | "display_name": "Python 3",
291 | "language": "python",
292 | "name": "python3"
293 | },
294 | "language_info": {
295 | "codemirror_mode": {
296 | "name": "ipython",
297 | "version": 3
298 | },
299 | "file_extension": ".py",
300 | "mimetype": "text/x-python",
301 | "name": "python",
302 | "nbconvert_exporter": "python",
303 | "pygments_lexer": "ipython3",
304 | "version": "3.8.5"
305 | }
306 | },
307 | "nbformat": 4,
308 | "nbformat_minor": 4
309 | }
310 |
--------------------------------------------------------------------------------
/04-annealing-applications/annealing-applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/04-annealing-applications/annealing-applications.pdf
--------------------------------------------------------------------------------
/04-annealing-applications/annotated-annealing-applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/04-annealing-applications/annotated-annealing-applications.pdf
--------------------------------------------------------------------------------
/04-annealing-applications/blank-annealing-applications.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/04-annealing-applications/blank-annealing-applications.pdf
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Olivia Di Matteo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Intro-QC-TRIUMF
2 | Lecture slides and notebooks for my first lecture series at TRIUMF, "An introduction to quantum computing and quantum annealing".
3 |
4 | To run the notebooks, you'll need:
5 | - Python 3 (Anaconda is best; if not, you'll need numpy and networkx)
6 | - IBM's [Qiskit](https://qiskit.org/terra)
7 | - D-Wave's [Ocean SDK](https://docs.ocean.dwavesys.com/en/latest/overview/install.html)
8 |
9 | Feel free to re-use these materials however you like, and let me know if you find any errors!
10 |
--------------------------------------------------------------------------------
/Resources.md:
--------------------------------------------------------------------------------
1 | # Resources
2 |
3 | I've pulled lecture materials from a variety of sources, but below is a collection that I've found particularly useful or interesting.
4 |
5 | This list is non-exhaustive, and I'll be adding to it periodically as the lectures progress.
6 |
7 | ## General quantum computing
8 | - [Introduction to Quantum Information and Computation (Nielsen and Chuang)](https://www.amazon.ca/Quantum-Computation-Information-10th-Anniversary/dp/1107002176/ref=sr_1_1?crid=UDDSOP619RB&keywords=quantum+computation+and+quantum+information&qid=1557595705&s=gateway&sprefix=quantum+computation+%2Caps%2C311&sr=8-1); basically the quantum computing 'bible'
9 | - [Quantum computing for the very curious](https://quantum.country/qcvc) (by Nielsen from Nielsen and Chuang)
10 | - [Learning Quantum Computing with Python and Q#](https://www.manning.com/books/learn-quantum-computing-with-python-and-q-sharp) (early access link to a new textbook, being written by some of my collaborators)
11 | - [Quantum Computing: A Gentle Introduction](https://www.amazon.ca/Quantum-Computing-Introduction-Eleanor-Rieffel/dp/0262526670); haven't looked through it myself, but have heard good things from friends
12 | - [Adiabatic Quantum Computing](http://arxiv.org/abs/1611.04471)
13 | - [Some lecture notes on projective measurements and teleportation](https://www.people.vcu.edu/~sgharibian/courses/CMSC491/notes/Lecture%203%20-%20Measurement.pdf)
14 |
15 | ## Hardware
16 | - [Quantum computing report](https://quantumcomputingreport.com/), a webpage that keeps track of major companies and startups and their hardware progress
17 | - [An outlook on superconducting circuits.]([https://science.sciencemag.org/content/339/6124/1169.full)
18 | - [A Youtube playlist by D-Wave about quantum annealing](https://www.youtube.com/playlist?list=PLPvKnT7dgEsvVQwGgrlUVXBa2J6PAW8a4)
19 | - [Experimental Comparison of Two Quantum Computing Architectures](https://arxiv.org/abs/1702.01852) (discusses superconducting qubits and trapped ions)
20 | - [Quantum computing with neutral atoms](https://physicstoday.scitation.org/doi/10.1063/PT.3.3626)
21 | - [Quantum computing with atomic qubits and Rydberg interactions](https://arxiv.org/abs/1605.05207)
22 | - [A Quantum Engineer's Guide to Superconducting Qubits](http://arxiv.org/abs/1904.06560) (a bit too technical for me, but maybe suitable for people with a more hardware experience)
23 |
24 |
25 | ## Algorithms and applications
26 | - [A NASA Perspective on Quantum Computing](https://arxiv.org/abs/1704.04836), mostly about quantum annealing but gives some nice applications and a technical discussion of hardware
27 | - [A list of problems that are QMA-complete](https://arxiv.org/abs/1212.6312), i.e. hard even for quantum computers
28 | - [Ground-state energy estimation of the water molecule on a trapped ion quantum computer](https://arxiv.org/abs/1902.10171)
29 | - [Quantum Computational Chemistry](http://arxiv.org/abs/1808.10402), a very clear and methodical overview of the field
30 | - [Ising formulation of many NP problems](http://arxiv.org/abs/1302.5843)
31 |
32 | ## Software
33 | - [IBM Qiskit documentation](https://qiskit.org/documentation/)
34 | - [Rigetti's Forest SDK](https://www.rigetti.com/forest)
35 | - [D-Wave Ocean overview](https://ocean.dwavesys.com/)
36 | - [Google's Cirq Python library](https://github.com/quantumlib/Cirq)
37 | - [Overview of software by Xanadu](https://www.xanadu.ai/software/) (it's Beatles themed!)
38 | - [Microsoft's Q# programming language](https://docs.microsoft.com/en-us/quantum/?view=qsharp-preview)
39 | - [Q++](https://github.com/vsoftco/qpp) (a quantum simulator written by ones of my collaborators at Waterloo)
40 | - [OpenFermion](https://github.com/quantumlib/openfermion) (Python libraries for simulation of Fermionic systems, quite intuitive to use)
41 |
42 |
--------------------------------------------------------------------------------
/fall-students-2019/lecture-01-annotated.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/fall-students-2019/lecture-01-annotated.pdf
--------------------------------------------------------------------------------
/fall-students-2019/lecture-01-full.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/fall-students-2019/lecture-01-full.pdf
--------------------------------------------------------------------------------
/fall-students-2019/lecture-02-annotated.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/fall-students-2019/lecture-02-annotated.pdf
--------------------------------------------------------------------------------
/fall-students-2019/lecture-02-full.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/fall-students-2019/lecture-02-full.pdf
--------------------------------------------------------------------------------
/summer-students-2019/lecture-01-annotated.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/summer-students-2019/lecture-01-annotated.pdf
--------------------------------------------------------------------------------
/summer-students-2019/lecture-01-blank.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/summer-students-2019/lecture-01-blank.pdf
--------------------------------------------------------------------------------
/summer-students-2019/lecture-01-unclear-topics.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/summer-students-2019/lecture-01-unclear-topics.pdf
--------------------------------------------------------------------------------
/summer-students-2019/lecture-01.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/summer-students-2019/lecture-01.pdf
--------------------------------------------------------------------------------
/summer-students-2019/lecture-02-blank.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/summer-students-2019/lecture-02-blank.pdf
--------------------------------------------------------------------------------
/summer-students-2019/lecture-02.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/summer-students-2019/lecture-02.pdf
--------------------------------------------------------------------------------
/syllabus.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/syllabus.pdf
--------------------------------------------------------------------------------
/winter-students-2020/lecture-01-annotated.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/winter-students-2020/lecture-01-annotated.pdf
--------------------------------------------------------------------------------
/winter-students-2020/lecture-01.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/winter-students-2020/lecture-01.pdf
--------------------------------------------------------------------------------
/winter-students-2020/lecture-02-annotated.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/winter-students-2020/lecture-02-annotated.pdf
--------------------------------------------------------------------------------
/winter-students-2020/lecture-02.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glassnotes/Intro-QC-TRIUMF/1b54a7fd9795ff5a8541c9a99fdb75365cbd113a/winter-students-2020/lecture-02.pdf
--------------------------------------------------------------------------------