Przejdź do głównej treści

Nierówność CHSH

Szacowany czas użycia: dwie minuty na procesorze Heron r3 (UWAGA: Jest to jedynie szacunek. Rzeczywisty czas wykonania może się różnić.)

Efekty uczenia się

Po ukończeniu tego samouczka powinieneś rozumieć następujące zagadnienia:

  • Jak skonstruować sparametryzowany obwód Bell-state CHSH i zmierzyć cztery wartości oczekiwane składające się na świadków CHSH.
  • Jak obliczać wartości oczekiwane wielu obserwabli na przestrzeni parametrów w jednym wywołaniu prymitywu EstimatorV2.
  • Jak sprawdzać przepływ pracy kwantowej na lokalnym symulatorze z szumem przy użyciu AerSimulator.from_backend przed wysłaniem zadania na sprzęt.
  • Jak skalować eksperyment CHSH do benchmarku splątania na poziomie całego urządzenia, uruchamiając wiele niezależnych par Bell równolegle na sprzęcie IBM Quantum®.

Wymagania wstępne

Zaleca się zapoznanie z następującymi tematami:

Tło

W tym samouczku przeprowadzisz eksperyment na komputerze kwantowym, aby zademonstrować naruszenie nierówności CHSH za pomocą prymitywu Estimator.

Nierówność CHSH, nazwana od nazwisk Clauser, Horne, Shimony i Holt, służy do eksperymentalnego udowodnienia twierdzenia Bella (1969). Twierdzenie to głosi, że lokalne teorie ze zmiennymi ukrytymi nie mogą wyjaśnić niektórych konsekwencji splątania w mechanice kwantowej. Naruszenie nierówności CHSH dowodzi, że mechanika kwantowa jest niezgodna z lokalnymi teoriami ze zmiennymi ukrytymi — jest to eksperyment fundamentalny dla naszego rozumienia mechaniki kwantowej.

Nagroda Nobla z fizyki w 2022 roku została przyznana Alainowi Aspectowi, Johnowi Clauserowi i Antonowi Zeilingerowi między innymi za ich pionierską pracę w dziedzinie kwantowej nauki o informacji, a w szczególności za eksperymenty ze splątanymi fotonami demonstrujące naruszenie nierówności Bella.

W tym eksperymencie stworzymy splątaną parę, w której mierzymy każdy Qubit w dwóch różnych bazach. Oznaczmy bazy dla pierwszego Qubitu jako AA i aa, a bazy dla drugiego Qubitu jako BB i bb. Pozwala nam to obliczyć wielkość CHSH S1S_1:

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

Każda obserwabla przyjmuje wartość +1+1 lub 1-1. Oczywiście jeden z wyrazów B±bB\pm b musi być równy 00, a drugi ±2\pm 2. Zatem S1=±2S_1 = \pm 2. Wartość średnia S1S_1 musi spełniać nierówność:

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

Rozwijając S1S_1 w kategoriach AA, aa, BB i bb, otrzymujemy:

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

Możesz zdefiniować kolejną wielkość CHSH S2S_2:

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

co prowadzi do kolejnej nierówności:

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

Jeśli mechanika kwantowa mogłaby być opisana przez lokalne teorie ze zmiennymi ukrytymi, nierówności te byłyby zawsze spełnione. Jak zostaje zademonstrowane w tym samouczku, mogą one być naruszone na komputerze kwantowym, a zatem mechanika kwantowa nie jest zgodna z lokalnymi teoriami ze zmiennymi ukrytymi.

Tworzymy splątaną parę, przygotowując stan Bella Φ+=00+112|\Phi^+\rangle = \frac{|00\rangle + |11\rangle}{\sqrt{2}}. Używając prymitywu Estimator, bezpośrednio uzyskujemy wartości oczekiwane AB,Ab,aB\langle AB \rangle, \langle Ab \rangle, \langle aB \rangle i ab\langle ab \rangle, bez rekonstruowania ich z surowych zliczeń. Mierzymy drugi Qubit w bazach ZZ i XX. Pierwszy Qubit jest mierzony również w prostopadłych bazach, ale z kątem obrotu θ\theta, który przesuwamy między 00 a 2π2\pi. Prymityw Estimator wyznacza tę przestrzeń parametrów w jednym zunifikowanym bloku prymitywnym (PUB).

Wymagania

Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane następujące elementy:

  • Qiskit SDK v2.0 lub nowszy, z obsługą wizualizacji
  • Qiskit Runtime v0.40 lub nowszy (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 lub nowszy (pip install qiskit-aer)

Konfiguracja

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

Przykład na małą skalę z symulatorem

Przed wysłaniem zadania na sprzęt sprawdzamy cały przepływ pracy na lokalnym symulatorze z szumem. Używamy AerSimulator.from_backend(backend), aby zbudować symulator dziedziczący model szumu i mapę sprzężeń wybranego Backend, dzięki czemu odpowiedź symulatora jest jakościowo zbliżona do oczekiwanej od sprzętu.

Krok 1: Odwzoruj klasyczne dane wejściowe na problem kwantowy

Zapisujemy obwód CHSH z jednym parametrem θ\theta, który przesuwa bazę pomiarową pierwszego Qubitu. Prymityw Estimator upraszcza analizę: zwraca wartości oczekiwane obserwabli bezpośrednio i może wyznaczać sparametryzowany obwód dla wielu wartości parametrów w jednym wywołaniu.

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

Następnie tworzymy listę 21 wartości fazy od 00 do 2π2\pi, dla których wyznaczamy sparametryzowany obwód (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]

Na koniec definiujemy obserwable. Pierwszy Qubit jest mierzony wzdłuż osi obróconych o θ\theta; drugi Qubit jest mierzony w bazach ZZ i XX. Przy takich wyborach cztery korelatory CHSH odpowiadają operatorom Pauliego ZZZZ, ZXZX, XZXZ i 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)]
)

Krok 2: Zoptymalizuj problem pod kątem wykonania na sprzęcie kwantowym

Prymitywy V2 przyjmują wyłącznie obwody i obserwable zgodne z instrukcjami i połączeniami obsługiwanymi przez docelowy system (obwody i obserwable zgodne z architekturą zestawu instrukcji, ISA). Budujemy AerSimulator na podstawie Backend i transpilujemy względem celu symulatora, tak aby ten sam menedżer przejść był ćwiczony od początku do końca.

# 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

Przekształcamy też obserwable tak, aby pasowały do układu Qubitów w stransponowanym obwodzie, używając SparsePauliOp.apply_layout.

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

Krok 3: Wykonaj przy użyciu prymitywów Qiskit

Uruchamiamy przestrzeń parametrów z EstimatorV2 w trybie aer_sim. Metoda run() Estimatora przyjmuje iterowalny zbiór PUBów. Każdy PUB ma format (circuit, observables, parameter_values, precision). Przekazujemy obie obserwable razem, aby współdzieliły tę samą przestrzeń parametrów.

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

Krok 4: Przetwarzanie końcowe i zwrócenie wyniku w żądanym formacie klasycznym

Estimator zwraca wartości oczekiwane dla obu obserwabli. Wykreślamy je w zależności od θ\theta wraz z granicą klasyczną (±2\pm 2) i granicą Tsirelson (±22\pm 2\sqrt{2}). Zacienione szare obszary zaznaczają lukę między nimi. Punkty leżące wewnątrz tych pasm naruszają nierówność 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

Świadkowie CHSH symulatora już przekraczają klasyczną granicę ±2\pm 2 dla kilku wartości θ\theta, nawet uwzględniając model szumu Backend. Szczyty są nieco poniżej granicy Tsirelson ±22\pm 2\sqrt{2} z powodu symulowanego szumu urządzenia. Po sprawdzeniu przepływu pracy przechodzimy do rzeczywistego sprzętu.

Przykład na dużą skalę na sprzęcie

Test CHSH jest z natury eksperymentem dwuqubitowym, więc nie skaluje się przez powiększanie jednego obwodu. Zamiast tego skaluje się przez uruchamianie wielu testów równolegle. Tutaj kafelkujemy Backend tyloma rozłącznymi parami Bell, ile pozwala jego mapa sprzężeń (dopasowanie mapy sprzężeń), i uruchamiamy niezależny podukład CHSH na każdej parze — wszystko w jednym zadaniu.

Zamienia to CHSH w benchmark splątania na poziomie całego urządzenia: zamiast jednej ręcznie wybranej pary testujemy splątanie na dużej części chipa jednocześnie, w realistycznych warunkach, gdzie każda para zmaga się z przesłuchami sąsiadów i błędami równoległych bramek. Naruszenie nierówności na każdej parze jednocześnie certyfikuje, że prawdziwe splątanie jest dostępne wszędzie na urządzeniu.

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

Jaśniejsze krzywe reprezentują poszczególne pary Bell, a pogrubione krzywe ich średnią po całym urządzeniu. Każda para wyznacza tę samą sinusoidę przewidzianą przez mechanikę kwantową, a rozrzut między jaśniejszymi krzywymi odzwierciedla zmienność szumu od pary do pary. Wszędzie tam, gdzie krzywa wchodzi w szare pasma, przekroczyła klasyczną granicę ±2\pm 2, a wydrukowane podsumowanie potwierdza, że praktycznie każda para narusza nierówność CHSH jednocześnie.

Szczyty nie osiągają granicy Tsirelson ±22\pm 2\sqrt{2} z powodu szumu urządzenia, ale wniosek jest jednoznaczny: Backend utrzymuje prawdziwe splątanie na całym chipie jednocześnie, nie tylko na jednej ręcznie wybranej parze. Na tym polega „skalowanie" eksperymentu CHSH: nie jako jeden większy obwód, lecz jako równoległy benchmark certyfikujący splątanie wszędzie naraz.

Kolejne kroki

Zalecenia

Jeśli ten materiał był dla ciebie interesujący, możesz zapoznać się z następującymi zasobami: