قطع الدوائر لتقليل العمق
تقدير الاستخدام: ثماني دقائق على معالج Eagle (ملاحظة: هذا تقدير فقط. قد يختلف وقت التشغيل الفعلي.)
الخلفية
يوضح هذا البرنامج التعليمي كيفية بناء Qiskit pattern لقطع البوابات في دائرة كمية بهدف تقليل عمق الدائرة. للاطلاع على نقاش أعمق حول قطع الدوائر، تفضل بزيارة توثيق إضافة Qiskit لقطع الدوائر.
المتطلبات
قبل البدء بهذا البرنامج التعليمي، تأكد من تثبيت ما يلي:
- Qiskit SDK الإصدار 2.0 أو أحدث، مع دعم التصور
- Qiskit Runtime الإصدار 0.22 أو أحدث (
pip install qiskit-ibm-runtime) - إضافة Qiskit لقطع الدوائر الإصدار 0.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
from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمية
سننفذ نمط Qiskit باتباع الخطوات الأربع الموضحة في التوثيق. في هذه الحالة، سنحاكي قيم التوقع على دائرة ذات عمق معين عن طريق قطع البوابات مما ينتج عنه بوابات swap، ثم تنفيذ التجارب الفرعية على دوائر أقل عمقاً. يرتبط قطع البوابات بالخطوة 2 (تحسين الدائرة للتنفيذ الكمي عن طريق تحليل البوابات البعيدة) والخطوة 4 (المعالجة اللاحقة لإعادة بناء قيم التوقع على الدائرة الأصلية). في الخطوة الأولى، سنولد دائرة من مكتبة دوائر Qiskit ونعرّف بعض المؤثرات.
- المدخل: معاملات كلاسيكية لتعريف دائرة
- المخرج: دائرة مجردة ومؤثرات
circuit = EfficientSU2(num_qubits=4, entanglement="circular").decompose()
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observables = PauliList(["ZZII", "IZZI", "IIZZ", "XIXI", "ZIZZ", "IXIX"])
circuit.draw("mpl", scale=0.8, style="iqp")
الخطوة 2: تحسين المسألة لتنفيذها على العتاد الكمي
- المدخل: دائرة مجردة ومؤثرات
- المخرج: دائرة هدف ومؤثرات ناتجة عن قطع البوابات البعيدة لتقليل عمق الدائرة بعد التحويل
نختار تخطيطاً أولياً يستلزم swap-تين لتنفيذ البوابات بين البتين الكميتين 3 و0 وswap-تين أخريين لإعادة البتات الكمية إلى مواضعها الأصلية. نختار optimization_level=3 وهو أعلى مستوى من التحسين المتاح مع مدير التمريرات الجاهز.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=circuit.num_qubits, simulator=False
)
pm = generate_preset_pass_manager(
optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend
)
transpiled_qc = pm.run(circuit)

print(f"Transpiled circuit depth: {transpiled_qc.depth()}")
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, style="iqp", fold=-1)
Transpiled circuit depth: 103
البحث عن البوابات البعيدة وقطعها: سنستبدل البوابات البعيدة (البوابات التي تربط البتين الكميتين غير المحليتين 0 و3) بكائنات TwoQubitQPDGate عبر تحديد مؤشراتها. ستستبدل cut_gates البوابات عند المؤشرات المحددة بكائنات TwoQubitQPDGate وستُعيد أيضاً قائمة من نسخ QPDBasis -- واحدة لكل تحليل بوابة. يحتوي كائن QPDBasis على معلومات حول كيفية تحليل البوابات المقطوعة إلى عمليات أحادية البت الكمي.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]
# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)
qpd_circuit.draw("mpl", scale=0.8)
توليد التجارب الفرعية للتشغيل على الخلفية: تقبل generate_cutting_experiments دائرة تحتوي على نسخ TwoQubitQPDGate ومؤثرات على شكل PauliList.
لمحاكاة قيمة التوقع للدائرة الكاملة الحجم، يتم توليد تجارب فرعية عديدة من التوزيع الاحتمالي شبه الكمي للبوابات المحللة، ثم تنفيذها على خلفية أو أكثر. يتحكم num_samples في عدد العينات المأخوذة من التوزيع، ويُعطى معامل مشترك واحد لكل عينة فريدة. لمزيد من المعلومات حول كيفية حساب المعاملات، راجع المواد التوضيحية.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)
للمقارنة، نلاحظ أن التجارب الفرعية لـ QPD ستكون أقل عمقاً بعد قطع البوابات البعيدة: فيما يلي مثال لتجربة فرعية مختارة بشكل عشوائي تم توليدها من دائرة QPD. انخفض عمقها بأكثر من النصف. يجب توليد وتقييم كثير من هذه التجارب الفرعية الاحتمالية لإعادة بناء قيمة التوقع للدائرة الأعمق.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pm.run(subexperiments[100])
print(f"Original circuit depth after transpile: {transpiled_qc.depth()}")
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}"
)
transpiled_qpd_circuit.draw(
"mpl", scale=0.6, style="iqp", idle_wires=False, fold=-1
)
Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
من ناحية أخرى، يستلزم القطع الحاجة إلى أخذ عينات إضافية. هنا نقطع ثلاث بوابات CNOT، مما ينتج عنه عبء أخذ عينات قدره . لمزيد من المعلومات حول عبء أخذ العينات الناتج عن قطع الدوائر، راجع توثيق Circuit Knitting Toolbox.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
الخطوة 3: التنفيذ باستخدام primitives في Qiskit
نفّذ الدوائر الهدف ("التجارب الفرعية") باستخدام Sampler Primitive.
- المدخل: الدوائر الهدف
- المخرج: توزيعات الاحتمال شبه الكمي
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)
# Set up the Qiskit Runtime Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)
# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()
print(job.job_id())
czypg1r6rr3g008mgp6g
الخطوة 4: المعالجة اللاحقة وإعادة النتيجة بالصيغة الكلاسيكية المطلوبة
استخدم نتائج التجارب الفرعية والمؤثرات الفرعية ومعاملات أخذ العينات لإعادة بناء قيمة التوقع للدائرة الأصلية.
المدخل: توزيعات الاحتمال شبه الكمي المخرج: قيم التوقع المعاد بناؤها
reconstructed_expvals = reconstruct_expectation_values(
results,
coefficients,
observables,
)
# Reconstruct final expectation value
final_expval = np.dot(reconstructed_expvals, [1] * len(observables))
print("Final reconstructed expectation value")
print(final_expval)
Final reconstructed expectation value
1.0751342773437473
ideal_expvals = [
Statevector(circuit).expectation_value(SparsePauliOp(observable))
for observable in observables
]
print("Ideal expectation value")
print(np.dot(ideal_expvals, [1] * len(observables)).real)
Ideal expectation value
1.2283177520039992
استطلاع البرنامج التعليمي
يُرجى ملء هذا الاستطلاع القصير لتقديم ملاحظاتك حول هذا البرنامج التعليمي. ستساعدنا آراؤك في تحسين محتوانا وتجربة المستخدم.