Benchmarking dynamicznych układów z ciętymi parami Bella
Szacowane użycie zasobów: 22 sekundy na procesorze Heron r2 (UWAGA: To jest tylko szacunek. Twój czas wykonania może się różnić.)
Cele nauki
Po przejściu przez ten samouczek użytkownicy powinni rozumieć:
- Jak budować dynamiczne układy z pomiarami w trakcie wykonywania układu i klasycznym sprzężeniem zwrotnym w celu teleportacji splątania między odległymi qubitami;
- Jak obliczać i interpretować metrykę błędu służącą do kwantyfikacji wierności pary Bella w urządzeniu;
- Jak identyfikować, które pary qubitów najlepiej nadają się do operacji opartych na LOCC, korzystając z wyników benchmarkingu.
Wymagania wstępne
Zalecamy, aby użytkownicy zapoznali się z poniższymi tematami przed przejściem przez ten samouczek:
- Podstawowe koncepcje informatyki kwantowej, w tym stany Bella, splątanie i bramki kwantowe;
- Znajomość dynamicznych układów (pomiary w trakcie wykonywania układu i klasyczne sprzężenie zwrotne);
- Podstawowa znajomość Qiskit SDK i Qiskit Runtime oraz dostęp do konta IBM Quantum®.
Tło
Sprzęt kwantowy jest zazwyczaj ograniczony do lokalnych interakcji, ale wiele algorytmów wymaga splątywania odległych qubitów lub nawet qubitów na oddzielnych procesorach. Dynamiczne układy - czyli układy z pomiarami w trakcie wykonywania i sprzężeniem zwrotnym - zapewniają sposób na pokonanie tych ograniczeń poprzez wykorzystanie komunikacji klasycznej w czasie rzeczywistym do efektywnej implementacji nielokalnych 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. To stanowi podstawę schematów lokalnych operacji i komunikacji klasycznej (LOCC), gdzie konsumujemy splątane stany zasobów (pary Bella) i komunikujemy wyniki pomiarów klasycznie, aby połączyć odległe qubity.
Jednym z obiecujących zastosowań LOCC jest realizacja wirtualnych bramek CNOT dalekiego zasięgu przez teleportację, jak pokazano w samouczku dotyczącym splątania dalekiego zasięgu. Zamiast bezpośredniej bramki CNOT dalekiego zasięgu (której łączność sprzętowa może nie pozwalać), tworzymy pary Bella i wykonujemy implementację bramki opartą na teleportacji. Jednak wierność takich operacji zależy od charakterystyki sprzętu. Dekoherencja qubitów podczas niezbędnego opóźnienia (podczas oczekiwania na wyniki pomiarów) i latencja komunikacji klasycznej mogą degradować splątany stan. Ponadto błędy w pomiarach w trakcie wykonywania układu są trudniejsze do korekcji niż błędy w pomiarach końcowych, ponieważ propagują się do reszty układu poprzez bramki warunkowe.
W eksperymencie referencyjnym autorzy wprowadzają benchmark wierności pary Bella, aby 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 czterokubitowy 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 lokalnie w nierozdrobnionej parze Bella (parze Bella utworzonej bezpośrednio za pomocą Hadamarda i CNOT, bez teleportacji), a następnie procedura teleportacji konsumuje tę parę Bella, aby splątać qubity 0 i 3. Qubity 1 i 2 są mierzone podczas wykonywania układu i na podstawie tych wyników stosowane są korekcje Pauliego (X na qubit 3 i Z na qubit 0). Qubity 0 i 3 są następnie pozostawione w stanie Bella na końcu układu.
Aby skwantyfikować jakość tej końcowej splątanej pary, mierzymy jej stabilizatory: konkretnie parzystość w bazie () i w bazie (). Dla doskonałej pary Bella obie te wartości oczekiwane są równe +1. W praktyce szum sprzętowy zredukuje 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 mierzy je w bazie . Z wyników uzyskujemy oszacowanie i dla tej pary qubitów. Używamy błędu średniokwadratowego (MSE) tych stabilizatorów względem wartości idealnej (1) jako prostej metryki wierności splątania. Niższy MSE oznacza, że dwa qubity osiągnęły stan Bella bliższy idealnemu (wyższa wierność), natomiast wyższy MSE wskazuje na więcej błędów. Skanując ten eksperyment przez urządzenie, możemy dokonać benchmarkingu możliwości pomiaru i sprzężenia zwrotnego różnych grup qubitów oraz zidentyfikować najlepsze pary qubitów do operacji LOCC.
Ten samouczek demonstruje eksperyment na urządzeniu IBM Quantum®, aby zilustrować, jak dynamiczne układy mogą być używane do generowania i oceny splątania między odległymi qubitami. Zmapujemy wszystkie czterokubitowe łańcuchy liniowe w urządzeniu, uruchomimy układ teleportacji na każdym z nich, a następnie zwizualizujemy rozkład wartości MSE. Ta procedura od końca do końca pokazuje, jak wykorzystać Qiskit Runtime i funkcje dynamicznych układów do podejmowania świadomych sprzętowo decyzji dotyczących cięcia układów lub dystrybucji algorytmów kwantowych w modularnym systemie.
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) - 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
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
), "The length of the chain must be a multiple of 4, "
f"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:, "
f"{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()
Przykład symulacji w małej skali
Przed uruchomieniem na prawdziwym QPU sprawdzamy, czy układ wytwarza parę Bella, testując go na bezszumowym symulatorze z czterokubitowym łańcuchem [0, 1, 2, 3]. Używamy Qiskit Runtime Sampler z AerSimulator jako trybem backendu do wykonania układów.
Krok 1: Mapowanie klasycznych wejść na problem kwantowy
Pierwszym krokiem jest stworzenie zestawu układów kwantowych do benchmarkingu wszystkich kandydujących połączeń par Bella dostosowanych do topologii urządzenia. Zaczynamy od budowania takich układów z czterokubitowym łańcuchem [0, 1, 2, 3].
Procedura create_bell_stab() wykonuje dla każdego łańcucha:
- Przygotowanie środkowej pary Bella: Zastosuj Hadamard na qubit 1 i CNOT z qubita 1 do qubita 2. To splątuje qubity 1 i 2 (tworząc stan Bella ).
- Splątanie skrajnych qubitów: Zastosuj CNOT z qubita 0 do qubita 1 i CNOT z qubita 2 do qubita 3. Łączy to początkowo oddzielne pary, tak że qubity 0 i 3 staną się splątane po kolejnych krokach. Hadamard na qubit 2 jest również stosowany (to, w połączeniu z poprzednimi 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ą z nimi splątane w większym czterokubitowym stanie.
- Pomiary w trakcie wykonywania układu i sprzężenie zwrotne: Qubity 1 i 2 (środkowe qubity) są mierzone w bazie obliczeniowej, dając dwa klasyczne bity. Na podstawie tych wyników pomiarów stosujemy operacje warunkowe: jeśli pomiar qubitu 1 (nazwijmy ten bit ) wynosi 1, stosujemy bramkę na qubit 3; jeśli pomiar qubitu 2 () wynosi 1, stosujemy bramkę na qubit 0. Te bramki warunkowe (realizowane za pomocą konstrukcji Qiskit
if_test/if_else) implementują standardowe korekcje teleportacji. "Cofają" losowe obroty Pauliego, które występują w wyniku projekcji qubitów 1 i 2, zapewniając, że qubity 0 i 3 kończą w znanym stanie 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 układu. W pierwszej wersji mierzymy stabilizator na qubitach 0 i 3. W drugiej wersji mierzymy stabilizator na tych qubitach.
Dla każdego czterokubitowego początkowego układu powyższa funkcja zwraca dwa układy (jeden dla , jeden dla pomiaru stabilizatora ). Układy te zawierają pomiary w trakcie wykonywania i operacje warunkowe (if/else), które są kluczowymi instrukcjami dynamicznego układu.
from qiskit_aer import AerSimulator
# 4-qubit chain for simulation
sim_layout = [[0, 1, 2, 3]]
aer_backend = AerSimulator()
sim_circuits = create_bell_stab(sim_layout)
sim_circuits[1].draw("mpl", fold=-1, idle_wires=False)
Krok 2: Optymalizacja problemu pod kątem wykonania na sprzęcie kwantowym
Przed wykonaniem naszych układów musimy je transpilować do operacji bramkowych obsługiwanych na wskazanym backendzie. Transpilacja zmapuje abstrakcyjny układ na fizyczne qubity i zestaw bramek wybranego backendu. Ponieważ wybraliśmy już konkretne fizyczne qubity dla każdego łańcucha (podając initial_layout do generatora układu), używamy optimization_level=0 transpilatora z tym ustalonym układem. Mówi to Qiskit, aby nie przypisywał ponownie qubitów ani nie wykonywał żadnych ciężkich optymalizacji, które mogłyby zmienić strukturę układu. Chcemy zachować sekwencję operacji (zwłaszcza bramek warunkowych) dokładnie tak, jak określono.
pm_sim = generate_preset_pass_manager(
optimization_level=0, backend=aer_backend, initial_layout=sim_layout[0]
)
isa_sim_circuits = pm_sim.run(sim_circuits)
isa_sim_circuits[1].draw("mpl", fold=-1, idle_wires=False)
Krok 3: Wykonanie z użyciem prymitywów Qiskit
Możemy teraz uruchomić eksperyment na bezszumowym backendzie symulatora.
# Run on noiseless simulator
sampler_sim = Sampler(mode=aer_backend)
sim_job = sampler_sim.run(isa_sim_circuits)
sim_mse = get_mse(sim_job.result(), sim_layout)
layout [0, 1, 2, 3]
qubits: [0, 1, 2, 3], mse:, 0.0
Krok 4: Przetwarzanie końcowe i zwrócenie wyniku w pożądanym klasycznym formacie
Ostatnim krokiem jest obliczenie metryki błędu średniokwadratowego (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 doskonale splątane w stanie Bella , oczekiwalibyśmy, że obie wartości wyniosą +1. Kwantyfikujemy odchylenie za pomocą MSE:
Ta wartość wynosi 0 dla doskonałej pary Bella i rośnie w miarę 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 czterokubitowej grupy. Bez szumu widzimy MSE = 0 w tym przykładzie symulacji w małej skali, zgodnie z oczekiwaniami.
Przykład sprzętu w dużej skali
Tutaj łączymy wszystkie te szczegóły w jeden przepływ pracy w większej skali, który jest następnie uruchamiany na prawdziwym sprzęcie kwantowym.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True)
Programowo przeszukujemy mapę sprzężenia urządzenia w poszukiwaniu wszystkich liniowo połączonych łańcuchów czterech qubitów. Każdy taki łańcuch (oznaczony indeksami qubitów ) służy jako przypadek testowy dla układu zamiany splątania. Identyfikując wszystkie możliwe ścieżki o długości 4, zapewniamy maksymalne pokrycie możliwych grupowań qubitów, które mogłyby realizować protokół.
Generujemy te łańcuchy, używając funkcji pomocniczej, która wykonuje zachłanne przeszukiwanie grafu urządzenia. Zwraca "paski" czterech czterokubitowych łańcuchów pogrupowanych w 16-kubitowe grupy. Grupowanie pozwala nam uruchamiać wiele czterokubitowych eksperymentów równolegle na odrębnych częściach chipa i efektywnie korzystać z całego urządzenia. Każdy 16-kubitowy pasek zawiera cztery rozłączne łańcuchy, co oznacza, że żaden qubit nie jest reużywany w tej grupie. Na przykład jeden pasek może składać się z łańcuchów , , i wszystkich spakowanych razem. Każdy qubit, który nie został uwzględniony w pasku, jest zwracany 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 układ dla każdego 16-kubitowego paska za pomocą funkcji create_bell_stab(). Na końcu tego kroku mamy listę układów obejmujących każdy czterokubitowy łańcuch w urządzeniu. Następnie transpilujemy i uruchamiamy układy na prawdziwym backendzie i przetwarzamy wyniki.
# -------------------------Step 1-------------------------
circuits = create_bell_stab(initial_layouts)
# -------------------------Step 2-------------------------
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)
# -------------------------Step 3-------------------------
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT_BDC"]
job = sampler.run(isa_circuits)
# -------------------------Step 4-------------------------
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.6302
qubits: [4, 5, 6, 7], mse:, 0.0949
qubits: [8, 9, 10, 11], mse:, 0.1729
qubits: [12, 13, 14, 15], mse:, 0.0473
layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]
qubits: [16, 23, 22, 21], mse:, 0.0533
qubits: [17, 27, 26, 25], mse:, 0.2966
qubits: [18, 31, 30, 29], mse:, 0.0447
qubits: [19, 35, 34, 33], mse:, 0.0392
layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]
qubits: [36, 41, 42, 43], mse:, 0.1577
qubits: [37, 45, 46, 47], mse:, 0.0705
qubits: [38, 49, 50, 51], mse:, 0.2914
qubits: [39, 53, 54, 55], mse:, 0.1711
layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]
qubits: [56, 63, 62, 61], mse:, 0.1236
qubits: [57, 67, 66, 65], mse:, 0.9969
qubits: [58, 71, 70, 69], mse:, 0.0631
qubits: [59, 75, 74, 73], mse:, 0.0301
layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]
qubits: [76, 81, 82, 83], mse:, 0.2787
qubits: [77, 85, 86, 87], mse:, 0.0497
qubits: [78, 89, 90, 91], mse:, 0.1271
qubits: [79, 93, 94, 95], mse:, 0.0468
layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]
qubits: [96, 103, 102, 101], mse:, 0.8657
qubits: [97, 107, 106, 105], mse:, 0.0399
qubits: [98, 111, 110, 109], mse:, 0.0667
qubits: [99, 115, 114, 113], mse:, 0.2444
layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]
qubits: [116, 121, 122, 123], mse:, 0.0429
qubits: [117, 125, 126, 127], mse:, 0.0487
qubits: [118, 129, 130, 131], mse:, 0.0823
qubits: [119, 133, 134, 135], mse:, 0.0583
layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]
qubits: [136, 143, 142, 141], mse:, 0.0209
qubits: [137, 147, 146, 145], mse:, 0.0384
qubits: [138, 151, 150, 149], mse:, 0.4941
qubits: [139, 155, 154, 153], mse:, 0.1062
Wyniki ujawniają szeroki zakres jakości splątania w urządzeniu. Potwierdza to odkrycie z artykułu, że może istnieć ponad rząd wielkości różnicy w wierności stanu Bella w zależności od tego, które fizyczne qubity są używane. W praktyce oznacza to, że pewne regiony lub połączenia w chipie są znacznie lepsze w wykonywaniu operacji pomiaru w trakcie wykonywania układu i sprzężenia zwrotnego niż inne. Czynniki takie jak błąd odczytu qubitów, czas życia qubitów i przesłuchy prawdopodobnie przyczyniają się do tych różnic. Na przykład, jeśli jeden łańcuch zawiera szczególnie zaszumiony qubit odczytu, pomiar w trakcie wykonywania układu może być zawodny, prowadząc do słabej wierności dla tej splątanej pary (wysoki MSE). 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 i frakcję par qubitów, które mają co najwyżej taki MSE, na osi y. Krzywa ta zaczyna się od zera i zbliża się do jedności, gdy próg rośnie, obejmując wszystkie punkty danych. Stromy wzrost przy niskim MSE wskazywałby, że wiele par ma wysoką wierność; powolny wzrost oznacza, że wiele par ma większe błędy. Adnotujemy CDF tożsamościami najlepszych par. Na wykresie każdy punkt w CDF odpowiada MSE jednego czterokubitowego łańcucha, a punkt jest oznaczony parą indeksów qubitów , które były splątane w tym eksperymencie. Ułatwia to identyfikację, które fizyczne pary qubitów są najlepszymi wykonawcami (skrajne lewe punkty CDF).
plot_mse_ecdfs(layouts_mse, combine_layouts=True)
Następne kroki
Jeśli ta praca wydała ci się interesująca, może zainteresować cię następujące materiały:
- Dowiedz się, jak zaimplementować splątanie dalekiego zasięgu z dynamicznymi układami
- Dowiedz się, jak symulować hamiltonowski Ising z kopnięciem z dynamicznymi układami