مدخلات ومخرجات Executor
إصدارات الحزم
تم تطوير الكود في هذه الصفحة باستخدام المتطلبات التالية. نوصي باستخدام هذه الإصدارات أو أحدث.
qiskit[all]~=2.6.1
qiskit-ibm-runtime~=0.50.0
samplomatic~=0.21.0
الـ Executor primitive هو جزء من نموذج التنفيذ الموجَّه، الذي يوفر مرونة أكبر عند تخصيص سير عمل تخفيف الأخطاء.
تختلف مدخلات ومخرجات Executor primitive اختلافًا كبيرًا عن مدخلات ومخرجات Sampler وEstimator primitives. على سبيل المثال، بدلاً من أخذ قائمة من PUBs كمدخل، يأخذ Executor كائن QuantumProgram، الذي يحتوي على قائمة من كائنات QuantumProgramItem. تمنحك هذه الفئات الحاوية مرونة أكبر من PUB، الذي هو هيكل بيانات صف (tuple) بسيط.
مخرج Executor هو QuantumProgramResult، وهو قابل للتكرار ويحتوي على عنصر واحد لكل QuantumProgramItem مدخل.
المدخلات: البرامج الكمية
كما ذُكِر سابقًا، المدخل لـ Executor primitive هو QuantumProgram، وهو قابل للتكرار من كائنات QuantumProgramItem. يمكن أن تكون هذه الكائنات من نوعين:
CircuitItem، الذي يُخزِّن عادةً Circuit وقيم معاملاتها (إن وجدت).SamplexItem، الذي يُخزِّن عادةً ما يلي:- Circuit نموذجية
- كائن samplex، يُستخدَم لتوليد مجموعات عشوائية من المعاملات في وقت التشغيل (مثلاً لإجراء التدوير أو حقن الضوضاء)
- وسيطات للـ samplex، والتي قد تتضمن قيم معاملات للدائرة الأصلية
كل من هذه العناصر يُمثِّل مهمة مختلفة يجب على Executor تنفيذها.
قبل البدء
بعض أمثلة الكود في هذه الصفحة تستخدم samplex، وهو جزء من حزمة Samplomatic. لذلك، قبل تشغيل تلك الكتل البرمجية، يجب تثبيت Samplomatic كما هو موضح في الكتلة البرمجية التالية. لمزيد من المعلومات، راجع وثائق Samplomatic.
pip install samplomatic
# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]
مثال: إنشاء QuantumProgram بمهمتين مختلفتين
أولاً هيِّئ برنامجك الكمي، ثم ألحِق عناصر البرنامج باستخدام append_circuit_item أو append_samplex_item (إذا كان samplex موجودًا)، كما هو موضح في الأمثلة التالية.
تُهيِّئ الخلية التالية QuantumProgram وتُحدِّد تنفيذ 1024 لقطة (shots) لكل تكوين من تكوينات كل عنصر في البرنامج.
على خلاف Sampler، يأخذ QuantumProgram قيمة واحدة فقط لعدد اللقطات (shots). إذا كنت تريد عددًا مختلفًا من اللقطات، فأنت بحاجة إلى QuantumProgram منفصل، أي إلى job (مهمة تشغيل) منفصلة.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit_ibm_runtime import Executor, QiskitRuntimeService
from qiskit.circuit import Parameter, QuantumCircuit
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager
# Initialize an empty program
program = QuantumProgram(shots=1024)
# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)
# `measure_all` adds a 3-bit classical register named "meas"
circuit.measure_all()
# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Generate a preset pass manager
# This will be used to convert the abstract circuit to an
# equivalent Instruction Set Architecture (ISA) circuit.
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)
# Transpile the circuit
isa_circuit = preset_pass_manager.run(circuit)
إلحاق CircuitItem
بعد ذلك، ألحِق الدائرة المستهدفة التي جرى Transpile لها وفق مجموعة تعليمات معمارية الـ Backend (ISA) بـ QuantumProgram. بما أن هذه الدائرة تحتوي على معاملَين، يجب أيضًا توفير قيم المعاملات (10 مجموعات في هذا المثال). تنفيذ هذه CircuitItem هو المهمة الأولى التي سيؤديها البرنامج.
# Append the transpiled circuit and an array
# containing 10 sets of parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(
10, 2
), # 10 sets of parameter values and 2 parameters
)
إلحاق SamplexItem
تُنفَّذ عناصر الدائرة دون أي عشوائية. على العكس من ذلك، تتيح لك عناصر الـ samplex تحديد كيفية جعل محتواها عشوائيًا. تستخدم الخلية التالية دالة generate_boxing_pass_manager() لتجميع بوابات الدائرة وقياساتها في صناديق وإضافة تعليق تدوير لكل صندوق. ثم تولِّد زوجًا مكوَّنًا من دائرة نموذجية وكائن samplex باستخدام دالة build().
تنفيذ هذه SamplexItem هو المهمة الثانية التي سيؤديها البرنامج.
راجع وثائق API في Samplomatic للاطلاع على التفاصيل الكاملة حول samplex وحججه. راجع دليل Samplomatic Transpiler للحصول على معلومات حول استخدام دالة generate_boxing_pass_manager().
# Transpile the circuit, additionally grouping gates and measurements into annotated boxes
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)
# Use the boxing pass manager to group gates
# and measurements into boxes and add
# a`Twirl` annotation.
preset_pass_manager.post_scheduling = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)
boxed_circuit = preset_pass_manager.run(circuit)
# Build the template circuit and the samplex. The template circuit has parametric gates
# without fixed values and the samplex randomly generates the parameter
# values on the server side at runtime to perform twirling.
template_circuit, samplex = build(boxed_circuit)
# Determine what arguments are required by the samplex.
# Input the arguments in samplex_arguments.
print(samplex.inputs())
TensorInterface(<
- 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
# the arguments required by the samplex.sample method
"parameter_values": np.random.rand(10, 2),
},
shape=(28, 10), # 28 randomizations and 10 sets of parameter values
)
# Initialize an Executor with the default options
executor = Executor(mode=backend)
# Submit the job
job = executor.run(program)
# Retrieve the result
result = job.result()
المخرجات
مخرج Executor هو QuantumProgramResult، وهو قابل للتكرار. يحتوي على إدخال واحد لكل QuantumProgramItem مدخل بنفس ترتيب عناصر الإدخال. كل من هذه العناصر المخرجة هو قاموس حيث المفاتيح هي سلاسل نصية تتوافق مع أسماء السجلات الكلاسيكية في الدوائر المدخلة (من بين أمور أخرى)، بحيث لا تحتاج بعد الآن إلى حفظ هذه الأسماء كما كنت تفعل مع مخرج Sampler. قيم القاموس من النوع np.ndarray.
تحتوي نتيجة المثال السابق على هذه العناصر:
نتيجة CircuitItem
يحتوي العنصر الأول على نتائج تشغيل المهمة الأولى (CircuitItem) في البرنامج. يحتوي على مفتاح واحد، meas، وهو اسم السجل الكلاسيكي في الدائرة المدخلة. تقابل قيمة هذا المفتاح np.ndarray بالشكل (parameter sets, shots, register bits)، أي (10, 1024, 3) في المثال أعلاه.
الكود التالي يُوضِّح كيفية الوصول إلى هذه المعلومات:
# Access the results of the classical register of task #0, a CircuitItem
result_0 = result[0]["meas"]
print(f"Result shape: {result_0.shape}")
Result shape: (10, 1024, 3)
يمتد المحور الأيمن في result_0 على البتات الكلاسيكية بترتيب little-endian. وبالتالي يحمل الفهرس 0 نتيجة البت الكلاسيكي 0، وهو البت الأقل أهمية.
نتيجة SamplexItem
يحتوي العنصر الثاني على نتائج تشغيل المهمة الثانية (SamplexItem) في البرنامج. يحتوي هذا العنصر على عدة مفاتيح. المفتاح meas، وهو اسم السجل الكلاسيكي للدائرة المدخلة، يقابل مصفوفة نتائج ذلك السجل. لهذه المصفوفة الشكل (randomizations, parameter sets, shots, classical bits)، أي (28, 10, 1024, 3) في هذا المثال.
إضافة إلى ذلك، يحتوي الناتج على المفتاح measurement_flips.meas، وهو تصحيحات قلب البتات لإلغاء تدوير القياس (twirling) للسجل meas. سيكون شكل هذا الناتج (28, 10, 1, 3) في مثالنا لأن لقطة (shot) واحدة فقط مطلوبة لتنفيذ قلب البتات.
لاحظ أن المحور الأيمن في result_1 يمتد على البتات الكلاسيكية بترتيب little-endian.
# Access the results of the classical register of task #1
result_1 = result[1]["meas"]
print(f"Result shape: {result_1.shape}")
# Access the bit-flip corrections
flips_1 = result[1]["measurement_flips.meas"]
print(f"Bit-flip corrections shape: {flips_1.shape}")
# Undo the bit flips via classical XOR
unflipped_result_1 = result_1 ^ flips_1
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)
اعرض فترات التنفيذ
يرسل استدعاء Executor.run() الواحد مهمة واحدة. لكن من جهة الخادم، لا تشغّل الخدمة بالضرورة كل تهيئة من البرنامج الكمومي في الوقت نفسه. وبدلًا من ذلك، تجمّع الدارات المرتبطة بقيم لعناصر البرنامج في أجزاء، وتشغّل جزءًا واحدًا في كل مرة على QPU. لا تحتاج إلى تقسيم برنامجك أو إرسال مهام إضافية؛ فالتجزئة تحدث تلقائيًا داخل المهمة الواحدة. تحتوي السمة metadata للنتيجة على معلومات توقيت عن هذه الأجزاء في result.metadata.chunk_timing. هذه الطوابع الزمنية هي أوقات البدء والانتهاء الفعلية لكل جزء على QPU. يمكنك استخدام هذه المعلومات لمعرفة متى جُمعت بيانات معينة على QPU، ولفهم كيف وزّع Executor العمل، أو لتشخيص مشكلات الأداء.
chunk_timing قائمة تحتوي على ChunkSpan واحد لكل جزء نُفِّذ. لكل ChunkSpan السمات التالية:
-
startوstop: الطابعان الزمنيان بتوقيت UTC لوقت بدء تنفيذ الجزء على QPU ووقت توقفه. -
parts: قائمة من كائناتChunkPartتصف عناصر البرنامج التي ساهمت في الجزء. يحتوي كلChunkPartعلى ما يلي:-
idx_item: فهرس عنصر البرنامج، بالترتيب الذي أُلحقت به العناصر فيQuantumProgram. -
size: عدد التهيئات (عناصر شكل العنصر) من ذلك العنصر التي نُفِّذت في هذا الجزء. فمثلًا، للعنصرSamplexItemفي المثال السابق الشكل(28, 10)، لذا فله 280 تهيئة في المجموع.
-
تطبع الشيفرة التالية مدة كل جزء ومحتويات كل جزء للمهمة المرسلة سابقًا:
for idx, span in enumerate(result.metadata.chunk_timing):
duration = (span.stop - span.start).total_seconds()
print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
for part in span.parts:
print(f" Item {part.idx_item}: {part.size} configurations")
Chunk 0: 2026-11-06 01:47:46.009718+00:00 to 2026-11-06 01:49:05.577720+00:00 (79.568 s)
Item 0: 10 configurations
Item 1: 280 configurations
تغلّف الخاصية QuantumProgramResult.timing القائمة نفسها في كائن ChunkTiming، الذي يُبلغ أيضًا عن معلومات عامة حول مجموعة الأجزاء:
timing = result.timing
print("Number of chunks:", len(timing))
print(" Start of the first chunk:", timing.start)
print(" End of the last chunk:", timing.stop)
print(" Total duration (s):", timing.duration)
Number of chunks: 1
Start of the first chunk: 2026-11-06 01:47:46.009718+00:00
End of the last chunk: 2026-11-06 01:49:05.577720+00:00
Total duration (s): 79.568002
صوِّر فترات التنفيذ
استخدم الدالة draw_chunk_timings لتصوير توقيتات الأجزاء على رسم أعمدة تفاعلي. المحور السيني هو الزمن، ويُرسم كل جزء كقطعة أفقية تمتد بين وقتَي بدايته ونهايته. ويعرض المحور الصادي العدد التراكمي للتهيئات المنجزة (أو النسبة المئوية من إجمالي حمل العمل، إذا ضبطت normalize_y=True). مرّر المؤشر فوق قطعة لرؤية الطوابع الزمنية والمدة وأجزاء ذلك الجزء.
تُعيد الدالة شكل plotly، يمكنك عرضه مباشرةً أو حفظه في ملف أو كليهما. وهي تقبل إما القائمة chunk_timing أو كائن ChunkTiming. يمكنك تمرير عدة منها، مثلًا من مهام مختلفة، لرسمها على الرسم نفسه. اضبط common_start=True لمحاذاتها عند وقت بدء مشترك بغرض المقارنة.
تتطلب draw_chunk_timings الحزمة plotly. يمكنك تثبيتها بتشغيل pip install plotly.
قد يُظهر التصوير فجوات بين تنفيذ الأجزاء. تمر جميع المهام المرسلة إلى QPU حقيقية بمعالجة مسبقة كلاسيكية، مثل الترجمة إلى نبضات يمكن إرسالها إلى العتاد. ولأن هذه المعالجة المسبقة يمكن أن تجري على التوازي، تُخرج الخدمة عدة مهام من الطابور وتبدأها في وقت واحد. غير أن QPU لا تستطيع معالجة سوى وحدة عمل واحدة في كل مرة. لذلك، إذا ولّدت مهمة عدة أجزاء، فقد تتداخل أجزاؤها مع أجزاء مهام مستخدمين آخرين، وقد يضطر كل جزء إلى انتظار دوره على QPU. وهذا هو سبب ظهور فجوات بين الأجزاء.
from qiskit_ibm_runtime.visualization import draw_chunk_timings
fig = draw_chunk_timings(result.timing, names="Executor job")
# Alternatively, call the `draw()` method of the ChunkTiming object
# fig = result.timing.draw()
# Uncomment the following line to display the figure
# fig.show(renderer="notebook")
# Save to a file
# fig.write_html("chunk_timings.html")
الخطوات التالية
- استكشف أمثلة تستخدم Executor.
- تعرَّف على نموذج التنفيذ الموجَّه.
- افهم بث Executor.