انتقل إلى المحتوى الرئيسي

قياس أداء الدوائر الكمومية الديناميكية باستخدام أزواج بيل المقطوعة

تقدير الاستخدام: 22 ثانية على معالج Heron r2 (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي لديك.)

الخلفية النظرية

عادةً ما يقتصر الأجهزة الكمومية على التفاعلات المحلية، غير أن كثيرًا من الخوارزميات تستلزم تشابك كيوبتات بعيدة، أو حتى كيوبتات على معالجات منفصلة. توفر الدوائر الكمومية الديناميكية - أي الدوائر التي تشتمل على قياسات أثناء الدارة وتغذية أمامية (feedforward) - وسيلةً للتغلب على هذه القيود، وذلك بالاستعانة بالاتصالات الكلاسيكية الآنية لتنفيذ عمليات كمومية غير محلية بصورة فعّالة. في هذا النهج، يمكن لنتائج القياس في جزء من الدارة (أو في وحدة معالجة كمومية مستقلة) أن تُشغّل بوابات على جزء آخر بصورة شرطية، مما يُتيح نقل التشابك عبر مسافات طويلة. ويُشكّل هذا الأساس لمخططات العمليات المحلية والاتصالات الكلاسيكية (LOCC)، حيث نستهلك حالات موارد متشابكة (أزواج بيل) وننقل نتائج القياس كلاسيكيًا لربط الكيوبتات البعيدة.

ومن أبرز تطبيقات LOCC تحقيق بوابات CNOT الافتراضية بعيدة المدى عبر النقل الكمومي (teleportation)، كما هو موضح في درس التشابك بعيد المدى. بدلًا من استخدام بوابة 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 أن الكيوبتين حققتا حالة بيل أقرب إلى المثالية (دقة أعلى)، في حين يُشير ارتفاعه إلى أخطاء أكثر. من خلال إجراء هذه التجربة على نطاق الجهاز كاملًا، يمكننا قياس قدرة القياس والتغذية الأمامية لمجموعات مختلفة من الكيوبتات وتحديد أفضل الأزواج لعمليات LOCC.

يستعرض هذا الدرس التجربة على جهاز IBM Quantum® لتوضيح كيفية استخدام الدوائر الكمومية الديناميكية لتوليد التشابك وتقييمه بين الكيوبتات البعيدة. سنرسم خريطة لجميع سلاسل الكيوبتات الخطية الرباعية على الجهاز، ونُشغّل دارة النقل الكمومي على كل منها، ثم نُصوِّر توزيع قيم MSE. توضح هذه العملية الشاملة كيفية الاستفادة من Qiskit Runtime وميزات الدوائر الكمومية الديناميكية لاتخاذ خيارات واعية بالجهاز عند تقطيع الدوائر أو توزيع الخوارزميات الكمومية عبر نظام معياري.

المتطلبات

قبل البدء في هذا الدرس، تأكد من تثبيت ما يلي:

  • Qiskit SDK الإصدار 2.0 أو أحدث، مع دعم التصوير البياني
  • Qiskit Runtime الإصدار 0.40 أو أحدث (pip install 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 (الذي يُشكّل مع بوابات CNOT السابقة جزءًا من قياس بيل على الكيوبتين 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: تحسين المسألة لتنفيذها على الأجهزة الكمومية

قبل تنفيذ دوائرنا على الأجهزة الفعلية، نحتاج إلى نقلها (transpile) لتتوافق مع القيود المادية للجهاز. ستُعيّن عملية النقل الدارة المجردة على الكيوبتات المادية ومجموعة البوابات الخاصة بالجهاز المختار. بما أننا اخترنا بالفعل كيوبتات مادية محددة لكل سلسلة (بتحديد 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)

Output of the previous code cell

الخطوة 3: التنفيذ باستخدام Qiskit primitives

يمكننا الآن تشغيل التجربة على الجهاز الكمومي. نستخدم 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).