Перейти до основного вмісту

Бенчмаркінг динамічних схем із розрізаними парами Белла

Приблизна оцінка використання: 22 секунди на процесорі Heron r2 (ПРИМІТКА: Це лише оцінка. Ваш час виконання може відрізнятися.)

Передумови

Квантове обладнання зазвичай обмежене локальними взаємодіями, але багато алгоритмів потребують заплутування віддалених кубітів або навіть кубітів на окремих процесорах. Динамічні схеми — тобто схеми з вимірюванням у середині схеми та зворотним зв'язком — надають спосіб подолати ці обмеження, використовуючи класичний зв'язок у реальному часі для ефективної реалізації нелокальних квантових операцій. У цьому підході результати вимірювань з однієї частини схеми (або одного QPU) можуть умовно запускати вентилі на іншій, дозволяючи нам телепортувати заплутаність на великі відстані. Це формує основу схем локальних операцій та класичного зв'язку (LOCC), де ми використовуємо заплутані ресурсні стани (пари Белла) та класично передаємо результати вимірювань для зв'язування віддалених кубітів.

Одне з перспективних застосувань LOCC — реалізація віртуальних далекодіючих вентилів CNOT шляхом телепортації, як показано в посібнику з далекодіючого заплутування. Замість прямого далекодіючого CNOT (який може не підтримуватися зв'язністю обладнання) ми створюємо пари Белла та виконуємо реалізацію вентиля на основі телепортації. Однак точність таких операцій залежить від характеристик обладнання. Декогеренція кубітів під час необхідної затримки (очікування результатів вимірювань) та затримка класичного зв'язку можуть погіршити заплутаний стан. Крім того, помилки при вимірюваннях у середині схеми важче виправити, ніж помилки при фінальних вимірюваннях, оскільки вони поширюються на решту схеми через умовні вентилі.

У базовому експерименті автори вводять бенчмарк точності пар Белла для визначення, які частини пристрою найкраще підходять для заплутування на основі LOCC. Ідея полягає у виконанні невеликої динамічної схеми на кожній групі з чотирьох зв'язаних кубітів процесора. Ця чотирикубітна схема спочатку створює пару Белла на двох середніх кубітах, а потім використовує їх як ресурс для заплутування двох крайніх кубітів за допомогою LOCC. Конкретно, кубіти 1 і 2 підготовлюються в нерозрізану пару Белла локально (за допомогою вентилів Адамара та CNOT), а потім процедура телепортації використовує цю пару Белла для заплутування кубітів 0 і 3. Кубіти 1 і 2 вимірюються під час виконання схеми, і на основі цих результатів застосовуються корекції Паулі (вентиль X на кубіті 3 та Z на кубіті 0). Після цього кубіти 0 і 3 залишаються в стані Белла наприкінці схеми.

Для кількісної оцінки якості цієї фінальної заплутаної пари ми вимірюємо її стабілізатори: зокрема, парність у базисі ZZ (Z0Z3Z_0Z_3) та в базисі XX (X0X3X_0X_3). Для ідеальної пари Белла обидва ці математичні очікування дорівнюють +1. На практиці апаратний шум зменшить ці значення. Тому ми повторюємо схему двічі для кожної пари кубітів: одна схема вимірює кубіти 0 і 3 у базисі ZZ, а інша — у базисі XX. З результатів ми отримуємо оцінку Z0Z3\langle Z_0Z_3\rangle та X0X3\langle X_0X_3\rangle для цієї пари кубітів. Ми використовуємо середньоквадратичну помилку (MSE) цих стабілізаторів відносно ідеального значення (1) як просту метрику точності заплутування. Менше значення MSE означає, що два кубіти досягли стану Белла, ближчого до ідеального (вища точність), тоді як більше значення MSE вказує на більшу помилку. Скануючи цей експеримент по всьому пристрою, ми можемо оцінити можливості вимірювання та зворотного зв'язку різних груп кубітів та визначити найкращі пари кубітів для операцій LOCC.

Цей посібник демонструє експеримент на пристрої IBM Quantum® для ілюстрації того, як динамічні схеми можуть використовуватися для генерації та оцінки заплутаності між віддаленими кубітами. Ми визначимо всі чотирикубітні лінійні ланцюги на пристрої, запустимо схему телепортації на кожному з них, а потім візуалізуємо розподіл значень MSE. Ця наскрізна процедура показує, як використовувати Qiskit Runtime та функції динамічних схем для прийняття апаратно-усвідомлених рішень щодо розрізання схем або розподілу квантових алгоритмів у модульній системі.

Вимоги

Перед початком цього посібника переконайтеся, що у Вас встановлено наступне:

  • Qiskit SDK v2.0 або новішої версії з підтримкою візуалізації
  • Qiskit Runtime v0.40 або новішої версії (pip install qiskit-ibm-runtime)

Налаштування

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

Крок 1: Відображення класичних вхідних даних на квантову задачу

Перший крок — створити набір квантових схем для бенчмаркінгу всіх кандидатних зв'язків пар Белла, адаптованих до топології пристрою. Ми програмно здійснюємо пошук по карті зв'язності пристрою для всіх лінійно з'єднаних ланцюгів із чотирьох кубітів. Кожен такий ланцюг (позначений індексами кубітів [q0q1q2q3][q0-q1-q2-q3]) слугує тестовим випадком для схеми обміну заплутаністю. Визначивши всі можливі шляхи довжиною 4, ми забезпечуємо максимальне покриття можливих груп кубітів, які могли б реалізувати протокол.

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

Ми генеруємо ці ланцюги за допомогою допоміжної функції, яка виконує жадібний пошук на графі пристрою. Вона повертає «смуги» з чотирьох чотирикубітних ланцюгів, об'єднаних у 16-кубітні групи (динамічні схеми наразі обмежують розмір регістра вимірювань до 16 кубітів). Об'єднання дозволяє нам запускати кілька чотирикубітних експериментів паралельно на різних частинах чипа та ефективно використовувати весь пристрій. Кожна 16-кубітна смуга містить чотири непересічні ланцюги, що означає, що жоден кубіт не використовується повторно в цій групі. Наприклад, одна смуга може складатися з ланцюгів [0123][0-1-2-3], [4567][4-5-6-7], [891011][8-9-10-11] та [12131415][12-13-14-15], упакованих разом. Будь-який кубіт, який не увійшов до смуги, повертається у змінній 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)

Далі ми конструюємо схему для кожної 16-кубітної смуги. Процедура виконує наступне для кожного ланцюга:

  • Підготовка середньої пари Белла: Застосовуємо вентиль Адамара на кубіті 1 та CNOT від кубіта 1 до кубіта 2. Це заплутує кубіти 1 і 2 (створюючи стан Белла Φ+=(00+11)/2|\Phi^+\rangle = (|00\rangle + |11\rangle)/\sqrt{2}).
  • Заплутування крайніх кубітів: Застосовуємо CNOT від кубіта 0 до кубіта 1, та CNOT від кубіта 2 до кубіта 3. Це зв'язує початково окремі пари, щоб кубіти 0 і 3 стали заплутаними після наступних кроків. Також застосовується вентиль Адамара на кубіті 2 (це, у поєднанні з попередніми CNOT, формує частину вимірювання Белла на кубітах 1 і 2). На цьому етапі кубіти 0 і 3 ще не заплутані, але кубіти 1 і 2 заплутані з ними у більшому чотирикубітному стані.
  • Вимірювання в середині схеми та зворотний зв'язок: Кубіти 1 і 2 (середні кубіти) вимірюються в обчислювальному базисі, даючи два класичні біти. На основі результатів цих вимірювань ми застосовуємо умовні операції: якщо результат вимірювання кубіта 1 (назвемо цей біт m12m_{12}) дорівнює 1, ми застосовуємо вентиль XX на кубіті 3; якщо результат вимірювання кубіта 2 (m21m_{21}) дорівнює 1, ми застосовуємо вентиль ZZ на кубіті 0. Ці умовні вентилі (реалізовані за допомогою конструкції Qiskit if_test/if_else) реалізують стандартні корекції телепортації. Вони «скасовують» випадкові перекиди Паулі, що виникають через проєкцію кубітів 1 і 2, забезпечуючи, що кубіти 0 і 3 опиняються у відомому стані Белла незалежно від результатів вимірювань. Після цього кроку кубіти 0 і 3 в ідеалі мають бути заплутані у стані Белла Φ+|\Phi^+\rangle.
  • Вимірювання стабілізаторів пари Белла: Потім ми розділяємо на дві версії схеми. У першій версії ми вимірюємо стабілізатор ZZZZ на кубітах 0 і 3. У другій версії ми вимірюємо стабілізатор XXXX на цих кубітах.

Для кожного чотирикубітного початкового розміщення наведена вище функція повертає дві схеми (одну для вимірювання стабілізатора ZZZZ, іншу для XXXX). Наприкінці цього кроку ми маємо список схем, що покривають кожен чотирикубітний ланцюг на пристрої. Ці схеми включають вимірювання в середині схеми та умовні (if/else) операції, які є ключовими інструкціями динамічної схеми.

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

Результат виконання попередньої комірки коду

Крок 2: Оптимізація задачі для виконання на квантовому обладнанні

Перед виконанням наших схем на реальному обладнанні нам потрібно транспілювати їх відповідно до фізичних обмежень пристрою. Транспіляція відобразить абстрактну схему на фізичні кубіти та набір гейтів обраного пристрою. Оскільки ми вже обрали конкретні фізичні кубіти для кожного ланцюга (надавши initial_layout генератору схем), ми використовуємо optimization_level=0 транспілятора з фіксованим розміщенням. Це вказує Qiskit не перепризначати кубіти та не виконувати жодних складних оптимізацій, які могли б змінити структуру схеми. Ми хочемо зберегти послідовність операцій (особливо умовних гейтів) саме такою, як було задано.

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)

Вивід попередньої комірки коду

Крок 3: Виконання за допомогою примітивів Qiskit

Тепер ми можемо запустити експеримент на квантовому пристрої. Ми використовуємо Qiskit Runtime та його примітив Sampler для ефективного виконання пакету схем.

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

Крок 4: Постобробка та повернення результату в бажаному класичному форматі

Останній крок — обчислити метрику середньоквадратичної похибки (MSE) для кожної протестованої групи кубітів та узагальнити результати. Для кожного ланцюга ми тепер маємо виміряні Z0Z3\langle Z_0Z_3\rangle та X0X3\langle X_0X_3\rangle. Якби кубіти 0 та 3 були ідеально заплутані у стані Белла Φ+|\Phi^+\rangle, ми очікували б, що обидва ці значення дорівнюватимуть +1. Ми кількісно оцінюємо відхилення за допомогою MSE:

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

Це значення дорівнює 0 для ідеальної пари Белла та зростає в міру того, як заплутаний стан стає більш зашумленим (при випадкових результатах, що дають математичне сподівання близько 0, MSE наближатиметься до 1). Код обчислює цю MSE для кожної чотирикубітної групи.

Результати виявляють широкий діапазон якості заплутування по всьому пристрою. Це підтверджує висновок статті про те, що варіація точності стану Белла може перевищувати порядок величини залежно від того, які фізичні кубіти використовуються. На практиці це означає, що певні ділянки або зв'язки на чипі значно краще справляються з операціями вимірювання всередині схеми та прямого зв'язку, ніж інші. Такі фактори, як похибка зчитування кубітів, час життя кубітів та перехресні завади, ймовірно, спричиняють ці відмінності. Наприклад, якщо один ланцюг містить особливо зашумлений кубіт зчитування, вимірювання всередині схеми може бути ненадійним, що призведе до низької точності для цієї заплутаної пари (високе 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

Нарешті, ми візуалізуємо загальну продуктивність, побудувавши графік кумулятивної функції розподілу (CDF) значень MSE для всіх ланцюгів. Графік CDF показує поріг MSE на осі x та частку пар кубітів, що мають не більше цього MSE, на осі y. Ця крива починається з нуля та наближається до одиниці в міру того, як поріг зростає, охоплюючи всі точки даних. Крутий підйом поблизу низького MSE свідчив би про те, що багато пар мають високу точність; повільний підйом означає, що багато пар мають більші похибки. Ми анотуємо CDF ідентифікаторами найкращих пар. На графіку кожна точка CDF відповідає MSE одного чотирикубітного ланцюга, і ми позначаємо точку парою індексів кубітів [q0,q3][q0, q3], які були заплутані в цьому експерименті. Це дозволяє легко визначити, які фізичні пари кубітів є найкращими (крайні ліві точки на CDF).

plot_mse_ecdfs(layouts_mse, combine_layouts=True)

Вивід попередньої комірки коду

Посилання