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_backendprima 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:
- Entanglement in action, una lezione del corso sugli stati di Bell e il gioco CHSH.
SparsePauliOpe l'introduzione alle primitive Qiskit.
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 e e le basi per il secondo qubit come e . Questo ci consente di calcolare la quantità CHSH :
Ogni osservabile è oppure . Chiaramente, uno dei termini deve essere e l'altro deve essere . Pertanto, . Il valore medio di deve soddisfare la disuguaglianza:
Espandendo in termini di , , e si ottiene:
Puoi definire un'altra quantità CHSH :
che porta a un'altra disuguaglianza:
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 . Utilizzando la primitiva Estimator, otterremo direttamente i valori di aspettazione e , senza doverli ricostruire dai conteggi grezzi. Misureremo il secondo qubit nelle basi e . Il primo qubit verrà misurato anch'esso in basi ortogonali, ma con un angolo di rotazione che spazieremo tra e . 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 , 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")
Successivamente, creiamo un elenco di 21 valori di fase da a a cui valutare il circuito parametrizzato (, , , ..., , ).
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 ; il secondo qubit viene misurato in e . Con queste scelte, i quattro correlatori CHSH corrispondono agli operatori di Pauli , , e :
# <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")
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 insieme al limite classico () e al limite di Tsirelson (). 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)",
)
I witness CHSH del simulatore superano già il limite classico di per diversi valori di , anche con il modello di rumore del backend. I picchi si fermano poco sotto il limite di Tsirelson 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)

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 , e il riepilogo stampato conferma che sostanzialmente ogni coppia viola la disuguaglianza di CHSH contemporaneamente.
I picchi si fermano sotto il limite di Tsirelson 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
Se hai trovato questo lavoro interessante, potresti essere interessato al seguente materiale:
- Entanglement in action: una lezione del corso di John Watrous sugli stati di Bell e il gioco CHSH.
- Inizia con la primitiva Estimator: una guida su PUB e scansioni di parametri.
- Benchmarking in tempo reale per la selezione dei qubit: un altro modo per caratterizzare la qualità dei qubit e dell'entanglement su un dispositivo.
- Riferimento API
SparsePauliOp.