サンプラーの入出力
このページのコードは、以下の要件に基づいて開発されました。 これらのバージョン以降のご利用をお勧めします。
qiskit[all]~=2.5.1 qiskit-ibm-runtime~=0.47.0
このページでは、 IBM Quantum® Compute Service 上でワークロードを実行する「 qiskit-ibm-runtime Sampler」プリミティブの入力と出力の概要について説明します。 Sampler では、「 プリミティブ・ユニファイド・ブロック( PUB )」 と呼ばれるデータ構造を使用することで、ベクトル化されたワークロードを効率的に定義することができます。 これらは、Samplerプリミティブのメソッド run() への入力として使用され、そのメソッドは定義されたワークロードをジョブとして実行します。 その後、ジョブが完了すると、結果は、使用されたPUBと、プリミティブから指定された実行時オプションの両方に依存する形式で返されます。
入力
各 PUB は、以下の形式になっています:
(<single circuit>, <one or more optional parameter value>, <optional shots>),
項目は parameter values 複数存在する場合があり、各項目は選択した回路に応じて、配列または単一のパラメータのいずれかになります。 また、入力には測定値が含まれている必要があります。
Samplerプリミティブの場合、 PUB には最大3つの値を格納できます:
- 1つ以上の
Parameterオブジェクトを含む単一QuantumCircuitの 注:これらの回路には、サンプリング対象となる各量子ビットの測定手順も記載する必要があります。 - に対して回路をバインドするためのパラメータ値のコレクション(実行時にバインドする必要があるオブジェクトが
Parameter使用される場合にのみ必要) - (任意)回路を測定するためのショット数
以下のコードは、 Sampler プリミティブへのベクトル化された入力の例を示しており、 IBM®RuntimeJobV2 バックエンド上で単一のオブジェクトとして実行します。
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()出力
1つ以上のPUBがQPUに送信されて実行され、ジョブが正常に完了すると、データはコンテナオブジェクト PrimitiveResult として返され、このオブジェクトには RuntimeJobV2.result() メソッドを呼び出すことでアクセスできます。 には、各 PrimitiveResultPUB の実行結果を含む SamplerPubResult オブジェクトの反復可能なリストが含まれています。 これらのデータは、回路の出力のサンプルです。
このリストの各要素は、プリミティブの run() メソッドに送信された PUB に対応しています(たとえば、20個のPUBで送信されたジョブは、20 SamplerPubResult 個のオブジェクトのリストを含むオブジェクト PrimitiveResult を返します。各オブジェクトは、それぞれの PUB に対応しています)。
各オブジェクト SamplerPubResult は、 data とという2 metadata つの属性を持っています。
- この
data属性は、実際の測定値や標準偏差などを含む、DataBinカスタマイズされたデータセットです。 データ・ビンは辞書のようなオブジェクトであり、回路内の各要素につきBitArray``ClassicalRegister1つずつ格納されています。 - この
BitArrayクラスは、順序付けられたショットデータを格納するためのコンテナです。 サンプリングされたビット列を、2次元配列内のバイトとして格納します。 この配列の最も左側の軸は順序付きショットを、最も右側の軸はバイトをそれぞれ表しています。 - この
metadata属性には、使用された実行時オプションに関する情報が含まれています(詳細は、このページの「 結果のメタデータ 」セクションで後述します)。
以下は、データ構造 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
├── ...
├── ...
└── ...
簡単に言えば、1つのジョブは オブジェクトを PrimitiveResult 返し、1つ以上の SamplerPubResult オブジェクトのリストを含んでいます。 これらの SamplerPubResult オブジェクトは、そのジョブに送信された各 PUB の測定データを保存します。
最初の例として、次の10キュービット回路を見てみましょう:
# 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")Output:
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 255]
[ 0 0]
[ 0 1]
...
[ 3 0]
[ 0 0]
[ 3 254]]
場合によっては、バイト形式からビット文字列 BitArray に変換しておくと便利なことがあります。 この get_count メソッドは、ビット列とその出現回数を対応付けた辞書を返します。
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")Output:
Counts: {'1111111111': 1346, '0000000000': 1754, '0000000001': 55, '1000000000': 56, '1111111110': 92, '0111111111': 23, '1011111111': 15, '0001111111': 23, '1111011011': 1, '1111111101': 45, '1111111011': 108, '1111110111': 32, '0100000000': 10, '0000000111': 21, '0011111111': 21, '1111110000': 26, '1101111111': 47, '1111011111': 23, '1111111010': 6, '1100000000': 45, '1111100000': 32, '1110000000': 21, '1111101111': 13, '0010000000': 14, '0000000011': 19, '0000000101': 2, '0000001110': 2, '0000100000': 4, '0000001111': 20, '1111111100': 22, '0000010000': 5, '1101110111': 4, '1011111101': 1, '0000000010': 15, '0000001000': 12, '1111110110': 7, '1111000000': 3, '0010000001': 1, '0111011111': 3, '1001111111': 3, '1101111011': 3, '0000011111': 16, '0000011110': 3, '0001111011': 1, '1011111011': 3, '1111110011': 4, '1111101011': 2, '0000000100': 6, '1110111111': 12, '1111111000': 17, '0000111111': 5, '0001111101': 2, '1101100000': 2, '1101110001': 1, '1000001111': 2, '1111101110': 1, '1110111101': 1, '1101111101': 2, '1110000100': 1, '0100011111': 1, '1110000010': 1, '0011111110': 2, '0111111110': 1, '1111110010': 1, '0111110111': 1, '0000000110': 1, '0101111111': 1, '1101011111': 1, '1111001111': 1, '1110011111': 1, '0011111000': 2, '1101111110': 3, '1110111110': 1, '0110000000': 2, '1110000111': 1, '0000010111': 3, '0001000000': 3, '0111101111': 1, '0000011100': 1, '1000000001': 1, '1111011010': 1, '0000001010': 1, '1111100111': 2, '1111100011': 2, '0000001101': 1, '0111001111': 1, '1111111001': 1, '1101111000': 1, '0111110000': 1, '1111000111': 1, '1010000000': 1, '0011110000': 1, '1100000001': 1, '1011001101': 1, '0000001100': 1, '1100111111': 1, '1110111011': 1, '1111011101': 1, '1000011111': 1, '1101111001': 1, '0101101111': 1, '0000011011': 1, '0000111011': 1, '0111111100': 1, '1011100000': 1, '0011111011': 1, '0000010010': 1, '1001111011': 1}
回路に複数の古典的レジスタが含まれる場合、結果はそれぞれ異なる BitArray オブジェクトに格納されます。 次の例では、従来のレジスタを2つの独立したレジスタに分割することで、前のスニペットを修正しています:
# 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}")Output:
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")Output:
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[0]
[0]
[0]
...
[1]
[0]
[1]]
The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 0]
[ 0 0]
[ 1 255]
...
[ 1 255]
[ 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 (4096, 1).
The bytes in `beta` after bit-wise slicing:
[[0]
[0]
[7]
...
[7]
[0]
[7]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 0]
[ 0 0]
[ 1 255]
[ 0 0]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.115234375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.02392578125
The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 0 0]
[ 0 0]
[ 3 254]
...
[ 3 255]
[ 0 0]
[ 3 255]]
結果のメタデータ
実行結果に加え、および SamplerPubResult``PrimitiveResult オブジェクトには、送信されたジョブに関するメタデータ属性が含まれています。 提出されたすべてのPUBに関する情報(利用可能な各種実行時オプションなど)を含むメタデータは にあり PrimitiveResult.metatada、各 PUB 固有のメタデータは にあります SamplerPubResult.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},")Output:
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:10', stop='2026-08-01 08:21:13', size=4096>)])},
'version' : 2,
The metadata of the PubResult result is:
'circuit_metadata' : {},
実行スパンを表示する
IBM Quantum Compute Service で実行されたジョブ SamplerV2 の結果には、メタデータに実行タイミング情報が含まれています。
このタイミング情報を利用することで、特定のショットがQPU上で実行された時期の上限と下限を特定することができます。
ショットは「 ExecutionSpan オブジェクト」にグループ化されており、各オブジェクトには開始時刻、終了時刻、およびその期間内に収集されたショットの詳細が指定されています。
実行スパンは、メソッド ExecutionSpan.mask を指定することで、そのウィンドウ期間中にどのデータが実行されたかを特定します。 このメソッドは、任意のプリミティブ統合ブロック( PUB ) のインデックスを受け取り、そのウィンドウ内で実行されたすべてのショットに対して True となるブール値のマスクを返します。 PUBは、Sampler実行コールに渡された順序でインデックス付けされます。 例えば、 PUB の形状が (2, 3) であり、4回のショットで実行された場合、マスクの形状は となります (2, 3, 4)。 詳細については、execution_span API のページをご覧ください。
実行スパン情報を確認するには、によって返される結果のメタデータを確認してください。この 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)Output:
ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:37', stop='2026-08-01 08:21:38', 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])Output:
ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:37', stop='2026-08-01 08:21:38', 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)Output:
Number of execution spans: 1
Start of the first span: 2026-08-01 08:21:37.606114
End of the last span: 2026-08-01 08:21:38.960352
Total duration (s): 1.354238
特定のスパンを抽出して確認する:
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)Output:
Start of first span: 2026-08-01 08:21:37.606114
End of first span: 2026-08-01 08:21:38.960352
#shots in first span: 24
個別の実行期間で指定された時間枠が重複する可能性があります。 これは、QPUが一度に複数の実行を行っていたからではなく、量子実行と並行して行われる可能性のある特定の古典的処理による副産物である。 保証されるのは、参照されたデータが報告された実行期間内に確実に発生したことですが、時間枠の幅が可能な限り狭いとは限りません。