الانتقال من Sampler إلى Executor
يصف هذا الدليل كيفية نقل أحمال عمل أخذ العينات الكمية من بدائية Sampler الخاصة بـ IBM Quantum® إلى بدائية Executor.
بدائية Executor هي جزء من نموذج التنفيذ الموجَّه. جميع المكونات في نموذج التنفيذ الموجَّه حالياً في مرحلة بيتا وقد لا تكون مستقرة. أنت مدعو لاختبارها وتقديم ملاحظاتك بفتح مشكلة في مستودعات Samplomatic أو qiskit-ibm-runtime على GitHub.
هل يجب عليك الانتقال؟
ليس على الجميع الانتقال من Sampler إلى Executor. هناك اختلافات كثيرة بين البدائيتين، لكن الإرشادات التالية يمكن أن تساعدك في تحديد ما إذا كنت ستنتقل أم لا:
انتقل إلى Executor إذا كنت عالم معلومات كمية يُجري تجارب على نطاق المنفعة ويحتاج إلى تحكم دقيق وقابل للتكرار في تقنيات مثل التفتيل Pauli، وتعلم وحقن نموذج الضوضاء، وتغييرات الأساس — أو من يحتاج إلى إحدى القدرات الإضافية التي يوفرها Executor.
استمر في استخدام Sampler إذا كنت تريد واجهة بسيطة وعالية المستوى وتريد أن تتولى البدائية إدارة قمع الأخطاء والتخفيف من حدتها نيابة عنك.
القيود والتحذيرات
نظراً لأن Executor ونموذج التنفيذ الموجَّه في مرحلة بيتا، لاحظ ما يلي قبل أن تقرر الانتقال:
-
لا يوجد دعم للمحاكي بعد: على عكس Sampler، الذي يحتوي على تنفيذ
AerSamplerفيqiskit-aerللمحاكاة المحلية، لا توجد حالياً واجهة خلفية محاكية لـ Executor. من المتوقع أن يصل دعم المحاكي قريباً. في هذه الأثناء، لا يزال بإمكانك فحص وأخذ عينات من دائرة القالب محلياً للتحقق من صحة سير عملك قبل إرساله إلى الجهاز. -
يغطي هذا الدليل Sampler فقط، وليس Estimator. الانتقال من Estimator إلى Executor أكثر تعقيداً بكثير من الانتقال من Sampler لأن Estimator يحسب قيم التوقع بدلاً من إرجاع عينات خام. إعادة إنتاج سلوك Estimator باستخدام Executor يتطلب معالجة إضافية بعد المعالجة. لا تزال الدوال المساعدة للانتقال من Estimator إلى Executor قيد التطوير، لذا يصف هذا الدليل عمداً سير عمل Sampler فقط.
الاختلافات الرئيسية بين Executor وSampler
يأخذ كل من Sampler وExecutor عينات من سجلات الإخراج للدوائر الكمية، لكنهما يستهدفان مستخدمين مختلفين:
-
Sampler هو تجريد عالي المستوى. له الخصائص التالية:
-
لديه قمع أخطاء مدمج (فك الاقتران الديناميكي والتفتيل).
-
يتخذ قرارات ضمنية نيابة عنك.
-
صُمِّم بحيث يمكن لمطوري الخوارزميات التركيز على الابتكار بدلاً من تحويل البيانات.
-
-
Executor جزء من نموذج التنفيذ الموجَّه. يختلف عن Sampler بطرق عديدة وله الخصائص التالية:
-
ليس لديه قمع أو تخفيف أخطاء مدمج. بدلاً من ذلك، تلتقط قصد التصميم الخاص بك من جانب العميل (باستخدام تعليقات الدائرة وsamplex)، ويُنقل التوليد المكلف لمتغيرات الدائرة إلى جانب الخادم.
-
لا يتخذ أي قرارات ضمنية. يتبع توجيهاتك بدقة، مما يمنحك تحكماً وشفافية كاملين.
-
يكشف Executor وSamplomatic معاً عن قدرات إضافية لا يوفرها Sampler، بما في ذلك (على سبيل المثال لا الحصر) ما يلي:
- مجموعات تفتيل أكثر: يتيح لك Samplomatic اختيار مجموعة التفتيل
التي تُطبَّق لكل صندوق، بدلاً من الاقتصار على الاستراتيجية الواحدة التي يطبقها Sampler نيابة عنك. يدعم أيضاً مجموعات تفتيل غير Pauli، مثل مجموعة
التفتيل
"local_c1". - القياسات النووية (kerneled) والمصنَّفة معاً: يؤدي تعيين
QuantumProgram.meas_level = "both"(تمت إضافته فيqiskit-ibm-runtimev0.48.0) إلى طلب وجود القياسات المصنَّفة والنووية معاً في النتائج، بدلاً من اختيار نوع قياس واحد لكل مهمة. - التفتيل للدوائر ذات البوابات الكسرية: يمكن لـ Executor تطبيق التفتيل على الدوائر التي تحتوي على بوابات كسرية.
- تخفيف أخطاء دقيق وقابل للتركيب: على سبيل المثال، اختيار أي طبقات الدائرة يجب تخفيفها وضبط معدلات الضوضاء المحقونة في الدائرة.
Notes- من المتوقع أن يتم إصدار القدرات الجديدة المستقبلية لـ Executor أولاً وقد لا يتم نقلها إلى Sampler. إذا كنت تعتمد على الوصول إلى أحدث الميزات، فإن Executor هو الخيار الأكثر ثباتاً في المستقبل.
- لا توفر حزمة Qiskit الأساسية بعد
فئة أساسية لبدائية Executor (وهي توفرها لـ
SamplerV2).
- مجموعات تفتيل أكثر: يتيح لك Samplomatic اختيار مجموعة التفتيل
التي تُطبَّق لكل صندوق، بدلاً من الاقتصار على الاستراتيجية الواحدة التي يطبقها Sampler نيابة عنك. يدعم أيضاً مجموعات تفتيل غير Pauli، مثل مجموعة
التفتيل
-
التخطيط المفاهيمي
يوضح الجدول التالي كيفية تعيين مفاهيم Sampler إلى Executor.
| المفهوم | Sampler | Executor |
|---|---|---|
| الاستيراد | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| المدخل | قائمة من PUBs (مجموعات tuples) | QuantumProgram من كائنات QuantumProgramItem |
| الدارة والمعاملات | مجموعة (circuit, params, shots) | program.append_circuit_item(circuit, circuit_arguments=...) |
| Twirling | TwirlingOptions | بشكل صريح عبر صناديق موسومة (annotated boxes) وsamplex (append_samplex_item) |
| استدعاء التشغيل | sampler.run([pub, ...]) | executor.run(program) |
| نوع النتيجة | PrimitiveResult من SamplerPubResult | QuantumProgramResult (قابل للتكرار) |
| الوصول إلى البيانات | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| إدارة الضوضاء | خيارات مدمجة | يجب تركيبها يدويًا (annotations، samplex، NoiseLearnerV3) |
نظرة عامة على خطوات الانتقال
الخطوة 1. تثبيت الحزم المطلوبة
يتطلب Executor ونموذج التنفيذ الموجَّه حزمة samplomatic:
pip install qiskit qiskit-ibm-runtime samplomatic
# For visualization support:
# pip install samplomatic[vis]
- يُوصى بـ
qiskit-ibm-runtimev0.48.0 لأنه يضيف خيارmeas_level = "both"ومجموعة التفتيلlocal_c1. - مطلوب
qiskit >= 2.3.0. - مطلوب
samplomatic >= 0.18.0.
الخطوة 2. تغيير عمليات الاستيراد
Sampler:
from qiskit_ibm_runtime import SamplerV2 as Sampler
Executor:
from qiskit_ibm_runtime import Executor, QuantumProgram
الخطوة 3. استبدال زوجيات PUB بـ QuantumProgram
بدلاً من تمرير قائمة من الزوجيات (PUBs)، عند استخدام Executor، تبني QuantumProgram وتُلحق به عناصر.
يقبل QuantumProgram عناصر الدائرة وعناصر samplex:
-
append_circuit_item: يُلحقCircuitItem، وهو دائرة و(اختيارياً) قيم معاملاتها. يُنفَّذ كما هو، دون أي عشوائية.استخدم هذا عندما تريد فقط أخذ عينة من دائرة، تماماً كما يفعل Sampler مع PUB لا يحتوي على تفتيل؛ على سبيل المثال، عند إرسال مهمة أخذ عينات بسيطة، أو عندما تكون قد ضمَّنت بالفعل يدوياً أي متغيرات تريدها.
-
append_samplex_item: يُلحقsamplexItem، وهو دائرة قالب بالإضافة إلى samplex يولِّد مجموعات معاملات عشوائية على جانب الخادم.استخدم هذا عندما تريد عشوائية محتوى الدائرة. الحالة الأساسية هي مع التفتيل (البوابة أو القياس) أو حقن الضوضاء. تحل هذه القدرة محل التفتيل المدمج في Sampler.
يمكن لـ QuantumProgram واحد قبول كلا نوعي العناصر؛ كل عنصر يُضاف يُنفَّذ كمهمة مستقلة وينتج إدخاله الخاص في النتائج. بشكل عام، استخدم append_circuit_item عندما لا تحتاج دائرتك إلى العشوائية (randomized). وإلا، استخدم append_samplex_item.
توضّح الأقسام التالية كل نهج بالترتيب: الدوائر المُعامَلة (parameterized) التي تستخدم
append_circuit_item، وترحيل التبريم (twirling) باستخدام append_samplex_item.
في أمثلة الشيفرة التالية، يشير isa_circuit إلى الدائرة التي تم تحويلها (transpiled) لتتوافق مع بنية مجموعة التعليمات (ISA) الخاصة بالـ Backend المستهدف. تحتوي isa_circuit هذه على معاملين.
الخطوة 3أ. ترحيل الدوائر المُعامَلة
مع Sampler، تكون قيم المعاملات هي العنصر الثاني من زوج PUB. مع Executor،
مرّرها كـ circuit_arguments إلى append_circuit_item.
Sampler:
params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)
Executor
program = QuantumProgram(shots=1024)
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, circuit.num_parameters), # 10 sets
)
# CircuitItem result shape: (parameter_sets, shots, register_bits) -> (10, 1024, 2)
result_0 = result[0]["meas"]
الخطوة 3ب. ترحيل التبريم المدمج إلى تعليقات توضيحية صريحة
هذا هو التغيير الأكثر أهمية. يطبّق Sampler التبريم (twirling) نيابةً عنك باستخدام الخيارات (options). مع Executor، تُعلن عن هذه النية صراحةً باستخدام الصناديق ذات التعليقات التوضيحية (annotated boxes) وsamplex (من Samplomatic).
Sampler (التبريم باستخدام الخيارات):
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True
Executor (التبريم باستخدام الصناديق و samplex):
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager
# 1. Group gates and measurements into annotated boxes with twirling annotations
boxes_pm = generate_boxing_pass_manager(
enable_gates=True, # gate twirling
enable_measures=True, # measurement twirling
)
boxed_circuit = boxes_pm.run(isa_circuit)
# 2. Build the (template circuit, samplex) pair.
# The template circuit's single-qubit gates are replaced by parameterized gates;
# the samplex encodes how to generate the randomized parameters at runtime.
template_circuit, samplex = build(boxed_circuit)
# 3. Append as a samplex item, specifying the number of randomizations
program = QuantumProgram(shots=1024)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(10, 2), # original circuit params
},
shape=(28, 10), # 28 randomizations x 10 parameter sets
)
نظرًا لأن الدائرة القالب (template circuit) والـ samplex يُبنيان على جانب العميل، يمكنك فحصها وأخذ عينات منها محليًا للتحقق من الناتج قبل إرسال أي شيء إلى العتاد (hardware).
التحقق: أخذ عينة من الدائرة القالب محليًا
يمكنك سحب عمليات العشوائية (randomizations) من الـ samplex وربطها بالدائرة
القالب للتأكد من أن الـ samplex ينتج قيم المعاملات التي تتوقعها.
قيم المعاملات التي تُعيدها samplex.sample متوافقة مباشرة مع
معاملات الدائرة القالب.
# Check which inputs the samplex requires (for the twirling example above,
# this is just the original circuit's parameter values).
print(samplex.inputs())
# Bind the required inputs, then draw a few randomizations locally.
inputs = samplex.inputs().bind(
parameter_values=np.random.rand(2), # one set of the original circuit's params
)
outputs = samplex.sample(inputs, num_randomizations=3)
# Assign one randomization's parameter values to the template circuit and inspect it.
bound_template = template_circuit.assign_parameters(outputs["parameter_values"][0])
bound_template.draw("mpl", idle_wires=False)
للمضي قدمًا، يمكنك التحقق من أن كل عملية عشوائية مكافئة منطقيًا
للدائرة الأصلية، على سبيل المثال، بتحويل كليهما إلى كائنات Operator ومقارنة تنفيذاتهما الأحادية (unitary implementations) (بعد
مراعاة تصحيحات outputs["measurement_flips.<register>"] التي تُلغي
تبريم القياس (measurement twirling))، أو بمقارنة قيم التوقع من تشغيل StatevectorSampler أو StatevectorEstimator
محلي. راجع دليل Samplomatic
مدخلات ومخرجات Samplex
للحصول على شرح كامل.
الخطوة 4. تغيير طريقة طلب اللقطات (shots)
انقل عدد اللقطات (shots) من PUB إلى QuantumProgram(shots=...). في Executor، ينطبق shots على المهمة بأكملها. أرسل مهام متعددة إذا كنت بحاجة إلى أعداد لقطات مختلفة.
Sampler:
# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])
Executor:
# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)
الخطوة 5. تحديث الخيارات حسب الحاجة
تتوفر خيارات أقل لـ Executor مقارنةً بـ Sampler، لأن خيارات تخفيف الأخطاء (error mitigation) أصبحت الآن موجودة في التعليقات التوضيحية والـ samplex بدلاً من الخيارات.
هناك أيضًا اختلاف بنيوي في مكان وجود الإعدادات.
-
مع Sampler، كل شيء، بما في ذلك الخيارات التي تؤثر على معالجة النتائج بعد التنفيذ، يُهيَّأ على خيارات الـ Primitive أو في PUB.
-
مع Executor، تُضبط الخيارات التي تؤثر على كيفية تشكيل نتائج المهمة ومعالجتها بعد التنفيذ على
QuantumProgram، وليس علىExecutorOptions.
Examples:
| Sampler | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
يحتوي ExecutorOptions فقط على إعدادات تنفيذ وبيئة ذات مستوى أدنى لا تُغيّر بنية البيانات المُعادة. لديه ثلاث مجموعات على المستوى الأعلى:
-
environment(EnvironmentOptions) -
execution(ExecutionOptions): يحتوي على خيارات أقل من Sampler. على سبيل المثال، لا يوجد خيارmeas_typeفي Executor.
جدير بالذكر أن خياري twirling و dynamical_decoupling موجودان في Sampler لكن ليس في Executor. بدلاً من ذلك، تُعبَّر قيم هذه الخيارات من خلال نموذج التنفيذ الموجَّه (directed execution model).
Example:
from qiskit_ibm_runtime import Executor, ExecutorOptions
options = ExecutorOptions(
environment={"log_level": "INFO"},
execution={"init_qubits": True},
)
# or mutate after construction:
options = ExecutorOptions()
options.environment.log_level = "INFO"
options.execution.init_qubits = True
executor = Executor(mode=backend, options=options)
الخطوة 6. تحديث أمر run
مدخل مهمة Executor هو البرنامج (program)، بدلاً من PUBs.
Sampler:
# Submit a job
sampler.run([(isa_circuit, parameter_values)])
Executor:
# Submit a job
executor.run(program)
الخطوة 7. تغيير طريقة الوصول إلى النتائج
في Executor، النتائج هي مصفوفات NumPy، وليست كائنات BitArray. استخدم سلسلة الاسم كفهرس (result[0]["meas"]) واحصل على np.ndarray في المقابل. لا حاجة لتذكر مسار السمة .data.<register>.
للتحديث من Sampler إلى Executor، غيّر result[i].data.<reg> (BitArray) إلى result[i]["<reg>"] (np.ndarray)، ثم أعد كتابة المعالجة اللاحقة القائمة على get_counts كعمليات NumPy.
| Task | Sampler | Executor |
|---|---|---|
| Get register data | result[0].data.meas | result[0]["meas"] |
| Data type | BitArray | np.ndarray |
| Counts dictionary | result[0].data.meas.get_counts() | Post-process the array manually |
| Multiple registers | result[0].data.<name> per register | result[0]["<name>"] per register |
| CircuitItem array shape | - | (parameter_sets, shots, register_bits) |
| SamplexItem array shape | - | (randomizations, parameter_sets, shots, register_bits) |
| Undo measurement twirling | Automatic | result[i]["measurement_flips.<name>"] + XOR |
توفر BitArray الخاصة بالـ Sampler دوال مساعدة (get_counts، slice_bits، slice_shots، expectation_values، وأقنعة الاختيار البعدي). يُعيد الـ Executor مصفوفات NumPy خام حتى تتمكن من إجراء هذه المعالجة اللاحقة باستخدام عمليات NumPy القياسية.
الخطوة 8. التعامل مع النتائج المُبرَمة (twirled) (تصحيحات قلب البت)
عندما تطبّق تبريم القياس (measurement twirling) من خلال SamplexItem، يُعيد Executor القياسات
الخام (المُبرَمة) بالإضافة إلى تصحيحات قلب البت (bit-flip) اللازمة لإلغاء التبريم.
عليك تطبيقها يدويًا؛ لا شيء يُصحَّح ضمنيًا.
عند استخدام Executor، ألغِ التبريم صراحةً باستخدام تصحيحات measurement_flips.<reg> وعملية XOR، كما هو موضح في المثال التالي:
# SamplexItem result shape: (randomizations, parameter_sets, shots, register_bits)
result_1 = result[1]["meas"] # example: (28, 10, 1024, 2)
# Bit-flip corrections to undo measurement twirling
flips_1 = result[1]["measurement_flips.meas"] # example: (28, 10, 1, 2)
# Undo the twirling through classical XOR (broadcasts over the shots axis)
unflipped_result_1 = result_1 ^ flips_1
لا توجد خطوة مكافئة في Sampler لأنه يُلغي التبريم نيابةً عنك.
مثال كامل: ترحيل مهمة أخذ عينات أساسية
Sampler
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# 2. Circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()
# 3. Transpile to ISA
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
# 4. Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit,)], shots=25)
result = job.result()
# 5. Access results: a BitArray keyed by register name
counts = result[0].data.meas.get_counts()
Executor
import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram
# 1. Account + backend (unchanged)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# 2. Circuit (unchanged)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()
# 3. Transpile to ISA (unchanged)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
# 4. Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)
# 5. Run
executor = Executor(mode=backend)
job = executor.run(program)
result = job.result()
# 6. Access results: a plain np.ndarray keyed by register name
# shape = (shots, register_bits)
meas = result[0]["meas"]