The new Qiskit Textbook beta is now available. Try it out now
Superdense Coding

This notebook demonstrates the Superdense Coding (SDC) protocol. We first use Qiskit's simulator to test our quantum circuit, and then try it out on a real quantum computer.

Contents

  1. Superdense Coding and Quantum Teleportation
  2. The Process
    2.1 Step 1
    2.2 Step 2
    2.3 Step 3
  3. Simulating the Superdense Coding Protocol
    3.1 3.1 Visualizing Our Measurements
  4. Superdense Coding on a Real Quantum Computer

1. The Difference between Superdense Coding and Quantum Teleportation

Quantum teleportation and superdense coding are closely related, to avoid confusion we need to clarify the difference.

Quantum teleportation is a process by which the state of qubit ($|\psi\rangle$) can be transmitted from one location to another, using two bits of classical communication and a Bell pair. In other words, we can say it is a protocol that destroys the quantum state of a qubit in one location and recreates it on a qubit at a distant location, with the help of shared entanglement. Superdense coding is a procedure that allows someone to send two classical bits to another party using just a single qubit of communication.

Teleportation Superdense Coding
Transmit one
qubit using two
classical bits
Transmit two
classical bits
using one
qubit

The teleportation protocol can be thought of as a flipped version of the superdense coding protocol, in the sense that Alice and Bob merely “swap their equipment.”

2. The Process

image1

2.1 Step 1

The process starts with a third party, who we'll call Charlie. Two qubits are prepared by Charlie in an entangled state. He initially starts the 2 qubits in the basis state $|0\rangle$. He applies Hadamard gate ($H$) to the first qubit to create superposition. He then applies CNOT gate ($CX$) using the first qubit as a control and the second as the target. This is the entangled state (Bell pair) we mentioned earlier.

Outcome States

We start in the state:

$$|00\rangle = |0\rangle_A\otimes |0\rangle_B$$

where the qubit to be sent to Alice is labeled with $A$ and the qubit to be sent to Bob is labeled $B$. Charlie first applies a Hadamard gate to the first qubit, which creates superposition and we get the state:

$$|+0\rangle = \tfrac{1}{\sqrt{2}}(|00\rangle + |10\rangle)$$

Then Charlie applies the CNOT gate. The CNOT gate entangles both qubits, i.e. it flips the target if the control is $|1\rangle$. Note that the control qubit is our rightmost qubit.

$$\text{CNOT} \tfrac{1}{\sqrt{2}}(|00\rangle + |10\rangle) = \tfrac{1}{\sqrt{2}}(|00\rangle + |11\rangle)$$

2.2 Step 2

Charlie sends the first qubit to Alice and the second qubit to Bob. The goal of the protocol is for Alice to send 2 classical bits of information to Bob using her qubit. But before she does, she needs to apply a set of quantum gates to her qubit depending on the 2 bits of information she wants to send:

Encoding Rules for Superdense Coding (Alice protocol):

Intended Message Applied Gate Resulting State ($\cdot\tfrac{1}{\sqrt{2}}$)
00 $I$ $|00\rangle + |11\rangle$
01 $X$ $|10\rangle + |01\rangle$
10 $Z$ $|00\rangle - |11\rangle$
11 $ZX$ $-|10\rangle + |01\rangle$

Thus if she wants to send a 00, she does nothing to her qubit (apply the identity ($I$) gate). If she wants to send a 01, then she applies the $X$ gate. Depending on what she wants to send, she applies the appropriate gate, then sends her qubit to Bob for the final step in the process.

2.3 Step 3

Bob receives Alice's qubit (leftmost qubit) and uses his qubit to decode Alice's message. Notice that he does not need to have knowledge of the state in order to decode it — he simply uses the restoration operation.

Bob applies a CNOT gate using the leftmost qubit as control and the rightmost as target. Then he applies a Hadamard gate and finally performs a measurement on both qubits to extract Alice's message.

Bob Receives ($\cdot\tfrac{1}{\sqrt{2}}$) After CNOT-gate ($\cdot\tfrac{1}{\sqrt{2}}$) After H-gate
$|00\rangle + |11\rangle$ $|00\rangle + |10\rangle$ $|00\rangle$
$|10\rangle + |01\rangle$ $|11\rangle + |01\rangle$ $|01\rangle$
$|00\rangle - |11\rangle$ $|00\rangle - |10\rangle$ $|10\rangle$
$-|10\rangle + |01\rangle$ $-|11\rangle + |01\rangle$ $|11\rangle$

3. Simulating the Superdense Coding Protocol

# Importing everything
from qiskit import QuantumCircuit
from qiskit import IBMQ, Aer, transpile, assemble
from qiskit.visualization import plot_histogram

We saw that to create an entangled pair, we needed to do a H-gate followed by a CNOT. Let's create a function that takes a QuantumCircuit and entangles the qubits with indices a and b:

def create_bell_pair():
    """
    Returns:
        QuantumCircuit: Circuit that produces a Bell pair
    """
    qc = QuantumCircuit(2)
    qc.h(1)
    qc.cx(1, 0)
    return qc

Next we need to encode our message. We saw that there were four possible messages we could send: 00, 10, 01 or 11. Let's create a function that takes this message and applies the appropriate gates for us:

def encode_message(qc, qubit, msg):
    """Encodes a two-bit message on qc using the superdense coding protocol
    Args:
        qc (QuantumCircuit): Circuit to encode message on
        qubit (int): Which qubit to add the gate to
        msg (str): Two-bit message to send
    Returns:
        QuantumCircuit: Circuit that, when decoded, will produce msg
    Raises:
        ValueError if msg is wrong length or contains invalid characters
    """
    if len(msg) != 2 or not set([0,1]).issubset({0,1}):
        raise ValueError(f"message '{msg}' is invalid")
    if msg[1] == "1":
        qc.x(qubit)
    if msg[0] == "1":
        qc.z(qubit)
    return qc

Finally, we need to decode our message, we saw we could do this using a CNOT followed by a H-gate. Let's create a function that does this for us too:

def decode_message(qc):
    qc.cx(1, 0)
    qc.h(1)
    return qc

Finally, we can put this together to complete our protocol.

# Charlie creates the entangled pair between Alice and Bob
qc = create_bell_pair()

# We'll add a barrier for visual separation
qc.barrier()

# At this point, qubit 0 goes to Alice and qubit 1 goes to Bob

# Next, Alice encodes her message onto qubit 1. In this case,
# we want to send the message '10'. You can try changing this
# value and see how it affects the circuit
message = '10'
qc = encode_message(qc, 1, message)
qc.barrier()
# Alice then sends her qubit to Bob.

# After recieving qubit 0, Bob applies the recovery protocol:
qc = decode_message(qc)

# Finally, Bob measures his qubits to read Alice's message
qc.measure_all()

# Draw our output
qc.draw()

3.1 Visualizing Our Measurements

aer_sim = Aer.get_backend('aer_simulator')
qobj = assemble(qc)
result = aer_sim.run(qobj).result()
counts = result.get_counts(qc)
print(counts)
plot_histogram(counts)
{'10': 1024}

Our simulator simulates a perfect quantum computer. We can see that, without errors, we get a 100% chance of measuring the correct message.

4. Superdense Coding on a Real Quantum Computer

We can now see this working on a real quantum computer. First, we want to load our account to get the least busy quantum system:

from qiskit import IBMQ
from qiskit.providers.ibmq import least_busy
shots = 1024

# Load local account information
IBMQ.load_account()
# Get the least busy backend
provider = IBMQ.get_provider(hub='ibm-q')
backend = least_busy(provider.backends(filters=lambda x: x.configuration().n_qubits >= 2 
                                       and not x.configuration().simulator 
                                       and x.status().operational==True))
print("least busy backend: ", backend)
# Run our circuit
t_qc = transpile(qc, backend, optimization_level=3)
job = backend.run(t_qc)
least busy backend:  ibmq_belem
# Monitoring our job
from qiskit.tools.monitor import job_monitor
job_monitor(job)
Job Status: job has successfully run
# Plotting our result
result = job.result()
plot_histogram(result.get_counts(qc))

As we see that there are a few results from the other three states when run in a real quantum computer. These are due to errors in the gates and qubit decoherence. We will learn more about these errors in later sections.

correct_results = result.get_counts(qc)[message]
accuracy = (correct_results/shots)*100
print(f"Accuracy = {accuracy:.2f}%")
Accuracy = 89.55%
import qiskit.tools.jupyter
%qiskit_version_table

Version Information

Qiskit SoftwareVersion
qiskit-terra0.18.0
qiskit-aer0.8.2
qiskit-ignis0.6.0
qiskit-ibmq-provider0.15.0
qiskit-aqua0.9.4
qiskit0.28.0
System information
Python3.7.7 (default, May 6 2020, 04:59:01) [Clang 4.0.1 (tags/RELEASE_401/final)]
OSDarwin
CPUs8
Memory (Gb)32.0
Mon Aug 23 19:51:51 2021 BST