Vai al contenuto principale

Disuguaglianza di CHSH

Stima d'uso: Due minuti su un processore Heron r3 (NOTA: Questa è solo una stima. Il tempo di esecuzione effettivo potrebbe variare.)

Obiettivi di apprendimento

Dopo aver completato questo tutorial, puoi aspettarti di comprendere le seguenti informazioni:

  • Come costruire un circuito CHSH parametrizzato con uno stato di Bell e misurare i quattro valori di aspettazione che compongono i witness CHSH.
  • Come calcolare i valori di aspettazione di più osservabili su una scansione di parametri in una singola chiamata alla primitiva EstimatorV2.
  • Come validare un workflow quantistico su un simulatore locale rumoroso con AerSimulator.from_backend prima di inviare al hardware.
  • Come scalare un esperimento CHSH in un benchmark di entanglement su tutto il dispositivo eseguendo molte coppie di Bell indipendenti in parallelo su hardware IBM Quantum®.

Prerequisiti

Si raccomanda di familiarizzare con i seguenti argomenti:

Contesto

In questo tutorial, eseguirai un esperimento su un computer quantistico per dimostrare la violazione della disuguaglianza di CHSH con la primitiva Estimator.

La disuguaglianza di CHSH, che prende il nome da Clauser, Horne, Shimony e Holt, viene utilizzata per testare sperimentalmente il teorema di Bell (1969). Il teorema afferma che le teorie a variabili nascoste locali non possono spiegare alcune conseguenze dell'entanglement nella meccanica quantistica. Dimostrare una violazione della disuguaglianza di CHSH mostra che la meccanica quantistica è incompatibile con le teorie a variabili nascoste locali, un esperimento fondamentale per la nostra comprensione della meccanica quantistica.

Il Premio Nobel per la Fisica del 2022 è stato assegnato ad Alain Aspect, John Clauser e Anton Zeilinger in parte per il loro lavoro pionieristico nella scienza dell'informazione quantistica e, in particolare, per i loro esperimenti con fotoni entangled che dimostrano la violazione delle disuguaglianze di Bell.

Per questo esperimento, creeremo una coppia entangled su cui misureremo ogni qubit in due basi diverse. Etichetteremo le basi per il primo qubit come AA e aa e le basi per il secondo qubit come BB e bb. Questo ci consente di calcolare la quantità CHSH S1S_1:

S1=A(Bb)+a(B+b).S_1 = A(B-b) + a(B+b).

Ogni osservabile è +1+1 oppure 1-1. Chiaramente, uno dei termini B±bB\pm b deve essere 00 e l'altro deve essere ±2\pm 2. Pertanto, S1=±2S_1 = \pm 2. Il valore medio di S1S_1 deve soddisfare la disuguaglianza:

S12.|\langle S_1 \rangle|\leq 2.

Espandendo S1S_1 in termini di AA, aa, BB e bb si ottiene:

S1=ABAb+aB+ab2.|\langle S_1 \rangle| = |\langle AB \rangle - \langle Ab \rangle + \langle aB \rangle + \langle ab \rangle| \leq 2.

Puoi definire un'altra quantità CHSH S2S_2:

S2=A(B+b)a(Bb),S_2 = A(B+b) - a(B-b),

che porta a un'altra disuguaglianza:

S2=AB+AbaB+ab2.|\langle S_2 \rangle| = |\langle AB \rangle + \langle Ab \rangle - \langle aB \rangle + \langle ab \rangle| \leq 2.

Se la meccanica quantistica potesse essere descritta da teorie a variabili nascoste locali, queste disuguaglianze sarebbero sempre valide. Come dimostrato in questo tutorial, esse possono essere violate su un computer quantistico, quindi la meccanica quantistica non è compatibile con le teorie a variabili nascoste locali.

Creeremo la coppia entangled preparando lo stato di Bell Φ+=00+112|\Phi^+\rangle = \frac{|00\rangle + |11\rangle}{\sqrt{2}}. Utilizzando la primitiva Estimator, otterremo direttamente i valori di aspettazione AB,Ab,aB\langle AB \rangle, \langle Ab \rangle, \langle aB \rangle e ab\langle ab \rangle, senza doverli ricostruire dai conteggi grezzi. Misureremo il secondo qubit nelle basi ZZ e XX. Il primo qubit verrà misurato anch'esso in basi ortogonali, ma con un angolo di rotazione θ\theta che spazieremo tra 00 e 2π2\pi. La primitiva Estimator valuta questa scansione di parametri in un singolo primitive unified bloc (PUB).

Requisiti

Prima di iniziare questo tutorial, assicurati di avere installato quanto segue:

  • Qiskit SDK v2.0 o successivo, con supporto per la visualizzazione
  • Qiskit Runtime v0.40 o successivo (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 o successivo (pip install qiskit-aer)

Configurazione

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime
# General
import numpy as np

# Qiskit imports
from qiskit import QuantumCircuit
from qiskit.circuit import Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# Qiskit Runtime imports
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator

# Qiskit Aer for local noisy simulation
from qiskit_aer import AerSimulator

# Plotting routines
import matplotlib.pyplot as plt
import matplotlib.ticker as tck
# Select an IBM Quantum backend.
service = QiskitRuntimeService()
backend = service.least_busy(
min_num_qubits=127, operational=True, simulator=False
)
backend.name
'ibm_pittsburgh'

Esempio su piccola scala con simulatore

Prima di inviare un job su hardware, validiamo l'intero workflow su un simulatore locale rumoroso. Utilizziamo AerSimulator.from_backend(backend) per costruire un simulatore che eredita il modello di rumore e la mappa di connettività del backend selezionato, in modo che la risposta del simulatore sia qualitativamente simile a quella attesa dall'hardware.

Passo 1: Mappare gli input classici su un problema quantistico

Scriviamo il circuito CHSH con un singolo parametro θ\theta, che definisce la base di misurazione del primo qubit. La primitiva Estimator semplifica l'analisi: restituisce direttamente i valori di aspettazione delle osservabili e può valutare un circuito parametrizzato a molti valori del parametro in una singola chiamata.

theta = Parameter(r"$\theta$")

chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
chsh_circuit.draw(output="mpl", idle_wires=False, style="iqp")

Output of the previous code cell

Successivamente, creiamo un elenco di 21 valori di fase da 00 a 2π2\pi a cui valutare il circuito parametrizzato (00, 0.1π0.1\pi, 0.2π0.2\pi, ..., 1.9π1.9\pi, 2π2\pi).

number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
# Phases need to be expressed as a list of lists for the Estimator PUB
individual_phases = [[ph] for ph in phases]

Infine definiamo le osservabili. Il primo qubit viene misurato lungo assi ruotati di θ\theta; il secondo qubit viene misurato in ZZ e XX. Con queste scelte, i quattro correlatori CHSH corrispondono agli operatori di Pauli ZZZZ, ZXZX, XZXZ e XXXX:

S1=ZZZX+XZ+XX,\langle S_1 \rangle = \langle ZZ \rangle - \langle ZX \rangle + \langle XZ \rangle + \langle XX \rangle, S2=ZZ+ZXXZ+XX.\langle S_2 \rangle = \langle ZZ \rangle + \langle ZX \rangle - \langle XZ \rangle + \langle XX \rangle.
# <S_1> = <ZZ> - <ZX> + <XZ> + <XX>
observable1 = SparsePauliOp.from_list(
[("ZZ", 1), ("ZX", -1), ("XZ", 1), ("XX", 1)]
)

# <S_2> = <ZZ> + <ZX> - <XZ> + <XX>
observable2 = SparsePauliOp.from_list(
[("ZZ", 1), ("ZX", 1), ("XZ", -1), ("XX", 1)]
)

Passo 2: Ottimizzare il problema per l'esecuzione su hardware quantistico

Le primitive V2 accettano solo circuiti e osservabili conformi alle istruzioni e alla connettività supportate dal sistema target (circuiti e osservabili ISA — instruction set architecture). Costruiamo l'AerSimulator a partire dal backend e traspiliamo rispetto al target del simulatore, in modo che lo stesso pass manager venga esercitato end-to-end.

# Build a noisy simulator from the ibm_pittsburgh backend
aer_sim = AerSimulator.from_backend(backend)

pm = generate_preset_pass_manager(target=aer_sim.target, optimization_level=3)
chsh_isa_circuit = pm.run(chsh_circuit)
chsh_isa_circuit.draw(output="mpl", idle_wires=False, style="iqp")

Output of the previous code cell

Trasformiamo anche le osservabili per adattarle al layout dei qubit del circuito traspilato usando SparsePauliOp.apply_layout.

isa_observable1 = observable1.apply_layout(layout=chsh_isa_circuit.layout)
isa_observable2 = observable2.apply_layout(layout=chsh_isa_circuit.layout)

Passo 3: Eseguire utilizzando le primitive Qiskit

Eseguiamo la scansione di parametri con EstimatorV2 in modalità aer_sim. Il metodo run() dell'Estimator accetta un iterabile di PUB. Ogni PUB ha il formato (circuit, observables, parameter_values, precision). Passiamo entrambe le osservabili insieme in modo che condividano la stessa scansione di parametri.

# Use the AerSimulator-backed Estimator to validate the workflow locally
estimator_sim = Estimator(mode=aer_sim)

pub = (
chsh_isa_circuit, # ISA circuit
[[isa_observable1], [isa_observable2]], # ISA observables
individual_phases, # Parameter values
)

sim_result = estimator_sim.run(pubs=[pub]).result()

Passo 4: Post-elaborare e restituire il risultato nel formato classico desiderato

L'Estimator restituisce i valori di aspettazione per entrambe le osservabili. Li tracciamo in funzione di θ\theta insieme al limite classico (±2\pm 2) e al limite di Tsirelson (±22\pm 2\sqrt{2}). Le regioni grigie ombreggiate segnano lo spazio tra i due. I punti che cadono all'interno di queste fasce violano la disuguaglianza di CHSH.

chsh1_sim = sim_result[0].data.evs[0]
chsh2_sim = sim_result[0].data.evs[1]

def plot_chsh(phases, chsh1, chsh2, title):
fig, ax = plt.subplots(figsize=(10, 6))

ax.plot(
phases / np.pi, chsh1, "o-", label=r"$\langle S_1 \rangle$", zorder=3
)
ax.plot(
phases / np.pi, chsh2, "o-", label=r"$\langle S_2 \rangle$", zorder=3
)

# classical bound +-2
ax.axhline(y=2, color="0.9", linestyle="--")
ax.axhline(y=-2, color="0.9", linestyle="--")

# quantum bound, +-2*sqrt(2)
ax.axhline(y=np.sqrt(2) * 2, color="0.9", linestyle="-.")
ax.axhline(y=-np.sqrt(2) * 2, color="0.9", linestyle="-.")
ax.fill_between(phases / np.pi, 2, 2 * np.sqrt(2), color="0.6", alpha=0.7)
ax.fill_between(
phases / np.pi, -2, -2 * np.sqrt(2), color="0.6", alpha=0.7
)

ax.xaxis.set_major_formatter(tck.FormatStrFormatter("%g $\\pi$"))
ax.xaxis.set_major_locator(tck.MultipleLocator(base=0.5))

ax.set_xlabel(r"$\theta$")
ax.set_ylabel("CHSH witness")
ax.set_title(title)
ax.legend()
plt.show()

plot_chsh(
phases,
chsh1_sim,
chsh2_sim,
"CHSH witnesses from AerSimulator (ibm_pittsburgh noise model)",
)

Output of the previous code cell

I witness CHSH del simulatore superano già il limite classico di ±2\pm 2 per diversi valori di θ\theta, anche con il modello di rumore del backend. I picchi si fermano poco sotto il limite di Tsirelson ±22\pm 2\sqrt{2} a causa del rumore simulato del dispositivo. Con il workflow validato, passiamo all'hardware reale.

Esempio su larga scala con hardware

Un test CHSH è intrinsecamente un esperimento su due qubit, quindi non scala aumentando le dimensioni di un singolo circuito. Scala invece eseguendo molti test in parallelo. Qui ricopriamo il backend con il maggior numero possibile di coppie di Bell disgiunte consentite dalla sua connettività (un matching della mappa di accoppiamento) ed eseguiamo un sotto-circuito CHSH indipendente su ogni coppia, tutto in un unico job.

Questo trasforma CHSH in un benchmark di qualità dell'entanglement su tutto il dispositivo: invece di una singola coppia scelta manualmente, testiamo l'entanglement su gran parte del chip contemporaneamente, in condizioni realistiche in cui ogni coppia è soggetta al crosstalk dei vicini e agli errori dei gate paralleli. Violare la disuguaglianza su ogni coppia simultaneamente certifica che l'entanglement genuino è disponibile ovunque sul dispositivo.

# -------------------------Step 1: Map classical inputs to a quantum problem-------------------------
# A CHSH test is bipartite, so we scale up by running one independent CHSH
# experiment on every disjoint Bell pair the device can host. A greedy
# matching of the coupling map gives a set of edges that share no qubits.
num_qubits = backend.num_qubits
used = set()
pairs = []
for qa, qb in backend.coupling_map.get_edges():
if qa not in used and qb not in used:
pairs.append((qa, qb))
used.update((qa, qb))
num_pairs = len(pairs)
print(
f"Tiling {backend.name} with {num_pairs} parallel Bell pairs "
f"({2 * num_pairs} of {num_qubits} qubits)"
)

# One parameterized CHSH sub-circuit per pair, all sharing the angle theta
theta = Parameter(r"$\theta$")
chsh_circuit = QuantumCircuit(num_qubits)
for qa, qb in pairs:
chsh_circuit.h(qa)
chsh_circuit.cx(qa, qb)
chsh_circuit.ry(theta, qa)

# Embed the two CHSH observables onto each pair's qubits (identity elsewhere)
obs1 = SparsePauliOp.from_list([("ZZ", 1), ("ZX", -1), ("XZ", 1), ("XX", 1)])
obs2 = SparsePauliOp.from_list([("ZZ", 1), ("ZX", 1), ("XZ", -1), ("XX", 1)])
observables = []
for qa, qb in pairs:
observables.append([obs1.apply_layout([qa, qb], num_qubits)])
observables.append([obs2.apply_layout([qa, qb], num_qubits)])

number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]

# -------------------------Step 2: Optimize problem for quantum hardware execution-------------------------
pm = generate_preset_pass_manager(target=backend.target, optimization_level=3)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [
[o[0].apply_layout(chsh_isa_circuit.layout)] for o in observables
]

# -------------------------Step 3: Execute using Qiskit primitives-------------------------
estimator_hw = Estimator(mode=backend)
estimator_hw.options.environment.job_tags = ["TUT_CI"]

pub = (chsh_isa_circuit, isa_observables, individual_phases)
job = estimator_hw.run(pubs=[pub])
print(f"Job ID: {job.job_id()}")
hw_result = job.result()

# -------------------------Step 4: Post-process and return result in desired classical format-------------------------
# evs has shape (2 * num_pairs, number_of_phases); rows alternate S1, S2
evs = np.asarray(hw_result[0].data.evs)
chsh1_all = evs[0::2]
chsh2_all = evs[1::2]

# A pair "violates" CHSH if its strongest witness exceeds the classical bound
peak = np.maximum(
np.abs(chsh1_all).max(axis=1), np.abs(chsh2_all).max(axis=1)
)
n_violate = int(np.sum(peak > 2))
print(
f"{n_violate}/{num_pairs} Bell pairs violated the CHSH inequality "
f"(mean peak witness {peak.mean():.2f}, classical bound 2)"
)

fig, ax = plt.subplots(figsize=(10, 6))

# Faint individual per-pair curves
for row in chsh1_all:
ax.plot(phases / np.pi, row, color="#1f77b4", alpha=0.2, lw=1)
for row in chsh2_all:
ax.plot(phases / np.pi, row, color="#ff7f0e", alpha=0.2, lw=1)

# Bold mean curves across all pairs
ax.plot(
phases / np.pi,
chsh1_all.mean(axis=0),
color="#1f77b4",
lw=2.5,
label=r"$\langle S_1 \rangle$ (mean)",
)
ax.plot(
phases / np.pi,
chsh2_all.mean(axis=0),
color="#ff7f0e",
lw=2.5,
label=r"$\langle S_2 \rangle$ (mean)",
)

# classical bound +-2 and Tsirelson bound +-2*sqrt(2)
ax.axhline(y=2, color="0.9", linestyle="--")
ax.axhline(y=-2, color="0.9", linestyle="--")
ax.axhline(y=np.sqrt(2) * 2, color="0.9", linestyle="-.")
ax.axhline(y=-np.sqrt(2) * 2, color="0.9", linestyle="-.")
ax.fill_between(phases / np.pi, 2, 2 * np.sqrt(2), color="0.6", alpha=0.7)
ax.fill_between(phases / np.pi, -2, -2 * np.sqrt(2), color="0.6", alpha=0.7)

ax.xaxis.set_major_formatter(tck.FormatStrFormatter("%g $\\pi$"))
ax.xaxis.set_major_locator(tck.MultipleLocator(base=0.5))
ax.set_xlabel(r"$\theta$")
ax.set_ylabel("CHSH witness")
ax.set_title(
f"CHSH witnesses for {num_pairs} parallel Bell pairs on {backend.name}"
)
ax.legend()
plt.show()
Tiling ibm_pittsburgh with 64 parallel Bell pairs (128 of 156 qubits)
Job ID: d86efd5g7okc73el0rp0
63/64 Bell pairs violated the CHSH inequality (mean peak witness 2.75, classical bound 2)

Output of the previous code cell

Le curve tenui rappresentano le singole coppie di Bell e le curve in grassetto sono la loro media su tutto il dispositivo. Ogni coppia traccia lo stesso sinusoide previsto dalla meccanica quantistica, e la dispersione tra le curve tenui riflette la variazione del rumore da coppia a coppia. Dove una curva entra nelle fasce grigie, ha superato il limite classico di ±2\pm 2, e il riepilogo stampato conferma che sostanzialmente ogni coppia viola la disuguaglianza di CHSH contemporaneamente.

I picchi si fermano sotto il limite di Tsirelson ±22\pm 2\sqrt{2} a causa del rumore del dispositivo, ma la conclusione è inequivocabile: il backend sostiene un entanglement genuino sull'intero chip contemporaneamente, non solo su una singola coppia scelta manualmente. Questo è il senso in cui l'esperimento CHSH "scala": non come un circuito più grande, ma come un benchmark parallelo che certifica l'entanglement ovunque contemporaneamente.

Passi successivi

Raccomandazioni

Se hai trovato questo lavoro interessante, potresti essere interessato al seguente materiale: