Przejdź do głównej treści

Testowanie dynamicznych układów scalonych z ciętymi parami Bella

Szacowane zużycie zasobów: 22 sekundy na procesorze Heron r2 (UWAGA: Jest to jedynie szacunek. Rzeczywisty czas wykonania może się różnić.)

Tło

Sprzęt kwantowy jest zazwyczaj ograniczony do lokalnych interakcji, ale wiele algorytmów wymaga splątania odległych Qubitów, a nawet Qubitów na oddzielnych procesorach. Dynamiczne układy — czyli układy z pomiarami w trakcie obwodu i feedforwardem — umożliwiają przezwyciężenie tych ograniczeń poprzez wykorzystanie klasycznej komunikacji w czasie rzeczywistym do efektywnej realizacji nielokanych operacji kwantowych. W tym podejściu wyniki pomiarów z jednej części układu (lub jednego QPU) mogą warunkowo wyzwalać bramki w innej, pozwalając nam teleportować splątanie na duże odległości. Stanowi to podstawę schematów lokalnych operacji i klasycznej komunikacji (LOCC), w których zużywamy splątane stany zasobów (pary Bella) i przekazujemy wyniki pomiarów klasycznie, aby połączyć odległe Qubity.

Jednym z obiecujących zastosowań LOCC jest realizacja wirtualnych bramek CNOT dalekiego zasięgu poprzez teleportację, jak pokazano w samouczku o splątaniu dalekiego zasięgu. Zamiast bezpośredniej bramki CNOT dalekiego zasięgu (której sprzęt może nie obsługiwać ze względu na ograniczenia połączeń), tworzymy pary Bella i implementujemy bramkę opartą na teleportacji. Jednak wierność takich operacji zależy od charakterystyki sprzętu. Dekoherencja Qubitów podczas niezbędnego opóźnienia (w oczekiwaniu na wyniki pomiarów) oraz opóźnienie w klasycznej komunikacji mogą degradować stan splątany. Ponadto błędy w pomiarach w trakcie obwodu są trudniejsze do skorygowania niż błędy w pomiarach końcowych, ponieważ propagują się do pozostałej części układu poprzez bramki warunkowe.

W eksperymencie referencyjnym autorzy wprowadzają test porównawczy wierności par Bella, który pozwala zidentyfikować, które części urządzenia najlepiej nadają się do splątania opartego na LOCC. Idea polega na uruchomieniu małego dynamicznego układu na każdej grupie czterech połączonych Qubitów w procesorze. Ten cztero-qubitowy układ najpierw tworzy parę Bella na dwóch środkowych Qubitach, a następnie używa ich jako zasobu do splątania dwóch skrajnych Qubitów za pomocą LOCC. Konkretnie, Qubity 1 i 2 są przygotowywane w nieciętą parę Bella lokalnie (za pomocą Hadamarda i CNOT), a następnie procedura teleportacji zużywa tę parę Bella do splątania Qubitów 0 i 3. Qubity 1 i 2 są mierzone podczas wykonywania układu, a na podstawie tych wyników stosowane są korekcje Pauliego (X na Qubit 3 i Z na Qubit 0). Qubity 0 i 3 pozostają następnie w stanie Bella na końcu układu.

Aby ocenić jakość tej końcowej pary splątanej, mierzymy jej stabilizatory: konkretnie parzystość w bazie ZZ (Z0Z3Z_0Z_3) i w bazie XX (X0X3X_0X_3). Dla idealnej pary Bella obie te wartości oczekiwane są równe +1. W praktyce szum sprzętowy zmniejszy te wartości. Dlatego powtarzamy układ dwukrotnie dla każdej pary Qubitów: jeden układ mierzy Qubity 0 i 3 w bazie ZZ, a drugi w bazie XX. Na podstawie wyników uzyskujemy oszacowanie Z0Z3\langle Z_0Z_3\rangle i X0X3\langle X_0X_3\rangle dla danej pary Qubitów. Używamy średniego błędu kwadratowego (MSE) tych stabilizatorów względem wartości idealnej (1) jako prostego wskaźnika wierności splątania. Niższy MSE oznacza, że dwa Qubity osiągnęły stan Bella bliższy idealnemu (wyższa wierność), podczas gdy wyższy MSE wskazuje na większy błąd. Skanując ten eksperyment po całym urządzeniu, możemy testować porównawczo możliwości pomiaru i feedforwarda różnych grup Qubitów oraz identyfikować najlepsze pary Qubitów do operacji LOCC.

Ten samouczek demonstruje eksperyment na urządzeniu IBM Quantum®, aby zilustrować, jak dynamiczne układy mogą być wykorzystywane do generowania i oceny splątania między odległymi Qubitami. Zmapujemy wszystkie czwórki liniowych łańcuchów na urządzeniu, uruchomimy układ teleportacji na każdym z nich, a następnie zwizualizujemy rozkład wartości MSE. Ta procedura end-to-end pokazuje, jak wykorzystać Qiskit Runtime i funkcje dynamicznych układów, aby podejmować świadome sprzętowo decyzje dotyczące cięcia układów lub dystrybucji algorytmów kwantowych w systemie modularnym.

Wymagania

Przed rozpoczęciem tego samouczka upewnij się, że masz zainstalowane:

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

Konfiguracja

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
from qiskit import QuantumCircuit

from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler import generate_preset_pass_manager

import numpy as np
import matplotlib.pyplot as plt

def create_bell_stab(initial_layouts):
"""
Create a circuit for a 1D chain of qubits (number of qubits must be a multiple of 4),
where a middle Bell pair is consumed to create a Bell at the edge.
Takes as input a list of lists, where each element of the list is a
1D chain of physical qubits that is used as the initial_layout for the transpiled circuit.
Returns a list of length-2 tuples, each tuple contains a circuit to measure the ZZ stabilizer and
a circuit to measure the XX stabilizer of the edge Bell state.
"""
bell_circuits = []
for (
initial_layout
) in initial_layouts: # Iterate over chains of physical qubits
assert (
len(initial_layout) % 4 == 0
), f"The length of the chain must be a multiple of 4, len(inital_layout)={len(initial_layout)}"
num_pairs = len(initial_layout) // 4

bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)

for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(c0, c1) = pair_idx * 4, pair_idx * 4 + 3 # edge qubits
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits

bell_parallel.h(q0)
bell_parallel.h(q1)
bell_parallel.cx(q1, q2)
bell_parallel.cx(q0, q1)
bell_parallel.cx(q2, q3)
bell_parallel.h(q2)

# add barrier BEFORE measurements and add id in conditional
bell_parallel.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits

bell_parallel.measure(q1, ca0)
bell_parallel.measure(q2, ca1)
# bell_parallel.barrier() #remove barrier after measurement

for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
with bell_parallel.if_test((ca0, 1)):
bell_parallel.x(q3)
with bell_parallel.if_test((ca1, 1)):
bell_parallel.z(q0)
bell_parallel.id(q0) # add id here for correct alignment

bell_zz = bell_parallel.copy()
bell_zz.barrier()
bell_xx = bell_parallel.copy()
bell_xx.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
bell_xx.h(q0)
bell_xx.h(q3)
bell_xx.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(c0, c1) = pair_idx * 4, pair_idx * 4 + 3 # edge qubits

bell_zz.measure(q0, c0)
bell_zz.measure(q3, c1)

bell_xx.measure(q0, c0)
bell_xx.measure(q3, c1)

bell_circuits.append(bell_zz)
bell_circuits.append(bell_xx)

return bell_circuits

def get_mse(result, initial_layouts):
"""
given a result object and the initial layouts, returns a dict of layouts and their mse
"""
layout_mse = {}
for layout_idx, initial_layout in enumerate(initial_layouts):
layout_mse[tuple(initial_layout)] = {}

num_pairs = len(initial_layout) // 4

counts_zz = result[2 * layout_idx].data.c.get_counts()
total_shots = sum(counts_zz.values())

# Get ZZ expectation value
exp_zz_list = []
for pair_idx in range(num_pairs):
exp_zz = 0
for bitstr, shots in counts_zz.items():
bitstr = bitstr[::-1] # reverse order to big endian
b1, b0 = (
bitstr[pair_idx * 4],
bitstr[pair_idx * 4 + 3],
) # parse bitstring to get edge measurements for each 4-q chain
z_val0 = 1 if b0 == "0" else -1
z_val1 = 1 if b1 == "0" else -1
exp_zz += z_val0 * z_val1 * shots
exp_zz /= total_shots
exp_zz_list.append(exp_zz)

counts_xx = result[2 * layout_idx + 1].data.c.get_counts()
total_shots = sum(counts_xx.values())

# Get XX expectation value
exp_xx_list = []
for pair_idx in range(num_pairs):
exp_xx = 0
for bitstr, shots in counts_xx.items():
bitstr = bitstr[::-1] # reverse order to big endian
b1, b0 = (
bitstr[pair_idx * 4],
bitstr[pair_idx * 4 + 3],
) # parse bitstring to get edge measurements for each 4-q chain
x_val0 = 1 if b0 == "0" else -1
x_val1 = 1 if b1 == "0" else -1
exp_xx += x_val0 * x_val1 * shots
exp_xx /= total_shots
exp_xx_list.append(exp_xx)

mse_list = [
((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2
for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)
]

print(f"layout {initial_layout}")
for idx in range(num_pairs):
layout_mse[tuple(initial_layout)][
tuple(initial_layout[4 * idx : 4 * idx + 4])
] = mse_list[idx]
print(
f"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, {round(mse_list[idx],4)}"
)
# print(f'exp_zz: {round(exp_zz_list[idx],4)}, exp_xx: {round(exp_xx_list[idx],4)}')
print(" ")
return layout_mse

def plot_mse_ecdfs(layouts_mse, combine_layouts=False):
"""
Plot CDF of MSE data for multiple layouts. Optionally combine all data in a single CDF
"""

if not combine_layouts:
for initial_layout, layouts in layouts_mse.items():
sorted_layouts = dict(
sorted(layouts.items(), key=lambda item: item[1])
) # sort layouts by mse

# get layouts and mses
layout_list = list(sorted_layouts.keys())
mse_list = np.asarray(list(sorted_layouts.values()))

# convert to numpy
x = np.array(mse_list)
y = np.arange(1, len(x) + 1) / len(x)

# Prepend (x[0], 0) to start CDF at zero
x = np.insert(x, 0, x[0])
y = np.insert(y, 0, 0)

# Create the plot
plt.plot(
x,
y,
marker="x",
linestyle="-",
label=f"qubits: {initial_layout}",
)

# add qubits labels for the edge pairs
for xi, yi, q in zip(x[1:], y[1:], layout_list):
plt.annotate(
[q[0], q[3]],
(xi, yi),
textcoords="offset points",
xytext=(5, -10),
ha="left",
fontsize=8,
)

elif combine_layouts:
all_layouts = {}
all_initial_layout = []
for (
initial_layout,
layouts,
) in layouts_mse.items(): # puts together all layout information
all_layouts.update(layouts)
all_initial_layout += initial_layout

sorted_layouts = dict(
sorted(all_layouts.items(), key=lambda item: item[1])
) # sort layouts by mse

# get layouts and mses
layout_list = list(sorted_layouts.keys())
mse_list = np.asarray(list(sorted_layouts.values()))

# convert to numpy
x = np.array(mse_list)
y = np.arange(1, len(x) + 1) / len(x)

# Prepend (x[0], 0) to start CDF at zero
x = np.insert(x, 0, x[0])
y = np.insert(y, 0, 0)

# Create the plot
plt.plot(
x,
y,
marker="x",
linestyle="-",
label=f"qubits: {sorted(list(set(all_initial_layout)))}",
)

# add qubit labels for the edge pairs
for xi, yi, q in zip(x[1:], y[1:], layout_list):
plt.annotate(
[q[0], q[3]],
(xi, yi),
textcoords="offset points",
xytext=(5, -10),
ha="left",
fontsize=8,
)

plt.xscale("log")
plt.xlabel("Mean squared error of ⟨ZZ⟩ and ⟨XX⟩")
plt.ylabel("Cumulative distribution function")
plt.title("CDF for different initial layouts")
plt.grid(alpha=0.3)
plt.show()

Krok 1: Odwzorowanie klasycznych danych wejściowych na problem kwantowy

Pierwszym krokiem jest stworzenie zestawu obwodów kwantowych służących do benchmarkingu wszystkich kandydujących połączeń par Bella, dostosowanych do topologii urządzenia. Programowo przeszukujemy mapę sprzężeń urządzenia w celu znalezienia wszystkich liniowo połączonych łańcuchów czterech Qubitów. Każdy taki łańcuch (oznaczony indeksami Qubitów [q0q1q2q3][q0-q1-q2-q3]) stanowi przypadek testowy dla obwodu zamiany splątania. Identyfikując wszystkie możliwe ścieżki długości 4, zapewniamy maksymalne pokrycie możliwych grup Qubitów mogących realizować protokół.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True)

Generujemy te łańcuchy za pomocą funkcji pomocniczej wykonującej zachłanne przeszukiwanie grafu urządzenia. Zwraca ona „pasy" czterech cztero-Qubitowych łańcuchów zebranych w 16-Qubitowe grupy (dynamiczne obwody aktualnie ograniczają rozmiar rejestru pomiarowego do 16 Qubitów). Grupowanie pozwala nam uruchamiać wiele cztero-Qubitowych eksperymentów równolegle na różnych częściach układu i efektywnie wykorzystywać całe urządzenie. Każdy 16-Qubitowy pas zawiera cztery rozłączne łańcuchy, co oznacza, że żaden Qubit nie jest ponownie używany w obrębie tej grupy. Na przykład jeden pas może składać się z łańcuchów [0123][0-1-2-3], [4567][4-5-6-7], [891011][8-9-10-11] i [12131415][12-13-14-15] zebranych razem. Wszystkie Qubity, które nie zostały włączone do pasa, są zwracane w zmiennej leftover.

from itertools import chain
from collections import defaultdict

def stripes16_from_backend(backend):
"""
Creates stripes of 16 qubits, four non-overlapping four-qubit chains, that cover as much of
the coupling map as possible. Returns any unused qubits as leftovers.
"""
# get the undirected adjacency list
edges = backend.coupling_map.get_edges()
graph = defaultdict(set)
for u, v in edges:
graph[u].add(v)
graph[v].add(u)

qubits = sorted(graph) # all qubit indices that appear

# greedy search for 4-long linear chains (blocks) ────────────
used = set() # qubits already placed in a block
blocks = [] # each block is a four-qubit list

for q in qubits: # deterministic order for reproducibility
if q in used:
continue # already consumed by earlier block

# depth-first "straight" walk of length 3 without revisiting nodes
def extend(path):
if len(path) == 4:
return path
tip = path[-1]
for nbr in sorted(graph[tip]): # deterministic
if nbr not in path and nbr not in used:
maybe = extend(path + [nbr])
if maybe:
return maybe
return None

block = extend([q])
if block: # found a 4-node path
blocks.append(block)
used.update(block)

# bundle four four-qubit blocks into one 16-qubit stripe (max number of measurement compatible with if-else)
stripes = [
list(chain.from_iterable(blocks[i : i + 4]))
for i in range(0, len(blocks) // 4 * 4, 4) # full groups of four
]

leftovers = set(qubits) - set(chain.from_iterable(stripes))
return stripes, leftovers
initial_layouts, leftover = stripes16_from_backend(backend)

Następnie konstruujemy obwód dla każdego 16-Qubitowego pasa. Procedura wykonuje dla każdego łańcucha następujące kroki:

  • Przygotowanie środkowej pary Bella: zastosowanie bramki Hadamarda na Qubicie 1 i bramki CNOT z Qubita 1 na Qubit 2. Splątuje to Qubity 1 i 2 (tworząc stan Bella Φ+=(00+11)/2|\Phi^+\rangle = (|00\rangle + |11\rangle)/\sqrt{2}).
  • Splątanie skrajnych Qubitów: zastosowanie bramki CNOT z Qubita 0 na Qubit 1 oraz bramki CNOT z Qubita 2 na Qubit 3. Łączy to początkowo oddzielne pary tak, że Qubity 0 i 3 zostaną splątane po kolejnych krokach. Stosowana jest również bramka Hadamarda na Qubicie 2 (to, w połączeniu z poprzednimi bramkami CNOT, tworzy część pomiaru Bella na Qubitach 1 i 2). W tym momencie Qubity 0 i 3 nie są jeszcze splątane, ale Qubity 1 i 2 są splątane z nimi w większym cztero-Qubitowym stanie.
  • Pomiary śródukładowe i feedforward: Qubity 1 i 2 (środkowe Qubity) są mierzone w bazie obliczeniowej, dając dwa klasyczne bity. Na podstawie wyników pomiarów stosujemy operacje warunkowe: jeśli pomiar Qubita 1 (nazwijmy ten bit m12m_{12}) wynosi 1, stosujemy bramkę XX na Qubicie 3; jeśli pomiar Qubita 2 (m21m_{21}) wynosi 1, stosujemy bramkę ZZ na Qubicie 0. Te warunkowe bramki (realizowane przy użyciu konstrukcji if_test/if_else w Qiskit) implementują standardowe korekcje teleportacji. „Cofają" one losowe błędy Pauliego powstałe w wyniku rzutowania Qubitów 1 i 2, zapewniając, że Qubity 0 i 3 kończą w znanych stanach Bella, niezależnie od wyników pomiarów. Po tym kroku Qubity 0 i 3 powinny być idealnie splątane w stanie Bella Φ+|\Phi^+\rangle.
  • Pomiar stabilizatorów pary Bella: następnie dzielimy się na dwie wersje obwodu. W pierwszej wersji mierzymy stabilizator ZZZZ na Qubitach 0 i 3. W drugiej wersji mierzymy stabilizator XXXX na tych Qubitach.

Dla każdego cztero-Qubitowego układu początkowego powyższa funkcja zwraca dwa obwody (jeden dla stabilizatora ZZZZ, jeden dla XXXX). Na końcu tego kroku mamy listę obwodów pokrywających każdy cztero-Qubitowy łańcuch na urządzeniu. Obwody te zawierają pomiary śródukładowe i operacje warunkowe (if/else), które są kluczowymi instrukcjami obwodów dynamicznych.

circuits = create_bell_stab(initial_layouts)
circuits[-1].draw("mpl", fold=-1)

Output of the previous code cell

Krok 2: Optymalizacja problemu pod kątem wykonania na sprzęcie kwantowym

Przed uruchomieniem naszych obwodów na rzeczywistym sprzęcie musimy je transpilować, aby dopasować je do fizycznych ograniczeń urządzenia. Transpilacja odwzoruje abstrakcyjny obwód na fizyczne Qubity i zestaw bramek wybranego urządzenia. Ponieważ wybraliśmy już konkretne fizyczne Qubity dla każdego łańcucha (podając initial_layout do generatora obwodów), używamy optimization_level=0 Transpilera z tym ustalonym układem. Oznacza to, że Qiskit nie będzie przypisywał Qubitów ponownie ani wykonywał ciężkich optymalizacji mogących zmienić strukturę obwodu. Chcemy zachować sekwencję operacji (zwłaszcza bramki warunkowe) dokładnie tak jak ją określiliśmy.

isa_circuits = []
for ind, init_layout in enumerate(initial_layouts):
pm = generate_preset_pass_manager(
optimization_level=0, backend=backend, initial_layout=init_layout
)
isa_circ = pm.run(circuits[ind * 2 : ind * 2 + 2])
isa_circuits.extend(isa_circ)
isa_circuits[1].draw("mpl", fold=-1, idle_wires=False)

Output of the previous code cell

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

Możemy teraz uruchomić eksperyment na urządzeniu kwantowym. Używamy Qiskit Runtime i jego prymitywu Sampler do efektywnego wykonania partii obwodów.

sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["cut-bell-pair-test"]
job = sampler.run(isa_circuits)

Krok 4: Post-przetwarzanie i zwrócenie wyniku w pożądanym formacie klasycznym

Ostatnim krokiem jest obliczenie metryki średniokwadratowego błędu (MSE) dla każdej testowanej grupy Qubitów i podsumowanie wyników. Dla każdego łańcucha mamy teraz zmierzone Z0Z3\langle Z_0Z_3\rangle i X0X3\langle X_0X_3\rangle. Gdyby Qubity 0 i 3 były idealnie splątane w stanie Bella Φ+|\Phi^+\rangle, oczekiwalibyśmy, że obie te wartości wynoszą +1. Odchylenie kwantyfikujemy za pomocą MSE:

MSE=(Z0Z31)2+(X0X31)22.\text{MSE} = \frac{( \langle Z_0Z_3\rangle - 1)^2 + (\langle X_0X_3\rangle - 1)^2}{2}.

Wartość ta wynosi 0 dla idealnej pary Bella i rośnie wraz z tym, jak splątany stan staje się bardziej zaszumiony (przy losowych wynikach dających wartość oczekiwaną bliską 0, MSE zbliżałoby się do 1). Kod oblicza to MSE dla każdej cztero-Qubitowej grupy.

Wyniki ujawniają szeroki zakres jakości splątania w całym urządzeniu. Potwierdza to odkrycie z artykułu, że może istnieć różnica ponad rzędu wielkości w wierności stanu Bella w zależności od tego, które fizyczne Qubity są używane. W praktycznych kategoriach oznacza to, że pewne obszary lub połączenia na chipie są znacznie lepiej przystosowane do operacji splątania opartych na LOCC niż inne. Do tych różnic prawdopodobnie przyczyniają się takie czynniki jak błąd odczytu Qubita, czas życia Qubita i przesłuch. Na przykład jeśli jeden łańcuch zawiera szczególnie zaszumiony Qubit odczytu, pomiar śródukładowy może być zawodny, prowadząc do niskiej wierności tej pary splątanej (wysokie MSE).

layouts_mse = get_mse(job.result(), initial_layouts)
layout [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
qubits: [0, 1, 2, 3], mse:, 0.0312
qubits: [4, 5, 6, 7], mse:, 0.0491
qubits: [8, 9, 10, 11], mse:, 0.0711
qubits: [12, 13, 14, 15], mse:, 0.0436

layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]
qubits: [16, 23, 22, 21], mse:, 0.0197
qubits: [17, 27, 26, 25], mse:, 0.113
qubits: [18, 31, 30, 29], mse:, 0.0287
qubits: [19, 35, 34, 33], mse:, 0.0433

layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]
qubits: [36, 41, 42, 43], mse:, 0.1645
qubits: [37, 45, 46, 47], mse:, 0.0409
qubits: [38, 49, 50, 51], mse:, 0.0519
qubits: [39, 53, 54, 55], mse:, 0.0829

layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]
qubits: [56, 63, 62, 61], mse:, 0.8663
qubits: [57, 67, 66, 65], mse:, 0.0375
qubits: [58, 71, 70, 69], mse:, 0.0664
qubits: [59, 75, 74, 73], mse:, 0.0291

layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]
qubits: [76, 81, 82, 83], mse:, 0.0598
qubits: [77, 85, 86, 87], mse:, 0.313
qubits: [78, 89, 90, 91], mse:, 0.0679
qubits: [79, 93, 94, 95], mse:, 0.0505

layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]
qubits: [96, 103, 102, 101], mse:, 0.0302
qubits: [97, 107, 106, 105], mse:, 0.0384
qubits: [98, 111, 110, 109], mse:, 0.0375
qubits: [99, 115, 114, 113], mse:, 0.1051

layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]
qubits: [116, 121, 122, 123], mse:, 0.1624
qubits: [117, 125, 126, 127], mse:, 0.7246
qubits: [118, 129, 130, 131], mse:, 0.5919
qubits: [119, 133, 134, 135], mse:, 0.5277

layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]
qubits: [136, 143, 142, 141], mse:, 0.0383
qubits: [137, 147, 146, 145], mse:, 1.0187
qubits: [138, 151, 150, 149], mse:, 0.1531
qubits: [139, 155, 154, 153], mse:, 0.0471

Na koniec wizualizujemy ogólną wydajność, kreśląc dystrybuantę (CDF) wartości MSE dla wszystkich łańcuchów. Wykres CDF pokazuje próg MSE na osi x oraz ułamek par Qubitów mających co najwyżej to MSE na osi y. Krzywa ta zaczyna się od zera i zbliża się do jedności, gdy próg rośnie tak, aby objął wszystkie punkty danych. Strome wzniesienie przy niskim MSE oznaczałoby, że wiele par jest wysokiej wierności; powolne wzniesienie oznacza, że wiele par ma większe błędy. Adnotujemy CDF tożsamościami najlepszych par. Na wykresie każdy punkt na CDF odpowiada MSE jednego cztero-Qubitowego łańcucha, a punkt oznaczamy parą indeksów Qubitów [q0,q3][q0, q3], które zostały splątane w tym eksperymencie. Pozwala to łatwo zidentyfikować, które fizyczne pary Qubitów są najlepszymi wykonawcami (punkty skrajnie lewe na CDF).

plot_mse_ecdfs(layouts_mse, combine_layouts=True)

Output of the previous code cell

Referencje

[1] Carrera Vazquez, A., Tornow, C., Ristè, D. et al. Combining quantum processors with real-time classical communication. Nature 636, 75-79 (2024).