{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"qat-category": "1-Circuit generation in Python",
"qat-summary": "1-Creation of an EPR pair using two qubits"
},
"source": [
"\n",
"\n",
"# Quantum \"Hello World!\": Superposition and Measurement\n",
"\n",
"## Write and execute a real quantum algorithm!\n",
"\n",
"### We will create a **superposition state** using the **Hadamard** gate and then observe the resulting qubit state. \n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## First step: Create a program\n",
"\n",
"The first step towards creating a quantum circuit in 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",
"The following cell imports the functions from qat.lang.AQASM:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from qat.lang.AQASM import * # the asterisk '*' imports all functions from the library"
]
},
{
"cell_type": "markdown",
"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"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"prog = Program()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Second step: Allocate the qubits\n",
"\n",
"\n",
"We need **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,
"metadata": {},
"outputs": [],
"source": [
"qbits = prog.qalloc(1) "
]
},
{
"cell_type": "markdown",
"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",
"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,
"metadata": {},
"outputs": [],
"source": [
"prog.apply(H, qbits[0]) "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"metadata": {},
"source": [
"## Fourth step: Create and visualize the circuit\n",
"\n",
"MyQLM (and 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,
"metadata": {},
"outputs": [],
"source": [
"circuit = prog.to_circ()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now that we have a proper circuit, we can vizualize it using:\n",
"+ %qatdisplay CIRCUIT_NAME"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%qatdisplay circuit"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The circuit vizualisation is a useful tool to quickly verify if the implementation of a program went well."
]
},
{
"cell_type": "markdown",
"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 QLM the QPU is a classical simulator that *mimics 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:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"job1 = circuit.to_job()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The arguments given to the 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,
"metadata": {},
"outputs": [],
"source": [
"job2 = circuit.to_job(nbshots = 5)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"To make a simulation with our job on a QLM we need to submit it to a QPU.\n",
"\n",
"To do so we first need to:\n",
"+ import the function PyLinalg from qat.pylinalg\n",
"+ create a QPU by calling PyLinalg()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from qat.pylinalg import PyLinalg \n",
"qpu = PyLinalg() # PyLinalg comes from Python Linear algebra - the method used to simulate quantum mechanics"
]
},
{
"cell_type": "markdown",
"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,
"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",
"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,
"metadata": {},
"outputs": [],
"source": [
"for sample in result1:\n",
" print(\"state:\", sample.state, \"probability amplitude:\", sample.amplitude, \"probability:\", sample.probability, )"
]
},
{
"cell_type": "markdown",
"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."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, let us look at the result of the job that emulated 5 repeated measurements. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for sample in result2:\n",
" print(\"state:\", sample.state, \"probability amplitude:\", sample.amplitude, \"probability:\", sample.probability)\n"
]
},
{
"cell_type": "markdown",
"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.\n",
"\n",
"You can try changing the number of shots below, to see its effect on the probabilities:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"job3 = circuit.to_job(nbshots = 42)\n",
"\n",
"result3 = qpu.submit(job3)\n",
"\n",
"for sample in result3:\n",
" print(\"state:\", sample.state, \"probability:\", sample.probability)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Real world application: true random numbers"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Since the outcomes of quantum system's measurements are **fundamentally random**, they can be utilized in production of truly random numbers (as opposed to quasi-random numbers, whose patterns can be predicted with enough information). \n",
"\n",
"We review one more argument of the .to_job method that enables neat printing of our 'true random numbers'. \n",
"\n",
"By default the results of a job are **aggregated**. This means that if we launch measurements for example 100 times (nbshots=100), repeating outcomes are stored under one label along with the number they repeated (this is how we get and approximation for the probability of each outcome).\n",
"\n",
"It is possible to create a job *without* aggregating the results by using the argument **aggregate_data=False** in the to_job method. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# Create a job where we specify the number of shots and disable the aggregation of measurement outcomes\n",
"job4 = circuit.to_job(nbshots=10, aggregate_data=False)\n",
"\n",
"# Submit to the QPU\n",
"result4 = qpu.submit(job4)\n",
"\n",
"# And use it to draw 10 samples\n",
"for sample in result4:\n",
" print(\"We measured the state\", sample.state)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As you can see, the only information we are getting are the states of each execution.\n",
"\n",
"Let's modify the printing a little to get nice looking random number sequences:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"## we use the underscore '_' in front of 'state' to print without ket notation\n",
"## and include the argument end = \" \" to print the results in one line\n",
"for sample in result4:\n",
" print(sample._state, end = \" \") "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Note!\n",
"On some versions of myQLM, the underscore syntax gives an AttributeError. In this case, you can use the python .translate construct instead:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"## we use the underscore '_' in front of 'state' to print without ket notation\n",
"## and include the argument end = \" \" to print the results in one line\n",
"for sample in result4:\n",
" print(str(sample.state).translate(str.maketrans({'|': None, '>': None})), end = \" \")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Takeaway\n",
"\n",
"We have seen how to:\n",
"+ create a program\n",
"+ allocate some qubits\n",
"+ apply gates\n",
"+ generate a circuit\n",
"+ create a job (full distribution or measurement emulation. Aggregate data or not)\n",
"+ submit a job\n",
"+ display the result"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.8.6"
}
},
"nbformat": 4,
"nbformat_minor": 4
}