דלג לתוכן הראשי

קלטים ופלטים של 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 שנעשה בהם שימוש והן באפשרויות ה-Runtime שצוינו מהפרימיטיב.

קלטים

כל PUB הוא בפורמט:

(<מעגל בודד>, <ערך פרמטר אחד או יותר, אופציונלי>, <shots אופציונלי>),

יכולים להיות פריטי parameter values מרובים, וכל פריט יכול להיות מערך או פרמטר בודד, בהתאם למעגל שנבחר. בנוסף, הקלט חייב להכיל מדידות.

עבור הפרימיטיב 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()

פלטים

לאחר שאחד או יותר PUBs נשלחים ל-QPU לביצוע ומשימה מסתיימת בהצלחה, הנתונים מוחזרים כאובייקט מיכל PrimitiveResult שניגש אליו על ידי קריאה לשיטת RuntimeJobV2.result(). ה-PrimitiveResult מכיל רשימה ניתנת לחזרה של אובייקטי SamplerPubResult המכילים את תוצאות הביצוע לכל PUB. נתונים אלה הם דגימות של פלט המעגל.

כל רכיב ברשימה זו תואם ל-PUB שהוגש לשיטת run() של הפרימיטיב (לדוגמה, משימה שהוגשה עם 20 PUBs תחזיר אובייקט PrimitiveResult המכיל רשימה של 20 אובייקטי SamplerPubResult, אחד לכל PUB).

לכל אובייקט SamplerPubResult יש גם מאפיין data וגם מאפיין metadata.

  • מאפיין data הוא DataBin מותאם אישית המכיל את ערכי המדידה בפועל, סטיות תקן וכן הלאה. מיכלי הנתונים הם אובייקטים דמויי-מילון המכילים BitArray אחד לכל ClassicalRegister במעגל.
  • מחלקה BitArray היא מיכל לנתוני shots מסודרים. היא מאחסנת את מחרוזות הביטים שנדגמו כבתים בתוך מערך דו-ממדי. הציר השמאלי ביותר של מערך זה עובר על פני shots מסודרים, בעוד שהציר הימני ביותר עובר על פני בתים.
  • מאפיין metadata מכיל מידע על אפשרויות ה-Runtime שנעשה בהן שימוש (מוסבר מאוחר יותר בסעיף מטא-דאטה של תוצאות בדף זה).

להלן מתאר חזותי של מבנה הנתונים 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 שהוגשו (כגון אפשרויות ה-Runtime השונות הזמינות) ניתן למצוא ב-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 ביצע מספר פעולות בו-זמנית, אלא הוא תוצר של עיבוד קלאסי מסוים שעלול להתרחש בו-זמנית עם הביצוע הקוונטי. הערובה הניתנת היא שהנתונים המצוינים התרחשו בטווח הביצוע המדווח בוודאות, אך לאו דווקא שגבולות חלון הזמן הם הצרים ביותר האפשריים.