Вхідні та вихідні дані примітивів
Package versions
Код на цій сторінці розроблено з використанням наступних вимог. Ми рекомендуємо використовувати ці версії або новіші.
qiskit[all]~=2.4.0
Ця сторінка дає огляд вхідних і вихідних даних примітивів Qiskit. За допомогою цих примітивів ти можеш використовувати структуру даних, відому як Primitive Unified Bloc (PUB), для ефективного визначення векторизованих робочих навантажень. Ці PUB є фундаментальною одиницею роботи для виконання робочих навантажень. Вони використовуються як вхідні дані для методу run() примітивів Sampler і Estimator, які виконують визначене робоче навантаження як завдання. Після завершення завдання результати повертаються у форматі, що залежить від використаних PUB та будь-яких зазначених параметрів.
Огляд 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Примітка: ці схеми мають включати інструкції вимірювання для кожного кубіту, що підлягає вибірці. - Набір значень параметрів для прив'язки до схеми (потрібен лише якщо використовуються будь-які об'єкти
Parameter, які мають бути прив'язані під час виконання) - (Необов'язково) кількість знімків (shots) для вимірювання схеми
Наступний код демонструє приклад набору векторизованих вхідних даних для примітива Estimator.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
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.primitives import StatevectorEstimator
import numpy as np
# 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 without providing a backend
pm = generate_preset_pass_manager(optimization_level=1)
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, 10),
np.linspace(-4 * np.pi, 4 * np.pi, 10),
]
).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 = StatevectorEstimator()
estimator_pub = (transpiled_circuit, observables, params)
# Run the transpiled circuit
# using the set of parameters and observables.
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.
Estimator повертає одну оцінку математичного сподівання для кожного елемента транслюваної форми.
Ось кілька прикладів поширених шаблонів, виражених у термінах трансляції масивів. Їхнє візуальне представлення показано на малюнку нижче:
Набори значень параметрів представлені масивами 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. Таким чином, для цілей цих правил трансляції всі наступні елементи мають однакову форму:
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. PrimitiveResult містить ітерований список об'єктів PubResult, що містять результати виконання для кожного PUB. Наприклад, завдання, подане з 20 PUB, поверне об'єкт PrimitiveResult, що містить список із 20 PubResult, по одному для кожного PUB.
Кожен із цих об'єктів PubResult має атрибут data та необов'язковий атрибут metadata. Атрибут data — це налаштований DataBin, що містить оцінки математичних сподівань у випадку Estimator або вибірки виходів схеми у випадку Sampler.
Атрибут data може також містити іншу інформацію, що залежить від реалізації, наприклад стандартні відхилення. Атрибут metadata може містити додаткову інформацію про виконання відповідного PUB, що залежить від реалізації.
Нижче наведено візуальний контур структури даних PrimitiveResult:
- Estimator output
- Sampler output
└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object,
| | ## which includes data such as the following:
│ ├── 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,
| | ## which includes data such as the following:
| ├── 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[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── NAME_OF_CLASSICAL_REGISTER
│ │ └── BitArray of count data for first PUB (default is 'meas')
| |
│ └── NAME_OF_ANOTHER_CLASSICAL_REGISTER
│ └── BitArray of count data (exists only if more than one
| ClassicalRegister was specified in the circuit)
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| └── NAME_OF_CLASSICAL_REGISTER
| └── BitArray of count data for second PUB
├── ...
├── ...
└── ...
Вихідні дані Estimator
Як зазначалося раніше, дані, що повертаються в PubResult для примітива Estimator, залежать від реалізації. Наприклад, вони можуть містити масив математичних сподівань (PubResult.data.evs) і пов'язані стандартні відхилення (PubResult.data.stds).
Наведений нижче фрагмент коду описує формат 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, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10)), metadata={'target_precision': 0.0, 'circuit_metadata': {}})], metadata={'version': 2})
The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10))
And this DataBin has attributes: dict_keys(['evs', 'stds'])
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:
[[ 3.06161700e-16 4.52395120e-01 4.36594428e-01 2.16506351e-01
6.33718361e-01 -6.33718361e-01 -2.16506351e-01 -4.36594428e-01
-4.52395120e-01 -3.06161700e-16]
[ 1.22464680e-16 6.42787610e-01 9.84807753e-01 8.66025404e-01
3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01
-6.42787610e-01 -1.22464680e-16]
[ 4.89858720e-16 2.62002630e-01 -1.11618897e-01 -4.33012702e-01
9.25416578e-01 -9.25416578e-01 4.33012702e-01 1.11618897e-01
-2.62002630e-01 -4.89858720e-16]]
Вихідні дані Sampler
Коли завдання Sampler успішно завершується, повернутий об'єкт PrimitiveResult містить список SamplerPubResultів, по одному на PUB. Біна рні блоки даних цих об'єктів SamplerPubResult є словникоподібними об'єктами, що містять по одному BitArray для кожного ClassicalRegister у схемі.
Клас BitArray — це контейнер для впорядкованих даних знімків. Детальніше: він зберігає відібрані бітові рядки у вигляді байтів усередині двовимірного масиву. Крайня ліва вісь цього масиву проходить по впорядкованих знімках, тоді як крайня права вісь — по байтах.
Як перший приклад, розглянемо наступну десятикубітну схему:
from qiskit.primitives import StatevectorSampler
# 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)
sampler = StatevectorSampler()
# run the Sampler job and retrieve the results
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=1024, num_bits=10>))
BitArray: BitArray(<shape=(), num_shots=1024, num_bits=10>)
The shape of register `meas` is (1024, 2).
The bytes in register `alpha`, shot by shot:
[[ 0 0]
[ 3 255]
[ 0 0]
...
[ 3 255]
[ 3 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': 492, '1111111111': 532}
Коли схема містить більше одного класичного регістра, результати зберігаються в різних об'єктах 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
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=1024, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=1024, 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 (1024, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[1]
[1]
...
[0]
[0]
[1]]
The shape of register `beta` is (1024, 2).
The bytes in register `beta`, shot by shot:
[[ 1 255]
[ 1 255]
[ 1 255]
...
[ 0 0]
[ 0 0]
[ 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 (1024, 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:
[[ 1 255]
[ 1 255]
[ 1 255]
[ 1 255]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: -0.017578125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: -0.017578125
The shape of the merged results is (1024, 2).
The bytes of the merged results:
[[ 3 255]
[ 3 255]
[ 3 255]
...
[ 0 0]
[ 0 0]
[ 3 255]]
Метадані результату
На додаток до результатів виконання, об'єкти PrimitiveResult і PubResult містять необов'язковий атрибут 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:
'version' : 2,
The metadata of the PubResult result is:
'shots' : 1024,
'circuit_metadata' : {},