דלג לתוכן הראשי

בדיקת ביצועים של מעגלים דינמיים באמצעות זוגות בל חתוכים

אומדן שימוש: 22 שניות במעבד Heron r2 (הערה: זהו אומדן בלבד. זמן הריצה שלך עשוי להשתנות.)

רקע

חומרה קוונטית מוגבלת בדרך כלל לאינטראקציות מקומיות, אך אלגוריתמים רבים דורשים שזירה של קיוביטים מרוחקים או אפילו קיוביטים במעבדים נפרדים. מעגלים דינמיים - כלומר, מעגלים עם מדידה באמצע המעגל והזנה קדימה - מספקים דרך להתגבר על מגבלות אלה על ידי שימוש בתקשורת קלאסית בזמן אמת כדי ליישם ביעילות פעולות קוונטיות לא-מקומיות. בגישה זו, תוצאות מדידה מחלק אחד של מעגל (או מעבד קוונטי אחד) יכולות להפעיל באופן מותנה שערים על חלק אחר, ולאפשר לנו לטלפורט שזירה למרחקים ארוכים. זה מהווה את הבסיס לסכימות פעולות מקומיות ותקשורת קלאסית (LOCC), שבהן אנו צורכים מצבי משאבים משוזרים (זוגות בל) ומעבירים תוצאות מדידה באופן קלאסי כדי לקשר קיוביטים מרוחקים.

שימוש מבטיח אחד ב-LOCC הוא לממש שערי CNOT וירטואליים לטווח ארוך באמצעות טלפורטציה, כפי שמוצג בהדרכה על שזירה לטווח ארוך. במקום CNOT ישיר לטווח ארוך (שייתכן שקישוריות החומרה אינה מאפשרת), אנו יוצרים זוגות בל ומבצעים מימוש שער מבוסס טלפורטציה. עם זאת, הנאמנות של פעולות כאלה תלויה במאפייני החומרה. דה-קוהרנטיות של קיוביטים במהלך העיכוב ההכרחי (בזמן המתנה לתוצאות מדידה) וחביון תקשורת קלאסי יכולים לפגוע במצב המשוזר. כמו כן, שגיאות במדידות באמצע המעגל קשות יותר לתיקון מאשר שגיאות במדידות סופיות מכיוון שהן מתפשטות לשאר המעגל דרך השערים המותנים.

בניסוי הייחוס, המחברים מציגים מדד ביצועים לנאמנות זוג בל כדי לזהות אילו חלקים במכשיר מתאימים ביותר לשזירה מבוססת LOCC. הרעיון הוא להריץ מעגל דינמי קטן על כל קבוצה של ארבעה קיוביטים מחוברים במעבד. מעגל ארבעת הקיוביטים הזה יוצר תחילה זוג בל על שני קיוביטים אמצעיים, ואז משתמש בהם כמשאב לשזור את שני הקיוביטים הקצויים באמצעות LOCC. באופן קונקרטי, קיוביטים 1 ו-2 מוכנים לזוג בל לא-חתוך מקומית (באמצעות Hadamard ו-CNOT), ולאחר מכן שגרת טלפורטציה צורכת את זוג הבל הזה כדי לשזור את קיוביטים 0 ו-3. קיוביטים 1 ו-2 נמדדים במהלך ביצוע המעגל, ובהתבסס על תוצאות אלה, תיקוני Pauli (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 ואילך, עם תמיכה בvisualization
  • 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 קיוביטים. השגרה עושה את הדברים הבאים עבור כל שרשרת:

  • הכנת זוג בל אמצעי: החלת Hadamard על קיוביט 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 יהיו משוזרים לאחר השלבים הבאים. Hadamard על קיוביט 2 מוחל גם כן (זה, בשילוב עם ה-CNOTs הקודמים, מהווה חלק ממדידת בל על קיוביטים 1 ו-2). בשלב זה, קיוביטים 0 ו-3 עדיין לא משוזרים, אבל קיוביטים 1 ו-2 משוזרים איתם במצב גדול יותר של ארבעה קיוביטים.
  • מדידות באמצע מעגל והזנה קדימה: קיוביטים 1 ו-2 (הקיוביטים האמצעיים) נמדדים בבסיס החישובי, ומניבים שני ביטים קלאסיים. בהתבסס על תוצאות מדידה אלה, אנו מחילים פעולות מותנות: אם מדידת קיוביט 1 (נקרא לביט הזה m12m_{12}) היא 1, אנו מחילים שער XX על קיוביט 3; אם מדידת קיוביט 2 (m21m_{21}) היא 1, אנו מחילים שער ZZ על קיוביט 0. שערים מותנים אלה (הממומשים באמצעות המבנה if_test/if_else של Qiskit) מיישמים את תיקוני הטלפורטציה הסטנדרטיים. הם "מבטלים" את היפוכי Pauli האקראיים המתרחשים עקב הקרנת קיוביטים 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)

Output of the previous code cell

שלב 2: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית

לפני הרצת המעגלים שלנו על חומרה אמתית, אנו צריכים להעביר אותם כדי להתאים את האילוצים הפיזיים של המכשיר. טרנספילציה תמפה את המעגל המופשט על הקיוביטים הפיזיים וסט השערים של המכשיר שנבחר. מכיוון שכבר בחרנו קיוביטים פיזיים ספציפיים עבור כל שרשרת (על ידי מתן initial_layout ליוצר המעגל), אנו משתמשים ב-transpiler 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)

Output of the previous code cell

שלב 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 הזה עבור כל קבוצת ארבעה קיוביטים.

התוצאות מגלות טווח רחב של איכות שזירה על פני המכשיר. זה מאשר את הממצא של המאמר שיכול להיות שינוי של יותר מסדר גודל בנאמנות מצב בל בהתאם לאילו קיוביטים פיזיים נעשה שימוש. במונחים מעשיים, זה אומר שאזורים או קישורים מסוימים בשבב טובים הרבה יותר בביצוע מדידה באמצע מעגל ופעולות הזנה קדימה מאחרים. גורמים כמו שגיאת קריאת קיוביט, זמן חיים של קיוביט, ו-crosstalk כנראה תורמים להבדלים אלה. לדוגמה, אם שרשרת אחת כוללת קיוביט קריאה רועש במיוחד, המדידה באמצע המעגל עלולה להיות לא אמינה, מה שמוביל לנאמנות ضعיف עבור זוג משוזר זה (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)

Output of the previous code cell

אסמכתאות

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