Input dan output Sampler
Versi paket
Kode pada halaman ini dikembangkan menggunakan persyaratan berikut. Kami merekomendasikan menggunakan versi ini atau yang lebih baru.
qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0
Halaman ini memberikan gambaran umum tentang input dan output dari primitif Sampler qiskit-ibm-runtime, yang menjalankan workload pada IBM Quantum® Compute Service. Sampler memungkinkan kamu mendefinisikan workload tervektorisasi secara efisien dengan menggunakan struktur data yang dikenal sebagai Primitive Unified Bloc (PUB). PUB digunakan sebagai input untuk metode run() dari primitif Sampler, yang menjalankan workload yang didefinisikan sebagai sebuah job. Kemudian, setelah job selesai, hasilnya dikembalikan dalam format yang bergantung baik pada PUB yang digunakan maupun opsi runtime yang ditentukan dari primitif tersebut.
Input
Setiap PUB dalam format:
(<single circuit>, <one or more optional parameter value>, <optional shots>),
Bisa ada beberapa item parameter values, dan setiap item bisa berupa array atau parameter tunggal, tergantung sirkuit yang dipilih. Selain itu, input harus mengandung pengukuran.
Untuk primitif Sampler, PUB bisa mengandung paling banyak tiga nilai:
- Satu
QuantumCircuit, yang bisa mengandung satu atau lebih objekParameterCatatan: Sirkuit ini juga harus menyertakan instruksi pengukuran untuk masing-masing qubit yang akan disampling. - Kumpulan nilai parameter untuk mengikat sirkuit terhadap (hanya diperlukan jika ada objek
Parameteryang harus diikat saat runtime) - (Opsional) jumlah shots untuk mengukur sirkuit
Kode berikut mendemonstrasikan contoh kumpulan input tervektorisasi ke primitif Sampler dan mengeksekusinya pada backend IBM® sebagai satu objek 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()
Output
Setelah satu atau lebih PUB dikirim ke QPU untuk eksekusi dan job berhasil selesai, data dikembalikan sebagai objek kontainer PrimitiveResult yang diakses dengan memanggil metode RuntimeJobV2.result(). PrimitiveResult berisi daftar yang bisa diiterasi dari objek SamplerPubResult yang berisi hasil eksekusi untuk setiap PUB. Data ini adalah sampel dari output sirkuit.
Setiap elemen dalam daftar ini sesuai dengan PUB yang dikirimkan ke metode run() primitif (misalnya, job yang dikirim dengan 20 PUB akan mengembalikan objek PrimitiveResult yang berisi daftar 20 objek SamplerPubResult, satu untuk setiap PUB).
Setiap objek SamplerPubResult memiliki atribut data dan metadata.
- Atribut
dataadalahDataBinyang dikustomisasi berisi nilai pengukuran aktual, deviasi standar, dan sebagainya. Data bin adalah objek seperti dict yang berisi satuBitArrayperClassicalRegisterdalam sirkuit. - Kelas
BitArrayadalah kontainer untuk data shot yang terurut. Ia menyimpan bitstring yang disampling sebagai byte di dalam array dua dimensi. Sumbu paling kiri array ini merepresentasikan shot yang terurut, sedangkan sumbu paling kanan merepresentasikan byte. - Atribut
metadataberisi informasi tentang opsi runtime yang digunakan (dijelaskan nanti di bagian Metadata hasil di halaman ini).
Berikut adalah garis besar visual dari struktur data 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
├── ...
├── ...
└── ...
Sederhananya, satu job mengembalikan objek PrimitiveResult dan berisi daftar satu atau lebih objek SamplerPubResult. Objek SamplerPubResult ini kemudian menyimpan data pengukuran untuk setiap PUB yang dikirimkan ke job.
Sebagai contoh pertama, mari lihat sirkuit sepuluh qubit berikut:
# 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]]
Terkadang lebih mudah untuk mengonversi dari format byte di BitArray ke bitstring. Metode get_count mengembalikan dictionary yang memetakan bitstring ke jumlah kemunculannya.
# 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}
Ketika sirkuit mengandung lebih dari satu classical register, hasilnya disimpan dalam objek BitArray yang berbeda. Contoh berikut memodifikasi cuplikan sebelumnya dengan membagi classical register menjadi dua register yang berbeda:
# 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>)
Menggunakan objek BitArray untuk post-processing yang efisien
Karena array umumnya menawarkan performa yang lebih baik dibandingkan dictionary, disarankan untuk melakukan post-processing langsung pada objek BitArray daripada pada dictionary hasil hitungan (counts). Kelas BitArray menawarkan berbagai metode untuk melakukan beberapa operasi post-processing umum:
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]]
Metadata hasil
Selain hasil eksekusi, kedua objek PrimitiveResult dan SamplerPubResult mengandung atribut metadata tentang job yang dikirimkan. Metadata yang mengandung informasi untuk semua PUB yang dikirimkan (seperti berbagai opsi runtime yang tersedia) dapat ditemukan di PrimitiveResult.metatada, sementara metadata yang spesifik untuk setiap PUB ditemukan di SamplerPubResult.metadata.
Sampler sisi client mengirim workload-nya sebagai job Executor. Metadata dari hasil Executor yang mendasarinya, yang mencakup informasi waktu eksekusi bernama execution span, tersedia di bawah key "executor" pada PrimitiveResult.metadata.
Di bidang metadata, implementasi primitif bisa mengembalikan informasi apa pun yang berkaitan dengan eksekusi yang relevan bagi mereka, dan tidak ada pasangan kunci-nilai yang dijamin oleh primitif dasar. Oleh karena itu, metadata yang dikembalikan dalam implementasi primitif yang berbeda mungkin berbeda.
# 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' : {},
Melihat rentang eksekusi
Sampler sisi client memakai Executor untuk menjalankan job, yang mengeksekusi pekerjaannya di QPU dalam chunk. Waktu setiap chunk ada di result.metadata["executor"].chunk_timing, yaitu list objek ChunkSpan. Setiap ChunkSpan punya timestamp start dan stop serta list parts, tempat setiap part memberikan indeks item (idx_item) dan jumlah konfigurasi dari item itu (size) yang dijalankan dalam chunk tersebut.
Sampler membuat satu item program Executor untuk setiap PUB, jadi idx_item adalah indeks PUB, sesuai urutan PUB diberikan ke run(). Kalau twirling diaktifkan, setiap randomisasi dari setiap set nilai parameter dihitung sebagai konfigurasi terpisah.
Untuk memvisualisasikan waktunya, berikan list tersebut ke draw_chunk_timings, seperti ditunjukkan pada contoh berikut.
Untuk mengetahui arti timestamp chunk, cara membaca plot, dan kenapa bisa muncul jeda antar chunk, lihat Lihat execution span di panduan input dan output 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
Execution span di Sampler legacy
Mulai qiskit-ibm-runtime v0.50.0, Sampler legacy sisi server sudah deprecated. Sampler sisi klien tidak mengembalikan objek ExecutionSpans; pakai pengukuran waktu chunk yang dijelaskan sebelumnya. Lihat Migrasi ke primitif sisi klien untuk petunjuk migrasinya.
Klik untuk info lebih lanjut.
Hasil job dari Sampler legacy sisi server berisi informasi waktu eksekusi di result.metadata["execution"]["execution_spans"], dalam bentuk objek ExecutionSpans. Objek ini adalah wadah mirip list yang berisi objek ExecutionSpan, masing-masing menunjukkan waktu mulai, waktu berhenti, dan spesifikasi shot mana yang dikumpulkan dalam span tersebut.
Sebuah execution span menentukan data mana yang dieksekusi selama jendela waktunya lewat metode ExecutionSpan.mask. Metode ini, jika diberi indeks PUB apa pun, mengembalikan mask boolean yang bernilai True untuk semua shot yang dieksekusi selama jendela waktunya. Misalnya, jika sebuah PUB berbentuk (2, 3) dan dijalankan dengan empat shot, maka bentuk mask-nya adalah (2, 3, 4). Lihat halaman API execution_span untuk detail lengkapnya.
from qiskit.primitives import BitArray
spans = result.metadata["execution"]["execution_spans"]
# Ambil mask dari PUB ke-1 untuk span ke-0.
mask = spans[0].mask(0)
# Buat bit array baru yang hanya berisi data PUB-0 yang dikumpulkan selama span ini.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)
# Ambil subset span yang merujuk data di PUB 0 atau 2
spans.filter_by_pub([0, 2])
# Lihat informasi global tentang kumpulan execution span
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)
Untuk memvisualisasikan execution span legacy, pakai fungsi draw_execution_spans.
Kemungkinan jendela waktu yang ditentukan oleh rentang eksekusi yang berbeda bisa tumpang tindih. Ini bukan karena QPU mengeksekusi beberapa operasi secara bersamaan, melainkan merupakan produk dari pemrosesan klasik tertentu yang mungkin terjadi bersamaan dengan eksekusi kuantum. Jaminannya adalah bahwa data yang dirujuk pasti terjadi dalam rentang eksekusi yang dilaporkan, tetapi belum tentu batas jendela waktunya sesempit mungkin.