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

التشابك بعيد المدى باستخدام الدوائر الديناميكية

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

الخلفية

يُعدّ التشابك بعيد المدى بين الكيوبتات المتباعدة أمرًا صعبًا على الأجهزة ذات الاتصالية المحدودة. يوضح هذا البرنامج التعليمي كيف يمكن للدوائر الديناميكية توليد هذا التشابك من خلال تنفيذ بوابة X المتحكم بها بعيدة المدى (LRCX) باستخدام بروتوكول قائم على القياس.

باتباع نهج إليزا باومر وزملائها في 1، تستخدم الطريقة القياس في منتصف الدائرة والتغذية الأمامية لتحقيق بوابات ذات عمق ثابت بصرف النظر عن المسافة بين الكيوبتات. تُنشئ الطريقة أزواج بيل الوسيطة، وتقيس كيوبتًا واحدًا من كل زوج، وتُطبّق بوابات مشروطة كلاسيكيًا لنشر التشابك عبر الجهاز. يتجنب هذا النهج سلاسل SWAP الطويلة، مما يقلل من عمق الدائرة والتعرض لأخطاء البوابة ثنائية الكيوبت.

في هذا الدفتر، نُكيّف البروتوكول لأجهزة IBM Quantum® ونمدده لتشغيل عمليات LRCX متعددة بالتوازي، مما يتيح لنا استكشاف كيفية قياس الأداء مع عدد العمليات الشرطية المتزامنة.

المتطلبات

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

  • Qiskit SDK الإصدار 2.0 أو أحدث، مع دعم التصور
  • Qiskit Runtime ( pip install qiskit-ibm-runtime ) الإصدار 0.37 أو أحدث

الإعداد

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.visualization import plot_circuit_layout
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
import matplotlib.pyplot as plt
import numpy as np

الخطوة 1: تعيين المدخلات الكلاسيكية إلى مسألة كمومية

ننفّذ الآن بوابة CNOT بعيدة المدى بين كيوبتين متباعدتين، متبعين البنية الديناميكية للدائرة الموضحة أدناه (مُقتبسة من الشكل 1a في المرجع 1). الفكرة الأساسية هي استخدام "حافلة" من الكيوبتات المساعدة، المُهيّأة في الحالة 0|0\rangle، كوسيط لتحقيق نقل البوابة بعيد المدى.

دائرة CNOT بعيدة المدى

كما هو موضح في الشكل، تعمل العملية على النحو التالي:

  1. إعداد سلسلة من أزواج بيل تربط الكيوبتات المتحكمة والهدف عبر مساعدات وسيطة.
  2. إجراء قياسات بيل بين الكيوبتات المجاورة غير المتشابكة، مما ينقل التشابك خطوة بخطوة حتى تتشارك الكيوبتات المتحكمة والهدف زوج بيل.
  3. استخدام زوج بيل هذا لنقل البوابة، وتحويل CNOT محلية إلى CNOT حتمية بعيدة المدى بعمق ثابت.

يستبدل هذا النهج سلاسل SWAP الطويلة ببروتوكول ذي عمق ثابت، مما يقلل التعرض لأخطاء البوابة ثنائية الكيوبت ويجعل العملية قابلة للتطوير مع حجم الجهاز.

في ما يلي، سنستعرض أولًا التنفيذ بالدائرة الديناميكية لدائرة LRCX. وفي النهاية، سنقدم أيضًا تنفيذًا قائمًا على الموحدات للمقارنة، لإبراز مزايا الدوائر الديناميكية في هذا السياق.

(i) تهيئة الدائرة

نبدأ بمسألة كمومية بسيطة ستكون الأساس للمقارنة. تحديدًا، نُهيّئ دائرة بكيوبت تحكم عند الفهرس 0 ونطبق عليه بوابة هادامارد. ينتج عن ذلك حالة تراكب تُولّد، عند اقترانها بعملية X مضبوطة، حالة بيل (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} بين كيوبتي التحكم والهدف.

في هذه المرحلة، لا نبني بعد بوابة X المضبوطة بعيدة المدى (LRCX) ذاتها. بدلًا من ذلك، هدفنا تحديد دائرة أولية واضحة وبسيطة تُبرز دور LRCX. في الخطوة 2، سنوضح كيف يمكن تنفيذ LRCX كتحسين باستخدام الدوائر الديناميكية، ونقارن أداءها مع المكافئ الموحد. والجدير بالذكر أن بروتوكول LRCX يمكن تطبيقه على أي دائرة أولية. نستخدم هنا إعداد هادامارد البسيط لوضوح العرض.

distance = 6  # The distance of the CNOT gate, with the convention that a distance of zero is a nearest-neighbor CNOT.

def initialize_circuit(distance):
assert distance >= 0
control = 0 # control qubit
n = distance # number of qubits between target and control

qr = QuantumRegister(
n + 2, name="q"
) # Circuit with n qubits between control and target
cr = ClassicalRegister(
2, name="cr"
) # Classical register for measuring control and target qubits

k = int(n / 2) # Number of Bell States to be used

allcr = [cr]
if (
distance > 1
): # This classical register will be used to store ZZ measurements. It is only used for long-range CX gates with distance > 1
c1 = ClassicalRegister(
k, name="c1"
) # Classical register needed for post processing
allcr.append(c1)
if (
distance > 0
): # This classical register will be used to store XX measurements. It is only used if distance > 0
c2 = ClassicalRegister(
n - k, name="c2"
) # Classical register needed for post processing
allcr.append(c2)

qc = QuantumCircuit(qr, *allcr, name="CNOT")

# Apply a Hadamard gate to the control qubit such that the long-range CNOT gate will prepare a Bell state (|00> + |11>)/sqrt(2)
qc.h(control)

return qc

qc = initialize_circuit(distance)
qc.draw(fold=-1, output="mpl", scale=0.5)

Output of the previous code cell

الخطوة 2: تحسين المسألة لتنفيذها على الأجهزة الكمومية

في هذه الخطوة، نوضح كيفية بناء دائرة LRCX باستخدام الدوائر الديناميكية. الهدف هو تحسين الدائرة للتنفيذ على الأجهزة بتقليل العمق مقارنةً بالتنفيذ الموحد البحت. لإبراز الفوائد، سنعرض كلًا من بنية LRCX الديناميكية ومكافئها الموحد، ثم نقارن أداءهما لاحقًا بعد التحويل. والجدير بالذكر أنه بينما نُطبّق هنا LRCX على مسألة بسيطة مُهيّأة بهادامارد، يمكن تطبيق البروتوكول على أي دائرة تتطلب CNOT بعيدة المدى.

(ii) إعداد أزواج بيل

نبدأ بإنشاء سلسلة من أزواج بيل على طول المسار بين كيوبتي التحكم والهدف. إذا كانت المسافة فردية، نطبق أولًا CNOT من كيوبت التحكم إلى جاره، وهي CNOT التي سيُنقل تأثيرها لاحقًا. أما في حالة المسافة الزوجية، ستُطبَّق هذه CNOT بعد مرحلة إعداد أزواج بيل. تُشابك سلسلة أزواج بيل بعد ذلك أزواجًا متتالية من الكيوبتات، مما يُنشئ الموارد اللازمة لنقل معلومات التحكم عبر الجهاز.

# Determine where to start the Bell pair chain and add an extra CNOT when n is odd
def check_even(n: int) -> int:
"""Return 1 if n is even, else 2."""
return 1 if n % 2 == 0 else 2

def prepare_bell_pairs(qc, add_barriers=True):
n = qc.num_qubits - 2 # number of qubits between target and control
k = int(n / 2)

if add_barriers:
qc.barrier()

x0 = check_even(n)
if n % 2 != 0:
qc.cx(0, 1)

# Create k Bell pairs
for i in range(k):
qc.h(x0 + 2 * i)
qc.cx(x0 + 2 * i, x0 + 2 * i + 1)
return qc

qc = prepare_bell_pairs(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output of the previous code cell

(iii) قياس أزواج الكيوبتات المجاورة في أساس بيل

بعد ذلك، نقيس الكيوبتات المجاورة غير المتشابكة في أساس بيل (قياسات ثنائية الكيوبت لـ XXXX وZZZZ). يُنشئ ذلك زوج بيل بعيد المدى بين كيوبت الهدف والكيوبت المجاور لكيوبت التحكم (مع تصحيحات باولي التي ستُنفَّذ عبر التغذية الأمامية في الخطوة التالية). وبالتوازي، ننفّذ القياس التشابكي الذي ينقل بوابة CNOT لتعمل على كيوبت الهدف المقصود.

def measure_bell_basis(qc, add_barriers=True):
n = qc.num_qubits - 2 # number of qubits between target and control
k = int(n / 2)

if n > 1:
_, c1, c2 = qc.cregs
elif n > 0:
_, c2 = qc.cregs

# Determine where to start the Bell pair chain and add an extra CNOT when n is odd
x0 = 1 if n % 2 == 0 else 2

# Entangling layer that implements the Bell measurement (and additionally adds the CNOT to be teleported, if n is even)
for i in range(k + 1):
qc.cx(x0 - 1 + 2 * i, x0 + 2 * i)

for i in range(1, k + x0):
if i == 1:
qc.h(2 * i + 1 - x0)
else:
qc.h(2 * i + 1 - x0)

if add_barriers:
qc.barrier()

# Map the ZZ measurements onto classical register c1
for i in range(k):
if i == 0:
qc.measure(2 * i + x0, c1[i])
else:
qc.measure(2 * i + x0, c1[i])

# Map the XX measurements onto classical register c2
for i in range(1, k + x0):
if i == 1:
qc.measure(2 * i + 1 - x0, c2[i - 1])
else:
qc.measure(2 * i + 1 - x0, c2[i - 1])
return qc

qc = measure_bell_basis(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output of the previous code cell

(iv) بعد ذلك، تطبيق تصحيحات التغذية الأمامية لتصحيح عوامل باولي الجانبية

تُدخل قياسات أساس بيل مقادير جانبية من باولي يجب تصحيحها باستخدام النتائج المسجّلة. يتم ذلك في خطوتين. أولًا، نحتاج إلى حساب تكافؤ جميع قياسات ZZZZ، الذي يُستخدم بعد ذلك لتطبيق بوابة XX مشروطة على كيوبت الهدف. وبالمثل، يُحسب تكافؤ قياسات XXXX ويُستخدم لتطبيق بوابة ZZ مشروطة على كيوبت التحكم.

مع إطار التعابير الكلاسيكية الجديد في Qiskit، يمكن حساب هذه التكافؤات مباشرةً في طبقة المعالجة الكلاسيكية للدائرة. بدلًا من تطبيق سلسلة من البوابات الشرطية الفردية لكل بت قياس، يمكننا بناء تعبير كلاسيكي واحد يمثل XOR (التكافؤ) لجميع نتائج القياس ذات الصلة. يُستخدم هذا التعبير بعد ذلك كشرط في كتلة if_test واحدة، مما يتيح تطبيق بوابات التصحيح بعمق ثابت. يُبسّط هذا النهج الدائرة ويضمن عدم إضافة تصحيحات التغذية الأمامية تأخيرًا إضافيًا غير ضروري.

def apply_ffwd_corrections(qc):
control = 0 # control qubit
target = qc.num_qubits - 1 # target qubit
n = qc.num_qubits - 2 # number of qubits between target and control

k = int(n / 2)
x0 = check_even(n)

if n > 1:
_, c1, c2 = qc.cregs
elif n > 0:
_, c2 = qc.cregs

# First, let's compute the parity of all ZZ measurements
for i in range(k):
if i == 0:
parity_ZZ = expr.lift(
c1[i]
) # Store the value of the first ZZ measurement in parity_ZZ
else:
parity_ZZ = expr.bit_xor(
c1[i], parity_ZZ
) # Successively compute the parity via XOR operations

for i in range(1, k + x0):
if i == 1:
parity_XX = expr.lift(
c2[i - 1]
) # Store the value of the first XX measurement in parity_XX
else:
parity_XX = expr.bit_xor(
c2[i - 1], parity_XX
) # Successively compute the parity via XOR operations

if n > 0:
with qc.if_test(parity_XX):
qc.z(control)

if n > 1:
with qc.if_test(parity_ZZ):
qc.x(target)
return qc

qc = apply_ffwd_corrections(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output of the previous code cell

(v) أخيرًا، قياس كيوبتي التحكم والهدف

نُعرّف دالة مساعدة تُتيح قياس كيوبتي التحكم والهدف في أسس XXXX أو YYYY أو ZZZZ. للتحقق من حالة بيل (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}، يجب أن تكون القيم المتوقعة لـ XXXX وZZZZ كلتيهما +1+1، إذ إنهما مُثبِّتان للحالة. القياس YYYY مدعوم أيضًا هنا وسيُستخدم أدناه عند حساب الإخلاص.

def measure_in_basis(qc, basis="XX", add_barrier=True):
control = 0 # control qubit
target = qc.num_qubits - 1 # target qubit

assert basis in ["XX", "YY", "ZZ"]

qc = (
qc.copy()
) # We copy the circuit because we want to measure in different bases
cr = qc.cregs[0]

if add_barrier:
qc.barrier()

if basis == "XX":
qc.h(control)
qc.h(target)
elif basis == "YY":
qc.sdg(control)
qc.sdg(target)
qc.h(control)
qc.h(target)

qc.measure(control, cr[0])
qc.measure(target, cr[1])
return qc

qc_YY = measure_in_basis(qc.copy(), basis="YY")
display(
qc_YY.draw(output="mpl", fold=-1, scale=0.5)
) # Circuit for measuring in the YY basis

Output of the previous code cell

تجميع الخطوات معًا

نجمع الخطوات المختلفة المحددة أعلاه لإنشاء بوابة CX بعيدة المدى على طرفي خط أحادي البعد. تتضمن الخطوات:

  • تهيئة كيوبت التحكم في ket+\\ket{+}
  • إعداد أزواج بيل
  • قياس أزواج الكيوبتات المجاورة
  • تطبيق تصحيحات التغذية الأمامية المعتمدة على قياسات MCM
def lrcx(distance, prep_barrier=True, pre_measure_barrier=True):
qc = initialize_circuit(distance)
qc = prepare_bell_pairs(qc, prep_barrier)
qc = measure_bell_basis(qc, pre_measure_barrier)
qc = apply_ffwd_corrections(qc)
return qc

qc = lrcx(distance)
# Apply the measurement in the XX, YY, and ZZ bases
qc_XX, qc_YY, qc_ZZ = [
measure_in_basis(qc, basis=basis) for basis in ["XX", "YY", "ZZ"]
]

display(
qc_YY.draw(output="mpl", fold=-1, scale=0.5)
) # Circuit for measuring in the YY basis

Output of the previous code cell

توليد دوائر لمسافات مختلفة

نُولّد الآن دوائر CX بعيدة المدى لمجموعة من مسافات الفصل بين الكيوبتات. لكل مسافة، نبني دوائر تقيس في أسس XXXX وYYYY وZZZZ، والتي ستُستخدم لاحقًا لحساب قيم الإخلاص.

تشمل قائمة المسافات فصلًا قصير وبعيد المدى، حيث يقابل distance = 0 بوابة CX للجار الأقرب. ستُستخدم هذه المسافات ذاتها أيضًا لتوليد الدوائر الموحدة المقابلة لاحقًا للمقارنة.

distances = [
0,
1,
2,
3,
6,
11,
16,
21,
28,
35,
44,
55,
60,
] # Distances for long range CX. distance of 0 is a nearest-neighbor CX
distances.sort()
assert (
min(distances) >= 0
) # Only works for distance larger than 2 because classical register cannot be empty
basis_list = ["XX", "YY", "ZZ"]

circuits_dyn = []
for distance in distances:
for basis in basis_list:
circuits_dyn.append(
measure_in_basis(lrcx(distance, prep_barrier=False), basis=basis)
)
print(f"Number of circuits: {len(circuits_dyn)}")
circuits_dyn[14].draw(fold=-1, output="mpl", idle_wires=False)
Number of circuits: 39

Output of the previous code cell

التنفيذ القائم على الموحدات بتبادل الكيوبتات إلى المنتصف

للمقارنة، نفحص أولًا الحالة التي تُنفَّذ فيها بوابة CNOT بعيدة المدى باستخدام الوصلات القريبة والبوابات الموحدة. في الشكل التالي، يوجد على اليسار دائرة لبوابة CNOT بعيدة المدى تمتد على سلسلة أحادية البعد من n كيوبت خاضعة لوصلات الجيران الأقرب فقط. وفي الوسط، تفكيك موحد مكافئ قابل للتنفيذ ببوابات CNOT محلية بعمق دائرة O(n)O(n).

دائرة CNOT بعيدة المدى

يمكن تنفيذ الدائرة في الوسط على النحو التالي:

def cnot_unitary(distance):
"""Generate a long range CNOT gate using local CNOTs on a 1D chain of qubits subject to n
nearest-neighbor connections only.

Args:
distance (int) : The distance of the CNOT gate, with the convention that a distance of 0 is a nearest-neighbor CNOT.

Returns:
QuantumCircuit: A Quantum Circuit implementing a long-range CNOT gate between qubit 0 and qubit distance+1
"""
assert distance >= 0
n = distance # number of qubits between target and control

qr = QuantumRegister(
n + 2, name="q"
) # Circuit with n qubits between control and target
cr = ClassicalRegister(
2, name="cr"
) # Classical register for measuring control and target qubits

qc = QuantumCircuit(qr, cr, name="CNOT_unitary")

control_qubit = 0

qc.h(control_qubit) # Prepare the control qubit in the |+> state

k = int(n / 2)
qc.barrier()
for i in range(control_qubit, control_qubit + k):
qc.cx(i, i + 1)
qc.cx(i + 1, i)
qc.cx(-i - 1, -i - 2)
qc.cx(-i - 2, -i - 1)
if n % 2 == 1:
qc.cx(k + 2, k + 1)
qc.cx(k + 1, k + 2)
qc.barrier()
qc.cx(k, k + 1)
for i in range(control_qubit, control_qubit + k):
qc.cx(k - i, k - 1 - i)
qc.cx(k - 1 - i, k - i)
qc.cx(k + i + 1, k + i + 2)
qc.cx(k + i + 2, k + i + 1)
if n % 2 == 1:
qc.cx(-2, -1)
qc.cx(-1, -2)

return qc

الآن نبني جميع الدوائر الموحدة، ونبني الدوائر التي تقيس في أسس XXXX وYYYY وZZZZ، تمامًا كما فعلنا للدوائر الديناميكية أعلاه.

circuits_uni = []
for distance in distances:
for basis in basis_list:
circuits_uni.append(
measure_in_basis(cnot_unitary(distance), basis=basis)
)

print(f"Number of circuits: {len(circuits_uni)}")
circuits_uni[14].draw(fold=-1, output="mpl", idle_wires=False)
Number of circuits: 39

Output of the previous code cell

الآن بعد أن توفرت لدينا دوائر ديناميكية وموحدة لمجموعة من المسافات، نحن جاهزون للتحويل. نحتاج أولًا إلى اختيار جهاز خلفية.

# Set up access to IBM Quantum devices
from qiskit.circuit import IfElseOp

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=156
)

تضمن الخطوة التالية أن الخلفية تدعم تعليمة if_else، وهي مطلوبة للإصدار الأحدث من الدوائر الديناميكية. نظرًا لأن هذه الميزة لا تزال في مرحلة الوصول المبكر، نُضيف صراحةً IfElseOp إلى هدف الخلفية إذا لم يكن متاحًا بالفعل.

if "if_else" not in backend.target.operation_names:
backend.target.add_instruction(IfElseOp, name="if_else")

استخدام سلسلة إخلاص الطبقة لاختيار السلسلة أحادية البعد

بما أننا نريد مقارنة أداء الدوائر الديناميكية والموحدة على سلسلة أحادية البعد، نستخدم سلسلة إخلاص الطبقة لاختيار طوبولوجيا خطية لأفضل سلسلة من الكيوبتات من الجهاز. يضمن ذلك تحويل كلا النوعين من الدوائر في ظل قيود الاتصالية ذاتها، مما يتيح مقارنة عادلة لأدائهما.

# This selects best qubits for longest distance and uses the same control for all lengths
lf_qubits = backend.properties().to_dict()[
"general_qlists"
] # best linear chain qubits
chosen_layouts = {
distance: [
val["qubits"]
for val in lf_qubits
if val["name"] == f"lf_{distances[-1] + 2}"
][0][: distance + 2]
for distance in distances
}
print(chosen_layouts[max(distances)]) # best qubits at each distance
[10, 11, 12, 13, 14, 15, 19, 35, 34, 33, 39, 53, 54, 55, 59, 75, 74, 73, 72, 71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 56, 63, 62, 61, 76, 81, 82, 83, 84, 85, 77, 65, 66, 67, 68, 69, 78, 89, 90, 91, 98, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101]
isa_circuits_dyn = []
isa_circuits_uni = []

# Using the same initial layouts for both circuits for better apples to apples comparison
for qc in circuits_dyn:
pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
initial_layout=chosen_layouts[qc.num_qubits - 2],
)
isa_circuits_dyn.append(pm.run(qc))

for qc in circuits_uni:
pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
initial_layout=chosen_layouts[qc.num_qubits - 2],
)
isa_circuits_uni.append(pm.run(qc))
print(
f"2Q depth: {isa_circuits_dyn[14].depth(lambda x: x.operation.num_qubits == 2)}"
)
isa_circuits_dyn[14].draw("mpl", fold=-1, idle_wires=0)
2Q depth: 2

Output of the previous code cell

print(
f"2Q depth: {isa_circuits_uni[14].depth(lambda x: x.operation.num_qubits == 2)}"
)
isa_circuits_uni[14].draw("mpl", fold=-1, idle_wires=False)
2Q depth: 13

Output of the previous code cell

تصور الكيوبتات المستخدمة في دائرة LRCX

في هذا القسم، ندرس كيفية رسم دائرة LRCX على الأجهزة. نبدأ بتصور الكيوبتات المادية المستخدمة في الدائرة ثم ندرس كيف تؤثر المسافة بين التحكم والهدف في التخطيط على عدد العمليات.

# Note: the qubit coordinates must be hard-coded.
# The backend API does not currently provide this information directly.
# If using a different backend, you will need to adjust the coordinates accordingly,
# or set the qubit_coordinates = None to use the default layout coordinates.

def _heron_coords_r2():
"""Generate coordinates for the Heron layout in R2. Note"""
cord_map = np.array(
[
[
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
],
-1
* np.array([j for i in range(15) for j in [i] * [16, 4][i % 2]]),
],
dtype=int,
)

hcords = []
ycords = cord_map[0]
xcords = cord_map[1]
for i in range(156):
hcords.append([xcords[i] + 1, np.abs(ycords[i]) + 1])

return hcords

# Visualize the active qubits in the circuit layout
plot_circuit_layout(
circuit=isa_circuits_uni[-1],
backend=backend,
view="physical",
qubit_coordinates=_heron_coords_r2(),
)

Output of the previous code cell

الخطوة 3: التنفيذ باستخدام عناصر Qiskit الأولية

في هذه الخطوة، ننفّذ التجربة على الخلفية المحددة. نستخدم أيضًا المعالجة الدُفعية لتشغيل التجربة بكفاءة عبر تجارب متعددة. يتيح تشغيل تجارب متكررة حساب المتوسطات للحصول على مقارنة أكثر دقة بين الطريقتين الموحدة والديناميكية، فضلًا عن قياس تباينها من خلال مقارنة الانحرافات عبر عمليات التشغيل.

print(backend.name)
ibm_kingston

اختر عدد التجارب ونفّذ المعالجة الدُفعية.

num_trials = 10
jobs_uni = []
jobs_dyn = []
with Batch(backend=backend) as batch:
sampler = Sampler(mode=batch)
for _ in range(num_trials):
jobs_uni.append(sampler.run(isa_circuits_uni, shots=1024))
jobs_dyn.append(sampler.run(isa_circuits_dyn, shots=1024))

الخطوة 4: المعالجة اللاحقة وإعادة النتيجة بالصيغة الكلاسيكية المطلوبة

بعد تنفيذ التجارب بنجاح، نُعالج الآن أعداد القياسات لاستخلاص مقاييس ذات معنى. في هذه الخطوة، نقوم بـ:

  • تحديد مقاييس الجودة لتقييم أداء بوابة CX بعيدة المدى.
  • حساب القيم المتوقعة لعوامل باولي من نتائج القياس الخام.
  • استخدام هذه القيم لحساب إخلاص حالة بيل المولّدة.

يوفر هذا التحليل صورة واضحة عن مدى أداء الدوائر الديناميكية مقارنةً بتنفيذ الأساس الموحد.

مقاييس الجودة

لتقييم نجاح بروتوكول CX بعيد المدى، نقيس مدى قرب حالة الإخراج من حالة بيل المثالية. تتمثل طريقة مريحة لقياس ذلك في حساب إخلاص الحالة باستخدام القيم المتوقعة لعوامل باولي. يمكن حساب الإخلاص لحالة بيل على حالة التحكم والهدف بعد معرفة XX\braket{XX} وYY\braket{YY} وZZ\braket{ZZ}. وتحديدًا:

F=14(1+XXYY+ZZ) F = \frac{1}{4} (1 + \braket{XX} - \braket{YY} + \braket{ZZ})

لحساب هذه القيم المتوقعة من بيانات القياس الخام، نُعرّف مجموعة من الدوال المساعدة:

  • compute_ZZ_expectation: بالنظر إلى أعداد القياسات، تحسب القيمة المتوقعة لعامل باولي ثنائي الكيوبت في الأساس ZZ.
  • compute_fidelity: تجمع القيم المتوقعة لـ XXXX وYYYY وZZZZ في تعبير الإخلاص أعلاه.
  • get_counts_from_bitarray: أداة مساعدة لاستخراج الأعداد من كائنات نتائج الخلفية.
def compute_ZZ_expectation(counts):
total = sum(counts.values())
expectation = 0
for bitstring, count in counts.items():
# Ensure bitstring is 2 bits
z1 = (-1) ** (int(bitstring[-1]))
z2 = (-1) ** (int(bitstring[-2]))
expectation += z1 * z2 * count
return expectation / total

def compute_fidelity(counts_xx, counts_yy, counts_zz):
xx, yy, zz = [
compute_ZZ_expectation(c) for c in [counts_xx, counts_yy, counts_zz]
]
return 1 / 4 * (1 + xx - yy + zz)

نحسب الإخلاص لدوائر CX الديناميكية بعيدة المدى. لكل مسافة، نستخرج نتائج القياس في أسس XX\braket{XX} وYY\braket{YY} وZZ\braket{ZZ}. تُدمج هذه النتائج باستخدام الدوال المساعدة المُعرَّفة سابقًا لحساب الإخلاص وفق F=14(1+XXYY+ZZ)F = \tfrac{1}{4} \big( 1 + \langle XX \rangle - \langle YY \rangle + \langle ZZ \rangle \big). يوفر ذلك الإخلاص الملاحَظ للبروتوكول المُنفَّذ ديناميكيًا عند كل مسافة.

fidelities_dyn = []

# loop over trials
for job in jobs_dyn:
result_dyn = job.result()
trial_fidelities = []
# loop over all distances
for ind, dist in enumerate(distances):
counts_xx = result_dyn[ind * 3].data.cr.get_counts()
counts_yy = result_dyn[ind * 3 + 1].data.cr.get_counts()
counts_zz = result_dyn[ind * 3 + 2].data.cr.get_counts()
trial_fidelities.append(
compute_fidelity(counts_xx, counts_yy, counts_zz)
)
fidelities_dyn.append(trial_fidelities)
# average over trials for each distance
avg_fidelities_dyn = np.mean(fidelities_dyn, axis=0)
std_fidelities_dyn = np.std(fidelities_dyn, axis=0)

الآن نحسب الإخلاص لدوائر CX الموحدة بعيدة المدى، وذلك بالطريقة ذاتها التي اتبعناها للدوائر الديناميكية أعلاه.

fidelities_uni = []

# loop over trials
for job in jobs_uni:
result_uni = job.result()
trial_fidelities = []
# loop over all distances
for ind, dist in enumerate(distances):
counts_xx = result_uni[ind * 3].data.cr.get_counts()
counts_yy = result_uni[ind * 3 + 1].data.cr.get_counts()
counts_zz = result_uni[ind * 3 + 2].data.cr.get_counts()
trial_fidelities.append(
compute_fidelity(counts_xx, counts_yy, counts_zz)
)
fidelities_uni.append(trial_fidelities)
# average over trials for each distance
avg_fidelities_uni = np.mean(fidelities_uni, axis=0)
std_fidelities_uni = np.std(fidelities_uni, axis=0)

رسم النتائج

لتقدير النتائج بصريًا، تُرسم الخلية أدناه قيم الإخلاص المقدّرة للبوابة المقاسة عند مسافات متفاوتة بين الكيوبتات المتشابكة للطريقتين.

fig, ax = plt.subplots()

# Unitary with error bars
ax.errorbar(
distances,
avg_fidelities_uni,
yerr=std_fidelities_uni,
fmt="o-.",
color="c",
ecolor="c",
elinewidth=1,
capsize=4,
label="Unitary",
)
# Dynamic with error bars
ax.errorbar(
distances,
avg_fidelities_dyn,
yerr=std_fidelities_dyn,
fmt="o-.",
color="m",
ecolor="m",
elinewidth=1,
capsize=4,
label="Dynamic",
)
# Random gate baseline
ax.axhline(y=1 / 4, linestyle="--", color="gray", label="Random gate")

legend = ax.legend(frameon=True)
for text in legend.get_texts():
text.set_color("black")
legend.get_frame().set_facecolor("white")
legend.get_frame().set_edgecolor("black")
ax.set_title(
"Bell State Fidelity vs Control–Target Separation", color="black"
)
ax.set_xlabel("Distance", color="black")
ax.set_ylabel("Bell state fidelity", color="black")
ax.grid(linestyle=":", linewidth=0.6, alpha=0.4, color="gray")
ax.set_ylim((0.2, 1))
ax.set_facecolor("white")
fig.patch.set_facecolor("white")
for spine in ax.spines.values():
spine.set_visible(True)
spine.set_color("black")
ax.tick_params(axis="x", colors="black")
ax.tick_params(axis="y", colors="black")
plt.show()

Output of the previous code cell

من مخطط الإخلاص أعلاه، لم يتفوق LRCX باستمرار على التنفيذ الموحد المباشر. في الواقع، حقّقت الدائرة الموحدة إخلاصًا أعلى عند فصل قصير بين التحكم والهدف. إلا أنه عند المسافات الأكبر، يبدأ الدائرة الديناميكية في تحقيق إخلاص أفضل من التنفيذ الموحد. هذا السلوك غير مفاجئ على الأجهزة الحالية: في حين تُقلل الدوائر الديناميكية عمق الدائرة بتجنب سلاسل SWAP الطويلة، فإنها تُدخل وقت دائرة إضافيًا من قياسات منتصف الدائرة والتغذية الكلاسيكية الأمامية وتأخيرات مسار التحكم. يُزيد التأخير المُضاف من التراجع الكمي وأخطاء القراءة، مما قد يفوق فوائد تقليل العمق عند المسافات القصيرة.

ومع ذلك، نلاحظ نقطة تقاطع حيث يتفوق النهج الديناميكي على الموحد. يعكس ذلك مباشرةً الفروق في قياس العمق: يتزايد عمق الدائرة الموحدة خطيًا مع المسافة بين الكيوبتات، بينما يبقى عمق الدائرة الديناميكية ثابتًا.

النقاط الرئيسية:

  • الفائدة الفورية للدوائر الديناميكية: الدافع الرئيسي في الوقت الراهن هو تقليل عمق البوابات ثنائية الكيوبت، وليس بالضرورة تحسين الإخلاص.
  • سبب تراجع الإخلاص اليوم: كثيرًا ما يهيمن وقت الدائرة المُضاف من عمليات القياس والكلاسيكية، خاصةً عندما يكون الفصل بين التحكم والهدف صغيرًا.
  • المستقبل: مع تحسّن الأجهزة، ولا سيما القراءة الأسرع وزمن استجابة التحكم الكلاسيكي الأقصر وتقليل الحمل الزائد لمنتصف الدائرة، يُتوقع أن تنعكس هذه التخفيضات في العمق والمدة على مكاسب إخلاص قابلة للقياس.
# Compute metrics for each distance, skipping the basis circuits since they are identical for each distance
depths_2q_dyn = [
c.depth(lambda x: x.operation.num_qubits == 2)
for c in isa_circuits_dyn[::3]
]
meas_dyn = [
sum(1 for instr in c.data if instr.operation.name == "measure")
for c in isa_circuits_dyn[::3]
]

depths_2q_uni = [
c.depth(lambda x: x.operation.num_qubits == 2)
for c in isa_circuits_uni[::3]
]
meas_uni = [
sum(1 for instr in c.data if instr.operation.name == "measure")
for c in isa_circuits_uni[::3]
]

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].plot(
distances, depths_2q_uni, "o-.", color="c", label="Unitary (2Q depth)"
)
axes[0].plot(
distances, depths_2q_dyn, "o-.", color="m", label="Dynamic (2Q depth)"
)
axes[0].set_xlabel("Number of qubits between control and target")
axes[0].set_ylabel("Two-qubit depth")
axes[0].grid(True, linestyle=":", linewidth=0.6, alpha=0.4)
axes[0].legend()

axes[1].plot(
distances, meas_uni, "o-.", color="c", label="Unitary (# measurements)"
)
axes[1].plot(
distances, meas_dyn, "o-.", color="m", label="Dynamic (# measurements)"
)
axes[1].set_xlabel("Number of qubits between control and target")
axes[1].set_ylabel("Number of measurements")
axes[1].grid(True, linestyle=":", linewidth=0.6, alpha=0.4)
axes[1].legend()

fig.suptitle("Scaling of Unitary vs Dynamic LRCX with Distance", fontsize=12)

plt.tight_layout()
plt.show()

Output of the previous code cell

يُبرز مخطط العمق ثنائي الكيوبت هذا الميزة الرئيسية لـ LRCX المُنفَّذ بالدوائر الديناميكية: يظل الأداء ثابتًا بشكل جوهري مع زيادة الفصل بين كيوبتي التحكم والهدف. في المقابل، يتزايد التنفيذ الموحد خطيًا مع المسافة بسبب سلاسل SWAP المطلوبة. يلتقط العمق القياس المنطقي للعمليات ثنائية الكيوبت، بينما يعكس عدد القياسات الحمل الإضافي للدوائر الديناميكية. هذه القياسات فعّالة إذ تُجرى بالتوازي، لكنها لا تزال تُدخل تكلفة ثابتة على الأجهزة الحالية.

سبب تراجع الإخلاص اليوم: كثيرًا ما يهيمن وقت الدائرة المُضاف من عمليات القياس والكلاسيكية، خاصةً عندما يكون الفصل بين التحكم والهدف صغيرًا. على سبيل المثال، متوسط طول القراءة على معالج Heron r2 هو 2,280 نانوثانية، في حين أن طول بوابة 2Q يبلغ 68 نانوثانية فحسب.

مع تحسّن زمن استجابة القياس والكلاسيكي، نتوقع أن يُحقق القياس الثابت للعمق وعدد القياسات الثابت للدوائر الديناميكية مزايا واضحة في الإخلاص ووقت التشغيل على الدوائر الأكبر.

المراجع

[1] التشابك الفعّال بعيد المدى باستخدام الدوائر الديناميكية، بقلم Elisa Bäumer, Vinay Tripathi, Derek S. Wang, Patrick Rall, Edward H. Chen, Swarnadeep Majumder, Alireza Seif, Zlatko K. Minev. IBM Quantum, (2023). https://arxiv.org/abs/2308.13065