Vai al contenuto principale
Questo progetto è in fase beta. Hai trovato un bug? Apri una issue su GitHub. Hai una domanda o un'idea? Chiedi su GitHub Discussions — apprezziamo il tuo feedback!
Com'è la qualità della traduzione su questa pagina?

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

Output of the previous code cell

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 (A,B)(\mathsf{A},\mathsf{B}) nello stato ∣ϕ+⟩\vert \phi^+\rangle (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 ∣0⟩\vert 0\rangle di Q\mathsf{Q} per ottenere un vettore di stato quantistico casuale da teleportare. Applicando l'inverso (cioè la trasposta coniugata) di quel gate a B\mathsf{B} dopo l'esecuzione del protocollo, possiamo verificare che lo stato sia stato teleportato misurando e controllando che sia tornato allo stato ∣0⟩\vert 0\rangle.

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()))
[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}

Ora creiamo un nuovo circuito di test che prima applica il nostro gate casuale a Q\mathsf{Q}, poi esegue il circuito di teleportazione, e infine applica l'inverso del nostro gate casuale al qubit B\mathsf{B} e misura. Il risultato dovrebbe essere 00 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"))

Output of the previous code cell

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 00, indicando che il qubit Q\mathsf{Q} è stato teleportato con successo in B\mathsf{B}, mentre gli altri due bit dovrebbero essere approssimativamente uniformi.

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

Output of the previous code cell

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

Output of the previous code cell

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 A\mathsf{A}, Bob detiene un qubit B\mathsf{B}, e insieme la coppia (A,B)(\mathsf{A},\mathsf{B}) si trova nello stato ∣ϕ+⟩\vert\phi^+\rangle. Alice desidera trasmettere due bit classici a Bob, che indicheremo con cc e dd, 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:

Superdense coding circuit

In parole, ecco cosa fa Alice:

  1. Se d=1d=1, Alice esegue un gate ZZ sul suo qubit A\mathsf{A} (e se d=0d=0 non lo fa).

  2. Se c=1c=1, Alice esegue un gate XX sul suo qubit A\mathsf{A} (e se c=0c=0 non lo fa).

Alice invia quindi il suo qubit A\mathsf{A} a Bob.

Quando Bob riceve il qubit A\mathsf{A}, prima esegue un gate controlled-NOT, con A\mathsf{A} come controllo e B\mathsf{B} come target, e poi applica un gate di Hadamard ad A\mathsf{A}. Poi misura B\mathsf{B} per ottenere cc e A\mathsf{A} per ottenere dd, 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 ∣ϕ+⟩,\vert\phi^+\rangle, e a seconda dei bit cc e d,d, Alice lascia questo stato invariato o lo sposta in uno degli altri stati di Bell applicando I,\mathbb{I}, X,X, Z,Z, o XZXZ al suo qubit 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}

Le azioni di Bob hanno i seguenti effetti sui quattro stati di 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}

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 cd=00cd = 00, allora lo stato di (B,A)(\mathsf{B},\mathsf{A}) quando Bob riceve A\mathsf{A} è ∣ϕ+⟩\vert \phi^+\rangle. Trasforma questo stato in ∣00⟩\vert 00\rangle e ottiene cd=00cd = 00.

  • Se cd=01cd = 01, allora lo stato di (B,A)(\mathsf{B},\mathsf{A}) quando Bob riceve A\mathsf{A} è ∣ϕ−⟩\vert \phi^-\rangle. Trasforma questo stato in ∣01⟩\vert 01\rangle e ottiene cd=01cd = 01.

  • Se cd=10cd = 10, allora lo stato di (B,A)(\mathsf{B},\mathsf{A}) quando Bob riceve A\mathsf{A} è ∣ψ+⟩\vert \psi^+\rangle. Trasforma questo stato in ∣10⟩\vert 10\rangle e ottiene cd=10cd = 10.

  • Se cd=11cd = 11, allora lo stato di (B,A)(\mathsf{B},\mathsf{A}) quando Bob riceve A\mathsf{A} è ∣ψ−⟩\vert \psi^-\rangle. Trasforma questo stato in −∣11⟩-\vert 11\rangle e ottiene cd=11cd = 11. (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: q0\mathsf{q}_0 per il qubit in alto e q1\mathsf{q}_1 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"))

Output of the previous code cell

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

Output of the previous code cell

Ora usiamo un qubit aggiuntivo come generatore di bit casuali — essenzialmente per lanciare monete equilibrate. Lo useremo per scegliere casualmente cc e dd, 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"))

Output of the previous code cell

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

Output of the previous code cell

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 Ry(θ)R_y(\theta) 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)

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

Ora creeremo un job usando il simulatore Aer che esegue il circuito una sola volta per una data coppia di input (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

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.