サンプリング推定
量子コンピュータの演算子の期待値を推定するためには、サンプリング測定が必要です。サンプリング測定では、量子回路の実行とそれに続く量子ビットの測定を複数回繰り返します。この繰り返し測定の統計量を用いて演算子の期待値を推定します。
実際の量子コンピュータでサンプリング推定を行う前に、量子回路シミュレータでどのようにシミュレーションできるかを見てみましょう。このチュートリアルでは、以下の方法を紹介します。
Operator
のグループピング- グルーピング結果から測定回路を構築
- 測定回路にショットを割り当て、サンプリング
- サンプリング結果から期待値を再構築
前提条件
このチュートリアルで使用するQURI Partsモ ジュール: quri-parts-circuit
,quri-parts-core
,quri-parts-qulacs
以下のようにインストールすることができます:
!pip install "quri-parts[qulacs]"
概要
このチュートリアルの主な目的は、sampling estimatorを用いてサンプリング推定を行う方法を紹介することです。sampling estimatorとは、サンプリング実験によって期待値を計算するQuantumEstimator
のことです。準備として、期待値を推定したい演算子と状態を作成します。
from quri_parts.core.operator import Operator, pauli_label, PAULI_IDENTITY
from quri_parts.core.state import quantum_state
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,
})
print("Operator:")
print(op)
state = quantum_state(4, bits=0b0101)
print("")
print("State:")
print(state)
#output
Operator:
0.25*Z0 + 2.0*Z1 Z2 + (0.5+0.25j)*X1 X2 + 1j*Z1 Y3 + (1.5+0.5j)*Z2 Y3 + 2j*X1 Y3 + 3.0*I
State:
ComputationalBasisState(qubit_count=4, bits=0b101, phase=0π/2)
ここでは,sampling estimatorを作成し,使用するためのサンプルコードを提供します。以下の仕様でsampling estimatorを作成します:
- 総ショット数: 5000
- 測定ファクトリ: ビットごとに可換なパウリへのグルーピング
- ショットアロケータ: 比例ショット配分
「測定ファクトリ」と「ショットアロケータ」については後のセクションで説明します。とりあえず、一時的にこれらを当然と考えて、それらを使ってsampling estimatorを作ってみましょう:
from quri_parts.core.estimator.sampling import create_sampling_estimator
from quri_parts.core.measurement import bitwise_commuting_pauli_measurement
from quri_parts.core.sampling.shots_allocator import create_proportional_shots_allocator
from quri_parts.qulacs.sampler import create_qulacs_vector_concurrent_sampler
total_shots = 5000
concurrent_sampler = create_qulacs_vector_concurrent_sampler()
measurement_factory = bitwise_commuting_pauli_measurement
shots_allocator = create_proportional_shots_allocator()
sampling_estimator = create_sampling_estimator(
total_shots,
concurrent_sampler,
measurement_factory,
shots_allocator
)
サンプリングの結果を見てみましょう:
estimate = sampling_estimator(op, state)
print("Estimated expectation value:", estimate.value)
print("Standard error of this sampling estimation:", estimate.error)
#output
Estimated expectation value: (0.6527269558463302+0.02582389806961259j)
Standard error of this sampling estimation: 0.070823557319059
サンプリング推定の結果を、vector estimatorによる正確な結果と比較することができます。
from quri_parts.qulacs.estimator import create_qulacs_vector_estimator
vector_estimator = create_qulacs_vector_estimator()
vector_estimator(op, state)
#output
_Estimate(value=(0.75+0j), error=0.0)
演算子を可換なパウリ文字列にグループ化
サンプリング実験では、演算子の期待値を見積もる方法の一つとして、各パウリ文字列の期待値を見積もり、それらを合計する方法があります。しかし、複数のパウリ文字列が可換であれば、一度に測定した方が効率的です。従って、最初のステップは、パウリ文字列をいくつかの可換パウリ文字列のセットにグループ化することです。このパウリ文字列のグループ化は、演算子推定の文脈で重要な研究課題です。
次に、グループ化の結果から、測定とサンプリングのための各グループの回路を構成する必要があり、これを測定回路と呼びます。測定回路に含まれる明示的なゲートは、グループ内のパウリ文字列に依存します。
ここでは、演算子と上記で構築した状態を使用します:
print("Operator:")
for pl, coeff in op.items():
print(f" + {coeff} * {pl}")
print("")
print("State:", state)
#output
Operator:
+ 0.25 * Z0
+ 2.0 * Z1 Z2
+ (0.5+0.25j) * X1 X2
+ 1j * Z1 Y3
+ (1.5+0.5j) * Z2 Y3
+ 2j * X1 Y3
+ 3.0 * I
State: ComputationalBasisState(qubit_count=4, bits=0b101, phase=0π/2)
QURI Partsでどのように
- 演算子のグループ化
- 測定回路の構築
- 測定結果によるパウリ文字列の再構築
を行うかを説明します。最後に、便利なオブジェクトを紹介して、このセクションを締めくくります:CommutablePauliSetMeasurement
という便利なオブジェクトを紹介します。
パウリのグループ化
QURI Partsでは、グループ化関数はPauliGrouping
で表されます。これらは、Operator
またはPauliLabel
のセットをCommutablePauliSet
のセットに変換する関数です。関数のシグネチャは次式で与えられます:
from typing import Callable, Set, Union, Iterable
from typing_extensions import TypeAlias
from quri_parts.core.operator import PauliLabel
CommutablePauliSet: TypeAlias = Set[PauliLabel]
#: Represents a grouping function
PauliGrouping = Callable[
[Union[Operator, Iterable[PauliLabel]]], Iterable[CommutablePauliSet]
]
例として、最も単純なパウリのグループ化の1つであるビット単位のグループ化を使用する。この場合、グループはパウリ文字列のビット単位の可換性に基づいて決定されます。このグループ分けは次のようにテストできます:
from quri_parts.core.operator.grouping import bitwise_pauli_grouping
pauli_sets = bitwise_pauli_grouping(op)
print(pauli_sets)
#output
frozenset({frozenset({PauliLabel({(1, <SinglePauli.X: 1>), (3, <SinglePauli.Y: 2>)})}), frozenset({PauliLabel()}), frozenset({PauliLabel({(1, <SinglePauli.X: 1>), (2, <SinglePauli.X: 1>)})}), frozenset({PauliLabel({(0, <SinglePauli.Z: 3>)}), PauliLabel({(2, <SinglePauli.Z: 3>), (1, <SinglePauli.Z: 3>)})}), frozenset({PauliLabel({(2, <SinglePauli.Z: 3>), (3, <SinglePauli.Y: 2>)}), PauliLabel({(3, <SinglePauli.Y: 2>), (1, <SinglePauli.Z: 3>)})})})
グループ化メソッドはパウリラベルのセットのfrozensetを返すので、上記は少し複雑に見えます。
print(f"Number of groups: {len(pauli_sets)}")
for i, pauli_set in enumerate(pauli_sets):
labels = ", ".join([str(pauli) for pauli in pauli_set])
print(f"Group {i} contains: {labels}")
#output
Number of groups: 5
Group 0 contains: X1 Y3
Group 1 contains: I
Group 2 contains: X1 X2
Group 3 contains: Z0, Z1 Z2
Group 4 contains: Z2 Y3, Z1 Y3
ここで、QURI Partsで使用可能なグループ化関数の一覧を示します:
グループ化関数 | 説明 |
---|---|
individual_pauli_grouping | グループ化なし |
bitwise_pauli_grouping | ビットごとの可換性に基づくパウリ文字列の可換性のチェック |
sorted_injection_grouping | arXiv:1908.06942で説明 |
測定回路
可換パウリセットの測定を行うには、測定前に適用する回路を構築する必要があります。ビット単位のパウリグループ化の場合、対応する回路はbitwise_commuting_pauli_measurement_circuit
関数で構築することができます:
from quri_parts.core.measurement import bitwise_commuting_pauli_measurement_circuit
from quri_parts.circuit import QuantumCircuit
from quri_parts.circuit.utils.circuit_drawer import draw_circuit
pauli_set = {pauli_label("X0 Z2 Y3"), pauli_label("X0 Z1 Y3")}
measurement_circuit = bitwise_commuting_pauli_measurement_circuit(pauli_set)
draw_circuit(QuantumCircuit(4, gates=measurement_circuit))
#output
___
| H |
0 --|0 |---------
|___|
1 ----------------
2 ----------------
___ ___
|Sdg| | H |
3 --|1 |---|2 |-
|___| |___|