Tutorials
qcr:2606.55941.1

Superdense Coding with Amazon Braket

This tutorial implements the superdense coding protocol on Amazon Braket, a foundational result in quantum communication that lets a sender transmit two classical bits by physically sending only a single qubit, provided the sender and receiver pre-share an entangled pair. It is the conceptual counterpart to quantum teleportation and a vivid demonstration that entanglement effectively doubles a quantum channel's classical capacity. The notebook walks through the full protocol using the Braket SDK: it first creates a shared Bell pair between the two parties, then encodes a chosen two-bit message by applying one of four Pauli operations (I, X, Z, or XZ) to the sender's qubit, which rotates the joint state into one of the four orthogonal Bell states. The single encoded qubit is then handed to the receiver, who runs a Bell-state measurement to decode both classical bits. The example builds the encode-and-decode circuit, runs it on a Braket simulator, and confirms the two recovered bits match the message that was sent. It is a clear, hands-on illustration of entanglement as a communication resource, implemented with Braket's circuit and simulation primitives.
Qubit
Circuit-based
Uploaded 2 days ago
13
Views
GitHub582
Citing this entry? Use this QCR ID
Uploaded by
QL
QCR Librarian

Overview

amazon-braket/amazon-braket-examples
582261
In [ ]:
# --- Setup cell added by QCR (not part of the original tutorial) ---
# Source: amazon-braket/amazon-braket-examples @ 0c0818f315479aab9deebed7e7ed7533ac581923, Apache License 2.0.
# Installs the example's dependencies. If a later cell still reports a missing
# package, restart the runtime/kernel and run again from the top.
%pip install -q amazon-braket-sdk==1.117.3 matplotlib

Superdense Coding

In [1]:
# Use Braket SDK Cost Tracking to estimate the cost to run this example
from braket.tracking import Tracker

t = Tracker().start()

In this tutorial, we construct an implementation of the superdense coding protocol via Amazon Braket's SDK. Superdense coding is a method of transmitting two classical bits by sending only one qubit. Starting with a pair of entanged qubits, the sender (aka Alice) applies a certain quantum gate to their qubit and sends the result to the receiver (aka Bob), who is then able to decode the full two-bit message.

If Alice wants to send a two-bit message to Bob using only classical channels, she would need to send two classical bits. However, with the help of quantum entanglement, Alice can do this by sending just one qubit. By ensuring that Alice and Bob initially share an entangled state of two qubits, they can devise a strategy such that Alice can transmit her two-bit message by sending her single qubit.

To implement superdense coding, Alice and Bob need to share or otherwise prepare a maximally entangled pair of qubits (i.e., a Bell pair). Alice then selects one of the four possible messages to send with two classical bits: 00, 01, 10, or 11. Depending on which two-bit string she wants to send, Alice applies a corresponding quantum gate to encode her desired message. Finally, Alice sends her own qubit to Bob, which Bob then uses to decode the message by undoing the initial entangling operation.

Note that superdense coding is closely related to quantum teleportation. In teleportation, one uses an entangled pair (an e-bit) and two uses of a classical channel to simulate a single use of a quantum channel. In superdense coding, one uses an e-bit and a single use of a quantum channel to simulate two uses of a classical channel.

Detailed Steps

  1. Alice and Bob initially share a Bell pair. This can be prepared by starting with two qubits in the |0⟩ state, then applying the Hadamard gate (𝐻) to the first qubit to create an equal superposition, and finally applying a CNOT gate (𝐶𝑋) between the two qubits to produce a Bell pair. Alice holds one of these two qubits, while Bob holds the other.
  2. Alice selects one of the four possible messages to send Bob. Each message corresponds to a unique set of quantum gate(s) to apply to her own qubit, illustrated in the table below. For example, if Alice wants to send the message "01", she would apply the Pauli X gate.
  3. Alice sends her qubit to Bob through the quantum channel.
  4. Bob decodes Alice's two-bit message by first applying a CNOT gate using Alice's qubit as the control and his own qubit as the target, and then a Hadamard gate on Alice's qubit to restore the classical message.
Message Alice's encoding State Bob receives
(non-normalized)
After 𝐶𝑋 gate
(non-normalized)
After 𝐻 gate
00 𝐼 |00⟩ + |11⟩ |00⟩ + |10⟩ |00⟩
01 𝑋 |10⟩ + |01⟩ |11⟩ + |01⟩ |01⟩
10 𝑍 |00⟩ - |11⟩ |00⟩ - |10⟩ |10⟩
11 𝑍𝑋 |01⟩ - |10⟩ |01⟩ - |11⟩ |11⟩

Circuit Diagram

Circuit used to send the message "00". To send other messages, swap out the identity (𝐼) gate.

Code

In [2]:
# Import Braket libraries
import matplotlib.pyplot as plt

from braket.circuits import Circuit
from braket.devices import LocalSimulator
Version: 1.53.3

Typically, we recommend running circuits with fewer than 25 qubits on the local simulator to avoid latency bottlenecks. The on-demand, high-performance simulator SV1 is better suited for larger circuits up to 34 qubits. For demonstration purposes, we are going to continue this example with the local simulator, but it is easy to switch over to SV1 by commenting out the LocalSimulator line below and uncommenting the sv1 line.

In [3]:
# Set up device: local simulator or the on-demand simulator
device = LocalSimulator()
# device = AwsDevice(Devices.Amazon.SV1)
In [4]:
# Function to run quantum task, check the status thereof and collect results
def get_result(device, circ):
    # get number of qubits
    num_qubits = circ.qubit_count

    # specify desired results_types
    circ.probability()

    # submit quantum task: define quantum task (asynchronous)
    if device.name == "StateVectorSimulator":
        task = device.run(circ, shots=1000)
    else:
        task = device.run(circ, shots=1000)

    # Get ID of submitted quantum task
    #     print('Task ID :', task_id)

    # Wait for job to complete
    status_list = []
    status = task.state()
    status_list += [status]
    print("Status:", status)

    # Only notify the user when there's a status change
    while status != "COMPLETED":
        status = task.state()
        if status != status_list[-1]:
            print("Status:", status)
        status_list += [status]

    # get result
    result = task.result()

    # get metadata

    # get output probabilities
    probs_values = result.values[0]

    # get measurement results
    measurement_counts = result.measurement_counts

    # print measurement results
    print("measurement_counts:", measurement_counts)

    # bitstrings
    format_bitstring = "{0:0" + str(num_qubits) + "b}"
    bitstring_keys = [format_bitstring.format(ii) for ii in range(2**num_qubits)]

    # plot probabalities
    plt.bar(bitstring_keys, probs_values)
    plt.xlabel("bitstrings")
    plt.ylabel("probability")
    plt.xticks(rotation=90)
    plt.show()

    return measurement_counts

Alice and Bob initially share a Bell pair. Let's create this now:

In [5]:
circ = Circuit()
circ.h([0])
circ.cnot(0, 1)
Circuit('instructions': [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)]), 'control': QubitSet([]), 'control_state': (), 'power': 1)])

Define Alice's encoding scheme according to the table above. Alice selects one of these messages to send.

In [6]:
# Four possible messages and their corresponding gates
message = {
    "00": Circuit().i(0),
    "01": Circuit().x(0),
    "10": Circuit().z(0),
    "11": Circuit().x(0).z(0),
}
In [7]:
# Select message to send.  Let's start with '01' for now
m = "01"

Alice encodes her message by applying the gates defined above

In [8]:
# Encode the message
circ.add_circuit(message[m])
Circuit('instructions': [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': X('qubit_count': 1), 'target': QubitSet([Qubit(0)]), 'control': QubitSet([]), 'control_state': (), 'power': 1)])

Alice then sends her qubit to Bob so that Bob has both qubits in his lab. Bob decodes Alice's message by disentangling the two qubits:

In [9]:
circ.cnot(0, 1)
circ.h([0])
Circuit('instructions': [Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': X('qubit_count': 1), 'target': QubitSet([Qubit(0)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': CNot('qubit_count': 2), 'target': QubitSet([Qubit(0), Qubit(1)]), 'control': QubitSet([]), 'control_state': (), 'power': 1), Instruction('operator': H('qubit_count': 1), 'target': QubitSet([Qubit(0)]), 'control': QubitSet([]), 'control_state': (), 'power': 1)])

The full circuit now looks like

In [10]:
print(circ)
T  : |0|1|2|3|4|
                
q0 : -H-C-X-C-H-
        |   |   
q1 : ---X---X---

T  : |0|1|2|3|4|

By measuring the two qubits in the computational basis, Bob can read off Alice's two qubit message

In [11]:
counts = get_result(device, circ)
print(counts)
Status: COMPLETED
measurement_counts: Counter({'01': 1000})
Counter({'01': 1000})

We can check that this scheme works for the other possible messages too:

In [12]:
for m in message:
    # Reproduce the full circuit above by concatenating all of the gates:
    newcirc = Circuit().h([0]).cnot(0, 1).add_circuit(message[m]).cnot(0, 1).h([0])

    # Run the circuit:
    counts = get_result(device, newcirc)

    print("Message: " + m + ". Results:")
    print(counts)
Status: COMPLETED
measurement_counts: Counter({'00': 1000})
Message: 00. Results:
Counter({'00': 1000})
Status: COMPLETED
measurement_counts: Counter({'01': 1000})
Message: 01. Results:
Counter({'01': 1000})
Status: COMPLETED
measurement_counts: Counter({'10': 1000})
Message: 10. Results:
Counter({'10': 1000})
Status: COMPLETED
measurement_counts: Counter({'11': 1000})
Message: 11. Results:
Counter({'11': 1000})
In [13]:
print("Quantum Task Summary")
print(t.quantum_tasks_statistics())
print(
    "Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).",
)
print(
    f"Estimated cost to run this example: {t.qpu_tasks_cost() + t.simulator_tasks_cost():.2f} USD",
)
Quantum Task Summary
{}
Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2).
Estimated cost to run this example: 0.00 USD

Join the Discussion

Comments (0)

No comments yet. Be the first to share your thoughts!

Indexed by QCR Librarian

This entry was created automatically from publicly available records. QCR links to public sources and only stores repository content where the license permits redistribution.

Versions

v1 Latest
Jun 17, 2026
qcr:2606.55941.1

Cite all versions? Use the base QCR ID to always reference the latest version of this entry.

Tools used

Amazon Braket SDK

Keywords

superdense-coding
entanglement
braket
communication
bell-states

You may also like5