Implementazione con Qiskit
In questa lezione implementiamo alcune delle idee della lezione sull'entanglement in azione, usando 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
Ecco un'implementazione tramite circuito quantistico del protocollo di teleportazione.
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"))
Il circuito fa uso di alcune funzionalità di Qiskit che non abbiamo ancora visto nelle lezioni precedenti, tra cui le funzioni barrier e if_test.
La funzione barrier crea una separazione visiva che rende il diagramma del circuito più leggibile, e impedisce anche a Qiskit di eseguire varie semplificazioni e ottimizzazioni attraverso la barriera durante la compilazione quando i circuiti vengono eseguiti su hardware reale.
La funzione if_test applica un'operazione in modo condizionale a seconda di un bit o registro classico.
Il circuito inizializza prima nello stato (che non fa parte del protocollo stesso), seguito dalle operazioni di Alice, poi dalle sue misurazioni, e infine dalle operazioni di Bob. Per verificare che il protocollo funzioni correttamente, applicheremo un gate a qubit singolo generato casualmente allo stato inizializzato di per ottenere un vettore di stato quantistico casuale da teleportare. Applicando l'inverso (cioè la trasposta coniugata) di quel gate a dopo l'esecuzione del protocollo, possiamo verificare che lo stato sia stato teleportato misurando e controllando che sia tornato allo stato .
Per prima cosa scegliamo casualmente un gate unitario a qubit singolo.
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()))
Ora creiamo un nuovo circuito di test che prima applica il nostro gate casuale a , poi esegue il circuito di teleportazione, e infine applica l'inverso del nostro gate casuale al qubit e misura. Il risultato dovrebbe essere con certezza.
# 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"))
Infine, eseguiamo il simulatore Aer su questo circuito e tracciamo un istogramma degli output. Vedremo le statistiche per tutti e tre i bit classici: il bit in basso/a sinistra dovrebbe essere sempre , indicando che il qubit è stato teleportato con successo in , mentre gli altri due bit dovrebbero essere approssimativamente uniformi.
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))
Possiamo anche filtrare le statistiche per concentrarci esclusivamente sul qubit del risultato del test, in questo modo:
filtered_statistics = marginal_distribution(statistics, [2])
display(plot_histogram(filtered_statistics))
Codifica superdensa (superdense coding)
Il superdense coding è un protocollo che, in un certo senso, raggiunge un obiettivo complementare alla teleportazione. Invece di consentire la trasmissione di un qubit usando due bit classici di comunicazione (al costo di un e-bit di entanglement), permette la trasmissione di due bit classici usando un qubit di comunicazione quantistica (anche in questo caso, al costo di un e-bit di entanglement).
In dettaglio, abbiamo un mittente (Alice) e un destinatario (Bob) che condividono un e-bit di entanglement. Secondo le convenzioni adottate nella lezione, questo significa che Alice detiene un qubit , Bob detiene un qubit , e insieme la coppia si trova nello stato . Alice desidera trasmettere due bit classici a Bob, che indicheremo con e , e lo farà inviandogli un qubit.
È ragionevole considerare questa operazione come meno interessante di quella che realizza la teleportazione. Inviare qubit sarà probabilmente molto più difficile che inviare bit classici nel prevedibile futuro, al punto che scambiare un qubit di comunicazione quantistica per due bit di comunicazione classica, oltretutto al costo di un e-bit, sembra difficilmente conveniente. Tuttavia, questo non implica che il superdense coding non sia interessante, perché lo è sicuramente.
In linea con il tema della lezione, uno dei motivi per cui il superdense coding è interessante è che dimostra un uso concreto e (nel contesto della teoria dell'informazione) piuttosto sorprendente dell'entanglement. Un famoso teorema nella teoria dell'informazione quantistica, noto come teorema di Holevo, implica che senza l'uso di uno stato entangled condiviso, è impossibile comunicare più di un bit di informazione classica inviando un singolo qubit. (Il teorema di Holevo è più generale di così. La sua formulazione precisa è tecnica e richiede una spiegazione, ma questa è una delle sue conseguenze.) Quindi, attraverso il superdense coding, l'entanglement condiviso consente effettivamente il raddoppio della capacità di trasporto di informazione classica dell'invio di qubit.
Protocollo
Il seguente diagramma di circuito quantistico descrive il protocollo di superdense coding:

In parole, ecco cosa fa Alice:
-
Se , Alice esegue un gate sul suo qubit (e se non lo fa).
-
Se , Alice esegue un gate sul suo qubit (e se non lo fa).
Alice invia quindi il suo qubit a Bob.
Quando Bob riceve il qubit , prima esegue un gate controlled-NOT, con come controllo e come target, e poi applica un gate di Hadamard ad . Poi misura per ottenere e per ottenere , con misurazioni nella base standard in entrambi i casi.
Analisi
L'idea alla base di questo protocollo è semplice: Alice sceglie effettivamente quale stato di Bell vuole condividere con Bob, invia a Bob il suo qubit, e Bob misura per determinare quale stato di Bell Alice ha scelto.
Cioè, inizialmente condividono e a seconda dei bit e Alice lascia questo stato invariato o lo sposta in uno degli altri stati di Bell applicando o al suo qubit
Le azioni di Bob hanno i seguenti effetti sui quattro stati di Bell:
Questo può essere verificato direttamente, calcolando i risultati delle operazioni di Bob su questi stati uno alla volta.
Quindi, quando Bob esegue le sue misurazioni, è in grado di determinare quale stato di Bell Alice ha scelto. Verificare che il protocollo funzioni correttamente è una questione di controllare ogni caso:
-
Se , allora lo stato di quando Bob riceve è . Trasforma questo stato in e ottiene .
-
Se , allora lo stato di quando Bob riceve è . Trasforma questo stato in e ottiene .
-
Se , allora lo stato di quando Bob riceve è . Trasforma questo stato in e ottiene .
-
Se , allora lo stato di quando Bob riceve è . Trasforma questo stato in e ottiene . (Il fattore di fase negativo non ha alcun effetto qui.)
Implementazione del superdense coding
Ecco una semplice implementazione del superdense coding in cui specifichiamo il circuito stesso in base ai bit da trasmettere. Per prima cosa scegliamo due bit da trasmettere. (In seguito li sceglieremo casualmente, ma per ora facciamo semplicemente una scelta arbitraria.)
c = "1"
d = "0"
Ora costruiamo il circuito di conseguenza. Qui lasceremo che Qiskit usi i nomi predefiniti per i qubit: per il qubit in alto e per quello in basso.
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"))
Non c'è molto di nuovo qui, tranne la funzione measure_all, che misura tutti i qubit e inserisce i risultati in un unico registro classico (e quindi avendo due bit in questo caso).
L'esecuzione del simulatore Aer produce l'output atteso.
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
Ora usiamo un qubit aggiuntivo come generatore di bit casuali — essenzialmente per lanciare monete equilibrate. Lo useremo per scegliere casualmente e , e poi eseguire il protocollo di superdense coding.
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"))
L'esecuzione del simulatore Aer mostra i risultati: i bit classici di Alice e Bob concordano sempre.
result = AerSimulator().run(test).result()
statistics = result.get_counts()
display(plot_histogram(statistics))
Implementazione con Qiskit
Possiamo implementare il gioco CHSH, insieme alla strategia quantistica definita sopra, in Qiskit come segue.
Per prima cosa, ecco la definizione del gioco stesso, che consente di inserire una strategia arbitraria come argomento.
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
Ora creeremo una funzione che produce un circuito in base alle domande per Alice e Bob. Per semplicità lasceremo ai qubit i loro nomi predefiniti, e utilizzeremo il gate integrato per le azioni di Alice e 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
Ecco i quattro possibili circuiti, a seconda delle domande poste.
# 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)
Ora creeremo un job usando il simulatore Aer che esegue il circuito una sola volta per una data coppia di input .
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
Infine, giocheremo al gioco 1000 volte e calcoleremo la frazione di partite vinte dalla strategia.
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
Possiamo anche definire una strategia classica e vedere quanto bene funziona. Questa è solo una strategia — altre possono essere testate modificando il codice — ma è tra le strategie classiche ottimali.
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
Giochiamo di nuovo al gioco 1000 volte per vedere quanto bene funziona.
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
Sebbene ci sia della casualità in gioco, le statistiche difficilmente si discostano molto dopo 1000 esecuzioni. La strategia quantistica vince circa l'85% delle volte, mentre una strategia classica non può vincere più di circa il 75% delle volte.