อินพุตและเอาต์พุตของ Sampler
เวอร์ชันแพ็คเกจ
โค้ดในหน้านี้ได้รับการพัฒนาโดยใช้ข้อกำหนดต่อไปนี้ แนะนำให้ใช้เวอร์ชันเหล่านี้หรือใหม่กว่า
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
หน้านี้ให้ภาพรวมของอินพุตและเอาต์พุตของ Qiskit Runtime Sampler primitive ซึ่งรันเวิร์กโหลดบนทรัพยากรประมวลผล IBM Quantum® Sampler ช่วยให้คุณกำหนดเวิร์กโหลดแบบ vectorized ได้อย่างมีประสิทธิภาพโดยใช้โครงสร้างข้อมูลที่เรียกว่า Primitive Unified Bloc (PUB) ซึ่งใช้เป็นอินพุตสำหรับเมธอด run() ของ Sampler primitive ที่รันเวิร์กโหลดที่กำหนดเป็นงาน จากนั้นหลังจากงานเสร็จสิ้น ผลลัพธ์จะถูกส่งคืนในรูปแบบที่ขึ้นอยู่กับทั้ง PUB และตัวเลือก runtime ที่ระบุจาก primitive
อินพุต
PUB แต่ละอันมีรูปแบบ:
(<circuit เดียว>, <ค่าพารามิเตอร์เสริมหนึ่งหรือมากกว่า>, <shots เสริม>),
สามารถมีรายการ parameter values หลายรายการ และแต่ละรายการสามารถเป็นอาร์เรย์หรือพารามิเตอร์เดียว ขึ้นอยู่กับ circuit ที่เลือก นอกจากนี้ อินพุตต้องมีการวัด
สำหรับ Sampler primitive PUB สามารถมีค่าได้สูงสุดสามค่า:
QuantumCircuitเดียวที่อาจมีออบเจ็กต์Parameterหนึ่งหรือมากกว่า หมายเหตุ: circuit เหล่านี้ควรมีคำสั่งการวัดสำหรับ qubit แต่ละตัวที่จะทำ sampling ด้วย- คอลเลกชันของค่าพารามิเตอร์สำหรับ bind กับ circuit (จำเป็นเฉพาะเมื่อมีออบเจ็กต์
Parameterที่ต้อง bind ที่ runtime) - (เสริม) จำนวน shots สำหรับวัด circuit
โค้ดต่อไปนี้แสดงตัวอย่างชุดอินพุตแบบ vectorized สำหรับ Sampler primitive และรันบน IBM® backend เป็นออบเจ็กต์ 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 มีรายการแบบ iterable ของออบเจ็กต์ SamplerPubResult ที่มีผลลัพธ์การรันสำหรับ PUB แต่ละตัว ข้อมูลเหล่านี้คือตัวอย่างของเอาต์พุต circuit
แต่ละองค์ประกอบในรายการนี้สอดคล้องกับ PUB ที่ส่งไปยังเมธอด run() ของ primitive (เช่น งานที่ส่งพร้อม PUB 20 ตัวจะส่งคืนออบเจ็กต์ PrimitiveResult ที่มีรายการออบเจ็กต์ SamplerPubResult 20 ตัว ตัวหนึ่งสำหรับแต่ละ PUB)
แต่ละออบเจ็กต์ SamplerPubResult มีทั้งแอตทริบิวต์ data และ metadata
- แอตทริบิวต์
dataคือDataBinที่กำหนดเองซึ่งมีค่าการวัดจริง ส่วนเบี่ยงเบนมาตรฐาน และอื่นๆ data bins เป็นออบเจ็กต์คล้าย dict ที่มีBitArrayหนึ่งตัวต่อClassicalRegisterใน circuit - คลาส
BitArrayเป็นคอนเทนเนอร์สำหรับข้อมูล shot ที่มีลำดับ เก็บ bitstrings ที่ sampling ไว้เป็นไบต์ภายในอาร์เรย์สองมิติ แกนซ้ายสุดของอาร์เรย์นี้ดำเนินการบน 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 แต่ละตัวที่ส่งไปยังงาน
สำหรับตัวอย่างแรก ลองดู circuit 10 qubit ต่อไปนี้:
# 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 เป็น bitstrings เมธอด get_count ส่งคืน dictionary ที่ map bitstrings กับจำนวนครั้งที่เกิดขึ้น
# 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}
เมื่อ circuit มี classical register มากกว่าหนึ่งตัว ผลลัพธ์จะถูกเก็บไว้ในออบเจ็กต์ BitArray ที่แตกต่างกัน ตัวอย่างต่อไปนี้แก้ไข snippet ก่อนหน้าโดยแบ่ง classical register เป็น register แยกกันสองตัว:
# 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 สำหรับการประมวลผลหลังที่มีประสิทธิภาพ
เนื่องจากอาร์เรย์โดยทั่วไปมีประสิทธิภาพดีกว่า dictionary จึงแนะนำให้ทำการประมวลผลหลังบนออบเจ็กต์ BitArray โดยตรงแทน dictionary ของจำนวนนับ คลาส 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 ยังมีแอตทริบิวต์ metadata เกี่ยวกับงานที่ส่ง metadata ที่มีข้อมูลสำหรับ PUB ทั้งหมดที่ส่ง (เช่น ตัวเลือก runtime ต่างๆ ที่มีอยู่) สามารถพบได้ใน PrimitiveResult.metadata ขณะที่ metadata ที่เฉพาะเจาะจงสำหรับแต่ละ PUB พบได้ใน SamplerPubResult.metadata
ข้อมูลเมตาผลลัพธ์ Sampler ยังมีข้อมูลเวลาการรันที่เรียกว่า execution span
ในช่อง metadata การใช้งาน primitive สามารถคืนข้อมูลใดก็ได้เกี่ยวกับการรันที่เกี่ยวข้อง และไม่มีคู่คีย์-ค่าใดที่รับประกันโดย primitive พื้นฐาน ดังนั้น metadata ที่คืนมาอาจแตกต่างกันในการใช้งาน primitive ที่แตกต่างกัน
# 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' : {},
ดู execution spans
ผลลัพธ์ของงาน SamplerV2 ที่รันใน Qiskit Runtime มีข้อมูลเวลาการรันใน metadata
ข้อมูลเวลานี้สามารถใช้กำหนดขอบเขตบนและล่างของ timestamp สำหรับเวลาที่ shots เฉพาะถูกรันบน QPU
Shots จะถูกจัดกลุ่มเป็นออบเจ็กต์ ExecutionSpan ซึ่งแต่ละอันระบุเวลาเริ่มต้น เวลาสิ้นสุด และข้อกำหนดของ shots ที่เก็บใน span
execution span ระบุว่าข้อมูลใดถูกรันระหว่างหน้าต่างของมันโดยให้เมธอด ExecutionSpan.mask เมธอดนี้เมื่อให้ดัชนี Primitive Unified Block (PUB) ใดก็ตาม จะคืน boolean mask ที่เป็น True สำหรับ shots ทั้งหมดที่รันระหว่างหน้าต่างนั้น PUB ถูก index ตามลำดับที่ให้ในการเรียก Sampler run ตัวอย่างเช่น ถ้า PUB มีรูปร่าง (2, 3) และรันด้วย 4 shots รูปร่างของ mask จะเป็น (2, 3, 4) ดูหน้า API execution_span สำหรับรายละเอียดเพิ่มเติม
เพื่อดูข้อมูล execution span ให้ตรวจสอบ metadata ของผลลัพธ์ที่ส่งคืนโดย SamplerV2 ซึ่งมาในรูปแบบออบเจ็กต์ ExecutionSpans ออบเจ็กต์นี้เป็นคอนเทนเนอร์คล้ายรายการที่มีอินสแตนซ์ของ subclass ของ 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)
สามารถกรอง execution spans เพื่อรวมข้อมูลที่เกี่ยวข้องกับ PUB เฉพาะ โดยเลือกตาม index:
# 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>)])
ดูข้อมูลทั่วไปเกี่ยวกับคอลเลกชัน execution spans:
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
ดึงและตรวจสอบ span เฉพาะ:
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
เป็นไปได้ที่หน้าต่างเวลาที่ระบุโดย execution spans ที่แตกต่างกันจะซ้อนทับกัน นี่ไม่ใช่เพราะ QPU กำลังรันหลายการรันพร้อมกัน แต่เป็นผลจากการประมวลผล classical บางอย่างที่อาจเกิดขึ้นพร้อมกับการรัน quantum การรับประกันที่ให้คือข้อมูลที่อ้างอิงเกิดขึ้นในช่วง execution span ที่รายงานอย่างแน่นอน แต่ไม่จำเป็นว่าขีดจำกัดของหน้าต่างเวลาจะแน่นที่สุดเท่าที่เป็นไปได้