Quantum Teleportation From Scratch to Magic. Part 2 — The How? Doing It on a Real Device

Edwin Agnew
The Startup
Published in
11 min readJul 26, 2020

--

With the theory done, we can now teleport a real qubit on a real device!

This is the second part of my series on quantum teleportation. The first part covers the basics of quantum computing and ends with a description of the circuit which we will be using in this article. In this part, we will pick up there and learn how to code it using a python package qiskit. Along the way, you’ll also learn a bit about some of the current challenges in quantum computing. If you feel confused at any point, have another read of the first part or ask me in the questions!

Prerequisites:

  • Python and Jupyter notebook installed
  • An IBMQ account
  • Familiarity with quantum computing notation and the circuit below
  • A pair of red socks with at least one hole in

So this is the magic circuit:

Circuit for Quantum Teleportation

To summarise:

  • Alice and Bob share a Bell pair, β₀₀.
  • Alice wants to send an arbitrary qubit, ψ, to Bob.
  • Alice has the top two qubits in the diagram, Bob has the bottom one.
  • Alice applies two gates to her qubits.
  • Alice measures her qubits and sends the results (classically) to Bob.
  • Bob applies the appropriate corrections and recovers ψ!

Qiskit

This tutorial will be programmed in qiskit. Qiskit is an open-source python package developed by IBM. It makes the creation and simulation of quantum circuits extremely easy. You can also send your circuits to one of IBM’s 9 publically available quantum computers, as you’ll see later.

To install qiskit, open terminal/cmd and type:

pip install qiskit

Then open a new jupyter notebook (you can do this in any python environment, but jupyter is the best way to follow along).

Our first disappointment of the article is that we are not actually going to teleport a qubit very far. The current technology only allows us to run a single circuit on a single quantum computer at any one time and so we will only be teleporting our qubit from the top of the circuit to the bottom. You will have to pretend that Alice and Bob separate themselves after creating their Bell pair.

So to create our lonesome circuit, import qiskit and create a 3 qubit circuit as follows:

from qiskit import QuantumCircuit

qc = QuantumCircuit(3)

Simple as that! To see it type qc.draw() and you should see:

Unsurprisingly, not a lot going on so far. We can, however, see that the qubits are labelled q_0, q_1 and q_2. We would prefer to distinguish between Alice’s and Bob’s qubits. To do that we need to create separate ‘quantum registers’ which can be named anything. We’ll make one for Alice (with 2 qubits) and one for Bob (with 1 qubit). To do that type:

from qiskit import QuantumRegister
alice_reg = QuantumRegister(2, name="alice")
bob_reg = QuantumRegister(1, name="bob")
qc = QuantumCircuit(alice_reg, bob_reg)
qc.draw()

This should give:

This looks a bit cumbersome, so change the last line to qc.draw(output='mpl') and it will format the diagram much more nicely:

We are finally ready to apply some gates.

Preparing Bell Pair

The first step in our protocol is for Alice and Bob to share a Bell pair. Remember from my first article to that to prepare a Bell pair we use the |+> state as a control in a CNOT and 0 as the target. In qiskit, that’s as simple as telling it which operations to apply on which qubits:

qc.h(alice_reg[1]) 
qc.cx(alice_reg[1], bob_reg[0])

Drawing the circuit should now give:

Preparing a Bell pair in qiskit (NB all qubits start in 0 state)

Remember how last time I mentioned that measuring a Bell pair many times will randomly give 00 and 11, but never anything else? Remember that Einstein wasn’t so convinced? Well, let’s test it!

All we need to do measure the qubits. To do that we‘ll create some ClassicalRegisters and add them to the circuit (there are easier ways to do this, but we have to do it this way, for now, to allow the measurement results to be used in the circuit later on).

from qiskit import ClassicalRegisteralice_bell_class_reg = ClassicalRegister(1)
qc.add_register(alice_bell_class_reg)
bob_class_reg = ClassicalRegister(1)
qc.add_register(bob_class_reg)

This adds the classical registers to the bottom of the circuit:

And then to measure the results, simply do:

qc.measure(alice_reg[1], alice_bell_class_reg) #measures Alice's second qubit into the first classical bit
qc.measure(bob_reg[0], bob_class_reg) #measures Bob's first qubit in the second classical bit

Then we can either simulate the circuit or run it on a real device.

To simulate our circuit:

from qiskit import BasicAer, execute
backend = BasicAer.get_backend('qasm_simulator')
results = execute(qc, backend=backend).result()
results.get_counts()

This simulates the circuit 1024 times by default and gives me:

So we see that we have roughly random proportions of 00 and 11! Suck on that, local realism.

“Nay”, say you naysayers, “you have only simulated it. The qubits are even right next to each other. Show me the real deal”. Have patience my friends, all in good time. Unfortunately, the technology is not yet ready for us to separate these qubits very far either, as their lifetime is around 100μs. And yes, while it is true that this is a mere simulation, see here for a comprehensive list of the times that it has been experimentally verified outside of a quantum computer.

Still not good enough for you? Alright then, let’s run it on the real thing.

The first step is to link your IBMQ account with your qiskit (I’ve obviously not shown you my key, and I’ve stored in the past so yours should say something different):

You can then see what devices are available for you:

IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
provider.backends()

This corresponds to the list of devices on the IBM quantum experience website:

Full list doesn’t fit in a screenshot

So all we need to do is choose a device and send it off. I’m going to choose the least busy.

# get the least-busy backend at IBM and run the quantum circuit there
from qiskit.providers.ibmq import least_busy
backend = least_busy(provider.backends(simulator=False))
print(backend)
job_exp = execute(qc, backend=backend, shots=1024)
exp_result = job_exp.result()
exp_measurement_result = exp_result.get_counts(qc)
print(exp_measurement_result)

Here is what I got:

Uh oh, it’s not just 00 and 11 :( . I can hear those naysayers, with newfound confidence and a hint of smugness, telling me “you kept promising we’d get these magic results but as soon as you do it on a real quantum computer it fails, screw you man”. At first glance, it does indeed seem like I’ve been leading you on like absolute mugs. Luckily for both of us, that’s not the case.

Plotting the histogram of the results shows that we did what we were hoping did happen most of the time:

The rare occurrences of 01 and 10 are, unfortunately, due to errors in the quantum computer. Error rates on quantum computers are still very real problems. When I ran my circuit, it was executed on ‘ibmq_vigo’. Clicking on that device on the IBM quantum experience website, it shows me this information:

Error rates for a quantum computer (July 2020)

This diagram tells me how many qubits the device has, which qubits can be connected by CNOTS (the arrows) and the typical error rates (the colours). The CNOT error rates are therefore between 7/10³ and 2/10² so its not that surprising that we had 70 (52 + 18) errors out of 1024 runs of the circuit. Of course, there is a lot of work going into decreasing these errors and it will be very interesting to see how much better they will be in just a years time (why not share this article with all your friends so they remind you to read it again then?)

Alright so there you are, you have performed real magic on a real quantum computer, admittedly with some errors along the way.

Preparing ψ

The second step in our protocol is for Alice to have the qubit ψ. The whole point of ψ is that it could be anything. In our example, we’ll make it applying a quarter of an X gate. An X gate is implemented as a rotation on the x-axis, known as rx, by angle π and so to do a quarter of that we can just do and rx rotation by π/4 (feel free to apply any amount you would like). We will apply it on Alice’s first qubit. We’ll also add a classical register to measure it into later

from math import pi
qc.rx(pi/4, alice_reg[0])
alice_psi_class_reg = ClassicalRegister(1)
qc.add_register(alice_psi_class_reg)

Now restart your kernel and run your code again from the beginning, missing out the cells which measured, simulated and ran the circuit as we don’t want to destroy our precious Bell pair quite yet. Adding a barrier to separate our stages then drawing the circuit again, you should now see:

To see what the effect of that Rx gate is, I’m going to measure the first qubit and simulate it a few times. I’ll show my results so you don’t need to code this bit if you don’t want.

We get mostly zeros with a few ones (don’t worry about the exact ratio or the second two qubits since we haven’t measured them). But if we apply an Rx gate with an angle -π/4, we’ll return to more consistent results.

Alright lovely stuff, so the plan is to apply this -π/4 rotation on the third qubit after we’ve done the teleportation, fixing it back to zero.

The actual circuit

In terms of our circuit diagram, we are now at ψ₀. So all we need to do is apply our two gates:

qc.cx(alice_reg[0], alice_reg[1])
qc.h(alice_reg[0])

Adding some more barriers for aesthetic convenience and drawing we now have:

Next, we’ll measure Alice’s qubits and then to send the information, you can use qiskit.c_ifas follows:

qc.measure(alice_reg[0], alice_psi_class_reg)
qc.measure(alice_reg[1], alice_bell_class_reg)
qc.x(bob_reg[0]).c_if(alice_bell_class_reg, 1) # Apply X to Bob if 2nd qubit measured to 1
qc.z(bob_reg[0]).c_if(alice_psi_class_reg, 1) # Apply Z if 1st qubit measured to 1

This adds these special lines to the circuit, which correspond to the double lines in our original circuit diagram:

The qubit has now been teleported and recovered! Finally, we’ll apply that -π/4 gate to ensure we measure zeros and then measure Bob’s qubit:

qc.rx(-pi/4, bob_reg[0]
qc.measure(bob_reg[0], bob_class_reg)

and simulate:

from qiskit import BasicAer, execute
backend = BasicAer.get_backend('qasm_simulator')
results = execute(qc, backend=backend).result()
results.get_counts()

Here is what I got:

The results look pretty random but when comparing to the final circuit diagram:

We see that the first and last qubits are the ones used for the correction. These happen randomly. More importantly, notice that the middle qubit is always 0. That is the one we teleported! So not only did we successfully teleport the qubit ψ, but we also correctly recovered it for every combination of Alice’s measurement. Very very cool.

Teleporting on a real device

So we are finally ready for the grand finale. Here’s the code again to find a device and run it:

provider = IBMQ.get_provider(hub='ibm-q')# get the least-busy backend at IBM and run the quantum circuit there
from qiskit.providers.ibmq import least_busy
backend = least_busy(provider.backends(simulator=False))
print(backend)
job_exp = execute(qc, backend=backend, shots=1024)
exp_result = job_exp.result()
exp_measurement_result = exp_result.get_counts(qc)
print(exp_measurement_result)

And…. it doesn't like it:

Not a particularly useful error message, but luckily I’m here to tell you that the problem is that we can’t actually use the classical communication channels on real quantum computers yet.

So is that it? Time to go home?

Fear not! We can still run the circuit but we will have to make a small compromise. We’re going to have to get rid of the c_if commands and replace them with CNOT and CZ (like a CNOT but performs a Z on the target if control is 1) gates before the measurement of Alice’s gates. Due to something called the principle of deferred measurement, there is actually no observable difference in doing this. However, this does slightly ruin our idea of long-distance teleportation since the qubits have to be connected on a quantum computer to perform these operations. Nevertheless, with current technology that is our only option.

Since we now no longer need to measure Alice’s qubits, we may as well delete those classical registers. Then, in the block which previously had the c_if operations write:

qc.cx(alice_reg[1], bob_reg[0])
qc.cz(alice_reg[0], bob_reg[0])

Then restarting the kernel and running again, the entire circuit should now look like:

The gate which looks like two controls is the CZ gate. If you’re wondering how you could tell which is the control, it actually doesn't matter because it adds a sign to the entire state if and only if both qubits are in the 1 state (remember Z|0> does nothing)

This circuit we can run on an actual quantum computer. So let’s do it:

from qiskit import IBMQ
IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
from qiskit import execute
from qiskit.providers.ibmq import least_busy
backend = least_busy(provider.backends(simulator=False))
print(backend)
job_exp = execute(qc, backend=backend, shots=1024)
exp_result = job_exp.result()
exp_measurement_result = exp_result.get_counts(qc)
print(exp_measurement_result)

Plotting the histogram of the results:

We can see that we got 0 an overwhelming number of times, the small fraction of 1 results being due to errors.

Congratulations! You have learnt how to teleport a qubit using qiskit on a real quantum computer, with some minor caveats.

[Note: since publishing this article, IBM have now introduced a feature called “mid circuit measurement” which does exactly what it says on the tin, and means you can run teleportation “properly” on a real device. For more info see https://quantum-computing.ibm.com/lab/docs/iql/manage/systems/midcircuit-measurement/]

If you got lost at any point along the way, here is a link to the final circuit code.

Thanks for reading!

Now check out part 3 of this series, a discussion about how any of this actually works.

--

--

Edwin Agnew
The Startup

Quantum Computing, Artificial Intelligence & Philosophy Enthusiast. Oxford Masters Student