Qiskit-Implementierung
In dieser Lektion implementieren wir einige der Ideen aus der Lektion über Verschränkung in der Praxis mit Qiskit.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"qiskit": "qiskit", "qiskit_aer": "qiskit-aer"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit import __version__
print(__version__)
2.1.1
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram, array_to_latex
from qiskit.result import marginal_distribution
from qiskit.circuit.library import UGate
from math import pi
import random
Hier ist eine Quantum-Circuit-Implementierung des Teleportationsprotokolls.
qubit = QuantumRegister(1, "Q")
ebit0 = QuantumRegister(1, "A")
ebit1 = QuantumRegister(1, "B")
a = ClassicalRegister(1, "a")
b = ClassicalRegister(1, "b")
protocol = QuantumCircuit(qubit, ebit0, ebit1, a, b)
# Prepare ebit used for teleportation
protocol.h(ebit0)
protocol.cx(ebit0, ebit1)
protocol.barrier()
# Alice's operations
protocol.cx(qubit, ebit0)
protocol.h(qubit)
protocol.barrier()
# Alice measures and sends classical bits to Bob
protocol.measure(ebit0, a)
protocol.measure(qubit, b)
protocol.barrier()
# Bob uses the classical bits to conditionally apply gates
with protocol.if_test((a, 1)):
protocol.x(ebit1)
with protocol.if_test((b, 1)):
protocol.z(ebit1)
display(protocol.draw(output="mpl"))
Der Circuit nutzt einige Qiskit-Funktionen, die wir in früheren Lektionen noch nicht gesehen haben, darunter die Funktionen barrier und if_test.
Die Funktion barrier erzeugt eine visuelle Trennung, die das Circuit-Diagramm leichter lesbar macht; außerdem verhindert sie, dass Qiskit während der Kompilierung verschiedene Vereinfachungen und Optimierungen über die Barrier hinweg vornimmt, wenn Circuits auf echter Hardware ausgeführt werden.
Die Funktion if_test wendet eine Operation bedingt in Abhängigkeit von einem klassischen Bit oder Register an.
Der Circuit initialisiert zunächst in einem -Zustand (was nicht Teil des Protokolls selbst ist), gefolgt von Alices Operationen, dann ihren Messungen und schließlich Bobs Operationen. Um zu testen, ob das Protokoll korrekt funktioniert, wenden wir ein zufällig erzeugtes Einzel-Qubit-Gate auf den initialisierten -Zustand von an, um einen zufälligen Quantenzustandsvektor zu erhalten, der teleportiert werden soll. Indem wir nach Ausführung des Protokolls das Inverse (d. h. die konjugiert-transponierte Matrix) dieses Gates auf anwenden, können wir verifizieren, dass der Zustand teleportiert wurde — wir messen und prüfen, ob er in den -Zustand zurückgekehrt ist.
Zunächst wählen wir zufällig ein unitäres Qubit-Gate aus.
random_gate = UGate(
theta=random.random() * 2 * pi,
phi=random.random() * 2 * pi,
lam=random.random() * 2 * pi,
)
display(array_to_latex(random_gate.to_matrix()))
Jetzt erstellen wir einen neuen Testcircuit, der zuerst unser zufälliges Gate auf anwendet, dann den Teleportationscircuit ausführt und schließlich das Inverse unseres zufälligen Gates auf das Qubit anwendet und misst. Das Ergebnis sollte mit Sicherheit sein.
# Create a new circuit including the same bits and qubits used in the
# teleportation protocol.
test = QuantumCircuit(qubit, ebit0, ebit1, a, b)
# Start with the randomly selected gate on Q
test.append(random_gate, qubit)
test.barrier()
# Append the entire teleportation protocol from above.
test = test.compose(protocol)
test.barrier()
# Finally, apply the inverse of the random unitary to B and measure.
test.append(random_gate.inverse(), ebit1)
result = ClassicalRegister(1, "Result")
test.add_register(result)
test.measure(ebit1, result)
display(test.draw(output="mpl"))
Zum Schluss führen wir den Aer-Simulator für diesen Circuit aus und zeigen ein Histogramm der Ausgaben an. Wir sehen die Statistiken für alle drei klassischen Bits: Das unterste, am weitesten links stehende Bit sollte stets sein — ein Zeichen dafür, dass Qubit erfolgreich nach teleportiert wurde —, während die anderen beiden Bits annähernd gleichverteilt sein sollten.
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))
Wir können die Statistiken auch filtern, um uns ausschließlich auf das Testresultat-Qubit zu konzentrieren:
filtered_statistics = marginal_distribution(statistics, [2])
display(plot_histogram(filtered_statistics))
Superdense Coding
Superdense Coding ist ein Protokoll, das in gewisser Weise ein komplementäres Ziel zur Teleportation verfolgt. Anstatt die Übertragung eines Qubits mithilfe von zwei klassischen Kommunikationsbits zu ermöglichen (auf Kosten eines E-Bits an Verschränkung), erlaubt es die Übertragung von zwei klassischen Bits mithilfe eines Qubits an Quantenkommunikation (wiederum auf Kosten eines E-Bits an Verschränkung).
Genauer gesagt haben wir eine Senderin (Alice) und einen Empfänger (Bob), die ein E-Bit Verschränkung teilen. Gemäß den in der Lektion geltenden Konventionen bedeutet dies, dass Alice ein Qubit hält, Bob ein Qubit hält und das Paar gemeinsam im Zustand ist. Alice möchte zwei klassische Bits an Bob übermitteln, die wir mit und bezeichnen, und sie wird dies erreichen, indem sie ihm ein Qubit schickt.
Es ist naheliegend, diese Leistung als weniger beeindruckend zu betrachten als das, was die Teleportation erreicht. Das Senden von Qubits dürfte auf absehbare Zeit so viel schwieriger bleiben als das Senden klassischer Bits, dass der Tausch eines Qubits Quantenkommunikation gegen zwei Bits klassischer Kommunikation — noch dazu auf Kosten eines E-Bits — kaum lohnenswert erscheint. Das bedeutet jedoch nicht, dass Superdense Coding uninteressant wäre — ganz im Gegenteil.
Passend zum Thema der Lektion liegt ein Grund, warum Superdense Coding interessant ist, darin, dass es eine konkrete und (im Kontext der Informationstheorie) durchaus bemerkenswerte Anwendung von Verschränkung zeigt. Ein berühmtes Theorem der Quanteninformationstheorie, bekannt als Holevos Theorem, besagt, dass es ohne einen geteilten verschränkten Zustand unmöglich ist, mehr als ein Bit klassischer Information durch das Senden eines einzelnen Qubits zu übertragen. (Holevos Theorem ist allgemeiner gefasst als dies. Seine genaue Aussage ist technisch und erfordert Erläuterungen, aber dies ist eine seiner Konsequenzen.) Durch Superdense Coding ermöglicht geteilte Verschränkung also effektiv eine Verdopplung der klassischen Informationskapazität beim Senden von Qubits.
Protokoll
Das folgende Quantum-Circuit-Diagramm beschreibt das Superdense-Coding-Protokoll:

In Worten beschrieben, geht Alice wie folgt vor:
-
Falls , wendet Alice ein -Gate auf ihr Qubit an (falls , tut sie es nicht).
-
Falls , wendet Alice ein -Gate auf ihr Qubit an (falls , tut sie es nicht).
Anschließend sendet Alice ihr Qubit an Bob.
Was Bob tut, wenn er das Qubit erhält: Zunächst wendet er ein controlled-NOT-Gate an, wobei das Kontrollqubit und das Zielqubit ist, und dann wendet er ein Hadamard-Gate auf an. Dann misst er , um zu erhalten, und , um zu erhalten — in beiden Fällen mit Standardbasismessungen.
Analyse
Die Idee hinter diesem Protokoll ist einfach: Alice wählt effektiv aus, welchen Bell-Zustand sie mit Bob teilen möchte, sie schickt Bob ihr Qubit, und Bob misst, um festzustellen, welchen Bell-Zustand Alice gewählt hat.
Das heißt, sie teilen anfänglich , und abhängig von den Bits und lässt Alice diesen Zustand entweder unverändert oder verschiebt ihn in einen der anderen Bell-Zustände, indem sie , , oder auf ihr Qubit anwendet.
Bobs Aktionen haben folgende Auswirkungen auf die vier Bell-Zustände:
Dies kann direkt überprüft werden, indem man die Ergebnisse von Bobs Operationen auf diese Zustände nacheinander berechnet.
Wenn Bob also seine Messungen durchführt, kann er bestimmen, welchen Bell-Zustand Alice gewählt hat. Die Korrektheit des Protokolls lässt sich durch Überprüfung aller Fälle bestätigen:
-
Falls , ist der Zustand von , wenn Bob erhält, . Er transformiert diesen Zustand in und erhält .
-
Falls , ist der Zustand von , wenn Bob erhält, . Er transformiert diesen Zustand in und erhält .
-
Falls , ist der Zustand von , wenn Bob erhält, . Er transformiert diesen Zustand in und erhält .
-
Falls , ist der Zustand von , wenn Bob erhält, . Er transformiert diesen Zustand in und erhält . (Der Phasenfaktor hat hier keinen Einfluss.)
Superdense-Coding-Implementierung
Hier ist eine einfache Implementierung von Superdense Coding, bei der wir den Circuit selbst in Abhängigkeit von den zu übertragenden Bits festlegen. Zunächst wählen wir zwei zu übertragende Bits. (Später werden wir sie zufällig wählen, aber für den Moment treffen wir einfach eine beliebige Wahl.)
c = "1"
d = "0"
Jetzt bauen wir den Circuit entsprechend auf. Hier lassen wir Qiskit die Standardnamen für die Qubits verwenden: für das obere Qubit und für das untere.
protocol = QuantumCircuit(2)
# Prepare ebit used for superdense coding
protocol.h(0)
protocol.cx(0, 1)
protocol.barrier()
# Alice's operations
if d == "1":
protocol.z(0)
if c == "1":
protocol.x(0)
protocol.barrier()
# Bob's actions
protocol.cx(0, 1)
protocol.h(0)
protocol.measure_all()
display(protocol.draw(output="mpl"))
Neu hier ist vor allem die Funktion measure_all, die alle Qubits misst und die Ergebnisse in ein einziges klassisches Register schreibt (in diesem Fall also zwei Bits).
Der Aer-Simulator liefert die erwartete Ausgabe.
result = AerSimulator().run(protocol).result()
statistics = result.get_counts()
for outcome, frequency in statistics.items():
print(f"Measured {outcome} with frequency {frequency}")
display(plot_histogram(statistics))
Measured 10 with frequency 1024
Jetzt verwenden wir ein zusätzliches Qubit als Zufallsbit-Generator — im Wesentlichen zum Werfen fairer Münzen. Damit wählen wir und zufällig aus und führen anschließend das Superdense-Coding-Protokoll aus.
rbg = QuantumRegister(1, "coin")
ebit0 = QuantumRegister(1, "A")
ebit1 = QuantumRegister(1, "B")
Alice_c = ClassicalRegister(1, "Alice c")
Alice_d = ClassicalRegister(1, "Alice d")
test = QuantumCircuit(rbg, ebit0, ebit1, Alice_d, Alice_c)
# Initialize the ebit
test.h(ebit0)
test.cx(ebit0, ebit1)
test.barrier()
# Use the 'coin' qubit twice to generate Alice's bits c and d.
test.h(rbg)
test.measure(rbg, Alice_c)
test.h(rbg)
test.measure(rbg, Alice_d)
test.barrier()
# Now the protocol runs, starting with Alice's actions, which depend
# on her bits.
with test.if_test((Alice_d, 1), label="Z"):
test.z(ebit0)
with test.if_test((Alice_c, 1), label="X"):
test.x(ebit0)
test.barrier()
# Bob's actions
test.cx(ebit0, ebit1)
test.h(ebit0)
test.barrier()
Bob_c = ClassicalRegister(1, "Bob c")
Bob_d = ClassicalRegister(1, "Bob d")
test.add_register(Bob_d)
test.add_register(Bob_c)
test.measure(ebit0, Bob_d)
test.measure(ebit1, Bob_c)
display(test.draw(output="mpl"))
Die Ausführung des Aer-Simulators zeigt die Ergebnisse: Die klassischen Bits von Alice und Bob stimmen stets überein.
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))
Qiskit-Implementierung
Wir können das CHSH-Spiel zusammen mit der oben definierten Quantenstrategie in Qiskit wie folgt implementieren.
Zunächst die Definition des Spiels selbst, bei der eine beliebige Strategie als Argument übergeben werden kann.
def chsh_game(strategy):
# This function runs the CHSH game, using the strategy (a function
# from two bits to two bits), returning 1 for a win and 0 for a loss.
# Choose x and y randomly
x, y = random.randint(0, 1), random.randint(0, 1)
# Use the strategy to determine a and b
a, b = strategy(x, y)
# Decide if the strategy wins or loses
if (a != b) == (x & y):
return 1 # Win
return 0 # Lose
Jetzt erstellen wir eine Funktion, die einen Circuit abhängig von den Fragen an Alice und Bob ausgibt. Der Einfachheit halber verwenden wir die Standardnamen für die Qubits und nutzen das eingebaute -Gate für die Aktionen von Alice und Bob.
def chsh_circuit(x, y):
# This function creates a `QuantumCircuit` implementing the quantum
# strategy described above (including the e-bit preparation).
qc = QuantumCircuit(2, 2)
# Prepare an e-bit
qc.h(0)
qc.cx(0, 1)
qc.barrier()
# Alice's actions
if x == 0:
qc.ry(0, 0)
else:
qc.ry(-pi / 2, 0)
qc.measure(0, 0)
# Bob's actions
if y == 0:
qc.ry(-pi / 4, 1)
else:
qc.ry(pi / 4, 1)
qc.measure(1, 1)
return qc
Hier sind die vier möglichen Circuits, abhängig davon, welche Fragen gestellt werden.
# Draw the four possible circuits
print("(x,y) = (0,0)")
display(chsh_circuit(0, 0).draw(output="mpl"))
print("(x,y) = (0,1)")
display(chsh_circuit(0, 1).draw(output="mpl"))
print("(x,y) = (1,0)")
display(chsh_circuit(1, 0).draw(output="mpl"))
print("(x,y) = (1,1)")
display(chsh_circuit(1, 1).draw(output="mpl"))
(x,y) = (0,0)
(x,y) = (0,1)
(x,y) = (1,0)
(x,y) = (1,1)
Jetzt erstellen wir einen Job mit dem Aer-Simulator, der den Circuit für ein gegebenes Eingabepaar genau einmal ausführt.
def quantum_strategy(x, y):
# This function runs the appropriate quantum circuit defined above
# one time and returns the measurement results
# Setting `shots=1` to run the circuit once
result = AerSimulator().run(chsh_circuit(x, y), shots=1).result()
statistics = result.get_counts()
# Determine the output bits and return them
bits = list(statistics.keys())[0]
a, b = bits[0], bits[1]
return a, b
Abschließend spielen wir das Spiel 1000 Mal und berechnen den Anteil der gewonnenen Runden.
NUM_GAMES = 1000
TOTAL_SCORE = 0
for _ in range(NUM_GAMES):
TOTAL_SCORE += chsh_game(quantum_strategy)
print("Fraction of games won:", TOTAL_SCORE / NUM_GAMES)
Fraction of games won: 0.867
Wir können auch eine klassische Strategie definieren und sehen, wie gut sie funktioniert. Das ist nur eine von mehreren Strategien — andere lassen sich durch Anpassen des Codes testen — aber sie gehört zu den optimalen klassischen Strategien.
def classical_strategy(x, y):
# This function implements just one example of an optimal classical
# strategy for the CHSH game. Other classical strategies can be
# implemented by changing the bit values assigned to a and b.
# Alice's answer
if x == 0:
a = 0
elif x == 1:
a = 1
# Bob's answer
if y == 0:
b = 1
elif y == 1:
b = 0
return a, b
Spielen wir das Spiel erneut 1000 Mal, um zu sehen, wie gut diese Strategie abschneidet.
NUM_GAMES = 1000
TOTAL_SCORE = 0
for _ in range(NUM_GAMES):
TOTAL_SCORE += chsh_game(classical_strategy)
print("Fraction of games won:", TOTAL_SCORE / NUM_GAMES)
Fraction of games won: 0.747
Obwohl Zufälligkeit im Spiel ist, ist es nach 1000 Durchläufen sehr unwahrscheinlich, dass die Statistiken stark abweichen. Die Quantenstrategie gewinnt in etwa 85 % der Fälle, während eine klassische Strategie höchstens in etwa 75 % der Fälle gewinnen kann.