メインコンテンツへスキップ

Samplerの入力と出力

パッケージバージョン

このページのコードは、以下の要件を使用して開発されました。 これらのバージョン以降を使用することをお勧めします。

qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1

このページでは、IBM Quantum®のコンピュートリソース上でワークロードを実行するQiskit Runtime SamplerプリミティブのInputとOutputの概要を説明します。SamplerはPrimitive Unified Bloc (PUB)と呼ばれるデータ構造を使って、ベクトル化されたワークロードを効率的に定義することを可能にします。これらはSamplerプリミティブのrun()メソッドへの入力として使用され、定義されたワークロードをジョブとして実行します。ジョブが完了した後、結果はPUBと指定されたランタイムオプションの両方に依存するフォーマットで返されます。

入力

各PUBは次の形式です:

(<単一circuit>, <1つ以上のオプションのパラメータ値>, <オプションのshots>),

複数のparameter valuesアイテムを含めることができ、各アイテムは選択したcircuitに応じて配列または単一パラメータのいずれかになります。また、入力には測定値が含まれている必要があります。

Samplerプリミティブの場合、PUBには最大3つの値を含めることができます:

  • 1つ以上のParameterオブジェクトを含む場合がある単一のQuantumCircuit 注意:これらのcircuitには、サンプリングする各qubitの測定命令も含める必要があります。
  • circuitに対してバインドするパラメータ値のコレクション θk\theta_k(ランタイムでバインドする必要があるParameterオブジェクトが使用されている場合のみ必要)
  • (オプション)circuitを測定する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()

出力

1つ以上のPUBがQPUに送信されてジョブが正常に完了すると、データはRuntimeJobV2.result()メソッドを呼び出してアクセスするPrimitiveResultコンテナオブジェクトとして返されます。PrimitiveResultには、各PUBの実行結果を含むSamplerPubResultオブジェクトの反復可能なリストが含まれています。これらのデータはcircuit出力のサンプルです。

このリストの各要素は、プリミティブのrun()メソッドに送信されたPUBに対応しています(たとえば、20個のPUBで送信されたジョブは、各PUBに対応する20個のSamplerPubResultオブジェクトのリストを含むPrimitiveResultオブジェクトを返します)。

SamplerPubResultオブジェクトはdata属性とmetadata属性の両方を持っています。

  • data属性は、実際の測定値、標準偏差などを含む、カスタマイズされたDataBinです。データビンはcircuit内のClassicalRegisterごとに1つのBitArrayを含むdict状のオブジェクトです。
  • BitArrayクラスは、順序付きshotデータのコンテナです。サンプリングされたビット文字列を2次元配列内のバイトとして格納します。この配列の左端の軸は順序付きshotを横断し、右端の軸はバイトを横断します。
  • 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
├── ...
├── ...
└── ...

簡単に言えば、単一のジョブはPrimitiveResultオブジェクトを返し、1つ以上のSamplerPubResultオブジェクトのリストを含みます。これらのSamplerPubResultオブジェクトは、ジョブに送信された各PUBの測定データを格納します。

最初の例として、次の10qubit circuitを見てみましょう:

# 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}

circuitに複数の古典レジスタが含まれている場合、結果は異なる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}")
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]]

結果メタデータ

実行結果に加えて、PrimitiveResultSamplerPubResultオブジェクトの両方に、送信されたジョブに関するmetadata属性が含まれています。すべての送信PUBに関する情報(さまざまなランタイムオプションなど)を含むmetadataはPrimitiveResult.metadataに、各PUBに固有のmetadataはSamplerPubResult.metadataに見つかります。

Samplerの結果metadataには、execution spanと呼ばれる実行タイミング情報も含まれています。

備考

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-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' : {},

実行スパンの表示

Qiskit Runtimeで実行されたSamplerV2ジョブの結果には、metadataに実行タイミング情報が含まれています。 このタイミング情報は、特定のshotがQPU上でいつ実行されたかについての上限と下限のタイムスタンプを設定するために使用できます。 ShotはExecutionSpanオブジェクトにグループ化されており、それぞれが開始時刻、終了時刻、およびそのスパンで収集されたshotの仕様を示します。

実行スパンは、ExecutionSpan.maskメソッドを提供することで、そのウィンドウ中にどのデータが実行されたかを指定します。このメソッドは、任意のPrimitive Unified Block (PUB)インデックスを指定すると、そのウィンドウ中に実行されたすべてのshotに対してTrueのブールマスクを返します。PUBはSampler run呼び出しに与えられた順序でインデックスされます。たとえば、PUBの形状が(2, 3)で4 shotsで実行された場合、マスクの形状は(2, 3, 4)になります。詳細については、execution_span APIページをご覧ください。

実行スパン情報を表示するには、SamplerV2から返された結果のmetadataを確認します。これはExecutionSpansオブジェクトの形式になっています。このオブジェクトは、SliceSpanなどのExecutionSpanサブクラスのインスタンスを含むリスト状のコンテナです。

例:

# 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に関連する情報のみを含めるようにフィルタリングできます。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が同時に複数の実行を行っているためではなく、量子実行と並行して発生する可能性がある特定の古典的処理のアーティファクトです。保証されていることは、参照されたデータが報告された実行スパンで確実に発生したということですが、タイムウィンドウの制限が可能な限りタイトであるとは限りません。