In the previous article on superdense coding we built a very simple Qiskit program that ran the superdense coding program straight from the website without really going in to why it works.
In this article we are going to build on our knowledge of
- CNOT Gate
- Hadamard Gate
- Bell States
- Entanglement (maximal correlation)
In particular we will make use of being able to create a superposition of two qubits by using the Hadamard gate and then kill off any unwanted, uncorrelated states using the CNOT gate. We will see that the desired outcome of applyinga CNOT gate here really depends on the initialisation states of the qubits: the all-zeroes initialisation yields maximally correlated Bell states after a Hadamard and a CNOT (it doesn’t if they’re initialised to the all-ones state!)
Recall that the Hadamard gate for 2 qubits is the -matrix
and applying this to the zero state qubit is
which, of course, after taking the absolute value-square produces either a 0 or a 1 with 50% upon measurement.
A Quick Note on the Initial All-Zeroes State
Applying this combination of Hadamard-and-then-CNOT to get the maximally correlated Bell states (a.k.a. entangled states) only works if the qubit register is initialised in the all-zeroes state – which is why the starting qubits are always . This is important, because if the starting qubits are , then the resulting states are the anti-correlated Bell states.
Hadamard in Qiskit
In Qiskit, we can apply a Hadamard gate to the first qubit with the following Python command:
qc is a
QuantumCircuit object, and
q is the first (indexed zeroeth) qubit.
In the previous article we discussed how a Hadamard gate following by a CNOT gate creates the maximally correlated qubits called Bell states.
Crucially, the states on the right-hand side contain only 2 of the possible 4 combination of states. The sequence of applying a Hadamard followed by a CNOT gate kills off 2 of the 4 states and leaves behind these maximally correlated qubits.
CNOT in Qiskit
In Qiskit, we can apply a CNOT gate to the first qubit with the following Python command:
The first qubit in the parentheses is the control qubit and the second is the target qubit.
We recall here that the first two Bell States are obviously maximally correlated (and hence entangled) because the state of the second qubit always has the same state of the first qubit. Even though measuring a 0 or a 1 on the first qubit is still totally random at 50% each, the state of the seconed qubit (also randomly 0 or 1 wih 50% chance) will be the same 0 or 1 as the first.
The second two Bell States are maximally anti-correlated.
Intuitively, these qubits that are maximally correlated or anti-correlated are entangled.
Reversing the Hadamard & CNOT
Since the Hadamard and CNOT gates are reversible we could actually apply Hadamard-then-CNOT-then-CNOT-then-Hadamard (a total of 4 gates) to completely undo everything!
This means that if we give someone a pair of entangled qubits then all they would have to do is apply the CNOT and then the Hadamard to reveal the initial states of the original input qubits!
But first, some housekeeping.
Please follow this section if it has been a while since you loaded up your virtual environment. Otherwise, if your are comfortable with your virtual quantum computer, you may want to skip ahead to the next section.
As it has been a while since I last looked at my virtual machine, I’m going to follow the next steps to ensure it is working.
So let’s spin up the virtual machine and load Terminal in Ubuntu wih
Ctrl+Alt+T and let’s get our quantum virtual environment back up and running.
We always start by updating the package manager just in case it has gone out-of-date between virtual machine uses:
$ sudo apt update && sudo apt upgrade
$ cd py_envs
and activate the Quantum virtual environment with
$ source Quantum/bin/activate
Note the command line now begins with
Verfiy we still have Git and Pip running by typing the following commands
(Quantum) $ git --version
(Quantum) $ pip --version
Re-initialise our Git Repository (repo) in the Quantum virtual environment by first changing directories, and then initialising with these commands:
(Quantum) $ cd Quantum
(Quantum) $ git init
This will re-initialise our
.git folder inside the Quantum virtual environment folder structure. Change directory to this folder to view the basic skeleton structure of an empty Git Repo:
(Quantum) $ cd .git/
Remind ourselves of our requirements package by opening it and scrolling down to our Qiskit libraries:
(Quantum) $ nano requirements.txt
ctrl+x to quit nano.
Go back one directory with
cd .. and check the status of git with
(Quantum) :~/py_envs/Quantum$ nano requirements.txt
ctrl+X to exit nano.
We begin by importing he Qiskit QuantumCircuit as qc, and the QuantumRegister as qr:
from qiskit import QuantumCircuit as qc
from qiskit import QuantumRegister as qr
from qiskit import ClassicalRegister as cr
and let us create a register of 2 qubits, a register of 2 classical qubits (to measure on), and initialise a circuit object:
q = qr(2, 'q')
c = cr(2, 'c')
circuit = qc(q, c)
Creating a Bell Pair Function
We know that applying a Hadamard followed immediately by a CNOT gate produces one of the Bell States (depending on the input – each combination of input: 00, 01, 10, or 11 maps to one of the 4 Bell States), we may as well create a function that does this:
def create_bell_pair(qc, q, q0, q1): qc.h(q[q0]) qc.cx(q[q0], q[q1])
Now, we can also create the inverse Bell creation function:
def inverse_bell_pair(qc, q, q0, q1): qc.cx(q[q0], q[q1]) qc.h(q[q0])
Your program should now look like this:
The Message Encoder
Now we are going to create our encoder function. This function will input some random message, called
message_to_send, which must be one of the four possible strings:
11 (not much room to move here since we are only working with 2 qubits!).
The problem is:
How do we encode a message?
Let us create, at the very least, a skeleton function to get started:
def encoder(qc, q, q0, message_to_encode): if message_to_encode == "00": pass # do nothing. elif message_to_encode == "10": pass # do something to encode it! elif message_to_encode == "01": pass # do something to encode it! elif message_to_encode == "11": pass # do something to encode it! else: print("Invalid message! Will just send '00'.")
So how do we encode the message?
Encoding the Message
OK, here are the rules:
- Alice wants to send 2 qubits worth of information.
- Alice can only encode 1 qubit worth of information.
- Therefore, the only gates the encoder can use are 1-qubit gates.
So let us try this:
- If the message Alice wants to send is the
00message, then she does nothing to her one qubit
- If the message is the
10message, then Alice will take her one qubit
q0and apply the one-qubit
- If the message is the
01message, then Alice will take her one qubit
q0and apply the one-qubit
- Finally, if the message is the
11message, then Alice will take her one qubit
q0and apply the one-qubit
Zgate, followed immediately by the one qubit
Therefore, our encoder function now looks like this:
def encoder(qc, q, q0, message_to_encode): if message_to_encode == "00": pass # do nothing. elif message_to_encode == "10": qc.x(q[q0]) # Apply the X gate. elif message_to_encode == "01": qc.z(q[q0]) # Apply the Z gate. elif message_to_encode == "11": qc.z(q[q0]) # Apply the Z gate and then... qc.x(q[q0]) # apply the X gate. else: print("Invalid message! Will just send '00'.")
You might be thinking at this point: “there is no way Alice can convey two bits of information to Bob by sending only one bit!”. And that’s a good thought! Classically, Bob would receive either a 0 or a 1 and have no way of knowing what Alice’s second bit is.
Quantumly, we are doing the same thing. Except we are somehow colouring the 0 and 1 a certain colour to convey what that second qubit is. See, colour is not a property one can play with classically. Quantumly, however, we can. Although it’s not called colour.
So how do we colour our qubit so that Bob knows what Alice’s second qubit is? You know, maybe Bob receives a red 0, and this tells him that the second qubit is a 0; and if Bob receives a blue 0, this means that the second qubit is a 1. Colouring qubits this way is conveying two bits of information using just one bit, plus utilising a extra degree of freedom (colour) that classical bits just don’t have.
Well, the encoding we just did is the effective colouring of the qubits!
X gate “colours” the qubit a certain way, as does the
Z gate and the
ZX gate. Let’s see how that works.
Alice Sends 11
Let’s suppose that Alice wants to send two qubits worth of information: the
11 state, but can only send 1 qubit to Bob. According to the superdense coding protocol (the
inverse_bell_pair function), the 2-qubit register is initialised in to the all-zero state and passed through a Hadamard gate:
so the first qubit () has a 50% chance of being measured in the 0 state and a 50% chance of being measured in the 1 state, similarly for the second qubit ().
With 2 possibilities for 2 qubits, that is 4 combinations altogether:
We destroy two of those four possibilities with a CNOT gate, resulting in:
…and we have our Bell State (entangled – maximally correlated).
From here, we use our Superdense Coding Protocol.
Since Alice is sending the
11 state we now run the register through the
ZX gate. First up, the
Since both and are eigenvectors of the -gate, the states are left unchanged. But the eigenvalues are 1 and -1, so we have:
and, as you can see, without physically using an extra qubit, we have essentially “coloured” the qubit with a shade of negative one:
In the above illustration it is clear that the -gate did absolutely nothing to our qubits – see those lines mapping 0 to 0 and 1 to 1. However, the qubits all picked up a negative 1 phase, which we indicate by colouring them yellow.
Meanwhile, the qubit sent over to Bob had nothing done to them. So we grey them out.
Now for the -gate. This one is easy because it just flips any states in to and vice versa, thus:
and, as you can see, we still have an entangled state except that it is slightly coloured yellow (the negative phase that it picked up by the -gate) – and we are only going to use just one of the qubits (hence why the second qubit is greyed out).
But this gate is crucial, as we will see, because now the negative yellow colouring is on the zero! And we will need this to cancel out something later on…
Bob Receives Alice’s Qubit!
So, Bob opens up his mail and sees that he has been sent precisely one qubit from Alice. He does not look at this qubit, not yet! But he hopes that somehow, when he does look at it, he will learn two bits of information!
So Bob takes that qubit and performs a CNOT on it – with his (already received) qubit as the target.
The above illustration shows that for Alice sending the
11 message, the overall state of the quantum system is now:
Now, performing the CNOT gate:
which reflects the computation:
Note that the second on the right is not yellow in the illustration because the Hadamard gate makes a double-negative phase, which makes it positive again.
And now, it is clear, that the postive state cancels with the yellow negative , and the two positive states double up, giving two lots of the same state:
…and Bob can be sure that Alice’s encoded message was “11”.
The full program so far is:
import matplotlib as mpl import matplotlib.pyplot as plt from qiskit import QuantumCircuity as qc from qiskit import QuantumRegister as qr from qiskit import ClassicalRegister as cr mpl.use('TkAgg') def show_figure(fig): new_fig = plt.figure() new_mngr = new_fig.canvas.manager new_mngr.canvas.figure = fig fig.set_canvas(new_mngr.canvas) plt.show(fig) # Setup of basic 2-qubit register and circuit. # Create two registers: one quantum, and one classical: q = qr(2, 'q') c = cr(2, 'c') circuit = qc(q, c) def create_bell_pair(qc, q, q0, q1): qc.h(q[q0]) # Apply a Hadamard to the first qubit. qc.cx(q[q0], q[q1]) # Apply a CNOT, controlled on the first qubit def inverse_bell_pair(qc, q, q0, q1): qc.cx(q[q0], q[q1]) qc.h(q[q0]) def encoder(qc, q, q0, message_to_encode): if message_to_encode == "00": pass elif message_to_encode == "10": qc.x(q[q0]) elif message_to_encode == "01": qc.z(q[q0]) elif message_to_encode == "11": qc.z(q[q0]) qc.h(q[q0]) else: print("Invalid message! Will just send '00'.") # Main create_bell_pair(circuit, q, 0, 1) circuit.barrier() # Qubit 0 goes to Alice, and qubit 1 goes to Bob. # Alice wants to send the 2-bit message: message_to_send = "11" # Encode the 2-bit message on to 1 qubit: encoder(circuit, q, 0, message_to_send) circuit.barrier # Alice sends her single qubit to Bob. # Bob receives Alice's qubit and puts it with his # in a register. Then decodes it using the inverse # Bell state creator: inverse_bell_pair(circuit, q, 0, 1) circuit.barrier() # Plot Circuit diagram = circuit.draw(output='mpl') show_figure(diagram)
The circuit should output like this:
In this article we explored the Superdense Coding Protocol but utilising our advanced knowledge of the Hadamard gate, the CNOT gate, and understanding of maximally correlated (or entangled) qubits.
We then performed a trick whereby we changed the phase of one qubit from positive (green) to negative (yellow), with the colours helping us through the illustrations.
We were able to convey two bits worth of knowledge by using this colouring methodology but interacting with only one bit – something which is impossible to do with classical bits.
Finally we went through the protocol step-by-step with one of the four possible encodings that Alice could do to see what happens and show how Bob can decode the encoding to learn what the 2-bit message.
In the next article we will:
- explore the other 3 components of the Superdense Coding Protocol and,
- hook this up to a real quantum computer and run it.
My Code Repository can be found on my public GitHub:
This blog’s program can be found under
 Chapter 2.3 of Nielsen & Chuang “Quantum Computation and Quantum Infortmation” (2016)
 Chapter 7.2 of Hidary “Quantum Computing: An Applied Approach” (2019)