Вхідні та вихідні дані Sampler
Версії пакетів
Код на цій сторінці розроблено з такими вимогами. Рекомендуємо використовувати ці версії або новіші.
qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0
Ця сторінка дає огляд вхідних та вихідних даних примітиву qiskit-ibm-runtime Sampler, який виконує робочі навантаження на IBM Quantum® Compute Service. Sampler дозволяє ефективно визначати векторизовані робочі навантаження за допомогою структури даних, відомої як Primitive Unified Bloc (PUB). Вони використовуються як вхідні дані для методу run() примітиву Sampler, який виконує визначене робоче навантаження як завдання. Потім, після завершення завдання, результати повертаються у форматі, що залежить як від використаних PUB-ів, так і від параметрів runtime, вказаних примітивом.
Вхідні дані
Кожен PUB має формат:
(<single circuit>, <one or more optional parameter value>, <optional shots>),
Може бути кілька елементів parameter values, і кожен елемент може бути або масивом, або одним параметром, залежно від обраної схеми. Крім того, вхідні дані повинні містити вимірювання.
Для примітиву Sampler PUB може містити щонайбільше три значення:
- Одиночний
QuantumCircuit, який може містити один або кілька об'єктівParameterПримітка: ці кола також повинні містити інструкції вимірювання для кожного з кубітів для вибірки. - Колекція значень параметрів для прив'язки кола до (потрібна лише якщо використовуються об'єкти
Parameter, які мають бути прив'язані під час виконання) - (Необов'язково) кількість shots для вимірювання кола
Наступний код демонструє приклад набору векторизованих вхідних даних для примітива Sampler та виконує їх на бекенді 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є контейнером для впорядкованих даних 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:
[[ 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 — містять атрибут метаданих про надіслане завдання. Метадані, що містять інформацію для всіх надісланих PUB-ів (наприклад, різні параметри runtime) можна знайти у 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 частинами. Час виконання кожної частини міститься в result.metadata["executor"].chunk_timing — списку об'єктів ChunkSpan. Кожен ChunkSpan має часові позначки start і stop та список parts, де кожна частина вказує індекс елемента (idx_item) і кількість конфігурацій цього елемента (size), які виконувалися в цій частині.
Sampler створює один елемент програми Executor на кожен PUB, тому idx_item — це індекс PUB у порядку, у якому PUB-и було передано до 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 вважається застарілим (deprecated). Клієнтський Sampler не повертає об'єкти ExecutionSpans; натомість використовуй часові характеристики частин, описані раніше. Інструкції з міграції дивись у Міграція на клієнтські примітиви.
Натисни, щоб дізнатися більше.
Результати завдань із застарілого серверного 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"]
# Get the mask of the 1st PUB for the 0th span.
mask = spans[0].mask(0)
# Create a new bit array containing only the PUB-0 data collected during this span.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)
# Take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])
# View global information about the collection of 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)
Щоб візуалізувати застарілі проміжки виконання, використовуй функцію draw_execution_spans.
Можливо, що часові вікна, визначені різними часовими проміжками виконання, перекриваються. Це не означає, що QPU виконував кілька операцій одночасно, а є артефактом певної класичної обробки, яка може відбуватися паралельно з квантовим виконанням. Гарантія полягає в тому, що зазначені дані точно відбулися у звітному часовому проміжку виконання, але не обов'язково, що межі часового вікна є якомога вужчими.