import numpy as np
import qiskit.quantum_info as qi
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector, Operator
from qiskit.visualization import plot_histogram
sim = AerSimulator()
from util import zero, one, measure_outcome, Pretty
QI: Quantum Teleportation#
In this notebook, we’ll introduce quantum teleportation. It is a protocol that enables one party to send a qubit to another party using one shared entangled qubit and two classical bits. Importantly, this protocol does not violate no cloning.
References
https://learn.qiskit.org/course/ch-algorithms/quantum-teleportation
Introduction to Quantum Information Science: Lecture 10 by Scott Aaronson
Quantum Computation and Quantum Information: Chapter 1.3, Nielsen and Chuang
Problem#
Two parties, one named Alice and another named Bob.
Question: Can Alice send a qubit to Bob? That is, after the end of sending her qubit, she no longer has access to it.
Answer: yes, but only if Alice and Bob share an entangled qubit and uses two classical bits.
# Protocol uses 3 qubits and 2 classical bits in 2 different registers
qr_alice = QuantumRegister(2, name="q_Alice")
qr_bob = QuantumRegister(1, name="q_Bob")
crz, crx = ClassicalRegister(1, name="crz"), ClassicalRegister(1, name="crx")
teleportation_circuit = QuantumCircuit(qr_alice, qr_bob, crz, crx)
Step 1: Charlie creates entangled state#
Suppose Alice wants to transmit
We will start with an initial quantum state
Alice will own \(q_0\) and \(q_1\), Bob will own \(q_2\), and Charlie will entangle qubits \(q_1\) and \(q_2\).
def create_bell_pair(qc, a, b):
qc.h(a) # Put qubit a into state |+>
qc.cx(a, b) # CNOT with a as control and b as target
# Step 1: Charlie creates entangled state
create_bell_pair(teleportation_circuit, 1, 2) # qubits 1 and 2 are entangled now
teleportation_circuit.draw(output="mpl", style="iqp")
State after step 1#
After we entangle qubits \(|q_1\rangle\) and \(|q_2\rangle\), we obtain the state
alpha = 1/np.sqrt(3)
beta = np.sqrt(2/3)
psi = alpha*zero + beta*one
(zero ^ zero ^ psi).evolve(teleportation_circuit).draw("latex")
Step 2: Alice’s computation#
Alice applies
to the result of step 1.
def alice_gates(qc, q, a):
qc.cx(q, a)
qc.h(q)
## Step 2
teleportation_circuit.barrier(label="Step 1 | Step 2") # Use barrier to separate steps
alice_gates(teleportation_circuit, 0, 1)
teleportation_circuit.draw(output="mpl", style="iqp")
State after step 2#
After Alice performs here computation, we obtain the quantum state
(zero ^ zero ^ psi).evolve(teleportation_circuit).draw('latex')
# Save the circuit before measurement
U_steps_1_to_2 = Operator(teleportation_circuit)
Step 3: Transmission#
Alice measures qubit \(0\) and qubit \(1\), and sends them as classical bits to Bob.
Recall that qubit \(1\) is entangled with qubit \(2\), and that Bob has access to qubit \(2\).
def measure_and_send(qc, a, b):
qc.measure(a, 0)
qc.measure(b, 1)
# Step 3: Transmission
teleportation_circuit.barrier(label="Step 2 | Step 3")
measure_and_send(teleportation_circuit, 0, 1)
teleportation_circuit.draw(output="mpl", style="iqp")
Effect on quantum state#
As a reminder,
After measurement, Alice will have
\(|00\rangle\),
\(|10\rangle\),
\(|01\rangle\), and
\(|11\rangle\) all with equal probability.
Bob will have
\((\alpha|0\rangle + \beta|1\rangle)\) if Alice measures \(|00\rangle\),
\((\alpha|1\rangle + \beta|0\rangle)\) if Alice measures \(|10\rangle\),
\((\alpha|0\rangle - \beta|1\rangle)\) if Alice measures \(|01\rangle\), and
\((\alpha|1\rangle - \beta|0\rangle)\) if Alice measure \(|11\rangle\).
We can use density matrices to check these results.
# The state before step 3
v = (zero ^ zero ^ psi).evolve(U_steps_1_to_2)
v.draw("latex")
# The density matrix before step 3
rho = qi.DensityMatrix(v)
rho.draw("latex")
# Checking that we do indeed have a pure state
np.trace(rho.data @ rho.data)
np.complex128(0.9999999999999991+0j)
Recall partial measurement on density matrix#
Let \(\{ M_m \}_m\) be a set of measurement operators where \(m\) refers to the outcome so that it satisfies
Then the probability of obtaining outcome \(m\) after measuring is
and the state of the system after measurement is
zzz = zero ^ zero ^ zero; zzo = zero ^ zero ^ one;
zoz = zero ^ one ^ zero; zoo = zero ^ one ^ one;
ozz = one ^ zero ^ zero; ozo = one ^ zero ^ one;
ooz = one ^ one ^ zero; ooo = one ^ one ^ one;
# Construct partial measurements
Pi00 = np.outer(zzz, zzz) + np.outer(ozz, ozz)
Pi01 = np.outer(zzo, zzo) + np.outer(ozo, ozo)
Pi10 = np.outer(zoz, zoz) + np.outer(ooz, ooz)
Pi11 = np.outer(zoo, zoo) + np.outer(ooo, ooo)
Pretty(Pi00 + Pi01 + Pi10 + Pi11)
Checking the results when 00
is measured.
p_00, rho_00 = measure_outcome(rho, Pi00)
print(p_00)
Pretty(rho_00)
(0.2499999999999999+0j)
bob_00 = alpha * zero + beta * one
np.allclose(rho_00, np.outer((bob_00 ^ zero ^ zero), (bob_00 ^ zero ^ zero)))
True
Checking the results when 01
is measured.
p_01, rho_01 = measure_outcome(rho, Pi01)
print(p_01)
Pretty(rho_01)
(0.2499999999999999+0j)
bob_01 = alpha * zero - beta * one
np.allclose(rho_01, np.outer((bob_01 ^ zero ^ one), (bob_01 ^ zero ^ one)))
True
Step 4: Bob’s computation#
Bob now needs to reconstruct Alice’s qubit \(|q_1\rangle\) in his qubit \(|q_2\rangle\) using the two classical bits that Alice has provided. As a reminder, Bob at this point has
\((\alpha|0\rangle + \beta|1\rangle)\) if Alice measures \(|00\rangle\),
\((\alpha|1\rangle + \beta|0\rangle)\) if Alice measures \(|10\rangle\),
\((\alpha|0\rangle - \beta|1\rangle)\) if Alice measures \(|01\rangle\), and
\((\alpha|1\rangle - \beta|0\rangle)\) if Alice measure \(|11\rangle\).
The trick to reconstruct \(|\psi\rangle = \alpha|0\rangle + \beta|1\rangle\) is to realize that we just need to use the cloassical bits, i.e., the results of Alice’s measurement, to correct the phases of our quantum state. In particular,
no correction if \(|00\rangle\) is received,
a flip (X) if \(|10\rangle\) is received,
a phase flip (Z) if \(|01\rangle\) is received, and
a flip (X) and phase flip (Z) if \(|11\rangle\) is received.
We give this circuit now.
def bob_gates(qc, qubit, crz, crx):
# Here we use c_if to control our gates with a classical
# bit instead of a qubit
qc.x(qubit).c_if(crx, 1) # Apply gates if the registers
qc.z(qubit).c_if(crz, 1) # are in the state '1'
## Step 4: Bob's computation
teleportation_circuit.barrier(label="Step 3 | Step 4") # Use barrier to separate steps
bob_gates(teleportation_circuit, 2, crz, crx)
teleportation_circuit.draw(output="mpl", style="iqp")
State after step 4.#
After decoding, Bob has
\((\alpha|0\rangle + \beta|1\rangle)\) becomes \((\alpha|0\rangle + \beta|1\rangle)\),
\((\alpha|1\rangle + \beta|0\rangle)\) becomes \(X(\alpha|1\rangle + \beta|0\rangle) = \alpha|0\rangle + \beta|1\rangle\),
\((\alpha|0\rangle - \beta|1\rangle)\) becomes \(Z(\alpha|0\rangle - \beta|1\rangle) = \alpha|0\rangle + \beta|1\rangle\), and
\((\alpha|1\rangle - \beta|0\rangle)\) becomes \(ZX(\alpha|1\rangle - \beta|0\rangle) = \alpha|0\rangle + \beta|1\rangle\).
Thus Bob has indeed received the qubit \(|\psi\rangle\) from Alice. Moreover, Alice’s knowledge of \(|\psi\rangle\) has been destroyed.
Putting it together#
We gather all the pieces together into a single function to enable more experimentation.
def quantum_teleportation(psi: Statevector) -> QuantumCircuit:
# Create circuit
qr = QuantumRegister(3, name="q")
crz, crx = ClassicalRegister(1, name="crz"), ClassicalRegister(1, name="crx")
qc_tele = QuantumCircuit(qr, crz, crx)
# Initialize qubit 0 to the message that Alice wants to send
qc_tele.initialize(psi, 0)
# Step 1: Third party creates entangled state
create_bell_pair(qc_tele, 1, 2)
qc_tele.barrier(label="Step 1 | Step 2")
# Step 2: Alice's computation
alice_gates(qc_tele, 0, 1)
qc_tele.barrier(label="Step 2 | Step 3")
# Step 3: Transmit
measure_and_send(qc_tele, 0 ,1)
qc_tele.barrier(label="Step 3 | Step 4")
# Step 4: Bob's computation
bob_gates(qc_tele, 2, crz, crx)
return qc_tele
alpha = 1/np.sqrt(3); beta = np.sqrt(2/3)
psi = alpha*zero + beta*one
qc_tele = quantum_teleportation(psi)
qc_tele.draw(output="mpl", style="iqp")
cr_result = ClassicalRegister(1)
qc_tele.add_register(cr_result)
qc_tele.measure(2,2)
qc_tele.draw(output="mpl", style="iqp")
result = sim.run(qc_tele).result()
counts = result.get_counts(qc_tele)
plot_histogram(counts)
Summary#
Quantum teleportation is a protocol where one party can send 1 qubit to another party using 1 entangled qubit and 2 classical bits. Importantly, no cloning is not violated.