<jupyter_start><jupyter_text>SQUANCH demonstration: quantum teleportation<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
from squanch import *
% matplotlib inline<jupyter_output><empty_output><jupyter_text>$\newcommand{\ket}[1]{\lvert 1 \rangle}$This Jupyter notebook contains the source code for the [quantum teleportation demonstration](https://att-innovate.github.io/squanch/demos/quantum-teleportation.html). Refer to the documentation for more detailed explanations. Quantum teleportation allows two parties that share an entangled pair to transfer an arbitrary quantum state using only classical communication. This process has tremendous applicability to quantum networks, transferring fragile quantum states between distant nodes. Conceptually, quantum teleportation is the inverse of superdense coding.In general, all quantum teleportation experiments have the same underlying structure. Two distant parties, Alice and Bob, are connected via a classical information channel and share a maximally entangled state. Alice has an unknown state $\ket{\psi}$ which she wishes to send to Bob. She performs a joint projective measurement of her state and her half of the entangled state and communicates the outcomes to Bob, who operates on his half of the entangled state accordingly to reconstruct $\ket{\psi}$. In this demo, we’ll implement the canonical two-party quantum teleportation protocol:1. Alice generates an entangled two-particle state $\ket{AB} = \frac{1}{\sqrt{2}}\left(\ket{00}+\ket{11}\right)$, keeping half of the state and sending the other half to Bob.2. Alice entangles her qubit $\ket{\psi}$ with her ancilla $\ket{A}$ by applying controlled-not and Hadamard operators.3. Alice jointly measures $\ket{\psi}$ and $\ket{A}$ and communicates the outcomes to Bob through a classical channel. Bob’s qubit is now in one of four possible Bell states, one of which is $\ket{\psi}$, and he will use Alice’s two bits to recover $\ket{\psi}$.4. Bob applies a Pauli-X operator to his qubit if Alice’s ancilla $A$ collapsed to $\ket{1}$, and he applies a Pauli-Z operator to his qubit if her state $\ket{\psi}$ collapsed to $\ket{1}$. He has thus transformed $\ket{B}\mapsto\ket{\psi}$.\end{enumerate}This protocol is illustrated in the circuit diagram shown in FigureQuantum teleportation allows two parties that share an entangled pair to transfer a quantum state using classical communication. Conecptually, quantum teleportation is the inverse of superdense coding.![Teleportation circuit](../docs/source/img/teleportation-circuit.png)  Agent logic<jupyter_code>class Alice(Agent):
    '''Alice sends qubits to Bob using a shared Bell pair'''
    
    def distribute_bell_pair(self, a, b):
        # Create a Bell pair and send one particle to Bob
        H(a)
        CNOT(a, b)
        self.qsend(bob, b)

    def teleport(self, q, a):
        # Perform the teleportation
        CNOT(q, a)
        H(q)
        # Tell Bob whether to apply Pauli-X and -Z over classical channel
        bob_should_apply_x = a.measure() # if Bob should apply X
        bob_should_apply_z = q.measure() # if Bob should apply Z
        self.csend(bob, [bob_should_apply_x, bob_should_apply_z])

    def run(self):
        for qsystem in self.qstream:
            q, a, b = qsystem.qubits # q is state to teleport, a and b are Bell pair
            self.distribute_bell_pair(a, b)
            self.teleport(q, a)
class Bob(Agent):
    '''Bob receives qubits from Alice and measures the results'''

    def run(self):
        measurement_results = []
        for _ in self.qstream:
            # Bob receives a qubit from Alice
            b = self.qrecv(alice) 
            # Bob receives classical instructions from alice
            should_apply_x, should_apply_z = self.crecv(alice)
            if should_apply_x: X(b)
            if should_apply_z: Z(b)
            # Measure the output state
            measurement_results.append(b.measure())
        # Put results in output object
        self.output(measurement_results)<jupyter_output><empty_output><jupyter_text>Simple demonstrationIn this demo, we'll teleport a short list of eigenstates with no simulated errors.<jupyter_code># Prepare the initial states
qstream = QStream(3,10) # 3 qubits per trial, 10 trials
states_to_teleport = [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
for state, qsystem in zip(states_to_teleport, qstream):
    q = qsystem.qubit(0)
    if state == 1: X(q) # flip the qubits corresponding to 1 states

# Make and connect the agents
out = Agent.shared_output()
alice = Alice(qstream, out)
bob = Bob(qstream, out)
alice.qconnect(bob) # add a quantum channel
alice.cconnect(bob) # add a classical channel

# Run everything
alice.start()
bob.start()
alice.join()
bob.join()

print("Teleported states {}".format(states_to_teleport))
print("Received states   {}".format(out["Bob"]))<jupyter_output>Teleported states [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]
Received states   [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]<jupyter_text>Rotation demonstrationHere we rotate an ensemble of qubits by various angles and compare measured to expected results. Since this example takes longer to run, we'll demonstrate using the `Simulation` module to automatically monitor progress.<jupyter_code>angles = np.linspace(0, 2 * np.pi, 50)  # RX angles to apply
num_trials = 250  # number of trials for each angle

# Prepare the initial states in the stream
qstream = QStream(3, len(angles) * num_trials)
for angle in angles:
    for _ in range(num_trials):
        q, _, _ = qstream.next().qubits
        RX(q, angle)

# Make the agents and connect with quantum and classical channels
out = Agent.shared_output()
alice = Alice(qstream, out = out)
bob = Bob(qstream, out = out)
alice.qconnect(bob)
alice.cconnect(bob)

# Run the simulation
Simulation(alice, bob).run()

# Plot the results
results = np.array(out["Bob"]).reshape((len(angles), num_trials))
observed = np.mean(results, axis = 1)
expected = np.sin(angles / 2) ** 2
plt.plot(angles, observed, label = 'Observed')
plt.plot(angles, expected, label = 'Expected')
plt.legend()
plt.xlabel("Rotation angle $\\theta$")
plt.ylabel("Fractional $\left | 1 \\right >$ population")
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09573612228479485, 'fraction_numerical': 0.010458567980691875, 'mean_word_length': 4.012903225806451, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 17, 'https://': 1, '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': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '635554', 'license': 'MIT', 'n_tokens_mistral': 1888, 'n_tokens_neox': 1759, 'n_words': 709, 'path': 'demos/quantum-teleportation.ipynb', 'repo_name': 'frevson/squanch', 'size': '6215'}