انتقل إلى المحتوى الرئيسي

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

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

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

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1

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

المدخلات

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

(<دائرة واحدة>، <قيمة أو أكثر من قيم المعاملات الاختيارية>، <shots اختيارية>

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

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

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

يوضح الكود التالي مجموعة مثال من المدخلات المتجهية للأداة البدائية Sampler وتنفيذها على Backend IBM® كـ RuntimeJobV2 واحد.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
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,
SamplerV2 as 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:
[[ 0 0]
[ 3 255]
[ 0 0]
...
[ 3 255]
[ 2 255]
[ 3 255]]

قد يكون من المفيد أحياناً التحويل من تنسيق البايتات في 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: {'0000000000': 1649, '1111111111': 1344, '1111111000': 26, '1101111111': 40, '1111110000': 20, '0010000000': 32, '1000000000': 67, '1111110110': 4, '0000011110': 4, '0000000001': 78, '0010100000': 1, '1100000000': 37, '1111111110': 126, '1111110111': 35, '1111011111': 32, '0011111000': 1, '1011110111': 1, '0000011111': 48, '1111000000': 14, '0110000000': 1, '1110111110': 2, '1110011111': 4, '1111100000': 19, '1101111000': 1, '1111111011': 8, '0001011111': 3, '1110000000': 31, '0000000111': 25, '1110000001': 3, '0011111111': 24, '0000100000': 7, '1111111101': 30, '1111101111': 16, '0111111111': 37, '0000011101': 4, '0101111111': 4, '1011111110': 2, '0000000010': 17, '1011111111': 20, '0000100111': 1, '0010000111': 1, '1011010000': 1, '1101101111': 2, '1011110000': 1, '1000000001': 4, '0000001000': 23, '0011111110': 8, '1111111001': 1, '1100111111': 2, '0000011000': 2, '0001111110': 2, '0000111111': 20, '0001111111': 33, '1110111111': 11, '1010000000': 3, '0111011111': 2, '0000000100': 2, '0000000110': 2, '0000001111': 22, '0111101111': 1, '0000010111': 1, '0000000011': 15, '0001000010': 1, '1111111100': 19, '1111101000': 1, '0000001110': 2, '1011110100': 1, '0001000000': 11, '1001111111': 2, '0100000000': 6, '1100000011': 2, '1000001110': 1, '1100001111': 1, '0000010000': 3, '1101111110': 5, '0001111101': 1, '0001110111': 1, '0011000000': 2, '0111101110': 1, '1100000001': 1, '1111000001': 1, '0000000101': 1, '1101110111': 2, '0011111011': 1, '0000111110': 1, '1111101110': 3, '1111001000': 1, '1011111100': 1, '1111110101': 2, '1101001111': 1, '1111011110': 3, '1000011111': 1, '0000001001': 2, '1111010000': 1, '1110100010': 1, '1111110001': 2, '1101110000': 2, '0000010100': 1, '0111111110': 2, '0001000001': 1, '1000010000': 1, '1111011100': 1, '0111111100': 1, '1011101111': 1, '0000111101': 1, '1100011111': 2, '1101100000': 1, '1111011011': 1, '0010011111': 1, '0000110111': 3, '1111100010': 1, '1110111101': 1, '0000111001': 1, '1111100001': 1, '0001111100': 1, '1110011110': 1, '1100000010': 1, '0011110000': 1, '0001100111': 1, '1111010111': 1, '0010000001': 1, '0010000011': 1, '1101000111': 1, '1011111101': 1, '0000001100': 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:
[[0]
[0]
[0]
...
[0]
[0]
[0]]

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 0]
[ 1 248]
[ 0 0]
...
[ 0 0]
[ 0 0]
[ 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:
[[0]
[0]
[0]
...
[0]
[0]
[0]]

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

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

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

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

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

تتضمن البيانات الوصفية لنتيجة Sampler أيضاً معلومات توقيت التنفيذ تُسمى نطاق التنفيذ.

ملاحظة

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

# 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-05-13 14:23:00', stop='2026-05-13 14:23:02', size=4096>)])},
'version' : 2,

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

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

تحتوي نتائج مهام SamplerV2 المنفذة في Qiskit Runtime على معلومات توقيت التنفيذ في بياناتها الوصفية. يمكن استخدام معلومات التوقيت هذه لوضع حدود زمنية علوية وسفلية على وقت تنفيذ الـ shots المحددة على QPU. يتم تجميع الـ shots في كائنات ExecutionSpan، يشير كل منها إلى وقت بدء ووقت توقف وتحديد الـ shots التي تم جمعها في هذا النطاق.

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

لعرض معلومات نطاق التنفيذ، راجع البيانات الوصفية للنتيجة التي يعيدها SamplerV2، والتي تأتي في شكل كائن ExecutionSpans. هذا الكائن هو حاوية شبيهة بالقائمة تحتوي على مثيلات من فئات فرعية لـ ExecutionSpan، مثل SliceSpan.

مثال:

# Define two circuits, each with one parameter with two parameters.
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)

params = np.random.uniform(size=(2, 3)).T

sampler_pub = (transpiled_circuit, params)

# Instantiate the new Estimator object, then run the transpiled circuit
# using the set of parameters and observables.

job = sampler.run([sampler_pub], shots=4)

result = job.result()
spans = job.result().metadata["execution"]["execution_spans"]
print(spans)
ExecutionSpans([DoubleSliceSpan(<start='2026-05-13 14:23:20', stop='2026-05-13 14:23:21', size=24>)])
from qiskit.primitives import BitArray

# Get the mask of the 1st PUB for the 0th span.
mask = spans[0].mask(0)

# Decide whether the 0th shot of parameter set (1, 2) occurred in this span.
in_this_span = mask[2, 1, 0]

# Create a new bit array containing only the PUB-1 data collected during this span.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)

يمكن تصفية نطاقات التنفيذ لتشمل المعلومات المتعلقة بـ PUBs محددة، محددةً بمؤشراتها:

# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])
ExecutionSpans([DoubleSliceSpan(<start='2026-05-13 14:23:20', stop='2026-05-13 14:23:21', size=24>)])

عرض المعلومات العامة عن مجموعة نطاقات التنفيذ:

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)
Number of execution spans: 1
Start of the first span: 2026-05-13 14:23:20.441518
End of the last span: 2026-05-13 14:23:21.564845
Total duration (s): 1.123327

استخراج وفحص نطاق معين:

spans.sort()
print(" Start of first span:", spans[0].start)
print(" End of first span:", spans[0].stop)
print("#shots in first span:", spans[0].size)
Start of first span: 2026-05-13 14:23:20.441518
End of first span: 2026-05-13 14:23:21.564845
#shots in first span: 24
ملاحظة

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