SamplingBackend
前節(サンプリング推定)では、 量子回路シミュレータ上で、サンプリングを実行して演算子の期待値を推定する方法を説明しました。QURI Partsはプラットフォームに依存しないように設計されているため、実際の量子コンピュータ上でもほぼ同じコードを実行することができます。
QURI Partsでは、SamplingBackend
オブジェクトを使って実際のデバイスにジョブを投入します。このチュートリアルでは、Qiskit
やBraket
など、異なるプロバイダのデバイス間で共有されるいくつかの共通機能について説明します。プロバイダ固有の機能については、対応するチュートリアルのページを参照してください。
前提条件
このセクションでは、前セクション(サンプリング推定)で説明したトピックを必要としますので、このセクションの前に読んでおく必要があります。QURI Partsはすべてのプロバイダが提供するバックエンドに対応しています。お好みに応じてどれをインストールしても構いません。このチュートリアルでは、Amazon BraketとIBM 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 Parts
のSamplingBackend.sample
では、デバイスがサポートする最大ショット数以上の ショット数を投入することができます。これは、SamplingBackend
がn_shots
をSamplingJob
のバッチにまとめ、各バッチのショット数がデバイスがサポートする最大ショット数以下となるようなショット分配を行うためです。
一方、デバイスが最小ショット数に制約を課している場合があります。この場合、バッチ内のショット数がデバイスでサポートされている最小ショット数より少ない場合、バックエンドのenable_shots_roundup
引数を使用して、残りのバッチをどうするかを決めることができます。これが True
に設定されている場合、バックエンドは残りのバッチのショット数を指定された最小ショット数に丸めます。そうでなければ、バックエンドはそのバッチを無視します。
量子ビットマッピング
実際の量子デバイスを使用する場合、デバイスのキャリブレーションデータを調べて選択した特定のデバイスの量子ビットを使用したい場合があります。SamplingBackend
はqubit_mapping
引数でそのような使い方をサポートします。qubit_mapping
では、入力回路の量子ビットインデックスとデバイスの量子ビットを任意の1対1で対応付けることができます。例えば、回路の量子ビットをデバイスの量子ビットに0 → 3, 1 → 2, 2 → 0, 3 → 1のようにマッピングしたい場合は、以下のように指定します:
qubit_mapping = {0: 3, 1: 2, 2: 0, 3: 1}
これをサンプリングバックエンドに渡すことができます。デバイスからの測定結果は、元の量子ビットのインデックスで解釈されるように逆向きにマッピングされるため、結果は量子ビットのマッピングがない場合と同様になります。
実行前の回路変換
SamplingBackend
は入力回路を受け取ると、回路をバックエンドに送信する前に回路のトランパイルを行います。デフォルトで実行されるトランパイルは、バックエンドによって異なります。