メインコンテンツまでスキップ

SamplingBackend

前節(サンプリング推定)では、量子回路シミュレータ上で、サンプリングを実行して演算子の期待値を推定する方法を説明しました。QURI Partsはプラットフォームに依存しないように設計されているため、実際の量子コンピュータ上でもほぼ同じコードを実行することができます。

QURI Partsでは、SamplingBackendオブジェクトを使って実際のデバイスにジョブを投入します。このチュートリアルでは、QiskitBraketなど、異なるプロバイダのデバイス間で共有されるいくつかの共通機能について説明します。プロバイダ固有の機能については、対応するチュートリアルのページを参照してください。

前提条件

このセクションでは、前セクション(サンプリング推定)で説明したトピックを必要としますので、このセクションの前に読んでおく必要があります。QURI Partsはすべてのプロバイダが提供するバックエンドに対応しています。お好みに応じてどれをインストールしても構いません。このチュートリアルでは、Amazon BraketIBM Quantumを提供するバックエンドを例として使用します。インストール方法と使用方法については、それぞれのチュートリアルで説明します。

SamplingBackendとSampler

実デバイスを使用するには、SamplingBackendオブジェクトを作成し、そのバックエンドを使用するSamplerを作成する必要があります。これは、様々なバックエンドデバイス、デバイスに対する計算ジョブ、ジョブの結果を扱うための統一されたインターフェースを提供します。

SamplingBackendを使ってSamplerを作成することができます。まず、お好みのバックエンドプロバイダーでSamplingBackendを作成します。例えば、

from quri_parts.qiskit.backend import QiskitSamplingBackend
from quri_parts.braket.backend import BraketSamplingBackend
from qiskit_aer import AerSimulator
from braket.devices import LocalSimulator


# sampling_backend = QiskitSamplingBackend(backend=AerSimulator())
sampling_backend = BraketSamplingBackend(device=LocalSimulator())

SamplingBackendを使用

これらのバックエンドを直接使用することも可能ですが、後述するように通常は不要です。SamplingBackendにはsample()メソッドがあり、これはSamplingJobオブジェクトを返し、サンプリングジョブの結果を取り出すことができます:

from math import pi
from quri_parts.circuit import QuantumCircuit

circuit = QuantumCircuit(4)
circuit.add_X_gate(0)
circuit.add_H_gate(1)
circuit.add_Y_gate(2)
circuit.add_CNOT_gate(1, 2)
circuit.add_RX_gate(3, pi/4)

sampling_job = sampling_backend.sample(circuit, n_shots=1000)
sampling_result = sampling_job.result()

print(sampling_result.counts)
#output
Counter({3: 421, 5: 416, 13: 88, 11: 75})

SamplingBackendからSamplerを作成

バックエンドを直接使う代わりに、create_sampler_from_sampling_backend関数を使って、SamplingBackendからSamplerを作成することができます:

from quri_parts.core.sampling import create_sampler_from_sampling_backend

sampler = create_sampler_from_sampling_backend(
sampling_backend # you may replace it with other sampling backends you prefer.
)

作成したSamplerは通常通り使用できます:

sampling_count = sampler(circuit, 1000)
print(sampling_count)
#output
{11: 77, 13: 83, 3: 409, 5: 431}

サンプリング推定

ここでは、以前のサンプリング推定チュートリアルで使用したのと同じコードでサンプリング推定を実行する方法を説明します。SamplingEstimatorを作成するには、ConcurrentSamplerを指定する必要があります。

from quri_parts.core.sampling import create_concurrent_sampler_from_sampling_backend
from quri_parts.qiskit.backend import QiskitSamplingBackend
from quri_parts.braket.backend import BraketSamplingBackend
from qiskit_aer import AerSimulator
from braket.devices import LocalSimulator


# sampling_backend = QiskitSamplingBackend(backend=AerSimulator())
# concurrent_sampler = create_concurrent_sampler_from_sampling_backend(qiskit_sampling_backend)

sampling_backend = BraketSamplingBackend(device=LocalSimulator())
concurrent_sampler = create_concurrent_sampler_from_sampling_backend(sampling_backend)

そして、どちらのConcurrentSamplerを使っても、サンプリング推定を行うことができます。

from quri_parts.core.estimator.sampling import create_sampling_estimator
from quri_parts.core.state import quantum_state
from quri_parts.core.operator import Operator, pauli_label, PAULI_IDENTITY
from quri_parts.core.measurement import bitwise_commuting_pauli_measurement
from quri_parts.core.sampling.shots_allocator import create_weighted_random_shots_allocator

estimator = create_sampling_estimator(
5000,
concurrent_sampler,
bitwise_commuting_pauli_measurement,
create_weighted_random_shots_allocator(seed=777),
)

initial_state = quantum_state(4, bits=0b0101)
op = Operator({
pauli_label("Z0"): 0.25,
pauli_label("Z1 Z2"): 2.0,
pauli_label("X1 X2"): 0.5 + 0.25j,
pauli_label("Z1 Y3"): 1.0j,
pauli_label("Z2 Y3"): 1.5 + 0.5j,
pauli_label("X1 Y3"): 2.0j,
PAULI_IDENTITY: 3.0,
})

estimate = estimator(op, initial_state)

print(f'Estimated value: {estimate.value}')
print(f'Estimated error: {estimate.error}')
#output
Estimated value: (0.6730848581603831+0.042719018177966035j)
Estimated error: 0.07069176439271635

サンプリングバックエンドに共通なオプションや特徴

ショット分配

通常、実機では任意のショット数を実行することはできません。しかし、QURI PartsSamplingBackend.sampleでは、デバイスがサポートする最大ショット数以上のショット数を投入することができます。これは、SamplingBackendn_shotsSamplingJobのバッチにまとめ、各バッチのショット数がデバイスがサポートする最大ショット数以下となるようなショット分配を行うためです。

一方、デバイスが最小ショット数に制約を課している場合があります。この場合、バッチ内のショット数がデバイスでサポートされている最小ショット数より少ない場合、バックエンドのenable_shots_roundup引数を使用して、残りのバッチをどうするかを決めることができます。これが True に設定されている場合、バックエンドは残りのバッチのショット数を指定された最小ショット数に丸めます。そうでなければ、バックエンドはそのバッチを無視します。

量子ビットマッピング

実際の量子デバイスを使用する場合、デバイスのキャリブレーションデータを調べて選択した特定のデバイスの量子ビットを使用したい場合があります。SamplingBackendqubit_mapping引数でそのような使い方をサポートします。qubit_mappingでは、入力回路の量子ビットインデックスとデバイスの量子ビットを任意の1対1で対応付けることができます。例えば、回路の量子ビットをデバイスの量子ビットに0 → 3, 1 → 2, 2 → 0, 3 → 1のようにマッピングしたい場合は、以下のように指定します:

qubit_mapping = {0: 3, 1: 2, 2: 0, 3: 1}

これをサンプリングバックエンドに渡すことができます。デバイスからの測定結果は、元の量子ビットのインデックスで解釈されるように逆向きにマッピングされるため、結果は量子ビットのマッピングがない場合と同様になります。

実行前の回路変換

SamplingBackendは入力回路を受け取ると、回路をバックエンドに送信する前に回路のトランパイルを行います。デフォルトで実行されるトランパイルは、バックエンドによって異なります。