مراحل المُحوِّل (Transpiler)
إصدارات الحزم
الكود في هذه الصفحة طُوِّر باستخدام المتطلبات التالية. نوصي باستخدام هذه الإصدارات أو أحدث منها.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
تصف هذه الصفحة مراحل خط أنابيب التحويل المُعدّ مسبقًا في Qiskit SDK. هناك ست مراحل:
initlayoutroutingtranslationoptimizationscheduling
تُنشئ دالة generate_preset_pass_manager مدير مراحل مُعدّ مسبقًا مؤلَّفًا من هذه المراحل. تعتمد التمريرات المحددة التي تشكّل كل مرحلة على الوسائط الممررة إلى generate_preset_pass_manager. الوسيطة optimization_level هي وسيطة موضعية يجب تحديدها؛ وهي عدد صحيح يمكن أن يكون 0 أو 1 أو 2 أو 3. تشير القيم الأعلى إلى تحسين أقوى لكنه أكثر تكلفة (انظر الإعدادات الافتراضية لخيارات التحويل والضبط).
الطريقة الموصى بها لتحويل دائرة هي إنشاء مدير مراحل مُعدّ مسبقًا ثم تشغيله على الدائرة، كما هو موضح في التحويل باستخدام مديري التمريرات. ومع ذلك، يوجد بديل أبسط وإن كان أقل قابلية للتخصيص، وهو استخدام دالة transpile. تقبل هذه الدالة الدائرة مباشرةً كوسيطة. كما هو الحال مع generate_preset_pass_manager، تعتمد تمريرات المُحوِّل المستخدمة على الوسائط، مثل optimization_level، الممررة إلى transpile. في الواقع، تستدعي دالة transpile داخليًا generate_preset_pass_manager لإنشاء مدير مراحل مُعدّ مسبقًا وتشغيله على الدائرة.
مرحلة Init
لا تفعل هذه المرحلة الأولى الكثير بشكل افتراضي، وتكون مفيدة بشكل أساسي إذا أردت تضمين تحسيناتك ال أولية الخاصة. نظرًا لأن معظم خوارزميات التخطيط والتوجيه مُصمَّمة للعمل مع بوابات الكيوبت الواحد والكيوبتين فقط، تُستخدم هذه المرحلة أيضًا لترجمة أي بوابات تعمل على أكثر من كيوبتين إلى بوابات تعمل على كيوبت أو كيوبتين فقط.
لمزيد من المعلومات حول تطبيق تحسيناتك الأولية الخاصة لهذه المرحلة، راجع القسم الخاص بالإضافات وتخصيص مديري التمريرات.
مرحلة Layout
تتعلق المرحلة التالية بتخطيط أو اتصالية الـ Backend الذي ستُرسَل إليه الدائرة. بشكل عام، الدوائر الكمومية كيانات مجردة تكون فيها الكيوبتات "افتراضية" أو "منطقية" كتمثيل للكيوبتات الفعلية المستخدمة في الحسابات. لتنفيذ سلسلة من البوابات، يلزم وجود تعيين واحد لواحد من الكيوبتات "الافتراضية" إلى الكيوبتات "المادية" في جهاز كمومي فعلي. يُخزَّن هذا التعيين ككائن Layout وهو جزء من القيود المحددة ضمن بنية مجموعة تعليمات الـ Backend (ISA).

يُعدّ اختيار التعيين بالغ الأهمية لتقليل عدد عمليات SWAP المطلوبة لتعيين دائرة الإدخال على طبولوجيا الجهاز وضمان استخدام الكيوبتات الأكثر دقة في المعايرة. نظرًا لأهمية هذه المرحلة، يجرّب مديرو التمريرات المُعدّون مسبقًا عدة طرق مختلفة للعثور على أفضل تخطيط. عادةً ما يتضمن ذلك خطوتين: أولًا، محاولة العثور على تخطيط "مثالي" (تخطيط لا يتطلب أي عمليات SWAP)، ثم تمريرة بالاستدلال تحاول العثور على أفضل تخطيط للاستخدام إذا لم يُعثر على تخطيط مثالي. هناك Passes يُستخدم عادةً للخطوة الأولى:
TrivialLayout: يُعيِّن كل كيوبت افتراضي ببساطة إلى الكيوبت المادي بالرقم نفسه على الجهاز (أي [0,1,1,3] -> [0,1,1,3]). هذا سلوك تاريخي يُستخدم فقط فيoptimzation_level=1لمحاولة إيجاد تخطيط مثالي. إذا فشل، تُجرَّبVF2Layoutبعده.VF2Layout: هذهAnalysisPassتختار تخطيطًا مثاليًا من خلال معالجة هذه المرحلة كمسألة تشاكل الرسم الجزئي، يُحلّها خوارزمية VF2++. إذا وُجد أكثر من تخطيط واحد، تُشغَّل استدلالية تسجيل لاختيار التعيين ذي أقل متوسط خطأ.
ثم في مرحلة الاستدلال، تُستخدم تمريرتان افتراضيًا:
DenseLayout: تجد الرسم الفرعي للجهاز ذا الاتصالية الأعلى والذي يحتوي على نفس عدد الكيوبتات في الدائرة (تُستخدم لمستوى التحسين 1 إذا كانت هناك عمليات تدفق تحكم مثل IfElseOp موجودة في الدائرة).SabreLayout: تختار هذه التمريرة تخطيطًا بالبدء من تخطيط عشوائي أولي وتشغيل خوارزميةSabreSwapبشكل متكرر. تُستخدم هذه التمريرة فقط في مستويات التحسين 1 و2 و3 إذا لم يُعثر على تخطيط مثالي عبر تمريرةVF2Layout. لمزيد من التفاصيل حول هذه الخوارزمية، راجع الورقة البحثية arXiv:1809.02573.
مرحلة Routing
لتطبيق بوابة ثنائية الكيوبت بين كيوبتات غير متصلة مباشرةً على جهاز كمومي، يجب إدراج بوابة SWAP واحدة أو أكثر في الدائرة لنقل حالات الكيوبت حتى تصبح متجاورة على خريطة البوابات في الجهاز. كل بوابة SWAP تمثل عملية مكلفة وضوضائية. وبالتالي، يُعدّ إيجاد الحد الأدنى من بوابات SWAP اللازمة لتعيين دائرة على جهاز معين خطوة مهمة في عملية التحويل. لأسباب كفاءة، تُحسَب هذه المرحلة عادةً جنبًا إلى جنب مع مر حلة Layout افتراضيًا، لكنهما متمايزتان منطقيًا. تختار مرحلة Layout الكيوبتات المادية المراد استخدامها، بينما تُدرج مرحلة Routing القدر المناسب من بوابات SWAP لتنفيذ الدوائر باستخدام التخطيط المختار.
ومع ذلك، يصعب إيجاد أمثل تعيين SWAP. في الواقع، هي مسألة NP-Hard، وبالتالي باهظة التكلفة الحسابية لجميع الأجهزة الكمومية ودوائر الإدخال إلا الأصغر منها. للتغلب على ذلك، يستخدم Qiskit خوارزمية استدلالية عشوائية تسمى SabreSwap لحساب تعيين SWAP جيد لكن ليس بالضرورة الأمثل. يعني استخدام الأسلوب العشوائي أن الدوائر الناتجة غير مضمونة لتكون متطابقة عبر تشغيلات متكررة. في الواقع، يُنتج تشغيل الدائرة نفسها مرارًا توزيعًا من أعماق الدوائر وأعداد البوابات في المخرجات. لهذا السبب، يختار كثير من المستخدمين تشغيل دالة التوجيه (أو StagedPassManager بأكملها) مرات عديدة واختيار الدوائر ذات أقل عمق من توزيع المخرجات.
على سبيل المثال، لنأخذ دائرة GHZ من 15 كيوبت تُنفَّذ 100 مرة، باستخدام initial_layout "سيء" (غير متصل).
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib qiskit qiskit-ibm-runtime
import matplotlib.pyplot as plt
from qiskit import QuantumCircuit
from qiskit_ibm_runtime.fake_provider import FakeAuckland, FakeWashingtonV2
from qiskit.transpiler import generate_preset_pass_manager
backend = FakeAuckland()
ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))
depths = []
for seed in range(100):
pass_manager = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
layout_method="trivial", # Fixed layout mapped in circuit order
seed_transpiler=seed, # For reproducible results
)
depths.append(pass_manager.run(ghz).depth())
plt.figure(figsize=(8, 6))
plt.hist(depths, align="left", color="#AC557C")
plt.xlabel("Depth", fontsize=14)
plt.ylabel("Counts", fontsize=14)
Text(0, 0.5, 'Counts')
يوضح هذا التوزيع الواسع مدى صعوبة قيام مُعيِّن SWAP بحساب أفضل تعيين. لنكتسب بعض الفهم، لنلقِ نظرة على الدائرة المُنفَّذة وكذلك الكيوبتات التي اختيرت على العتاد.
ghz.draw("mpl", idle_wires=False)
from qiskit.visualization import plot_circuit_layout
# Plot the hardware graph and indicate which hardware qubits were chosen to run the circuit
transpiled_circ = pass_manager.run(ghz)
plot_circuit_layout(transpiled_circ, backend)
كما ترى، تضطر هذه الدائرة إلى تنفيذ بوابة ثنائية الكيوبت بين الكيوبتين 0 و14، اللذين يقعان في أماكن بعيدة جدًا على رسم الاتصالية. وبالتالي، يتطلب تشغيل هذه الدائرة إدراج بوابات SWAP لتنفيذ جميع البوابات ثنائية الكيوبت باستخدام تمريرة SabreSwap.
لاحظ أيضًا أن خوارزمية SabreSwap تختلف عن أسلوب SabreLayout الأشمل في المرحلة السابقة. افتراضيًا، يُشغِّل SabreLayout كلًا من التخطيط والتوجيه، ويُعيد الدائرة المحوَّلة. يُنجز ذلك لأسباب تقنية محددة مبيّنة في صفحة مرجع API الخاصة بالتمريرة.
مرحلة Translation
عند كتابة دائرة كمومية، أنت حر في استخدام أي بوابة كمومية (عملية أحادية) تريدها، إلى جانب مجموعة من العمليات غير البوابية مثل تعليمات قياس الكيوبت أو إعادة ضبطه. ومع ذلك، تدعم معظم الأجهزة الكمومية أصلًا عددًا محدودًا من عمليات البوابات الكمومية وغيرها. هذه البوابات الأصيلة جزء من تعريف ISA الهدف، وتُترجم هذه المرحلة من PassManagers المُعدَّة مسبقًا البوابات المحددة في الدائرة (أو تُفكّكها) إلى البوابات الأساسية الأصيلة للـ Backend المحدد. هذه خطوة مهمة تتيح تنفيذ الدائرة بواسطة الـ Backend، لكنها عادةً تؤدي إلى زيادة العمق وعدد البوابات.
ثمة حالتان خاصتان مهمتان بشكل استثنائي توضحان ما تفعله هذه المرحلة.
- إذا لم تكن بوابة SWAP بوابة أصيلة للـ Backend المستهدف، فهذا يتطلب ثلاث بوابات CNOT:
print("native gates:" + str(sorted(backend.operation_names)))
qc = QuantumCircuit(2)
qc.swap(0, 1)
qc.decompose().draw("mpl")
native gates:['cx', 'delay', 'for_loop', 'id', 'if_else', 'measure', 'reset', 'rz', 'switch_case', 'sx', 'x']
بما أنها حاصل ثلاث بوابات CNOT، فإن SWAP تُعدّ عملية مكلفة لأداء على الأجهزة الكمومية الضوضائية. ومع ذلك، فإن مثل هذه العمليات ضرورية عادةً لتضمين دائرة ضمن اتصالية البوابات المحدودة لكثير من الأجهزة. وبالتالي، يُعدّ تقليل عدد بوابات SWAP في الدائرة هدفًا رئيسيًا في عملية التحويل.
- بوابة Toffoli، أو بوابة النفي التحكمي المضاعف (
ccx)، هي بوابة ثلاثية الكيوبت. نظرًا لأن مجموعة البوابات الأساسية لدينا تشمل بوابات الكيوبت الواحد والكيوبتين فقط، يجب تفكيك هذه العملية. ومع ذلك، فإنها مكلفة للغاية:
qc = QuantumCircuit(3)
qc.ccx(0, 1, 2)
qc.decompose().draw("mpl")
لكل بوابة Toffoli في دائرة كمومية، قد ينفذ العتاد ما يصل إلى ست بوابات CNOT وعدد من بوابات الكيوبت الواحد. يُبرهن هذا المثال على أن أي خوارزمية تستخدم بوابات Toffoli متعددة ستنتهي كدائ رة ذات عمق كبير وستتأثر بالضوضاء تأثيرًا ملحوظًا.
مرحلة Optimization
تتمحور هذه المرحلة حول تفكيك الدوائر الكمومية إلى مجموعة البوابات الأساسية للجهاز المستهدف، ويجب أن تواجه الزيادة في العمق الناتجة عن مرحلتي التخطيط والتوجيه. لحسن الحظ، هناك روتينات كثيرة لتحسين الدوائر إما بدمج البوابات أو حذفها. في بعض الحالات، تكون هذه الأساليب فعّالة لدرجة أن الدوائر الناتجة أقل عمقًا من الإدخالات، حتى بعد التخطيط والتوجيه على طبولوجيا العتاد. في حالات أخرى، لا يمكن فعل الكثير، وقد تكون الحسابات صعبة التنفيذ على الأجهزة الضوضائية. هنا تبدأ مستويات التحسين المختلفة في التباين.
- في
optimization_level=1، تُعدّ هذه المرحلةOptimize1qGatesDecompositionوCXCancellation، اللتان تدمجان سلاسل بوابات الكيوبت الواحد وتُلغيان أي بوابات CNOT متتالية. - في
optimization_level=2، تستخدم هذه المرحلة تمريرةCommutativeCancellationبدلًا منCXCancellation، التي تحذف البوابات الزائدة باستغلال علاقات التبادلية. - في
optimization_level=3، تُعدّ هذه المرحلة التمريرات التالية:
علاوةً على ذلك، تُنفِّذ هذه المرحلة أيضًا بعض الفحوصات النهائية للتأكد من أن جميع التعليمات في الدائرة مؤلَّفة من البوابات الأساسية المتاحة على الـ Backend المستهدف.
يوضح المثال التالي باستخدام حالة GHZ تأثيرات إعدادات مستوى التحسين المختلفة على عمق الدائرة وعدد البوابات.
تتباين مخرجات التحويل بسبب مُعيِّن SWAP العشوائي. لذا، من المرجح أن تتغير الأرقام أدناه في كل مرة تُشغِّل فيها الكود.
يبني الكود التالي حالة GHZ من 15 كيوبت ويقارن optimization_levels للتحويل من حيث عمق الدائرة الناتجة وأعداد البوابات وأعداد البوابات متعددة الكيوبتات.
ghz = QuantumCircuit(15)
ghz.h(0)
ghz.cx(0, range(1, 15))
depths = []
gate_counts = []
multiqubit_gate_counts = []
levels = [str(x) for x in range(4)]
for level in range(4):
pass_manager = generate_preset_pass_manager(
optimization_level=level,
backend=backend,
seed_transpiler=1234,
)
circ = pass_manager.run(ghz)
depths.append(circ.depth())
gate_counts.append(sum(circ.count_ops().values()))
multiqubit_gate_counts.append(circ.count_ops()["cx"])
fig, (ax1, ax2) = plt.subplots(2, 1)
ax1.bar(levels, depths, label="Depth")
ax1.set_xlabel("Optimization Level")
ax1.set_ylabel("Depth")
ax1.set_title("Output Circuit Depth")
ax2.bar(levels, gate_counts, label="Number of Circuit Operations")
ax2.bar(levels, multiqubit_gate_counts, label="Number of CX gates")
ax2.set_xlabel("Optimization Level")
ax2.set_ylabel("Number of gates")
ax2.legend()
ax2.set_title("Number of output circuit gates")
fig.tight_layout()
plt.show()
الجدولة (Scheduling)
لا تُشغَّل هذه المرحلة الأخيرة إلا إذا طُلب ذلك صراحةً (مشابهة لمرحلة Init) ولا تعمل افتراضيًا (وإن كان بالإمكان تحديد أسلوب بضبط وسيطة scheduling_method عند استدعاء generate_preset_pass_manager). تُستخدم مرحلة الجدولة عادةً بعد ترجمة الدائرة إلى البوابات الأساسية المستهدفة وتعيينها على الجهاز وتحسينها. تُركّز هذه التمريرات على حساب وقت التعطل في الدائرة. على مستوى عالٍ، يمكن النظر إلى تمريرة الجدولة باعتبارها تُدرج تعليمات تأخير صريحة لحساب وقت التعطل بين تنفيذات البوابات وفحص مدة تشغيل الدائرة على الـ Backend.
إليك مثالًا:
ghz = QuantumCircuit(5)
ghz.h(0)
ghz.cx(0, range(1, 5))
# Use fake backend
backend = FakeWashingtonV2()
# Run with optimization level 3 and 'asap' scheduling pass
pass_manager = generate_preset_pass_manager(
optimization_level=3,
backend=backend,
scheduling_method="asap",
seed_transpiler=1234,
)
circ = pass_manager.run(ghz)
circ.draw(output="mpl", idle_wires=False)
أدرج المُحوِّل تعليمات Delay لحساب وقت التعطل على كل كيوبت. للحصول على فكرة أفضل عن توقيت الدائرة، يمكننا أيضًا النظر إليها باستخدام دالة timeline.draw():
تتضمن جدولة الدائرة جزأين: التحليل وتعيين القيود، تليهما تمريرة حشو. يتطلب الجزء الأول تشغيل تمريرة تحليل جدولة (وهي افتراضيًا
ALAPSchedulingAnalysis)، التي تحلل الدائرة وتسجّل وقت بدء كل تعليمة في الدائرة ضمن جدول. بمجرد أن تحصل الدائرة على جدول أولي، يمكن تشغيل تمريرات إضافية لحساب أي قيود توقيت على الـ Backend المستهدف. وأخيرًا، يمكن تنفيذ تمريرة حشو مثل PadDelay أو PadDynamicalDecoupling.
الخطوات التالية
- لمعرفة كيفية استخدام دالة
generate_preset_passmanager، ابدأ بموضوع الإعدادات الافتراضية وخيارات الضبط للتحويل. - واصل التعلم عن التحويل مع موضوع المُحوِّل مع مديري التمريرات.
- جرّب دليل مقارنة إعدادات المُحوِّل.
- اطّلع على توثيق Transpile API.