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

قطع الدائرة لشروط الحدود الدورية

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

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

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

أجهزة IBM Quantum® الحالية مستوية (planar). يمكن تضمين بعض السلاسل الدورية مباشرةً على الطوبولوجيا بحيث يكون الكيوبت الأول والأخير جارَين. غير أنه في المسائل الكبيرة بما يكفي، قد يكون الكيوبت الأول والأخير بعيدَين عن بعضهما، مما يستلزم عددًا كبيرًا من بوابات SWAP لتنفيذ العملية الثنائية بين هذين الكيوبتين. وقد تمت دراسة مسألة الحدود الدورية هذه في هذه الورقة.

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

تجدر الإشارة إلى أن هناك نوعين من القطع: قطع سلك الدائرة (يُسمى wire cutting)، أو استبدال بوابة ثنائية الكيوبت بعمليات أحادية الكيوبت متعددة (يُسمى gate cutting). في هذا الدفتر سنركز على gate cutting. لمزيد من التفاصيل حول gate cutting، راجع المواد التوضيحية في qiskit-addon-cutting والمراجع المقابلة. لمزيد من التفاصيل حول wire cutting، راجع درس قطع الأسلاك لتقدير قيم التوقع، أو الدروس في qiskit-addon-cutting.

المتطلبات

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

  • Qiskit SDK الإصدار 1.2 أو أحدث (pip install qiskit)
  • Qiskit Runtime الإصدار 0.3 أو أحدث (pip install qiskit-ibm-runtime)
  • إضافة Circuit cutting لـ Qiskit الإصدار 9.0 أو أحدث (pip install qiskit-addon-cutting)

الإعداد

# Added by doQumentation — installs packages not in the Binder environment
%pip install -q qiskit-addon-cutting
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal

from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, Batch

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

هنا سنُنشئ دائرة TwoLocal ونُعرِّف بعض المؤثرات القابلة للقياس (observables).

  • المدخل: معاملات لإنشاء دائرة
  • المخرج: دائرة مجردة ومؤثرات قابلة للقياس

نأخذ في الاعتبار entangler map فعّالًا من الناحية العتادية لدائرة TwoLocal مع ترابط دوري بين الكيوبت الأخير والأول في entangler map. يمكن أن يؤدي هذا الارتباط بعيد المدى إلى بوابات SWAP إضافية أثناء الـ transpilation، مما يزيد من عمق الدائرة.

اختيار الخلفية والتخطيط الأولي

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

في هذا الدفتر سنأخذ في الاعتبار سلسلة دورية أحادية البعد مكونة من 109 كيوبت، وهي أطول سلسلة أحادية البعد في طوبولوجيا جهاز IBM Quantum ذي 127 كيوبت. لا يمكن ترتيب سلسلة دورية مكونة من 109 كيوبت على جهاز بـ 127 كيوبت بحيث يكون الكيوبت الأول والأخير جارَين دون إضافة بوابات SWAP إضافية.

init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]

# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubits
109

بناء entangler map لدائرة TwoLocal

coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivity

تسمح دائرة TwoLocal بتكرار rotation_blocks وentangler map عدة مرات. في هذه الحالة، يحدد عدد التكرارات عدد البوابات الدورية التي يجب قطعها. نظرًا لأن تكلفة أخذ العينات تتزايد أسيًا مع عدد القطوعات (راجع درس قطع الأسلاك لتقدير قيم التوقع لمزيد من التفاصيل)، سنثبت عدد التكرارات على 2 في هذا الدفتر.

num_reps = 2
entangler_map = []

for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)

for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)
ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)

Output of the previous code cell

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

سنُعيِّن قيمة المعامل 00 لطبقتي Rx الأوليتين، وقيمة π\pi للطبقة الأخيرة. يضمن هذا أن تكون النتيجة المثالية لهذه الدائرة هي 1n|1\rangle^{\otimes n}، حيث nn هو عدد الكيوبتات. وبالتالي، تكون قيم التوقع لـ Zi\langle Z_i \rangle وZiZi+1\langle Z_i Z_{i+1} \rangle، حيث ii هو مؤشر الكيوبت، هي 1-1 و+1+1 على التوالي.

params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)

ansatz.assign_parameters(params, inplace=True)

اختيار المؤثرات القابلة للقياس

لقياس فوائد gate cutting نقيس قيم التوقع للمؤثرات 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle و1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle. كما ذُكر سابقًا، قيم التوقع المثالية هي 1-1 و+1+1 على التوالي.

observables = []

for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)

for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)

observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffs

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

  • المدخل: دائرة مجردة ومؤثرات قابلة للقياس
  • المخرج: دائرة هدف ومؤثرات قابلة للقياس ناتجة عن قطع البوابات بعيدة المدى

Transpile الدائرة

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

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

coupling_map = backend.configuration().coupling_map

# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])
pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)

virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)

Output of the previous code cell

قطع الترابطات الدورية بعيدة المدى

الآن نقطع البوابات في الدائرة المُحوَّلة. لاحظ أن البوابات الثنائية التي يجب قطعها هي تلك التي تربط الكيوبت الأخير بالأول في التخطيط.

# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(virtual_mapped_circuit.data)
if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}
== {init_layout[-1], init_layout[0]}
]

سنُطبِّق تخطيط الدائرة المُحوَّلة على المؤثر القابل للقياس.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

أخيرًا، يتم توليد التجارب الفرعية عن طريق أخذ عينات من أسس قياس وإعداد مختلفة.

qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)

لاحظ أن قطع الترابطات بعيدة المدى يؤدي إلى تنفيذ عينات متعددة من الدائرة تختلف في أسس القياس والإعداد. مزيد من المعلومات حول هذا الموضوع في Constructing a virtual two-qubit gate by sampling single-qubit operations وCutting circuits with multiple two-qubit unitaries.

عدد البوابات الدورية التي يجب قطعها يساوي عدد تكرارات طبقة TwoLocal، المُعرَّف بـ num_reps أعلاه. تكلفة أخذ العينات لـ gate cutting تساوي 6. لذلك، سيكون إجمالي عدد التجارب الفرعية 6num_reps6^{num\_reps}.

print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2

Transpile التجارب الفرعية

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

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

سنُطبِّق مسارَي BasisTranslator ثم Optimize1qGatesDecomposition لضمان أن جميع البوابات في هذه الدوائر تنتمي إلى مجموعة البوابات الأساسية. يُعدّ استخدام هذين المسارين أسرع من عملية الـ transpilation الكاملة، إذ لا تُنفَّذ خطوات أخرى كالتوجيه واختيار التخطيط الأولي مجددًا.

pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)

subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)

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

  • المدخل: الدوائر الهدف
  • المخرج: توزيعات الاحتمالية الشبه-كلاسيكية (Quasi-probability distributions)

نستخدم primitive من نوع SamplerV2 لتنفيذ الدوائر المقطوعة. نُعطِّل dynamical decoupling وtwirling حتى يكون أي تحسن نحصل عليه في النتائج راجعًا فقط إلى التطبيق الفعّال لـ gate cutting لهذا النوع من الدوائر.

options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False

الآن سنُرسِل المهام باستخدام وضع batch.

with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)

print(f"Job ID {cut_job.job_id()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()

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

  • المدخل: توزيعات الاحتمالية الشبه-كلاسيكية
  • المخرج: قيم التوقع المُعاد بناؤها
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

الآن نحسب متوسط مؤثرات Z من الوزن-1 والوزن-2.

cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])

print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495

التحقق المتقاطع: الحصول على قيمة التوقع غير المقطوعة

من المفيد التحقق المتقاطع من ميزة تقنية قطع الدائرة مقارنةً بالحالة غير المقطوعة. هنا سنحسب قيم التوقع دون قطع الدائرة. لاحظ أن مثل هذه الدائرة غير المقطوعة ستعاني من عدد كبير من بوابات SWAP اللازمة لتنفيذ العملية الثنائية بين الكيوبت الأول والأخير. سنستخدم دالة sampled_expectation_value للحصول على قيم التوقع للدائرة غير المقطوعة بعد الحصول على توزيع الاحتمالية عبر SamplerV2. هذا يُتيح استخدامًا موحدًا لـ primitive عبر جميع الحالات. غير أنه تجدر الإشارة إلى أنه كان بإمكاننا استخدام EstimatorV2 أيضًا لحساب قيم التوقع مباشرةً.

if ansatz.num_clbits == 0:
ansatz.measure_all()

pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)

transpiled_circuit = pm_uncut.run(ansatz)
sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])
uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()

الآن سنحسب متوسط قيم التوقع لجميع مؤثرات Z من الوزن-1 والوزن-2 دون قطع.

uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]

uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])

print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656

التمثيل المرئي

لنُمثِّل الآن بيانيًا التحسن الذي تحقق في مؤثرات الوزن-1 والوزن-2 عند استخدام gate cutting لدائرة السلسلة الدورية.

mpl.rcParams.update(mpl.rcParamsDefault)

fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))

ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]

br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]

plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)

plt.axhline(y=0, color="k", linestyle="-")

plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)

plt.legend(fontsize=14)
plt.show()

Output of the previous code cell

الملخص

باختصار، حسبنا متوسط قيم التوقع لمؤثرات Z من الوزن-1 والوزن-2 لسلسلة دورية أحادية البعد مكونة من 109 كيوبت. وللقيام بذلك:

  • أنشأنا خريطة اقتران افتراضية بإضافة ترابط بعيد المدى بين الكيوبت الأول والأخير في السلسلة الأحادية البعد، وأجرينا transpile للدائرة.
    • أتاح لنا الـ transpilation في هذه المرحلة تجنب تكلفة transpile كل تجربة فرعية على حدة بعد القطع،
    • أتاح لنا استخدام خريطة الاقتران الافتراضية تجنب بوابات SWAP الإضافية للعملية الثنائية بين الكيوبت الأول والأخير.
  • أزلنا الترابط بعيد المدى من الدائرة المُحوَّلة عبر gate cutting.
  • حوّلنا الدوائر المقطوعة إلى مجموعة البوابات الأساسية بتطبيق مسارات transpilation مناسبة.
  • نفّذنا الدوائر المقطوعة على جهاز IBM Quantum باستخدام primitive من نوع SamplerV2.
  • حصلنا على قيمة التوقع بإعادة بناء نتائج الدوائر المقطوعة.

الاستنتاج

نلاحظ من النتائج أن متوسط مؤثرات Z\langle Z \rangle من الوزن-1 ومؤثرات ZZ\langle ZZ \rangle من الوزن-2 تتحسن بشكل ملحوظ عند قطع البوابات الدورية. لاحظ أن هذه الدراسة لا تتضمن أي تقنيات لقمع الأخطاء أو تخفيفها. التحسن الملحوظ يعود فقط إلى الاستخدام الصحيح لـ gate cutting لهذه المسألة. كان يمكن تحسين النتائج أكثر باستخدام تقنيات التخفيف والقمع.

تُظهر هذه الدراسة مثالًا على الاستخدام الفعّال لـ gate cutting لتحسين أداء الحسابات الكمومية.

استطلاع الدرس

يُرجى إجراء هذا الاستطلاع القصير لتقديم ملاحظاتك حول هذا الدرس. ستساعدنا آراؤك في تحسين محتوانا وتجربة المستخدم.

رابط الاستطلاع