دمج خيارات تخفيف الأخطاء مع أداة Estimator الأساسية
تقدير الاستخدام: 7 دقائق على معالج Heron r2 (ملاحظة: هذا تقدير فقط. قد يختلف وقت التشغيل الفعلي.)
نتائج التعلم
نقترح أن يكون المستخدمون على دراية بالمواضيع التالية قبل الشروع في هذا الدرس التعليمي:
- أساسيات الفصل الديناميكي، وتخفيف أخطاء القياس، وتدوير البوابات، والاستقراء عند انعدام الضوضاء، كما هو موضح في هذا الدليل.
المتطلبات الأساسية
بعد الانتهاء من هذا الدرس التعليمي، يجب أن يفهم المستخدمون:
- كيفية تطبيق تقنيات تخفيف الأخطاء المذكورة بشكل انتقائي على الأجهزة.
- كيف تتباين هذه التقنيات من حيث قدرتها على تخفيف ضوضاء الأجهزة.
الخلفية
يستكشف هذا الدرس التعليمي خيارات قمع الأخطاء وتخفيفها المتاحة مع أداة Estimator الأساسية من Qiskit Runtime. يوضح هذا الدرس التعليمي كيفية تطبيق كل من الأساليب التالية بشكل مستقل:
- الفصل الديناميكي (Dynamical decoupling)
- تخفيف أخطاء القياس (Measurement error mitigation)
- تدوير البوابات (Gate twirling)
- الاستقراء عند انعدام الضوضاء (Zero-noise extrapolation - ZNE)
لاحظ أن البديل لتطبيق هذه التقنيات بشكل فردي هو تطبيقها باستخدام مستوى المرونة، حيث تأخذ resilience_level القيم 0، 1، 2:
- 0 : لا يُطبَّق أي تخفيف.
- 1 : يُطبَّق تخفيف أخطاء القياس.
- 2 : يُطبَّق تدوير البوابات وتخفيف أخطاء القياس وZNE.
في هذا الدرس التعليمي، ستقوم ببناء دائرة ومتغير قابل للقياس، ثم تُرسل المهام باستخدام أداة Estimator الأساسية مع مجموعات مختلفة من إعدادات تخفيف الأخطاء. بعد ذلك، ستُخطط النتائج لمراقبة تأثيرات الإعدادات المختلفة. تستخدم معظم أجزاء الدرس دائرة من 10 كيوبتات لتسهيل التصورات البصرية، وفي النهاية ستُوسّع سير العمل إلى 50 كيوبت.
المتطلبات
قبل البدء في هذا العرض التفصيلي، تأكد من تثبيت ما يلي:
- Qiskit SDK الإصدار 2.1 أو أحدث، مع دعم التصور البصري
- Qiskit Runtime الإصدار 0.40 أو أحدث (
pip install qiskit-ibm-runtime)
الإعداد
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
import numpy as np
from qiskit.circuit.library import efficient_su2, unitary_overlap
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import Batch, EstimatorV2 as Estimator
مثال على المحاكي في النطاق الصغير
سنتجاوز هذه الخطوة لأن تخفيف الأخطاء في وقت التشغيل غير مدعوم على المحاكيات.
مثال على الأجهزة الفعلية
الخطوة 1: تعيين المدخلات الكلاسيكية إلى مسألة كمومية
يفترض هذا العرض التفصيلي أن المسألة الكلاسيكية قد تم تعيينها إلى المجال الكمومي مسبقاً. ابدأ ببناء دائرة ومتغير قابل للقياس. وبينما تنطبق التقنيات المستخدمة هنا على أنواع كثيرة من الدوائر، يستخدم هذا العرض التفصيلي للبساطة دائرة efficient_su2 المضمّنة في مكتبة دوائر Qiskit.
efficient_su2 هي دائرة كمومية ذات معاملات مصممة لتنفيذها بكفاءة على الأجهزة الكمومية ذات الاتصال المحدود بين الكيوبتات، مع الحفاظ على قدر كافٍ من التعبيرية لحل مسائل في مجالات تطبيقية كالتحسين والكيمياء. تُبنى هذه الدائرة بالتناوب بين طبقات من بوابات أحادية الكيوبت ذات معاملات وطبقة تحتوي على نمط ثابت من بوابات ثنائية الكيوبت، لعدد مختار من التكرارات. يمكن تحديد نمط بوابات ثنائية الكيوبت من قِبل المستخدم. يمكنك هنا استخدام النمط المدمج pairwise لأنه يُقلل عمق الدائرة إلى أدنى حد بتجميع بوابات ثنائية الكيوبت بأكبر كثافة ممكنة. ويمكن تنفيذ هذا النمط باستخدام اتصال خطي فقط بين الكيوبتات.
n_qubits = 10
reps = 1
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
circuit.decompose().draw("mpl", scale=0.7)

أما المتغير القابل للقياس، فلنأخذ عامل Pauli الذي يعمل على الكيوبت الأخير، . لاحظ أن كون الكيوبت الأخير يقابل العنصر الأول في هذه السلسلة يعود إلى استخدام Qiskit للترميز ذي نهاية صغيرة (little-endian).
# Z on the last qubit (index -1) with coefficient 1.0
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
في هذه المرحلة، يمكنك المتابعة لتشغيل الدائرة وقياس المتغير. ومع ذلك، تريد أيضاً مقارنة مخرجات الجهاز الكمومي بالإجابة الصحيحة — أي القيمة النظرية للمتغير لو كانت الدائرة قد نُفِّذت بدون أخطاء. بالنسبة للدوائر الكمومية الصغيرة، يمكنك حساب هذه القيمة بمحاكاة الدائرة على حاسوب كلاسيكي، لكن هذا غير ممكن للدوائر الأكبر ذات النطاق العملي. يمكنك التحايل على هذه المشكلة بتقنية "الدائرة المرآتية" (المعروفة أيضاً بـ"الحساب وإلغاء الحساب")، وهي مفيدة لقياس أداء الأجهزة الكمومية.
الدائرة المرآتية
في تقنية الدائرة المرآتية، تقوم بتسلسل الدائرة مع دائرتها العكسية، التي تُشكَّل بعكس كل بوابة من بوابات الدائرة بترتيب معكوس. تُنفذ الدائرة الناتجة عامل الهوية، الذي يمكن محاكاته بسهولة. ونظراً لأن بنية الدائرة الأصلية محفوظة في الدائرة المرآتية، فإن تنفيذها يُعطي فكرة عن أداء الجهاز الكمومي على الدائرة الأصلية.
تُسند خلية الكود التالية معاملات عشوائية إلى دائرتك، ثم تبني الدائرة المرآتية باستخدام فئة unitary_overlap. قبل عكس الدائرة، أضف إليها تعليمة حاجز لمنع المُحوِّل من دمج الجزأين من الدائرة على جانبَي الحاجز مما قد ينتج عنه دائرة محوَّلة بدون أي بوابات.
# Generate random parameters
rng = np.random.default_rng(1234)
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
# Assign the parameters to the circuit
assigned_circuit = circuit.assign_parameters(params)
# Add a barrier to prevent circuit optimization of mirrored operators
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
mirror_circuit.decompose().draw("mpl", scale=0.7)

الخطوة 2: تحسين المسألة لتنفيذها على الأجهزة الكمومية
يجب عليك تحسين دائرتك قبل تشغيلها على الجهاز. تشمل هذه العملية عدة خطوات:
- اختيار تخطيط كيوبت يُعيّن الكيوبتات الافتراضية في دائرتك إلى كيوبتات فيزيائية على الجهاز.
- إدراج بوابات التبديل (swap gates) حسب الحاجة لتوجيه التفاعلات بين الكيوبتات غير المتصلة.
- ترجمة البوابات في دائرتك إلى تعليمات معمارية مجموعة التعليمات (ISA) التي يمكن تنفيذها مباشرة على الجهاز.
- تنفيذ تحسينات الدائرة لتقليل عمقها وعدد البوابات فيها.
يستطيع المُحوِّل المدمج في Qiskit تنفيذ كل هذه الخطوات نيابةً عنك. ونظراً لأن هذا المثال يستخدم دائرة فعّالة للأجهزة، يجب أن يتمكن المُحوِّل من اختيار تخطيط كيوبت لا يستلزم إدراج أي بوابات تبديل لتوجيه التفاعلات.
تحتاج إلى اختيار الجهاز الذي ستستخدمه قبل تحسين دائرتك. تطلب خلية الكود التالية الجهاز الأقل انشغالاً (least-busy) والذي يمتلك على الأقل 127 كيوبتاً.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
print(backend)
<IBMBackend('ibm_fez')>
يمكنك تحويل دائرتك إلى الواجهة الخلفية المختارة عن طريق إنشاء مدير تمرير ثم تشغيله على الدائرة. الطريقة السهلة لإنشاء مدير تمرير هي استخدام الدالة generate_preset_pass_manager. راجع التحويل باستخدام مديري التمرير للحصول على شرح أكثر تفصيلاً حول التحويل بمديري التمرير.
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=1234
)
isa_circuit = pass_manager.run(mirror_circuit)
isa_circuit.draw("mpl", idle_wires=False, scale=0.7, fold=-1)

تحتوي الدائرة المحوَّلة الآن على تعليمات ISA فقط. تم تحليل جميع البوابات إلى بوابات وتدويرات ، وبوابات CZ.
قامت عملية التحويل بتعيين الكيوبتات الافتراضية للدائرة إلى كيوبتات فيزيائية على الجهاز. تُخزَّن معلومات تخطيط الكيوبتات في خاصية layout للدائرة المحوَّلة. وبما أن المتغير القابل للقياس تم تعريفه أيضاً بدلالة الكيوبتات الافتراضية، فأنت بحاجة إلى تطبيق هذا التخطيط على المتغير، وهو ما يمكنك فعله باستخدام طريقة apply_layout في SparsePauliOp.
isa_observable = observable.apply_layout(isa_circuit.layout)
print("Original observable:")
print(observable)
print()
print("Observable with layout applied:")
print(isa_observable)
Original observable:
SparsePauliOp(['ZIIIIIIIII'],
coeffs=[1.+0.j])
Observable with layout applied:
SparsePauliOp(['IIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII'],
coeffs=[1.+0.j])
الخطوة 3: التنفيذ باستخدام الأدوات الأساسية في Qiskit
أنت الآن مستعد لتشغيل دائرتك باستخدام أداة Estimator الأساسية.
ستُرسل هنا خمس مهام منفصلة، تبدأ بدون أي قمع أو تخفيف للأخطاء، ثم تُمكِّن تدريجياً خيارات متنوعة لقمع الأخطاء وتخفيفها المتاحة في Qiskit Runtime. للاطلاع على معلومات حول الخيارات، ارجع إلى الصفحات التالية:
- نظرة عامة على جميع الخيارات
- الفصل الديناميكي
- المرونة، بما في ذلك تخفيف أخطاء القياس والاستقراء عند انعدام الضوضاء (ZNE)
- التدوير
نظراً لإمكانية تشغيل هذه المهام بشكل مستقل عن بعضها، يمكنك استخدام وضع الدُفعات للسماح لـ Qiskit Runtime بتحسين توقيت تنفيذها.
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_SS"
] # add tag for this small scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
الخطوة 4: المعالجة اللاحقة وإعادة النتيجة بالتنسيق الكلاسيكي المطلوب
أخيراً، يمكنك تحليل البيانات. ستسترد هنا نتائج المهام، وتستخرج منها قيم التوقع المقيسة، ثم ترسمها بما في ذلك أشرطة الخطأ لانحراف معياري واحد.
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()
على هذا النطاق الصغير، يصعب رؤية تأثير معظم تقنيات تخفيف الأخطاء، غير أن الاستقراء عند انعدام الضوضاء يُعطي تحسناً ملحوظاً. ومع ذلك، لاحظ أن هذا التحسن لا يأتي مجاناً، إذ تمتلك نتيجة ZNE أيضاً شريط خطأ أكبر.
مثال الأجهزة واسع النطاق
عند تطوير تجربة، من المفيد البدء بدائرة صغيرة لتسهيل التصورات والمحاكاة. بعد أن طوّرت سير العمل واختبرته على دائرة من 10 كيوبتات، يمكنك الآن توسيعه إلى 50 كيوبت. تُكرر خلية الكود التالية جميع خطوات هذا العرض التفصيلي، لكنها تُطبِّقها الآن على دائرة من 50 كيوبت.
n_qubits = 50
reps = 1
# Construct circuit and observable
circuit = efficient_su2(n_qubits, entanglement="pairwise", reps=reps)
observable = SparsePauliOp.from_sparse_list(
[("Z", [-1], 1.0)], num_qubits=n_qubits
)
# Assign parameters to circuit
params = rng.uniform(-np.pi, np.pi, size=circuit.num_parameters)
assigned_circuit = circuit.assign_parameters(params)
assigned_circuit.barrier()
# Construct mirror circuit
mirror_circuit = unitary_overlap(assigned_circuit, assigned_circuit)
# Transpile circuit and observable
isa_circuit = pass_manager.run(mirror_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
# Run jobs
pub = (isa_circuit, isa_observable)
jobs = []
with Batch(backend=backend) as batch:
estimator = Estimator(mode=batch)
estimator.options.environment.job_tags = [
"TUT_CEM_LS"
] # add tag for this large scale job
# Set number of shots
estimator.options.default_shots = 100_000
# Disable runtime compilation and error mitigation
estimator.options.resilience_level = 0
# Run job with no error mitigation
job0 = estimator.run([pub])
jobs.append(job0)
# Add dynamical decoupling (DD)
estimator.options.dynamical_decoupling.enable = True
estimator.options.dynamical_decoupling.sequence_type = "XpXm"
job1 = estimator.run([pub])
jobs.append(job1)
# Add readout error mitigation (DD + TREX)
estimator.options.resilience.measure_mitigation = True
job2 = estimator.run([pub])
jobs.append(job2)
# Add gate twirling (DD + TREX + Gate Twirling)
estimator.options.twirling.enable_gates = True
estimator.options.twirling.num_randomizations = "auto"
job3 = estimator.run([pub])
jobs.append(job3)
# Add zero-noise extrapolation (DD + TREX + Gate Twirling + ZNE)
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 3, 5)
estimator.options.resilience.zne.extrapolator = ("exponential", "linear")
job4 = estimator.run([pub])
jobs.append(job4)
# Retrieve the job results
results = [job.result() for job in jobs]
# Unpack the PUB results (there's only one PUB result in each job result)
pub_results = [result[0] for result in results]
# Unpack the expectation values and standard errors
expectation_vals = np.array(
[float(pub_result.data.evs) for pub_result in pub_results]
)
standard_errors = np.array(
[float(pub_result.data.stds) for pub_result in pub_results]
)
# Plot the expectation values
fig, ax = plt.subplots()
labels = ["No mitigation", "+ DD", "+ TREX", "+ Twirling", "+ ZNE"]
ax.bar(
range(len(labels)),
expectation_vals,
yerr=standard_errors,
label="experiment",
)
ax.axhline(y=1.0, color="gray", linestyle="--", label="ideal")
ax.set_xticks(range(len(labels)))
ax.set_xticklabels(labels)
ax.set_ylabel("Expectation value")
ax.legend(loc="upper left")
plt.show()
عند مقارنة نتائج 50 كيوبتاً بنتائج 10 كيوبتات من قبل، قد تلاحظ ما يلي (قد تختلف نتائجك بين التشغيلات):
- جميع التجارب تُعطي نتائج أقرب إلى القيمة المثالية وأشرطة الخطأ أصغر.
- قد يكون إضافة الفصل الديناميكي قد أضرّ بالأداء مقارنةً بحالة عدم التخفيف. هذا ليس مفاجئاً، لأن الدائرة كثيفة جداً. الفصل الديناميكي مفيد أساساً عندما توجد فجوات كبيرة في الدائرة تجلس خلالها الكيوبتات خاملة بدون تطبيق أي بوابات عليها. عندما لا تكون هذه الفجوات موجودة، لا يكون الفصل الديناميكي فعالاً، بل قد يُضعف الأداء فعلياً بسبب الأخطاء في نبضات الفصل الديناميكي نفسها. ربما كانت دائرة 10 كيوبتات صغيرة جداً لملاحظة هذا التأثير.
- مع الاستقراء عند انعدام الضوضاء، تكون النتيجة قريبة جداً من القيمة المثالية. هذا يُوضح قوة تقنية ZNE.
الخطوات التالية
إذا وجدت هذا العمل مثيراً للاهتمام، فقد تجد المواد التالية مفيدة حول بعض تقنيات تخفيف الأخطاء وقمعها الإضافية التي لم تُذكر في هذا الدرس التعليمي: