Sari la conținutul principal
Acest proiect este în versiune beta. Ai găsit o eroare? Deschide un issue pe GitHub. Ai o întrebare sau o idee? Întreabă în Discussions — ne-ar plăcea să aflăm părerea ta!
Cum este calitatea traducerii pe această pagină?

Implementare Qiskit

În această lecție, implementăm câteva dintre ideile din lecția despre entanglement în acțiune, folosind 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

Iată o implementare prin circuit cuantic a protocolului de teleportare.

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"))

Output of the previous code cell

Circuitul folosește câteva funcționalități ale Qiskit pe care nu le-am mai văzut în lecțiile anterioare, inclusiv funcțiile barrier și if_test. Funcția barrier creează o separare vizuală care face diagrama circuitului mai ușor de citit, și împiedică totodată Qiskit să efectueze diverse simplificări și optimizări de-a lungul barierei în timpul compilării, atunci când circuitele sunt rulate pe hardware real. Funcția if_test aplică o operație condiționat, în funcție de un bit sau registru clasic.

Circuitul inițializează mai întâi (A,B)(\mathsf{A},\mathsf{B}) în starea ∣ϕ+⟩\vert \phi^+\rangle (pas care nu face parte din protocol în sine), apoi urmează operațiile lui Alice, apoi măsurătorile sale, și în final operațiile lui Bob. Pentru a testa că protocolul funcționează corect, vom aplica o poartă aleatoare pe un singur qubit la starea inițializată ∣0⟩\vert 0\rangle a lui Q\mathsf{Q}, pentru a obține un vector de stare cuantică aleatoriu care să fie teleportat. Aplicând inversa (adică transpusa conjugată) acelei porți la B\mathsf{B} după rularea protocolului, putem verifica că starea a fost teleportată, măsurând pentru a constata că s-a întors la starea ∣0⟩\vert 0\rangle.

Mai întâi vom alege aleatoriu o poartă unitară pentru qubit.

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()))
[0.9897212158−0.0195080103−0.141673401i0.0603319186+0.1296609988i−0.8319925233+0.5360378028i] \begin{bmatrix} 0.9897212158 & -0.0195080103 - 0.141673401 i \\ 0.0603319186 + 0.1296609988 i & -0.8319925233 + 0.5360378028 i \\ \end{bmatrix}

Acum vom crea un nou circuit de testare care aplică mai întâi poarta noastră aleatoare la Q\mathsf{Q}, apoi rulează circuitul de teleportare, și în final aplică inversa porții noastre aleatoare la qubit-ul B\mathsf{B} și măsoară. Rezultatul ar trebui să fie 00 cu certitudine.

# 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"))

Output of the previous code cell

În final, să rulăm simulatorul Aer pe acest circuit și să afișăm o histogramă a ieșirilor. Vom vedea statisticile pentru toți cei trei biți clasici: bitul din jos/cel mai din stânga ar trebui să fie întotdeauna 00, indicând că qubit-ul Q\mathsf{Q} a fost teleportat cu succes în B\mathsf{B}, în timp ce ceilalți doi biți ar trebui să fie aproximativ uniformi.

result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))

Output of the previous code cell

Putem de asemenea filtra statisticile pentru a ne concentra exclusiv pe qubit-ul de rezultat al testului, dacă dorim, astfel:

filtered_statistics = marginal_distribution(statistics, [2])
display(plot_histogram(filtered_statistics))

Output of the previous code cell

Codificarea supradensă​

Codificarea supradensă este un protocol care, într-un anumit sens, urmărește un scop complementar față de teleportare. În loc să permită transmiterea unui qubit folosind doi biți clasici de comunicare (cu prețul unui e-bit de entanglement), acesta permite transmiterea a doi biți clasici folosind un qubit de comunicare cuantică (din nou, cu prețul unui e-bit de entanglement).

Mai în detaliu, avem un expeditor (Alice) și un destinatar (Bob) care împart un e-bit de entanglement. Conform convențiilor stabilite pentru lecție, aceasta înseamnă că Alice deține un qubit A\mathsf{A}, Bob deține un qubit B\mathsf{B}, iar perechea (A,B)(\mathsf{A},\mathsf{B}) se află împreună în starea ∣ϕ+⟩\vert\phi^+\rangle. Alice dorește să îi transmită lui Bob doi biți clasici, pe care îi vom nota cu cc și dd, și va realiza acest lucru trimițându-i un qubit.

Este rezonabil să privim această realizare ca fiind mai puțin impresionantă decât cea pe care o îndeplinește teleportarea. Trimiterea qubiților este probabil mult mai dificilă decât trimiterea biților clasici în viitorul previzibil, astfel că schimbul unui qubit de comunicare cuantică pentru doi biți de comunicare clasică, plus costul unui e-bit, abia pare să merite. Cu toate acestea, acest lucru nu înseamnă că codificarea supradensă nu este interesantă, căci cu siguranță este.

Potrivindu-se cu tema lecției, un motiv pentru care codificarea supradensă este interesantă este că demonstrează o utilizare concretă și (în contextul teoriei informației) destul de izbitoare a entanglement-ului. O celebră teoremă din teoria informației cuantice, cunoscută sub numele de teorema lui Holevo, implică faptul că fără utilizarea unei stări entanglate comune, este imposibil să se comunice mai mult de un bit de informație clasică prin trimiterea unui singur qubit. (Teorema lui Holevo este mai generală decât atât. Enunțul ei precis este tehnic și necesită explicații, dar aceasta este una dintre consecințele ei.) Deci, prin codificarea supradensă, entanglement-ul comun permite efectiv dublarea capacității de transport a informației clasice prin trimiterea qubiților.

Protocol​

Următoarea diagramă de circuit cuantic descrie protocolul de codificare supradensă:

Superdense coding circuit

Cu cuvinte, iată ce face Alice:

  1. Dacă d=1d=1, Alice aplică o poartă ZZ pe qubit-ul ei A\mathsf{A} (iar dacă d=0d=0 nu face nimic).

  2. Dacă c=1c=1, Alice aplică o poartă XX pe qubit-ul ei A\mathsf{A} (iar dacă c=0c=0 nu face nimic).

Apoi Alice îi trimite lui Bob qubit-ul ei A.\mathsf{A}.

Ceea ce face Bob când primește qubit-ul A\mathsf{A} este să aplice mai întâi o poartă controlled-NOT, cu A\mathsf{A} ca qubit de control și B\mathsf{B} ca qubit țintă, după care aplică o poartă Hadamard pe A\mathsf{A}. Apoi măsoară B\mathsf{B} pentru a obține cc și A\mathsf{A} pentru a obține dd, cu măsurători în baza standard în ambele cazuri.

Analiză​

Ideea din spatele acestui protocol este simplă: Alice alege practic ce stare Bell dorește să împartă cu Bob, îi trimite lui Bob qubit-ul ei, iar Bob măsoară pentru a determina ce stare Bell a ales Alice.

Adică, inițial împart ∣ϕ+⟩,\vert\phi^+\rangle, iar în funcție de biții cc și d,d, Alice fie lasă această stare neschimbată, fie o schimbă într-una dintre celelalte stări Bell aplicând I,\mathbb{I}, X,X, Z,Z, sau XZXZ pe qubit-ul ei A.\mathsf{A}.

(I⊗I)∣ϕ+⟩=∣ϕ+⟩(I⊗Z)∣ϕ+⟩=∣ϕ−⟩(I⊗X)∣ϕ+⟩=∣ψ+⟩(I⊗XZ)∣ϕ+⟩=∣ψ−⟩\begin{aligned} (\mathbb{I} \otimes \mathbb{I}) \vert \phi^+ \rangle & = \vert \phi^+\rangle \\ (\mathbb{I} \otimes Z) \vert \phi^+ \rangle & = \vert \phi^-\rangle \\ (\mathbb{I} \otimes X) \vert \phi^+ \rangle & = \vert \psi^+\rangle \\ (\mathbb{I} \otimes XZ) \vert \phi^+ \rangle & = \vert \psi^-\rangle \end{aligned}

Acțiunile lui Bob au următoarele efecte asupra celor patru stări Bell:

∣ϕ+⟩↦∣00⟩∣ϕ−⟩↦∣01⟩∣ψ+⟩↦∣10⟩∣ψ−⟩↦−∣11⟩\begin{aligned} \vert \phi^+\rangle & \mapsto \vert 00\rangle\\ \vert \phi^-\rangle & \mapsto \vert 01\rangle\\ \vert \psi^+\rangle & \mapsto \vert 10\rangle\\ \vert \psi^-\rangle & \mapsto -\vert 11\rangle\\ \end{aligned}

Acest lucru poate fi verificat direct, calculând rezultatele operațiunilor lui Bob asupra acestor stări, una câte una.

Deci, când Bob efectuează măsurătorile, poate determina ce stare Bell a ales Alice. Verificarea corectitudinii protocolului constă în examinarea fiecărui caz:

  • Dacă cd=00cd = 00, atunci starea perechii (B,A)(\mathsf{B},\mathsf{A}) când Bob primește A\mathsf{A} este ∣ϕ+⟩\vert \phi^+\rangle. El transformă această stare în ∣00⟩\vert 00\rangle și obține cd=00cd = 00.

  • Dacă cd=01cd = 01, atunci starea perechii (B,A)(\mathsf{B},\mathsf{A}) când Bob primește A\mathsf{A} este ∣ϕ−⟩\vert \phi^-\rangle. El transformă această stare în ∣01⟩\vert 01\rangle și obține cd=01cd = 01.

  • Dacă cd=10cd = 10, atunci starea perechii (B,A)(\mathsf{B},\mathsf{A}) când Bob primește A\mathsf{A} este ∣ψ+⟩\vert \psi^+\rangle. El transformă această stare în ∣10⟩\vert 10\rangle și obține cd=10cd = 10.

  • Dacă cd=11cd = 11, atunci starea perechii (B,A)(\mathsf{B},\mathsf{A}) când Bob primește A\mathsf{A} este ∣ψ−⟩\vert \psi^-\rangle. El transformă această stare în −∣11⟩-\vert 11\rangle și obține cd=11cd = 11. (Factorul de fază minus unu nu are niciun efect aici.)

Implementarea codificării supradense​

Iată o implementare simplă a codificării supradense în care specificăm circuitul în funcție de biții de transmis. Mai întâi vom alege doi biți de transmis. (Ulterior îi vom alege aleatoriu, dar deocamdată facem o alegere arbitrară.)

c = "1"
d = "0"

Acum vom construi circuitul corespunzător. Aici vom lăsa Qiskit să folosească numele implicite pentru qubiți: q0\mathsf{q}_0 pentru qubit-ul de sus și q1\mathsf{q}_1 pentru cel de jos.

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"))

Output of the previous code cell

Nu apare nimic deosebit de nou aici, cu excepția funcției measure_all, care măsoară toți qubiții și pune rezultatele într-un singur registru clasic (deci doi biți în acest caz).

Rulând simulatorul Aer obținem rezultatul așteptat.

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

Output of the previous code cell

Acum să folosim un qubit suplimentar ca generator de biți aleatorii — practic pentru a arunca monede echilibrate. Îl vom folosi pentru a alege aleatoriu cc și dd, și apoi vom rula protocolul de codificare supradensă.

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"))

Output of the previous code cell

Rulând simulatorul Aer, rezultatele arată că biții clasici ai lui Alice și Bob sunt întotdeauna identici.

result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))

Output of the previous code cell

Implementare în Qiskit​

Putem implementa jocul CHSH, împreună cu strategia cuantică definită mai sus, în Qiskit după cum urmează.

Mai întâi, iată definiția jocului în sine, care permite introducerea unei strategii arbitrare ca argument.

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

Acum vom crea o funcție care produce un circuit în funcție de întrebările pentru Alice și Bob. Vom lăsa qubiții cu numele lor implicite pentru simplitate și vom folosi poarta încorporată Ry(θ)R_y(\theta) pentru acțiunile lui Alice și 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

Iată cele patru circuite posibile, în funcție de întrebările puse.

# 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)

Output of the previous code cell

(x,y) = (0,1)

Output of the previous code cell

(x,y) = (1,0)

Output of the previous code cell

(x,y) = (1,1)

Output of the previous code cell

Acum vom crea un job folosind simulatorul Aer care rulează circuitul o singură dată pentru o pereche de intrare dată (x,y)(x,y).

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

În final, vom juca jocul de 1000 de ori și vom calcula proporția de jocuri câștigate de strategie.

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

Putem defini și o strategie clasică și să vedem cât de bine funcționează. Aceasta este doar o strategie — altele pot fi testate modificând codul — dar se numără printre strategiile clasice optime.

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

Să jucăm din nou jocul de 1000 de ori pentru a vedea cât de bine funcționează.

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

Deși există aleatorism implicat, statisticile au foarte puține șanse să se abată prea mult după 1000 de rulări. Strategia cuantică câștigă aproximativ 85% din timp, în timp ce o strategie clasică nu poate câștiga mai mult de aproximativ 75% din timp.