<jupyter_start><jupyter_text>Superdense CodingIn 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 Steps1. 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 DiagramCircuit used to send the message "00". To send other messages, swap out the identity (𝐼) gate.![circuit.png](attachment:circuit.png)  Code<jupyter_code># Print version of SDK
!pip show amazon-braket-sdk | grep Version

# Import Braket libraries
from braket.circuits import Circuit, Gate, Moments
from braket.circuits.instruction import Instruction
from braket.aws import AwsDevice
import matplotlib.pyplot as plt
import time<jupyter_output>Version: 1.0.0.post1<jupyter_text>Typically, we recommend running circuits with fewer than 25 qubits on the local simulator to avoid latency bottlenecks. The managed, high-performance simulator SV1 is better suited for larger circuits up to 34 qubits. Nevertheless, for demonstration purposes, we are going to continue this example with SV1 but it is easy to switch over to the local simulator by replacing the last line in the cell below with ```device = LocalSimulator()``` and importing the ```LocalSimulator```.__NOTE__: Please enter your desired device and S3 location (bucket and key) below. If you are working with the local simulator ```LocalSimulator()``` you do not need to specify any S3 location. However, if you are using the managed cloud-based device or any QPU devices you need to specify the S3 location where your results will be stored. In this case, you need to replace the API call ```device.run(circuit, ...)``` below with ```device.run(circuit, s3_folder, ...)```.<jupyter_code># Please enter the S3 bucket you created during onboarding in the code below
my_bucket = "amazon-braket-Your-Bucket-Name" # the name of the bucket
my_prefix = "Your-Folder-Name" # the name of the folder in the bucket
s3_folder = (my_bucket, my_prefix)

# Select device arn for the managed simulator
device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")
# Function to run quantum task, check the status thereof and collect results
def get_result(device, circ, s3_folder):
    
    # get number of qubits
    num_qubits = circ.qubit_count

    # specify desired results_types
    circ.probability()

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

    # Get ID of submitted task
    task_id = task.id
#     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
    metadata = result.task_metadata

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

    # get measurment results
    measurement_counts = result.measurement_counts

    # print measurment 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<jupyter_output><empty_output><jupyter_text>Alice and Bob initially share a Bell pair.  Let's create this now:<jupyter_code>circ = Circuit();
circ.h([0]);
circ.cnot(0,1);<jupyter_output><empty_output><jupyter_text>Define Alice's encoding scheme according to the table above.  Alice selects one of these messages to send.<jupyter_code># 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)
          }
# Select message to send.  Let's start with '01' for now
m = "01"<jupyter_output><empty_output><jupyter_text>Alice encodes her message by applying the gates defined above<jupyter_code># Encode the message
circ.add_circuit(message[m]);<jupyter_output><empty_output><jupyter_text>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:<jupyter_code>circ.cnot(0,1);
circ.h([0]);<jupyter_output><empty_output><jupyter_text>The full circuit now looks like<jupyter_code>print(circ)<jupyter_output>T  : |0|1|2|3|4|
                
q0 : -H-C-X-C-H-
        |   |   
q1 : ---X---X---

T  : |0|1|2|3|4|<jupyter_text>By measuring the two qubits in the computational basis, Bob can read off Alice's two qubit message<jupyter_code>counts = get_result(device, circ, s3_folder)
print(counts)<jupyter_output>Status: CREATED
Status: QUEUED
Status: RUNNING
Status: COMPLETED
measurement_counts: Counter({'01': 1000})<jupyter_text>We can check that this scheme works for the other possible messages too:<jupyter_code>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, s3_folder)
    
    print("Message: " + m + ". Results:")
    print(counts)<jupyter_output>Status: CREATED
Status: QUEUED
Status: RUNNING
Status: COMPLETED
measurement_counts: Counter({'00': 1000})
{'fraction_non_alphanumeric': 0.09349448986846783, 'fraction_numerical': 0.01824860765493542, 'mean_word_length': 4.072115384615385, 'pattern_counts': {'":': 4, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '130460', 'license': 'Apache-2.0', 'n_tokens_mistral': 2654, 'n_tokens_neox': 2495, 'n_words': 1036, 'path': 'examples/getting_started/4_Superdense_coding.ipynb', 'repo_name': 'FengShi0705/amazon-braket-examples', 'size': '8439'}