انتقل إلى المحتوى الرئيسي
هذا المشروع في مرحلة تجريبية. وجدت خطأ؟ افتح مشكلة على GitHub. لديك سؤال أو فكرة؟ اسأل في Discussions — نحب سماع ملاحظاتك!
كيف جودة الترجمة في هذه الصفحة؟

مدخلات ومخرجات Sampler

إصدارات الحزم

الكود في هذه الصفحة تم تطويره باستخدام المتطلبات التالية. نوصي باستخدام هذه الإصدارات أو أحدث.

qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0

تقدم هذه الصفحة نظرة عامة على مدخلات ومخرجات الأداة البدائية qiskit-ibm-runtime Sampler، التي تنفذ أعمال التشغيل على خدمة الحوسبة IBM Quantum®. يتيح لك Sampler تعريف أعمال تشغيل متجهية بكفاءة باستخدام بنية بيانات تُعرف بـ Primitive Unified Bloc (PUB). يتم استخدامها كمدخلات لطريقة run() للأداة البدائية Sampler، التي تنفذ أعمال التشغيل المحددة كمهمة. ثم، بعد اكتمال المهمة، يتم إرجاع النتائج بتنسيق يعتمد على كل من PUBs المستخدمة وخيارات وقت التشغيل المحددة من الأداة البدائية.

المدخلات​

كل PUB بالتنسيق التالي:

(<single circuit>, <one or more optional parameter value>, <optional shots>),

يمكن أن يكون هناك عدة عناصر parameter values، ويمكن أن يكون كل عنصر إما مصفوفة أو معلمة واحدة، اعتمادًا على الدارة المختارة. بالإضافة إلى ذلك، يجب أن يحتوي الإدخال على قياسات.

بالنسبة للأداة البدائية Sampler، يمكن أن يحتوي PUB على ثلاثة قيم كحد أقصى:

  • QuantumCircuit واحد، والذي قد يحتوي على كائن أو أكثر من كائنات Parameter ملاحظة: يجب أن تتضمن هذه الدوائر أيضاً تعليمات قياس لكل من القبتات المراد أخذ عيناتها.
  • مجموعة من قيم المعاملات لربط الدائرة بها θk\theta_k (مطلوبة فقط إذا كانت هناك كائنات Parameter يجب ربطها في وقت التشغيل)
  • (اختياري) عدد من الـ shots لقياس الدائرة به

يوضح الكود التالي مثالاً على مجموعة من المدخلات المتجهة إلى Sampler primitive وينفذها على backend من IBM® ككائن واحد من نوع RuntimeJobV2 .

# 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"}
_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.circuit import (
Parameter,
QuantumCircuit,
ClassicalRegister,
QuantumRegister,
)
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.containers import BitArray

from qiskit_ibm_runtime import QiskitRuntimeService

# Legacy server-side primitive before qiskit-ibm-runtime 0.50.0
# from qiskit_ibm_runtime import Sampler

# Client-side primitive introduced in qiskit-ibm-runtime 0.50.0
from qiskit_ibm_runtime.executor_sampler import Sampler

import numpy as np

# Instantiate runtime service and get
# the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()

# Transpile the circuit
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
layout = transpiled_circuit.layout

# Now define a sweep over parameter values, the last axis of dimension 2 is
# for the two parameters "a" and "b"
params = np.vstack(
[
np.linspace(-np.pi, np.pi, 100),
np.linspace(-4 * np.pi, 4 * np.pi, 100),
]
).T

sampler_pub = (transpiled_circuit, params)

# Instantiate the new Sampler object, then run the transpiled circuit
# using the set of parameters and observables.
sampler = Sampler(mode=backend)
job = sampler.run([sampler_pub])
result = job.result()

المخرجات​

بعد إرسال PUB أو أكثر إلى QPU للتنفيذ واكتمال مهمة بنجاح، يتم إرجاع البيانات كـ PrimitiveResult وهو كائن حاوية يتم الوصول إليه عن طريق استدعاء طريقة RuntimeJobV2.result(). يحتوي PrimitiveResult على قائمة قابلة للتكرار من كائنات SamplerPubResult تحتوي على نتائج التنفيذ لكل PUB. هذه البيانات هي عينات من مخرجات الدائرة.

يتوافق كل عنصر من هذه القائمة مع PUB مُرسَل إلى طريقة run() الخاصة بالأداة البدائية (على سبيل المثال، مهمة مُرسَلة مع 20 PUB ستعيد كائن PrimitiveResult يحتوي على قائمة من 20 كائن SamplerPubResult، يتوافق كل منها مع PUB).

يمتلك كل كائن SamplerPubResult سمتي data و metadata.

  • سمة data هي DataBin مخصصة تحتوي على قيم القياس الفعلية والانحرافات المعيارية وما شابه ذلك. صناديق البيانات هي كائنات شبيهة بالقاموس تحتوي على BitArray واحد لكل ClassicalRegister في الدائرة.
  • فئة BitArray هي حاوية لبيانات الـ shot المرتبة. تخزن سلاسل البت المأخوذة كعينة كبايتات داخل مصفوفة ثنائية الأبعاد. يمر المحور الأيسر لهذه المصفوفة على الـ shots المرتبة، بينما يمر المحور الأيمن على البايتات.
  • تحتوي سمة metadata على معلومات حول خيارات وقت التشغيل المستخدمة (موضح لاحقاً في قسم البيانات الوصفية للنتيجة في هذه الصفحة).

فيما يلي مخطط مرئي لبنية بيانات PrimitiveResult:

└── PrimitiveResult
├── SamplerPubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── NAME_OF_CLASSICAL_REGISTER
│ │ └── BitArray of count data (default is 'meas')
| |
│ └── NAME_OF_ANOTHER_CLASSICAL_REGISTER
│ └── BitArray of count data (exists only if more than one
| ClassicalRegister was specified in the circuit)
├── SamplerPubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| └── NAME_OF_CLASSICAL_REGISTER
| └── BitArray of count data for second pub
├── ...
├── ...
└── ...

بكل بساطة، تعيد مهمة واحدة كائن PrimitiveResult وتحتوي على قائمة من كائن أو أكثر من كائنات SamplerPubResult. ثم تخزن كائنات SamplerPubResult هذه بيانات القياس لكل PUB تم إرساله إلى المهمة.

كمثال أول، لنلقِ نظرة على الدائرة ذات عشرة قبتات التالية:

# generate a ten-qubit GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure_all()

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")

# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")
Databin: DataBin(meas=BitArray(<shape=(), num_shots=4096, num_bits=10>))

BitArray: BitArray(<shape=(), num_shots=4096, num_bits=10>)

The shape of register `meas` is (4096, 2).

The bytes in register `alpha`, shot by shot:
[[ 3 247]
[ 0 0]
[ 3 223]
...
[ 0 255]
[ 0 0]
[ 0 0]]

قد يكون من المفيد أحياناً التحويل من تنسيق البايتات في BitArray إلى سلاسل البت. تعيد طريقة get_count قاموساً يربط سلاسل البت بعدد مرات حدوثها.

# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'1111110111': 28, '0000000000': 1680, '1111011111': 35, '1111111111': 1467, '1110000000': 28, '0000000010': 15, '1111111011': 15, '1110111111': 7, '1000000000': 52, '1111101111': 16, '1111110000': 22, '0000000101': 3, '0001111101': 1, '0000000111': 17, '0000000001': 66, '1111111110': 119, '0000001000': 15, '0011111111': 24, '1111111101': 38, '0000001111': 32, '1100000000': 39, '1101111111': 51, '0000011111': 34, '1110000010': 2, '0010000000': 25, '0111111111': 21, '0001111111': 34, '1011111111': 16, '1000011111': 3, '0000001101': 1, '1111100000': 22, '1111111100': 22, '1111111000': 8, '0010001011': 1, '0011000000': 1, '0000000011': 16, '0011111100': 2, '1011110001': 1, '1101111100': 1, '1101111101': 1, '0000000110': 2, '0000000100': 2, '0001111110': 6, '0000001110': 4, '0011110111': 2, '1101111110': 3, '0111111011': 1, '0110000000': 1, '0000001011': 2, '1111110110': 3, '0111111000': 1, '1010000000': 2, '0001110111': 1, '1111101000': 1, '0010011110': 1, '1111100111': 1, '0111110111': 1, '0100000000': 5, '1101110111': 1, '0000001001': 1, '0010000001': 1, '1111000001': 1, '0000010000': 3, '1111101110': 1, '1111000000': 8, '0001011111': 3, '1000000001': 1, '1111010111': 2, '0010011111': 1, '1011111110': 1, '1101101111': 1, '1111011110': 2, '1111111010': 1, '1111010000': 1, '1101000000': 1, '1100001111': 1, '1100011111': 1, '0000111111': 4, '0010111111': 1, '1000000110': 1, '1110111110': 1, '1101011111': 2, '0000011000': 1, '1101110000': 2, '1011111011': 1, '1000111111': 1, '1011000000': 1, '1110000001': 2, '0101111111': 2, '1111110101': 1, '0010000011': 1, '0000011110': 1, '0000011101': 1, '0111000000': 1, '0001110000': 1, '0001000000': 4, '1101111000': 1, '1011111000': 1, '0111110000': 1, '1101110110': 1, '0011011111': 1, '0000100000': 2, '1111101100': 1, '1100001000': 1, '1010000011': 1, '0011111110': 1, '1010001110': 1, '1000001000': 1, '1000000011': 1, '1111101011': 1}

عندما تحتوي دائرة على أكثر من سجل كلاسيكي واحد، يتم تخزين النتائج في كائنات BitArray مختلفة. يعدّل المثال التالي المقتطف السابق بتقسيم السجل الكلاسيكي إلى سجلين مستقلين:

# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
qreg := QuantumRegister(10),
alpha := ClassicalRegister(1, "alpha"),
beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")
BitArray for register 'alpha': BitArray(<shape=(), num_shots=4096, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=4096, num_bits=9>)

استخدام كائنات BitArray للمعالجة اللاحقة عالية الأداء​

نظراً لأن المصفوفات توفر بشكل عام أداءً أفضل مقارنةً بالقواميس، فمن المستحسن إجراء أي معالجة لاحقة مباشرةً على كائنات BitArray بدلاً من قواميس العدد. تقدم فئة BitArray مجموعة من الطرق لإجراء بعض عمليات المعالجة اللاحقة الشائعة:

print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")

print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")

# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == 0b1
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")

# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")

# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)

# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
print(f"Exp. val. for observable `{o}` is: {e}")

# concatenate the bitstrings in `alpha` and `beta` to "merge" the results of the two
# registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[0]
[1]
...
[0]
[1]
[0]]

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 1 255]
[ 0 0]
[ 1 255]
...
[ 0 0]
[ 1 255]
[ 0 0]]

The shape of `beta` after post-selection is (0, 2).
The bytes in `beta` after post-selection:
[]
The shape of `beta` after bit-wise slicing is (4096, 1).
The bytes in `beta` after bit-wise slicing:
[[7]
[0]
[7]
...
[0]
[7]
[0]]

The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 1 255]
[ 0 0]
[ 1 255]
[ 1 255]
[ 1 255]]

Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.0595703125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.02783203125

The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 3 255]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 0 0]]

البيانات الوصفية للنتيجة​

بالإضافة إلى نتائج التنفيذ، يحتوي كلٌّ من كائنَي PrimitiveResult و SamplerPubResult على سمة بيانات وصفية عن المهمة التي تم إرسالها. يمكن العثور على البيانات الوصفية التي تحتوي على معلومات لجميع PUBs المُرسَلة (مثل خيارات وقت التشغيل المختلفة المتاحة) في PrimitiveResult.metatada، بينما توجد البيانات الوصفية الخاصة بكل PUB في SamplerPubResult.metadata.

يرسل Sampler من جهة العميل حمل عمله كمهمة Executor. البيانات الوصفية لنتيجة Executor الأساسية، التي تتضمن معلومات توقيت التنفيذ المسماة نطاقات التنفيذ، متاحة تحت المفتاح "executor" في PrimitiveResult.metadata.

ملاحظة

في حقل البيانات الوصفية، يمكن لتطبيقات الأدوات البدائية إرجاع أي معلومات تتعلق بالتنفيذ ذات صلة بها، ولا توجد أزواج مفتاح-قيمة مضمونة من قِبل الأداة البدائية الأساسية. وبالتالي، قد تختلف البيانات الوصفية المُعادة في تطبيقات الأدوات البدائية المختلفة.

# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
print(f"'{key}' : {val},")

print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
print(f"'{key}' : {val},")
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 07:46:47', stop='2026-09-01 07:46:49', size=4096>)])},
'version' : 2,

The metadata of the PubResult result is:
'circuit_metadata' : {},

عرض نطاقات التنفيذ​

يستخدم Sampler من جهة العميل Executor لتشغيل المهام، وهو ينفذ عمله على QPU في أجزاء (chunks). يوجد توقيت كل جزء في result.metadata["executor"].chunk_timing، وهي قائمة من كائنات ChunkSpan. يحتوي كل ChunkSpan على طابعين زمنيين start وstop وقائمة من parts، حيث يعطي كل جزء فهرس عنصر (idx_item) وعدد التكوينات من ذلك العنصر (size) التي تم تشغيلها في الجزء.

ينشئ Sampler عنصر برنامج Executor واحداً لكل PUB، لذا فإن idx_item هو فهرس الـ PUB، بالترتيب الذي أُعطيت به الـ PUBs إلى run(). إذا كان twirling مفعّلاً، فإن كل عملية عشوائية لكل مجموعة قيم معاملات تُحتسب كتكوين منفصل.

لتصوير التوقيتات، مرّر القائمة إلى draw_chunk_timings، كما هو موضح في المثال التالي.

لمعرفة ما تعنيه الطوابع الزمنية للأجزاء، وكيفية قراءة الرسم البياني، ولماذا قد تظهر فجوات بين الأجزاء، انظر عرض نطاقات التنفيذ في دليل مدخلات ومخرجات Executor.

# Define a circuit with one parameter.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)

# Define two PUBs, each with a different set of parameter values
sampler_pubs = [
(transpiled_circuit, np.random.uniform(size=(10, 1))),
(transpiled_circuit, np.random.uniform(size=(20, 1))),
]

job = sampler.run(sampler_pubs, shots=1024)
result = job.result()
from qiskit_ibm_runtime.visualization import draw_chunk_timings

# Note the "executor" key: the timing comes from the underlying Executor job
chunk_timing = result.metadata["executor"].chunk_timing

for idx, span in enumerate(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" PUB {part.idx_item}: {part.size} configurations")

fig = draw_chunk_timings(chunk_timing, names="Sampler job")

# Uncomment the following line to display the figure
# fig.show(renderer="notebook")

# Save to a file
# fig.write_html("chunk_timings.html")
Chunk 0: 2026-11-06 01:36:36.203348+00:00 to 2026-11-06 01:36:45.420844+00:00 (9.217 s)
PUB 0: 10 configurations
PUB 1: 20 configurations

نطاقات التنفيذ في Sampler القديم​

اعتباراً من qiskit-ibm-runtime الإصدار v0.50.0، أصبح Sampler القديم من جهة الخادم مهجوراً. لا يعيد Sampler من جهة العميل كائنات ExecutionSpans؛ استخدم توقيت الأجزاء الموصوف سابقاً بدلاً من ذلك. انظر الانتقال إلى primitives من جهة العميل للحصول على تعليمات الانتقال.

انقر لمعرفة المزيد.

تحتوي نتائج المهام من Sampler القديم من جهة الخادم على معلومات توقيت التنفيذ في result.metadata["execution"]["execution_spans"]، على شكل كائن ExecutionSpans. هذا الكائن عبارة عن حاوية شبيهة بالقائمة لكائنات ExecutionSpan، يشير كل منها إلى وقت بدء ووقت توقف وتحديد للـ shots التي تم جمعها في هذا النطاق.

يحدد نطاق التنفيذ البيانات التي تم تنفيذها أثناء نافذته الزمنية من خلال توفير الطريقة ExecutionSpan.mask. تعيد هذه الطريقة، عند إعطائها أي فهرس PUB، قناعاً منطقياً يكون True لجميع الـ shots المنفذة خلال نافذته الزمنية. فإذا كان لـ PUB مثلاً الشكل (2, 3) وتم تشغيله بأربع shots، فإن شكل القناع يكون (2, 3, 4). انظر صفحة API execution_span للتفاصيل الكاملة.

from qiskit.primitives import BitArray

spans = result.metadata["execution"]["execution_spans"]

# احصل على القناع للـ PUB الأول للنطاق رقم 0.
mask = spans[0].mask(0)

# أنشئ مصفوفة بتات جديدة تحتوي فقط على بيانات PUB-0 التي جُمعت خلال هذا النطاق.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)

# خذ المجموعة الجزئية من النطاقات التي تشير إلى بيانات في الـ PUBs 0 أو 2
spans.filter_by_pub([0, 2])

# اعرض المعلومات العامة حول مجموعة نطاقات التنفيذ
print("Number of execution spans:", len(spans))
print(" Start of the first span:", spans.start)
print(" End of the last span:", spans.stop)
print(" Total duration (s):", spans.duration)

لتصوير نطاقات التنفيذ القديمة، استخدم الدالة draw_execution_spans.

يمكن أن تتداخل النوافذ الزمنية المحددة بواسطة نطاقات التنفيذ المتمايزة. ليس هذا لأن QPU كان يُنفّذ عمليات متعددة في آنٍ واحد، بل هو نتاج معالجة كلاسيكية معينة قد تحدث بالتزامن مع التنفيذ الكمي. الضمان المقدم هو أن البيانات المُشار إليها حدثت بالتأكيد في نطاق التنفيذ المُبلَّغ عنه، لكن ليس بالضرورة أن حدود النافذة الزمنية أضيق ما يمكن.