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

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

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

الخلفية

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

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

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

المتطلبات

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

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

الإعداد

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 لتوسُّط نقل البوابة بعيدة المدى.

Long-range CNOT circuit

كما هو موضَّح في الشكل، تسير العملية على النحو الآتي:

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

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

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

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

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

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

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 على مسألة بسيطة مُهيَّأة بـ Hadamard، فإن البروتوكول قابل للتطبيق على أي دائرة تتطلَّب بوابة 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). يُنشئ هذا زوج بيل بعيد المدى بين كيوبت الهدف والكيوبت المجاور لكيوبت التحكم (مع تصحيحات Pauli، التي ستُنفَّذ عبر التغذية الأمامية في الخطوة التالية). بالتوازي مع ذلك، ننفِّذ القياس المتشابك الذي ينقل بوابة 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) تطبيق تصحيحات التغذية الأمامية لتصحيح مؤثِّرات Pauli الثانوية

تُدخِل قياسات أساس بيل مؤثِّرات Pauli ثانوية يجب تصحيحها باستخدام نتائج القياسات المسجَّلة. يتمُّ ذلك في خطوتين. أولًا، يجب حساب تكافؤ جميع قياسات 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{+}
  • إعداد أزواج بيل
  • قياس أزواج الكيوبتات المجاورة
  • تطبيق تصحيحات التغذية الأمامية المعتمدة على قياسات الدائرة في منتصف تنفيذها (MCMs)
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).

Long-range CNOT circuit

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

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, 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


الآن قم ببناء جميع دوائر unitary، وبناء الدوائر التي تقيس في أساسَي $XX$ و$YY$ و$ZZ$، تمامًا كما فعلنا مع الدوائر الديناميكية أعلاه.

```python
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

الآن بعد أن أصبح لدينا كلٌّ من الدوائر الديناميكية وdوائر unitary لنطاق من المسافات، أصبحنا مستعدين للتحويل (transpilation). نحتاج أولًا إلى اختيار جهاز backend.

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

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

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

استخدام سلسلة Layer Fidelity لاختيار سلسلة 1D

بما أننا نريد مقارنة أداء الدوائر الديناميكية ودوائر unitary على سلسلة 1D، نستخدم سلسلة Layer Fidelity لاختيار topology خطي يمثّل أفضل سلسلة من qubits في الجهاز. يضمن ذلك تحويل (transpile) كلا النوعين من الدوائر وفق نفس قيود الاتصال، مما يتيح مقارنة عادلة لأدائهما.

# 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

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

في هذا القسم، ندرس كيفية تعيين دائرة LRCX على العتاد (hardware). نبدأ بتصوير physical qubits المستخدمة في الدائرة، ثم ندرس كيف تؤثر المسافة بين control وtarget في layout على عدد العمليات.

# 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 primitives

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

print(backend.name)
ibm_kingston

اختر عدد المحاولات ونفِّذ batch execution.

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: المعالجة اللاحقة وإرجاع النتيجة بالصيغة الكلاسيكية المطلوبة

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

  • نحدّد مقاييس الجودة لتقييم أداء long-range CX.
  • نحسب قيم التوقع لمؤثرات Pauli من نتائج القياس الخام.
  • نستخدم هذه القيم لاحتساب fidelity حالة Bell المُولَّدة.

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

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

لتقييم نجاح بروتوكول long-range CX، نقيس مدى قرب حالة الإخراج من حالة Bell المثالية. من أنسب الطرق لقياس ذلك حساب fidelity الحالة باستخدام قيم توقع مؤثرات Pauli. يمكن حساب fidelity لحالة Bell على control وtarget بعد معرفة 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: بالنظر إلى أعداد القياسات، تحسب قيمة توقع مؤثر Pauli ثنائي qubit في أساس ZZ.
  • compute_fidelity: تجمع قيم توقع XXXX وYYYY وZZZZ في صيغة fidelity أعلاه.
  • get_counts_from_bitarray: أداة مساعدة لاستخراج الأعداد من كائنات نتائج backend.
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)

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

fidelities_dyn = []

# loop over trials
for job in jobs_dyn:
result_dyn = job.result()

```python
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)

الآن نحسب الدقة للدوائر الكمومية الأحادية (unitary) بعيدة المدى من نوع 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 نانوثانية، في حين لا يتجاوز طول بوابة البتين الكموميين 68 نانوثانية.

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

المراجع

[1] Efficient Long-Range Entanglement using Dynamic Circuits, by 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