{
"cells": [
{
"cell_type": "markdown",
"id": "83067351",
"metadata": {},
"source": [
"#### This notebook was created for the NeIC 2022 Quantum Computing Workshop held on 01.06.2022. Based on previous year notebooks.\n",
"\n",
"# **Quantum \"Hello World!\": Superposition and Measurement**\n",
"\n",
"#### Jake Muff - CSC IT Center for Science, FI\n",
"\n",
"In this workshop we will be writing and executing a real quantum algorithm using jupyter notebooks (python) and [myQLM](https://myqlm.github.io/index.html).\n",
"\n",
"We will be creating a **superposition state** using single qubit and a **Hadamard** gate. We will then observe the resulting qubit state. \n",
"\n",
"After this, there will be a (more) hands on task: Create a Quantum Random Number generator!\n"
]
},
{
"cell_type": "markdown",
"id": "fbd34d07",
"metadata": {},
"source": [
"## First step: Create a program\n",
"\n",
"The first step towards creating a quantum circuit on *myQLM* is to create a variable that will hold a corresponding **program**.\n",
"This is done by:\n",
"\n",
"+ importing the functions from **qat.lang.AQASM** -library\n",
"+ creating a program instance\n",
"\n",
"First we must import the functions from `qat.lang.AQASM`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7bd622c",
"metadata": {},
"outputs": [],
"source": [
"from qat.lang.AQASM import Program, H\n",
"# In practice we can use `import *` to import all functions from the library. However we do not need to here. "
]
},
{
"cell_type": "markdown",
"id": "780839e9",
"metadata": {},
"source": [
"Now we can create a **program object**.\n",
"\n",
"To do so you need:\n",
"+ to define a name for the variable of your program\n",
"+ to call from the AQASM library the function **Program**\n",
"\n",
"The program object is used to store the quantum circuits and contains a list of all the quantum operations before on the circuit as well as the qubits they are applied to. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "dddee5a9",
"metadata": {},
"outputs": [],
"source": [
"prog = Program()"
]
},
{
"cell_type": "markdown",
"id": "6812eadc",
"metadata": {},
"source": [
"## Second step: Allocate the qubits\n",
"\n",
"\n",
"We need to only one qubit in our program.\n",
"\n",
"We need to:\n",
"+ define the name for our register of qubits\n",
"+ call the function **qalloc** (\"qubit allocate\") on our program\n",
"+ define the number of qubits we want\n",
"\n",
"The following cell allocates (creates) one qubit to our program:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "773ae3a8",
"metadata": {},
"outputs": [],
"source": [
"qbits = prog.qalloc(1) # allocating 1 qubit for now"
]
},
{
"cell_type": "markdown",
"id": "23fbaf46",
"metadata": {},
"source": [
"## Third step: Applying gates\n",
"\n",
"Now, we can have access to our qubit using the name of the register.\n",
"\n",
"Registers behave like python list/arrays, for example if you named your register QUBIT_REGISTER:\n",
"+ QUBIT_REGISTER[0] is the first qubit.\n",
"+ QUBIT_REGISTER[1] is the second qubit.\n",
"\n",
"To *create a superposition*, we simply need to **apply** the Hadamard gate to the qubit:\n",
"To do so we need to:\n",
"+ specify on which program we wish to apply our gate\n",
"+ specify the gate we wish to apply\n",
"+ specify the name of the qubit register we wish to apply the gate\n",
"+ specify the index of the qubit inside the register\n",
"\n",
"\n",
"The following cell applies the Hadamard gate (**H**) to the first (and in this case only) qubit in the register:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8c239892",
"metadata": {},
"outputs": [],
"source": [
"prog.apply(H, qbits[0]) "
]
},
{
"cell_type": "markdown",
"id": "b84f0c17",
"metadata": {},
"source": [
"Mathematically, what have we just done? The hadamard gate "
]
},
{
"cell_type": "markdown",
"id": "45bcafc4",
"metadata": {},
"source": [
"Now we have a program where Hadamard gate is applied to one qubit.\n",
"\n",
"We now need to create the **quantum circuit** associated with this program."
]
},
{
"cell_type": "markdown",
"id": "9837f595",
"metadata": {},
"source": [
"## Fourth step: Create and visualize the circuit\n",
"\n",
"The QLM is based on an object called a **circuit**.\n",
"\n",
"Once a program is created it is possible to generate the circuit from it.\n",
"\n",
"A circuit can therefore be:\n",
"+ executed\n",
"+ optimized\n",
"+ used to create other circuits\n",
"\n",
"To create your circuit you will need to:\n",
"+ define the name of your cicuit\n",
"+ call the function **to_circ** on your program\n",
"\n",
"The following cell creates a circuit based on our program:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53b4f8ba",
"metadata": {},
"outputs": [],
"source": [
"circuit = prog.to_circ()"
]
},
{
"cell_type": "markdown",
"id": "84c8a8be",
"metadata": {},
"source": [
"We can now vizualize this circuit using the following command\n",
"+ %qatdisplay CIRCUIT_NAME\n",
"\n",
"Vizualizing the circuit can be a useful tool to quickly verify that we have created the correct circuit."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20332344",
"metadata": {},
"outputs": [],
"source": [
"%qatdisplay circuit"
]
},
{
"cell_type": "markdown",
"id": "390451b9",
"metadata": {},
"source": [
"## Fifth step: Execute and measure the circuit\n",
"\n",
"We now have a circuit object. This circuit can be made into a **job** that can be executed by a 'quantum processing unit', *i.e.*, a **QPU**. In *myQLM* the QPU is a classical simulator that *mimics (emulates) a real physical QPU*. It does this by keeping track of all the possible qubit states and their evolution. This requires a lot of memory and is in general possible only for a small number of qubits (< 50).\n",
"\n",
"To create a job we need to:\n",
"+ define the name for our job\n",
"+ call the method **to_job** on a circuit\n",
"\n",
"First, we will call `to_job` without any parameters:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a8dd2801",
"metadata": {},
"outputs": [],
"source": [
"job1 = circuit.to_job() # leaving the arguments empty corresponds to an infite number of shots\n",
"# Meaning we would get a theoretical result"
]
},
{
"cell_type": "markdown",
"id": "45445890",
"metadata": {},
"source": [
"The arguments given to `to_job()` method define the kind information we want to extract from our qubits after the circuit is executed. More precisely, whether we want to **emulate measurement** or take advantage of the fact that we are only simulating one, and thus have information of the **full distribution of states**. Giving no arguments as in the previous cell, defaults to the latter case, full distribution.\n",
"\n",
"**In reality**, when measuring physical qubits one observes only definite values corresponding to zeros and ones; the *probability amplitudes* describing the quantum state cannot be measured. \n",
"\n",
"To simulate this in pyAQASM, we need to give the `circuit.to_job()` method an argument: `nbshots`, and set it to equal the number of times we want to execute and measure the circuit. The following cell creates a job that corresponds to emulating **5** repeated measurements. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "05b48f42",
"metadata": {},
"outputs": [],
"source": [
"job2 = circuit.to_job(nbshots = 5)\n",
"# Now we are running with a finite number of shots"
]
},
{
"cell_type": "markdown",
"id": "5a03d8f7",
"metadata": {},
"source": [
"To run our job we need to use a simulator. Here is where we diverge from running this on a real quantum computer. The simulator that *myQLM* uses is called `PyLinalg()` and is written in python. More information can be found [here](https://myqlm.github.io/myqlm_specific/qat-pylinalg.html). \n",
"\n",
"The *QPU* or Quantum Processing Unit simulates the execution of quantum jobs with classical simulation methods. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8e73a9ab",
"metadata": {},
"outputs": [],
"source": [
"from qat.pylinalg import PyLinalg\n",
"#from qat.qpus import PyLinalg another way to import it. \n",
"\n",
"qpu = PyLinalg() # PyLinalg comes from Python Linear algebra - the method used to simulate quantum mechanics"
]
},
{
"cell_type": "markdown",
"id": "173034f0",
"metadata": {},
"source": [
"We can now submit the job to our simulator.\n",
"\n",
"To do so we need to use the function **submit** on our QPU and pass our job as a parameter. The output will be stored in the **result** object:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d3c9efed",
"metadata": {},
"outputs": [],
"source": [
"result1 = qpu.submit(job1) # results of the 'full distribution' job\n",
"\n",
"result2 = qpu.submit(job2) # results of the 'measurement emulation' job"
]
},
{
"cell_type": "markdown",
"id": "ce1a4027",
"metadata": {},
"source": [
"## Sixth step: Read out the result\n",
"\n",
"The result object is an array of **samples**. Samples hold information of the qubit register after the execution. The type of information depends again on the job that was submitted. Let us look at the two cases:\n",
"+ **full distribution**: samples hold probability amplitudes (and probabilities) of each possible state\n",
"+ **measurement emulation**: samples hold statistical probabilities of states, calculated from repeated measurements\n",
"\n",
"The samples are conveniently accessed using a *for loop*. The following cell displays the result from the evaluation of the *full distribution* job, job1:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "89cf5ed6",
"metadata": {},
"outputs": [],
"source": [
"for sample in result1:\n",
" print(\"state:\", sample.state, \"probability amplitude:\", sample.amplitude, \"probability:\", sample.probability)"
]
},
{
"cell_type": "markdown",
"id": "1e56d7de",
"metadata": {},
"source": [
"We see that the qubit is in an equal superposition of 0 and 1. Measuring the qubit would give |0> with 50% and |1> with 50%. \n",
"\n",
"Note that probabilities are connected to probability amplitudes by $P_\\alpha=|\\alpha|^2$, where $\\alpha$ is a probability amplitude of a state and $P_\\alpha$ is probability that this state is observed in a measurement.\n",
"\n",
"Finally, let us look at the result of the job that emulated 5 repeated measurements. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9460c84f",
"metadata": {},
"outputs": [],
"source": [
"for sample in result2:\n",
" print(\"state:\", sample.state, \"probability amplitude:\", sample.amplitude, \"probability:\", sample.probability)"
]
},
{
"cell_type": "markdown",
"id": "6ba49852",
"metadata": {},
"source": [
"Now, just like with real world quantum systems, the probability amplitudes of the states are unknown. The result consists of the statistical probabilities obtained from 5 repeated measurements. Because we only have 5 measurements the results are largely random and not converged. \n",
"\n",
"If we increase the number of `nbshots`, what would we see? Try for yourself!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39486c5f",
"metadata": {},
"outputs": [],
"source": [
"job3 = circuit.to_job(nbshots = 1000)\n",
"\n",
"result3 = qpu.submit(job3)\n",
"\n",
"for sample in result3:\n",
" print(\"state:\", sample.state, \"probability:\", sample.probability)"
]
},
{
"cell_type": "markdown",
"id": "ca0d4ce1",
"metadata": {},
"source": [
"## Summary"
]
},
{
"cell_type": "markdown",
"id": "bc5242db",
"metadata": {},
"source": [
"\n",
"1. Create a program with `Program()`\n",
"2. Allocate how many qubits you want and/or need with `qalloc(x)`\n",
"3. Apply the gates to your program with `apply(gate, qubits[i]`\n",
"4. Create the circuit with `to_circ()`\n",
"5. Vizualize the circuit with `qatdisplay circuit`\n",
"6. Create your job (`to_job()`)and decide how many 'shots' you want `nbshots=`\n",
"7. Define a QPU and submit the job to it `qpu.submit(job)`\n",
"7. Print your results\n",
"\n",
"In one cell this looks like:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "996591a3",
"metadata": {},
"outputs": [],
"source": [
"from qat.lang.AQASM import Program, H\n",
"from qat.pylinalg import PyLinalg\n",
"\n",
"qpu = PyLinalg()\n",
"\n",
"prog = Program()\n",
"\n",
"qbits = prog.qalloc(1)\n",
"\n",
"prog.apply(H, qbits[0])\n",
"\n",
"circuit = prog.to_circ()\n",
"\n",
"job3 = circuit.to_job(nbshots = 100)\n",
"\n",
"result3 = qpu.submit(job3)\n",
"\n",
"for sample in result3:\n",
" print(\"state:\", sample.state, \"probability:\", sample.probability)\n",
" \n",
"%qatdisplay circuit"
]
},
{
"cell_type": "markdown",
"id": "44a37ce7",
"metadata": {},
"source": [
"## Also check out\n",
"\n",
"- https://quantumtictactoe.com/\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "faf6094a",
"metadata": {},
"source": [
"## DonĀ“t forget\n",
"\n",
"Save your own copy of the notebook!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed59e229",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}