المترجم (Transpiler)
الدالة generate_boxing_pass_manager() أداة مرنة ومريحة لبناء qiskit.transpiler.PassManager يستطيع تجميع تعليمات الدائرة في صناديق معلَّقة بتعليقات. هذا يوفر بديلاً سهلاً للتجميع والتعليق اليدوي للدوائر، وهو مفيد لأتمتة سير العمل واختبار استراتيجيات مختلفة لوضع الدوائر في صناديق وما إلى ذلك.
يوضح هذا الدليل كيفية استخدام generate_boxing_pass_manager() ومعاملاتها. ولإبراز تأثير كل معامل من معاملات الدالة، تشير الأقسام التالية بشكل أساسي إلى الدائرة أدناه.
from qiskit.circuit import Parameter, QuantumCircuit
circuit = QuantumCircuit(4, 7)
circuit.h(1)
circuit.h(2)
circuit.cz(1, 2)
circuit.h(1)
circuit.cx(1, 0)
circuit.cx(2, 3)
circuit.measure(range(1, 4), range(3))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
for qubit in range(4):
circuit.rz(Parameter(f"th_{qubit}"), qubit)
circuit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit.measure(range(4), range(3, 7))
circuit.draw("mpl", scale=0.8)
تجميع العمليات في صناديق
يمكن ضبط المعامل enable_gates على True أو False لتحديد ما إذا كان يجب تجميع البوابات ثنائية الكيوبت في صناديق. وبالمثل، يحدد enable_measures ما إذا كان يجب تجميع عمليات القياس أم لا. المقطع التالي يعرض مثالاً تُجمَّع فيه البوابات وعمليات القياس معاً.
from samplomatic.transpiler import generate_boxing_pass_manager
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)

كما يظهر في الشكل، ينشئ مدير التمريرات (pass manager) نوعين من الصناديق: صناديق تحتوي على طبقة واحدة من البوابات ثنائية الكيوبت، وصناديق تحتوي على طبقة واحدة من عمليات القياس. يعكس هذا الفصل الممارسات المعتادة في بروتوكولات تعلّم الضجيج وتخفيفه، التي تستهدف عادة طبقات من العمليات المتجانسة. توضع البوابات ثنائية الكيوبت وعمليات القياس في أقصى صندوق إلى اليسار يستطيع استيعابها، وتوضع كل بوابة كيوبت واحد في نفس الصندوق مع بوابة الكيوبتين أو عملية القياس التي تسبقها.
الكود التالي يعرض مثالاً ضُبط فيه enable_gates على False. كما ترى، لا تُجمَّع البوابات ثنائية الكيوبت في صناديق، ولا بوابات الكيوبت الواحد التي تسبقها.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=False,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
اختيار طريقة تعليق الصناديق
كل صناديق البوابات ثنائية الكيوبت وصناديق القياس في الدائرة المُعادة تملك تعليقات تلبيس يساري. وبالأخص، كل الصناديق التي تحتوي على بوابات ثنائية الكيوبت تُعلَّق بـ Twirl. أما بالنسبة لصناديق القياس، فيمكن للمستخدمين الاختيار بين Twirl أو BasisTranform (مع ضبط mode على "measure") أو كليهما. الكود التالي يولّد دائرة تخضع فيها كل الصناديق لـ twirl، وتُعلَّق صناديق القياس إضافةً بـ BasisTranform.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
measure_annotations="all",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
تهيئة الدائرة لحقن الضجيج
يتيح لك inject_noise_targets تحديد الصناديق التي يجب أن تحصل على تعليق InjectNoise. على سبيل المثال، الكود التالي يولّد دائرة تملك فيها صناديق البوابات ثنائية الكيوبت تعليق InjectNoise بينما لا تملكه صناديق القياس.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
إذا احتوت الدائرة على صندوقين أو أكثر متكافئة حتى بوابات الكيوبت الواحد، فتُعلَّق كلها بتعليق InjectNoise له نفس قيمة ref. وبالتالي، يساوي عدد قيم ref الفريدة في الدائرة المُعادة عدد الصناديق الفريدة.
تؤثر قيمة inject_noise_strategy على تعليقات InjectNoise. القيم الممكنة هي:
-
modifier_ref='': موصى به عندما لا تحتاج إلى تعديل خرائط الضجيج قبل السحب منها. -
modifier_ref=ref: موصى به عندما تحتاج كل خرائط الضجيج إلى التحجيم بشكل موحّد بنفس العامل. -
قيمة فريدة لـ
modifier_ref: موصى بها عندما تحتاج كل خريطة ضجيج إلى التحجيم بعامل مختلف.
الكود التالي يولّد دائرة تملك فيها صناديق البوابات ثنائية الكيوبت تعليق InjectNoise بقيم modifier_ref فريدة.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
inject_noise_targets="gates",
inject_noise_strategy="individual_modification",
)
transpiled_circuit = boxing_pass_manager.run(circuit)
اختيار استراتيجية twirling
يبدأ مدير تمريرات الصناديق باختيار بوابات التشابك وتعليمات القياس التي يجب أن تظهر في نفس الصناديق. بعد ذلك، لن تكون الصناديق عموماً بعرض كامل، وإنما ستشمل على الأرجح مجموعة جزئية فقط من الكيوبتات النشطة في الدائرة أو الموجودة في هدف الـ Backend. في التهيئات النموذجية لمدير تمريرات الصناديق، ولأن كل الكيوبتات التي تؤثر عليها تعليمة الصندوق تخضع لـ twirl (حتى تلك التي لا تؤثر عليها أي تعليمة داخل الصندوق)، فإن وجود صناديق جزئية العرض يؤثر على تأثيرات تفصيل الضجيج الناتجة عن twirling. الكيوبتات الخاملة لا تخضع لـ twirl، ويمكن مثلاً أن تتراكم فيها أخطاء متماسكة أثناء تطبيق الصندوق. يوفر twirling_strategy خيارات لتوسيع الكيوبتات التي يملكها كل صندوق، وبالتالي تعديل عدد الكيوبتات الخاملة:
-
"active"(الافتراضي) لا يوسّع الصناديق لتشمل أي كيوبتات خاملة. -
"active_accum"يوسّع كل الصناديق لتشمل الكيوبتات التي أثّرت عليها تعليمة سابقة في الدائرة. هذا مفيد إذا أردت ترك الكيوبتات في بداية الدائرة في حالتها الأرضية أطول وقت ممكن بعدم عمل twirl لها حتى تصبح نشطة. -
"active_circuit"يوسّع كل الصناديق لتشمل الكيوبتات التي تؤثر عليها أي تعليمة في الدائرة. هذا مفيد إذا كانت لديك دائرة مهيكلة بطبقات تشابك متكررة وتريد تقليل عدد الصناديق الفريدة لأن كل واحد منها يحتاج إلى تعلّم ضجيجه. -
"all"يوسّع كل الصناديق لتشمل كل الكيوبتات في كل السجلات الكمومية للدائرة. هذا مفيد كامتداد لـ"active_circuit"عندما تريد أن تخضع كل الكيوبتات في QPU لـ twirl دائماً.
في المثال التالي، تُطبَّق كل استراتيجية على الدائرة الأساسية، ثم تُرسم النتائج.
circuit = QuantumCircuit(5, 2)
circuit.h(range(4))
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.cx(2, 3)
circuit.barrier()
circuit.h(range(4))
circuit.measure([1, 2], [0, 1])
circuit.draw("mpl", scale=0.6)
import matplotlib.pyplot as plt
plt.figure(figsize=(9, 6))
for idx, strategy in enumerate(["active", "active_accum", "active_circuit", "all"]):
ax = plt.subplot(2, 2, idx + 1)
pm = generate_boxing_pass_manager(twirling_strategy=strategy, remove_barriers="never")
transpiled_circuit = pm.run(circuit)
fig = transpiled_circuit.draw("mpl", scale=0.8, ax=ax)
ax.set_title(f"twirling_strategy='{strategy}'")
plt.tight_layout()

تحديد كيفية التعامل مع الحواجز (barriers)
افتراضياً، تُزال الحواجز من الدائرة بعد وضع كل بوابات التشابك وتعليمات القياس في صناديق حسب التعليمات، بما في ذلك امتداداتها الناتجة عن اختيار twirling_strategy، لكن قبل إضافة بوابات الكيوبت الواحد إلى الصندوق الذي يقع مباشرة على يمينها، إن أمكن.
على سبيل المثال، الدائرة التالية تحتوي على حاجز:
circuit_with_barrier = QuantumCircuit(4)
circuit_with_barrier.h(range(4))
circuit_with_barrier.cz(0, 1)
circuit_with_barrier.barrier()
circuit_with_barrier.cz(2, 3)
circuit_with_barrier.measure_all()
circuit_with_barrier.draw("mpl", scale=0.8)
السلوك الافتراضي يمنع بوابتي التشابك من الانتهاء في نفس الصندوق، مع أنه يسمح بامتصاص بوابات الكيوبت الواحد في الصندوق التالي المتاح رغم وجود الحاجز.
boxing_pass_manager = generate_boxing_pass_manager()
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
ضبط remove_barriers على 'never' يحافظ على الحواجز بحيث تكون بوابات الكيوبت الواحد مقيّدة.
boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="never")
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
وأخيراً، إذا أُزيلت الحواجز فوراً، فلن تكون بوابات التشابك مقيّدة بالانتهاء في صناديق منفصلة.
boxing_pass_manager = generate_boxing_pass_manager(remove_barriers="immediately")
transpiled_circuit = boxing_pass_manager.run(circuit_with_barrier)
transpiled_circuit.draw("mpl", scale=0.8)
كيف يعمل التقسيم إلى طبقات (stratification)
يشغّل مدير تمريرات الصناديق تمريرتين أساسيتين بالتسلسل:
-
GroupGatesIntoBoxes— يقسّم بوابات التشابك إلى طبقات داخل صناديق. تُتجاهل بوابات الكيوبت الواحد في هذه المرحلة؛ تُجمَّع البوابات ثنائية الكيوبت فقط. -
AbsorbSingleQubitGates— يمتص كل بوابة كيوبت واحد في أقرب صندوق مؤهل على يمينها.
أثناء التقسيم إلى طبقات، يجتاز GroupGatesIntoBoxes الـ DAG للدائرة بالترتيب الطوبولوجي ويضع كل بوابة ثنائية الكيوبت في أبكر مجموعة لم تُشغَل كيوباتها بعد. ثلاثة أنواع من التعليمات تعمل كـ فواصل (delimiters) تُفرغ المجموعة الحالية لكيوباتها، مما يجبر البوابات اللاحقة على تلك الكيوبتات على الدخول في مجموعة جديدة:
-
الحواجز (Barriers)
-
تعليمات الصناديق الموجودة
-
Measurements
بوابات الكيوبت الواحد ليست فواصل — فليس لها تأثير على التجميع وتُترك لـ AbsorbSingleQubitGates ليعالجها. لا يُجرى حالياً أي تحليل للتبادلية (commutativity)؛ يحترم التجميع الترتيب الطوبولوجي للدائرة كما هو.
لماذا يُعدّ remove_barriers="after_stratification" الوضع الافتراضي الموصى به
الوضع الافتراضي هو "after_stratification" لأنه يجمع بين فائدتين:
-
الحواجز تقيّد طريقة تجميع بوابات التشابك (فهي تعمل كفواصل أثناء التقسيم إلى طبقات).
-
تُزال الحواجز قبل تشغيل
AbsorbSingleQubitGates، لذلك لا تزال بوابات الكيوبت الواحد قادرة على العبور من مواضع الحواجز السابقة وأن تُمتَص في أقرب صندوق.
في المقابل:
-
"never"يُبقي الحواجز طوال الوقت، مما يحبس بوابات الكيوبت الواحد بين حاجز وصندوق، فيمنع امتصاصها ويترك دورانات بلا twirl في الدائرة. -
"immediately"يزيل الحواجز قبل التقسيم إلى طبقات، فلا يكون لها تأثير على التجميع. -
"finally"مفيد عندما تريد حواجز في الدائرة الناتجة (مثلاً للتصوير)، لكن بوابات الكيوبت الواحد الواقعة بين حاجز وصندوق لا تُمتَص.
تأطير دوائر ISA بصناديق باستخدام الحواجز (barriers)
عادةً ما تُبنى الدوائر ذات البنية المتكررة — مثل محاكاة هاملتونيان بطريقة تروتر، وansatze المتغيرة، وتسلسلات تضخيم الأخطاء — برمجيًا ثم تُحوَّل (transpile) إلى صيغة ISA. إضافة الصناديق يدويًا إلى دائرة محوَّلة أمر مرهق، لذا فإن سير العمل الطبيعي هو كالتالي:
-
ابنِ الدائرة المنطقية، مع إدراج حواجز (barriers) عند حدود الطبقات التي تريد أن تصبح حدودًا للصناديق.
-
حوِّل الدائرة إلى دوائر ISA. يحافظ المحوِّل (transpiler) في Qiskit على الحواجز أثناء التوجيه والتحسين.
-
شغِّل
generate_boxing_pass_manager()على دائرة ISA.
تلزم الحواجز عندما لا تغطي نهاية إحدى الطبقات جميع الكيوبتات. وإلا فإن الخوارزمية الطماعة ستضع بوابات الطبقة التالية — على تلك الكيوبتات "الحرة" — في المجموعة نفسها. وكمثال ملموس، لنأخذ محاكاة هايزنبرغ ZZ من خطوتين على حلقة من 5 كيوبتات. تحتوي كل خطوة تروتر على ثلاث طبقات من الروابط: الروابط الزوجية (0,1), (2,3)، والروابط الفردية (1,2), (3,4)، ورابط الالتفاف (4,0). بعد طبقة الروابط الفردية، يكون الكيوبتان 0 و4 حرّين. وبدون حاجز، يُجمَّع رابط الالتفاف (4,0) بطريقة طماعة مع روابط من خطوة تروتر التالية تستخدم هذين الكيوبتين الحرّين، فتختلط بوابات من خطوات مختلفة في الصندوق نفسه.
from qiskit.circuit import Parameter, QuantumCircuit
n = 5 # 5-qubit ring: bonds (0,1), (1,2), (2,3), (3,4), (4,0)
dt = Parameter("dt")
heisenberg = QuantumCircuit(n)
for step in range(2):
# Layer 1: even bonds (0,1) and (2,3)
for idx in range(0, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 2: odd bonds (1,2) and (3,4) -- qubits 0 and 4 are now free
for idx in range(1, n - 1, 2):
heisenberg.rzz(dt, idx, idx + 1)
heisenberg.barrier()
# Layer 3: wrap bond (4,0) -- both qubits were free after layer 2
heisenberg.rzz(dt, n - 1, 0)
if step < 1:
heisenberg.barrier() # separates the two Trotter steps
heisenberg.measure_all()
heisenberg.draw("mpl", scale=0.7)
يحوّل الكود التالي الدائرة إلى صيغة ISA، بافتراض أن الجهاز الأساسي يستخدم CX كبوابة تشابك أصلية. لاحظ أن الحواجز تبقى بعد التحويل وتظل ظاهرة في الدائرة الناتجة.
from qiskit.transpiler import generate_preset_pass_manager
preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
isa_circuit = preset_pm.run(heisenberg)
isa_circuit.draw("mpl", scale=0.5, fold=200)
بعد ذلك، طبّق مدير تمريرات التأطير على دائرة ISA. تصبح كل طبقة من طبقات الروابط الثلاث صندوقًا منفصلًا في كل خطوة تروتر.
boxing_pm = generate_boxing_pass_manager()
boxed_isa = boxing_pm.run(isa_circuit)
boxed_isa.draw("mpl", scale=0.5, fold=100)
دمج مدير تمريرات Samplomatic في مديري التمريرات المسبقة الإعداد في Qiskit
يوضح الكود أدناه كيفية دمج generate_boxing_pass_manager() في مدير تمريرات مسبق الإعداد في Qiskit. الدائرة المحوَّلة بصيغة ISA وتحتوي على صناديق.
from qiskit.transpiler import generate_preset_pass_manager
preset_pass_manager = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2]],
optimization_level=0,
)
boxing_pass_manager = generate_boxing_pass_manager()
# Run the boxing pass manager after the scheduling stage
preset_pass_manager.post_scheduling = boxing_pass_manager
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(0, 2)
circuit.measure_all()
transpiled_circuit = preset_pass_manager.run(circuit)
transpiled_circuit.draw("mpl", scale=0.8)
يمكن أيضًا الجمع بين هذا النمط وسير عمل تأطير ISA المعتمد على الحواجز بشكل طبيعي. يعمل مدير تمريرات التأطير كخطوة لاحقة للجدولة، بعد أن تكون الحواجز من الدائرة المنطقية الأصلية قد بقيت بعد التحويل، لذلك فهي توجّه التقسيم إلى طبقات كما هو متوقع.
preset_pm = generate_preset_pass_manager(
basis_gates=["rz", "sx", "cx"],
coupling_map=[[0, 1], [1, 2], [2, 3], [3, 4], [4, 0]],
optimization_level=1,
)
preset_pm.post_scheduling = generate_boxing_pass_manager()
boxed_heisenberg = preset_pm.run(heisenberg)
boxed_heisenberg.draw("mpl", scale=0.5, fold=100)
فحص التعليقات التوضيحية وتعديلها
بعد التأطير التلقائي، قد ترغب في فحص التعليقات التوضيحية على صناديق محددة أو تعديلها. على سبيل المثال، لإضافة حقن الضوضاء، أو إزالة التعليقات التوضيحية، أو تغيير مجموعة التقليب العشوائي (twirling). توفر Samplomatic مجموعة من الدوال المساعدة في samplomatic.utils لهذا الغرض. وجميعها تُرجع دائرة جديدة دون تعديل الأصلية.
تضيف extend_annotations() تعليقًا توضيحيًا واحدًا أو أكثر إلى كل صندوق في الدائرة.
from samplomatic import InjectNoise, Twirl
from samplomatic.utils import extend_annotations
boxed = generate_boxing_pass_manager().run(circuit_with_barrier)
# Add InjectNoise to every box
with_noise = extend_annotations(boxed, InjectNoise("my_noise_ref"))
with_noise.draw("mpl", scale=0.8)
تُبقي filter_annotations() على التعليقات التوضيحية من الأنواع المحددة فقط وتزيل الباقي. أما replace_annotations() فتطبّق دالة قابلة للاستدعاء على كل تعليق توضيحي؛ وتُرجع هذه الدالة قائمة بالتعليقات التوضيحية التي ستحل محله (أرجع [] للإزالة، أو قائمة بعنصر واحد للاستبدال، أو قائمة بعدة عناصر للتوسيع).
from samplomatic.utils import filter_annotations, replace_annotations
# Keep only Twirl annotations, drop everything else
twirl_only = filter_annotations(with_noise, Twirl)
# Replace the twirling group on every Twirl annotation
updated = replace_annotations(
twirl_only, lambda a: [Twirl(group="local_c1")] if isinstance(a, Twirl) else [a]
)
تقبل الدالة العامة map_annotations() أي دالة قابلة للاستدعاء تحوّل قائمة تعليقات توضيحية إلى قائمة جديدة، مما يمنحك تحكمًا كاملًا في التحويلات لكل صندوق. أما strip_annotations() فهي غلاف مريح يزيل جميع التعليقات التوضيحية من كل صندوق.
بالنسبة إلى التغييرات الجماعية مثل إضافة InjectNoise إلى جميع صناديق البوابات، فإن المعامل inject_noise_targets في generate_boxing_pass_manager() هو الخيار الأكثر إيجازًا. وتفيد هذه الأدوات في التعديلات الانتقائية لكل صندوق التي لا تغطيها معاملات مدير التمريرات.
بناء دائرة
كل مدير تمريرات ينتجه generate_boxing_pass_manager() يُرجع دوائر يمكن تحويلها بنجاح إلى زوج قالب/samplex بواسطة build(). على سبيل المثال، يستدعي الكود التالي الدالة build() على دائرة أنتجها مدير تمريرات التأطير.
from samplomatic import build
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit)
template, samplex = build(transpiled_circuit)
لضمان إمكانية بناء أي دائرة محوَّلة بنجاح، تعرف مديرو التمريرات كيف تضيف صناديق إضافية عند الحاجة. على سبيل المثال، لنتأمل الدائرة أدناه، التي تنتهي بكيوبت غير مقيس 0.
circuit_with_unmeasured_qubit = QuantumCircuit(4, 3)
circuit_with_unmeasured_qubit.cz(0, 1)
circuit_with_unmeasured_qubit.cz(2, 3)
for qubit in range(4):
circuit_with_unmeasured_qubit.rz(Parameter(f"th_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rx(Parameter(f"phi_{qubit}"), qubit)
circuit_with_unmeasured_qubit.rz(Parameter(f"lam_{qubit}"), qubit)
circuit_with_unmeasured_qubit.measure(range(1, 4), range(3))
circuit_with_unmeasured_qubit.draw("mpl", scale=0.8)
إن رسم صناديق مُلبَسة من اليسار حول البوابات وعمليات القياس سينتج دائرة فيها بوابات افتراضية غير مجمَّعة على الكيوبت 0، واستدعاء build() على هذه الدائرة سيؤدي إلى خطأ. ولتجنب ذلك، يمكن لمديري التمريرات التي يُرجعها generate_boxing_pass_manager() إضافة صناديق مُلبَسة من اليمين تعمل كمجمِّعات. على سبيل المثال، في الكود التالي، ينتهي الكيوبت 0 بصندوق مُلبَس من اليمين يلتقط البوابة الافتراضية غير المجمَّعة. كما توضع البوابات أحادية الكيوبت المؤثرة على الكيوبت 0 داخل الصندوق أيضًا لتقليل عمق الدائرة الناتجة.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=True,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)
في مثال آخر، يُضاف صندوق مُلبَس من اليمين لتجميع البوابات الافتراضية التي كانت ستبقى غير مجمَّعة بسبب عمليات القياس غير المؤطَّرة.
boxing_pass_manager = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
)
transpiled_circuit = boxing_pass_manager.run(circuit_with_unmeasured_qubit)
transpiled_circuit.draw("mpl", scale=0.8)