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 () i w bazie (). 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 , a drugi w bazie . Na podstawie wyników uzyskujemy oszacowanie i 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 ) 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 , , i 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 ).
- 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 ) wynosi 1, stosujemy bramkę na Qubicie 3; jeśli pomiar Qubita 2 () wynosi 1, stosujemy bramkę na Qubicie 0. Te warunkowe bramki (realizowane przy użyciu konstrukcji
if_test/if_elsew 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 . - Pomiar stabilizatorów pary Bella: następnie dzielimy się na dwie wersje obwodu. W pierwszej wersji mierzymy stabilizator na Qubitach 0 i 3. W drugiej wersji mierzymy stabilizator na tych Qubitach.
Dla każdego cztero-Qubitowego układu początkowego powyższa funkcja zwraca dwa obwody (jeden dla stabilizatora , jeden dla ). 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)

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)

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 i . Gdyby Qubity 0 i 3 były idealnie splątane w stanie Bella , oczekiwalibyśmy, że obie te wartości wynoszą +1. Odchylenie kwantyfikujemy za pomocą MSE:
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 , 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)