Вхідні та вихідні дані Sampler
Версії пакетів
Код на цій сторінці розроблено з такими вимогами. Рекомендуємо використовувати ці версії або новіші.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
Ця сторінка дає огляд вхідних та вихідних даних примітиву Sampler Qiskit Runtime, який виконує робочі навантаження на обчислювальних ресурсах IBM Quantum®. Sampler дозволяє ефективно визначати векторизовані робочі навантаження за допомогою структури даних, відомої як Primitive Unified Bloc (PUB). Вони використовуються як вхідні дані для методу run() примітиву Sampler, який виконує визначене робоче навантаження як завдання. Потім, після завершення завдання, результати повертаються у форматі, що залежить як від використаних PUB-ів, так і від параметрів runtime, вказаних примітивом.
Вхідні дані
Кожен PUB має формат:
(<одиночне коло>, <одне або кілька необов'язкових значень параметрів>, <необов'язкові shots>),
Може бути кілька елементів значень параметрів, кожен з яких може бути або масивом, або одним параметром залежно від обраного кола. Крім того, вхідні дані мають містити вимірювання.
Для примітиву Sampler PUB може містити щонайбільше три значення:
- Одиночний
QuantumCircuit, який може містити один або кілька об'єктівParameterПримітка: ці кола також повинні містити інструкції вимірювання для кожного з кубітів для вибірки. - Колекція значень параметрів для прив'язки кола до (потрібна лише якщо використовуються об'єкти
Parameter, які мають бути прив'язані під час виконання) - (Необов'язково) кількість shots для вимірювання кола
Наступний код демонструє приклад набору векторизованих вхідних даних для примітиву Sampler та виконує їх на бекенді 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є контейнером для впорядкованих даних 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 — містять атрибут метаданих про надіслане завдання. Метадані, що містять інформацію для всіх надісланих PUB-ів (наприклад, різні параметри 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, виконаних у цьому вікні. PUB-и індексуються в порядку, у якому їх було передано виклику Sampler run. Наприклад, якщо 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)
Часові проміжки виконання можна фільтрувати, щоб включати інформацію, що стосується конкретних PUB-ів, вибраних за їхніми індексами:
# 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 виконував кілька операцій одночасно, а є артефактом певної класичної обробки, яка може відбуватися паралельно з квантовим виконанням. Гарантія полягає в тому, що зазначені дані точно відбулися у звітному часовому проміжку виконання, але не обов'язково, що межі часового вікна є якомога вужчими.