Перейти до основного вмісту

Вхідні та вихідні дані примітивів

Нова модель виконання, зараз у бета-версії

Бета-версія нової моделі виконання вже доступна. Модель направленого виконання забезпечує більшу гнучкість при налаштуванні робочого процесу пом'якшення помилок. Дивись посібник Модель направленого виконання для отримання додаткової інформації.

Версії пакетів

Код на цій сторінці розроблено з використанням наступних вимог. Ми рекомендуємо використовувати ці версії або новіші.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

Ця сторінка дає огляд вхідних і вихідних даних примітивів Qiskit Runtime, що виконують робочі навантаження на обчислювальних ресурсах IBM Quantum®. Ці примітиви надають тобі можливість ефективно визначати векторизовані робочі навантаження за допомогою структури даних, відомої як Primitive Unified Bloc (PUB). Ці PUB є фундаментальною одиницею роботи, яку QPU потребує для виконання цих навантажень. Вони використовуються як вхідні дані для методу run() примітивів Sampler і Estimator, які виконують визначене робоче навантаження як завдання. Після завершення завдання результати повертаються у форматі, що залежить як від використаних PUB, так і від параметрів середовища виконання, зазначених у примітивах Sampler або Estimator.

Огляд PUB

Коли ти викликаєш метод run() примітива, основним обов'язковим аргументом є list з одного або більше кортежів — по одному для кожної схеми, що виконується примітивом. Кожен із цих кортежів вважається PUB, і обов'язкові елементи кожного кортежу в списку залежать від використовуваного примітива. Дані, що надаються цим кортежам, також можна організувати у різноманітних формах для забезпечення гнучкості робочого навантаження завдяки трансляції (broadcasting) — правила якої описано у наступному розділі.

Estimator PUB

Для примітива Estimator формат PUB повинен містити щонайбільше чотири значення:

  • Один QuantumCircuit, який може містити один або більше об'єктів Parameter
  • Список з одного або більше спостережуваних величин (observables), що визначають математичні сподівання для оцінки, організованих у масив (наприклад, одна спостережувана представляється як 0-вимірний масив, список спостережуваних — як 1-вимірний масив, тощо). Дані можуть бути в будь-якому форматі ObservablesArrayLike, наприклад Pauli, SparsePauliOp, PauliList або str.
    примітка

    Якщо у тебе є дві комутуючі спостережувані величини в різних PUB, але з однаковою схемою, вони не будуть оцінюватися за допомогою одного вимірювання. Кожен PUB представляє різний базис для вимірювання, і тому для кожного PUB потрібні окремі вимірювання. Щоб гарантувати, що комутуючі спостережувані величини оцінюються за допомогою одного вимірювання, вони повинні бути згруповані в одному PUB.

  • Набір значень параметрів для прив'язки до схеми. Це може бути вказано як єдиний масивоподібний об'єкт, де останній індекс відповідає об'єктам Parameter схеми, або опущено (або еквівалентно встановлено в None), якщо схема не має об'єктів Parameter.
  • (Необов'язково) цільова точність для оцінки математичних сподівань

Sampler PUB

Для примітива Sampler формат кортежу PUB містить щонайбільше три значення:

  • Один QuantumCircuit, який може містити один або більше об'єктів Parameter Примітка: ці схеми також повинні включати інструкції вимірювання для кожного кубіту, що підлягає вибірці.
  • Набір значень параметрів для прив'язки до схеми θk\theta_k (потрібен лише якщо використовуються будь-які об'єкти Parameter, які мають бути прив'язані під час виконання)
  • (Необов'язково) кількість знімків (shots) для вимірювання схеми

Наступний код демонструє приклад набору векторизованих вхідних даних для примітива Estimator і виконує їх на бекенді 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,
EstimatorV2 as Estimator,
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)

# 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

# Define three observables. The inner length-1 lists cause this array of
# observables to have shape (3, 1), rather than shape (3,) if they were
# omitted.
observables = [
[SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
[SparsePauliOp("XX")],
[SparsePauliOp("IY")],
]
# Apply the same layout as the transpiled circuit.
observables = [
[observable.apply_layout(layout) for observable in observable_set]
for observable_set in observables
]

# Estimate the expectation value for all 300 combinations of observables
# and parameter values, where the pub result will have shape (3, 100).
#
# This shape is due to our array of parameter bindings having shape
# (100, 2), combined with our array of observables having shape (3, 1).
estimator_pub = (transpiled_circuit, observables, params)

# Instantiate the new estimator object, then run the transpiled circuit
# using the set of parameters and observables.
estimator = Estimator(mode=backend)
job = estimator.run([estimator_pub])
result = job.result()

Правила трансляції (broadcasting)

PUB агрегують елементи з кількох масивів (спостережуваних величин і значень параметрів), дотримуючись тих самих правил трансляції, що й NumPy. Цей розділ коротко підсумовує ці правила. Для детального пояснення дивись документацію правил трансляції NumPy.

Правила:

  • Вхідні масиви не повинні мати однакову кількість вимірів.
    • Результуючий масив матиме таку саму кількість вимірів, що й вхідний масив з найбільшою кількістю вимірів.
    • Розмір кожного виміру — це найбільший розмір відповідного виміру.
    • Відсутні виміри вважаються такими, що мають розмір один.
  • Порівняння форм починається з крайнього правого виміру і продовжується вліво.
  • Два виміри сумісні, якщо їхні розміри рівні або якщо один із них дорівнює 1.

Приклади пар масивів, що транслюються:

A1     (1d array):      1
A2 (2d array): 3 x 5
Result (2d array): 3 x 5

A1 (3d array): 11 x 2 x 7
A2 (3d array): 11 x 1 x 7
Result (3d array): 11 x 2 x 7

Приклади пар масивів, що не транслюються:

A1     (1d array):  5
A2 (1d array): 3

A1 (2d array): 2 x 1
A2 (3d array): 6 x 5 x 4 # This would work if the middle dimension were 2, but it is 5.

EstimatorV2 повертає одну оцінку математичного сподівання для кожного елемента транслюваної форми.

Ось кілька прикладів поширених шаблонів, виражених у термінах трансляції масивів. Їхнє візуальне представлення показано на малюнку нижче:

Набори значень параметрів представлені масивами n x m, а масиви спостережуваних величин — одним або кількома стовпцевими масивами. Для кожного прикладу в попередньому коді набори значень параметрів поєднуються з масивом спостережуваних для отримання оцінок математичних сподівань.

  • Приклад 1: (трансляція одної спостережуваної) має набір значень параметрів розміром 5x1 і масив спостережуваних розміром 1x1. Один елемент масиву спостережуваних поєднується з кожним елементом набору значень параметрів для створення єдиного масиву 5x1, де кожен елемент є комбінацією оригінального елементу в наборі значень параметрів з елементом у масиві спостережуваних.

  • Приклад 2: (zip) має набір значень параметрів розміром 5x1 і масив спостережуваних розміром 5x1. Результат — масив 5x1, де кожен елемент є комбінацією n-го елементу набору значень параметрів з n-м елементом масиву спостережуваних.

  • Приклад 3: (зовнішній добуток/product) має набір значень параметрів розміром 1x6 і масив спостережуваних розміром 4x1. Їх комбінація дає масив 4x6, створений шляхом поєднання кожного елементу набору значень параметрів із кожним елементом масиву спостережуваних, і таким чином кожне значення параметра стає цілим стовпцем у виводі.

  • Приклад 4: (стандартне nd-узагальнення) має масив набору значень параметрів розміром 3x6 і два масиви спостережуваних розміром 3x1. Вони поєднуються для створення двох вихідних масивів 3x6 у спосіб, аналогічний попередньому прикладу.

Це зображення ілюструє кілька візуальних представлень трансляції масивів

# Broadcast single observable
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = SparsePauliOp("ZZZ") # shape ()
# >> pub result has shape (5,)

# Zip
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = [
SparsePauliOp(pauli) for pauli in ["III", "XXX", "YYY", "ZZZ", "XYZ"]
] # shape (5,)
# >> pub result has shape (5,)

# Outer/Product
parameter_values = np.random.uniform(size=(1, 6)) # shape (1, 6)
observables = [
[SparsePauliOp(pauli)] for pauli in ["III", "XXX", "YYY", "ZZZ"]
] # shape (4, 1)
# >> pub result has shape (4, 6)

# Standard nd generalization
parameter_values = np.random.uniform(size=(3, 6)) # shape (3, 6)
observables = [
[
[SparsePauliOp(["XII"])],
[SparsePauliOp(["IXI"])],
[SparsePauliOp(["IIX"])],
],
[
[SparsePauliOp(["ZII"])],
[SparsePauliOp(["IZI"])],
[SparsePauliOp(["IIZ"])],
],
] # shape (2, 3, 1)
# >> pub result has shape (2, 3, 6)
SparsePauliOp

Кожен SparsePauliOp вважається одним елементом у цьому контексті, незалежно від кількості операторів Паулі, що містяться в SparsePauliOp. Таким чином, для цілей цих правил трансляції всі наступні елементи мають однакову форму:

a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()

Наступні списки операторів, хоч і еквівалентні за інформаційним змістом, мають різні форми:

list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")] # shape (3, )

Огляд вихідних даних примітивів

Після того як один або кілька PUB надіслано до QPU для виконання і завдання успішно завершено, дані повертаються як об'єкт-контейнер PrimitiveResult, доступний через виклик методу RuntimeJobV2.result(). PrimitiveResult містить ітерований список об'єктів PubResult, що містять результати виконання для кожного PUB. Залежно від використовуваного примітива, ці дані будуть або математичними сподіваннями та їхніми похибками у випадку Estimator, або вибірками виходів схеми у випадку Sampler.

Кожен елемент цього списку відповідає кожному PUB, поданому до методу run() примітива (наприклад, завдання, подане з 20 PUB, поверне об'єкт PrimitiveResult, що містить список із 20 PubResult, по одному для кожного PUB).

Кожен із цих об'єктів PubResult має атрибути data і metadata. Атрибут data — це налаштований DataBin, що містить фактичні значення вимірювань, стандартні відхилення тощо. Цей DataBin має різні атрибути залежно від форми або структури відповідного PUB, а також від параметрів пом'якшення помилок, зазначених у примітиві, що використовувався для подачі завдання (наприклад, ZNE або PEC). Натомість атрибут metadata містить інформацію про параметри середовища виконання та пом'якшення помилок (пояснено пізніше в розділі Метадані результату на цій сторінці).

Нижче наведено візуальний контур структури даних PrimitiveResult:

└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── evs
│ │ └── List of estimated expectation values in the shape
| | specified by the first pub
│ └── stds
│ └── List of calculated standard deviations in the
| same shape as above
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| ├── evs
| │ └── List of estimated expectation values in the shape
| | specified by the second pub
| └── stds
| └── List of calculated standard deviations in the
| same shape as above
├── ...
├── ...
└── ...

Простіше кажучи, одне завдання повертає об'єкт PrimitiveResult, що містить список з одного або більше об'єктів PubResult. Ці об'єкти PubResult зберігають дані вимірювань для кожного PUB, поданого до завдання.

Кожен PubResult має різні формати та атрибути залежно від типу примітива, що використовувався для завдання. Деталі пояснено нижче.

Вихідні дані Estimator

Кожен PubResult для примітива Estimator містить щонайменше масив математичних сподівань (PubResult.data.evs) і пов'язані стандартні відхилення (або PubResult.data.stds, або PubResult.data.ensemble_standard_error залежно від використаного resilience_level), але може містити більше даних залежно від зазначених параметрів пом'якшення помилок.

Наведений нижче фрагмент коду описує формат PrimitiveResult (і пов'язаного PubResult) для завдання, створеного вище.

print(
f"The result of the submitted job had {len(result)} PUB and has a value:\n {result}\n"
)
print(
f"The associated PubResult of this job has the following data bins:\n {result[0].data}\n"
)
print(f"And this DataBin has attributes: {result[0].data.keys()}")
print(
"Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the\n\
number of parameters in the circuit -- combined with our array of observables having shape (3, 1). \n"
)
print(
f"The expectation values measured from this PUB are: \n{result[0].data.evs}"
)
The result of the submitted job had 1 PUB and has a value:
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})

The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100))

And this DataBin has attributes: dict_keys(['evs', 'stds', 'ensemble_standard_error'])
Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the
number of parameters in the circuit -- combined with our array of observables having shape (3, 1).

The expectation values measured from this PUB are:
[[ 0.00948597 0.12163221 0.29100944 0.40535344 0.46625814 0.54716103
0.57690846 0.59809047 0.5784682 0.50924868 0.4579837 0.40035644
0.37174056 0.32887613 0.25850853 0.26396412 0.25852429 0.26074166
0.29282485 0.34388535 0.37368314 0.43562138 0.46912323 0.51955146
0.54430185 0.55467261 0.5162183 0.52744696 0.47261781 0.42613541
0.35400013 0.33217125 0.29600426 0.27561903 0.25307754 0.25672088
0.28783701 0.36612701 0.40433263 0.44428286 0.51028376 0.55034507
0.55979913 0.57160124 0.54127534 0.49753533 0.42942659 0.32552331
0.20215918 0.04303087 -0.08115732 -0.18473659 -0.34015892 -0.44489319
-0.49112115 -0.54588034 -0.60601287 -0.55869218 -0.53353861 -0.51628053
-0.44978534 -0.38090252 -0.32481576 -0.28832245 -0.27057547 -0.26542929
-0.27054473 -0.29367389 -0.31531828 -0.38462352 -0.40276794 -0.47168997
-0.48548191 -0.5382924 -0.52716406 -0.53277032 -0.50776933 -0.48512907
-0.44335198 -0.38756463 -0.34438156 -0.29199194 -0.2729216 -0.24602918
-0.23527174 -0.3019153 -0.35159518 -0.38303379 -0.42434541 -0.47743033
-0.54652609 -0.5877912 -0.59175701 -0.57386895 -0.56416812 -0.48022381
-0.3853372 -0.2639702 -0.12030502 0.02081148]
[ 0.00581765 0.0552677 0.15998546 0.20725389 0.25452232 0.34178711
0.39196437 0.47050268 0.50031815 0.527952 0.57231161 0.64066903
0.72429779 0.77011181 0.78174711 0.86610308 0.88646487 0.91337151
0.94245978 0.98100173 0.97372966 1.00936279 1.01881647 1.0544496
1.01954368 1.03699664 0.99845469 1.03845105 1.00936279 1.00354513
0.95409508 0.95264067 0.91264431 0.91846196 0.8355604 0.80283611
0.77956549 0.74102354 0.69520953 0.64575948 0.58976457 0.53231524
0.43996 0.3956004 0.32069812 0.27706572 0.22470684 0.16653032
0.07272066 -0.00218162 -0.05817653 -0.06253977 -0.15853104 -0.25015908
-0.28506499 -0.34251432 -0.44359604 -0.44432324 -0.53158804 -0.60285429
-0.637033 -0.67630215 -0.71266249 -0.76793019 -0.81519862 -0.86464867
-0.90173621 -0.93155168 -0.9337333 -0.98245614 -0.99627307 -1.01518044
-1.01590764 -1.04863194 -1.00499955 -1.02827016 -1.01663485 -1.0108172
-1.02317971 -0.97518407 -0.96500318 -0.94682302 -0.901009 -0.87846559
-0.79556404 -0.84937733 -0.78101991 -0.73811472 -0.65521316 -0.57667485
-0.59921825 -0.49813653 -0.44577766 -0.36505772 -0.33524225 -0.25888556
-0.21161713 -0.12289792 -0.03781474 0.00654486]
[ 0.01315429 0.18799671 0.42203343 0.603453 0.67799397 0.75253494
0.76185256 0.72567827 0.65661825 0.49054535 0.3436558 0.16004385
0.01918334 -0.11235955 -0.26473006 -0.33817484 -0.36941628 -0.39188819
-0.35681008 -0.29323102 -0.22636339 -0.13812003 -0.08057002 -0.01534667
0.06906002 0.07234859 0.03398191 0.01644286 -0.06412716 -0.15127432
-0.24609482 -0.28829816 -0.32063579 -0.3672239 -0.32940532 -0.28939435
-0.20389148 -0.00876953 0.11345574 0.24280625 0.43080296 0.5683749
0.67963826 0.74760208 0.76185256 0.71800493 0.63414634 0.48451631
0.3315977 0.08824335 -0.10413812 -0.30693341 -0.52178679 -0.6396273
-0.69717731 -0.74924637 -0.76842971 -0.67306111 -0.53548918 -0.42970677
-0.26253768 -0.08550288 0.06303097 0.19128528 0.27404768 0.33379008
0.36064675 0.34420389 0.30309674 0.2132091 0.19073719 0.07180049
0.04494382 -0.02795286 -0.04932858 -0.03727049 0.00109619 0.04055906
0.13647575 0.20005481 0.27624007 0.36283913 0.3551658 0.38640723
0.32502055 0.24554673 0.07782954 -0.02795286 -0.19347767 -0.3781858
-0.49383393 -0.67744588 -0.73773637 -0.78268019 -0.793094 -0.70156207
-0.55905728 -0.40504248 -0.20279529 0.0350781 ]]

Як Estimator обчислює похибку

Окрім оцінки середнього значення спостережуваних, поданих у вхідних PUB (поле evs у DataBin), Estimator також намагається надати оцінку похибки, пов'язаної з цими математичними сподіваннями. Всі запити до estimator заповнюватимуть поле stds величиною, схожою на стандартну похибку середнього для кожного математичного сподівання, але деякі параметри пом'якшення помилок виробляють додаткову інформацію, наприклад ensemble_standard_error.

Розглянемо одну спостережувану O\mathcal{O}. За відсутності ZNE можна вважати, що кожен знімок виконання Estimator надає точкову оцінку математичного сподівання O\langle \mathcal{O} \rangle. Якщо точкові оцінки знаходяться у векторі Os, то значення, що повертається в ensemble_standard_error, еквівалентне наступному (де σO\sigma_{\mathcal{O}}стандартне відхилення оцінки математичного сподівання, а NshotsN_{shots} — кількість знімків):

σONshots,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{shots}} },

де всі знімки розглядаються як частина одного ансамблю. Якщо ти запросив гейтове twirling (twirling.enable_gates = True), можна відсортувати точкові оцінки O\langle \mathcal{O} \rangle на набори, що мають спільний twirl. Назвемо ці набори оцінок O_twirls, і їх є num_randomizations (кількість twirl). Тоді stds — це стандартна похибка середнього O_twirls, як у

σONtwirls,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{twirls}} },

де σO\sigma_{\mathcal{O}} — стандартне відхилення O_twirls, а NtwirlsN_{twirls} — кількість twirl. Коли twirling не увімкнено, stds і ensemble_standard_error рівні.

Якщо ти вмикаєш ZNE, тоді описані вище stds стають вагами в нелінійній регресії до моделі екстраполятора. Те, що в кінцевому підсумку повертається в stds у цьому випадку, — це невизначеність моделі підгонки, оцінена при коефіцієнті шуму, рівному нулю. При поганій підгонці або великій невизначеності підгонки повідомлені stds можуть ставати дуже великими. Коли ZNE увімкнено, pub_result.data.evs_noise_factors і pub_result.data.stds_noise_factors також заповнюються, щоб ти міг виконати власну екстраполяцію.

Вихідні дані Sampler

Коли завдання Sampler успішно завершується, повернутий об'єкт PrimitiveResult містить список SamplerPubResultів, по одному на PUB. Бінарні блоки даних цих об'єктів SamplerPubResult є словникоподібними об'єктами, що містять по одному BitArray для кожного ClassicalRegister у схемі.

Клас BitArray — це контейнер для впорядкованих даних знімків. Детальніше: він зберігає відібрані бітові рядки у вигляді байтів усередині двовимірного масиву. Крайня ліва вісь цього масиву проходить по впорядкованих знімках, тоді як крайня права вісь — по байтах.

Як перший приклад, розглянемо наступну десятикубітну схему:

# 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 254]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 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: {'1111111110': 199, '0000000000': 1337, '1111111111': 1052, '1111111000': 33, '1110000000': 65, '1100100000': 2, '1100000000': 25, '0010001110': 1, '0000000011': 30, '1111111011': 58, '1111111010': 25, '0000000110': 7, '0010000001': 11, '0000000001': 179, '1110111110': 6, '1111110000': 33, '1111101111': 49, '1110111111': 40, '0000111010': 2, '0100000000': 35, '0000000010': 51, '0000100000': 31, '0110000000': 7, '0000001111': 22, '1111111100': 24, '1011111110': 5, '0001111111': 58, '0000111111': 24, '1111101110': 10, '0000010001': 5, '0000001001': 2, '0011111111': 38, '0000001000': 11, '1111100000': 34, '0111111111': 45, '0000000100': 18, '0000000101': 2, '1011111111': 11, '1110000001': 13, '1101111000': 1, '0010000000': 52, '0000010000': 17, '0000011111': 15, '1110100001': 1, '0111111110': 9, '0000000111': 19, '1101111111': 15, '1111110111': 17, '0011111110': 5, '0001101110': 1, '0111111011': 6, '0100001000': 2, '0010001111': 1, '1111011000': 1, '0000111110': 4, '0011110010': 1, '1110111100': 2, '1111000000': 8, '1111111101': 27, '0000011110': 6, '0001000000': 5, '1111010000': 3, '0000011011': 4, '0001111110': 9, '1111011110': 6, '1110001111': 2, '0100000001': 7, '1110111011': 3, '1111101101': 2, '1101111110': 5, '1110000010': 7, '0111111000': 1, '1110111000': 1, '0000100001': 2, '1110100000': 6, '1000000001': 2, '0001011111': 1, '0000010111': 1, '1011111100': 1, '0111110000': 5, '0110111111': 2, '0010000010': 1, '0001111100': 4, '0011111001': 2, '1111110011': 1, '1110000011': 5, '0000001011': 8, '0100000010': 3, '1111011111': 13, '0010111000': 2, '0100111110': 1, '1111101000': 2, '1110110000': 2, '1100000001': 1, '0001110000': 3, '1011101111': 2, '1111000001': 2, '1111110001': 8, '1111110110': 4, '1100000010': 3, '0011000000': 2, '1110011111': 3, '0011101111': 3, '0010010000': 2, '0000100010': 1, '1100001110': 1, '0001111011': 4, '1010000000': 3, '0000001110': 5, '0000001010': 2, '0011111011': 4, '0100100000': 2, '1111110100': 1, '1111100011': 3, '0000110110': 1, '0001111101': 2, '1111100001': 2, '1000000000': 5, '0010000011': 3, '0010011111': 3, '0100001111': 1, '0100000111': 1, '1011101110': 1, '0011110111': 1, '1100000111': 1, '1100111111': 3, '0001111010': 1, '1101111011': 1, '0111111100': 2, '0100000110': 2, '0100000011': 2, '0001101111': 3, '0001000001': 1, '1111110010': 1, '0010100000': 1, '0011100000': 4, '1010001111': 1, '0101111111': 2, '1111101001': 1, '1110111101': 1, '0000011101': 1, '1110001000': 2, '0001111001': 1, '0101000000': 1, '1111111001': 5, '0001110111': 2, '0000111001': 1, '0100001011': 1, '0000010011': 1, '1011110111': 1, '0011110001': 1, '0000001100': 2, '0111010111': 1, '0001101011': 1, '1110010000': 2, '1110000100': 1, '0010111111': 3, '0111011100': 1, '1010001000': 1, '0000101110': 1, '0011111100': 2, '0000111100': 2, '1110011110': 1, '0011111000': 2, '0110100000': 1, '1001101111': 1, '1011000000': 1, '1101000000': 1, '1110001011': 1, '1110110111': 1, '0110111110': 1, '0011011111': 1, '0111100000': 1, '0000110111': 1, '0000010010': 2, '1111101100': 2, '1111011101': 1, '1101100000': 1, '0010111110': 1, '1101101110': 1, '1111001111': 1, '1101111100': 1, '1011111010': 1, '0001100000': 1, '1101110111': 1, '1100001011': 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]
[1]
[1]
...
[0]
[0]
[1]]

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 135]
[ 0 247]
[ 1 247]
...
[ 0 0]
[ 1 224]
[ 1 255]]

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]
[7]
[7]
...
[0]
[0]
[7]]

The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 135]
[ 0 247]
[ 1 247]
[ 1 128]
[ 1 255]]

Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.068359375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.06396484375

The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 1 15]
[ 1 239]
[ 3 239]
...
[ 0 0]
[ 3 192]
[ 3 255]]

Метадані результату

На додаток до результатів виконання, об'єкти PrimitiveResult і PubResult містять атрибут metadata про подане завдання. Метадані, що містять інформацію для всіх поданих PUB (наприклад, різні параметри середовища виконання), можна знайти в PrimitiveResult.metadata, тоді як метадані, специфічні для кожного PUB, знаходяться в PubResult.metadata.

примітка

У полі 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-01-15 08:07:33', stop='2026-01-15 08:07:36', size=4096>)])},
'version' : 2,

The metadata of the PubResult result is:
'circuit_metadata' : {},

Для завдань Sampler ти також можеш переглянути метадані результату, щоб зрозуміти, коли певні дані були запущені; це називається проміжком виконання (execution span).