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

アンザッツ

Victoria Lipinska がアンザッツとは何か、そして変分量子固有値ソルバー(VQE)の文脈でなぜそれが重要なのかを説明します。

参考文献

上記の動画で参照されている論文は以下の通りです。

アンザッツのコード

前のレッスンでは、対象分子のエネルギーを記述する__ハミルトニアン__を作成し、量子コンピュータで利用できる形式にマッピングしました。VQE は 変分回路 を使って量子状態を準備します。その状態を用いてハミルトニアンの期待値(エネルギー)を求めます。変分回路のパラメータは、計算が期待値の最小値に収束するまで繰り返し更新されます。量子化学の文脈では、この最小値が基底状態エネルギーに対応します。このレッスンでは、変分回路(ドイツ語で「アプローチ」や「手法」を意味する__アンザッツ__とも呼ばれます)に焦点を当てます。

このレッスンで学ぶこと:

  • 回路ライブラリに用意されている既製アンザッツの種類
  • アンザッツの特性を指定または変更する方法
  • 独自のアンザッツを構築する方法
  • 良いアンザッツと悪いアンザッツの例

Qiskit の回路ライブラリには、アンザッツとして利用できる多くのカテゴリの回路が含まれています。ここでは、最大2量子ビットに作用するゲートで構成される二局所回路(two-local circuits)に絞って説明します。Efficient SU2 はよく使われるアンザッツです。

efficient_su_2 回路は、SU(2)(パウリ回転ゲートのような2次の特殊ユニタリ群)にまたがる単一量子ビット演算の層と、CX エンタングルメントの層で構成されています。これは VQE のような変分量子アルゴリズムや量子機械学習(QML)の分類回路において有用なヒューリスティックなパターンです。

まず、SU(2) ゲートとして rx と y の2種類を指定した4量子ビットの efficient_su2 回路の例から始めます。エンタングルメントのスキームと繰り返し数も指定します。単純に .draw() で回路を表示すると、かなり抽象的な表現になります。より分かりやすい回路図を得るには .decompose().draw() を使い、ここでは output = "mpl" を指定します。

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit
from qiskit.circuit.library import efficient_su2

SU2_ansatz = efficient_su2(4, su2_gates=["rx", "y"], entanglement="linear", reps=1)
print(SU2_ansatz.draw())
SU2_ansatz.decompose().draw(output="mpl")
┌──────────┐┌───┐     ┌──────────┐   ┌───┐
q_0: ┤ Rx(θ[0]) ├┤ Y ├──■──┤ Rx(θ[4]) ├───┤ Y ├─────────────────────
├──────────┤├───┤┌─┴─┐└──────────┘┌──┴───┴───┐ ┌───┐
q_1: ┤ Rx(θ[1]) ├┤ Y ├┤ X ├─────■──────┤ Rx(θ[5]) ├───┤ Y ├─────────
├──────────┤├───┤└───┘ ┌─┴─┐ └──────────┘┌──┴───┴───┐┌───┐
q_2: ┤ Rx(θ[2]) ├┤ Y ├────────┤ X ├─────────■──────┤ Rx(θ[6]) ├┤ Y ├
├──────────┤├───┤ └───┘ ┌─┴─┐ ├──────────┤├───┤
q_3: ┤ Rx(θ[3]) ├┤ Y ├────────────────────┤ X ├────┤ Rx(θ[7]) ├┤ Y ├
└──────────┘└───┘ └───┘ └──────────┘└───┘

Output of the previous code cell

SU(2) ゲートは su2_gates = [...] で指定した順序と要素で、回路の最初と最後に配置されます。linear エンタングルメントスキームでは、CX ゲートが番号順の量子ビットを順番にエンタングルしていきます(0と1、次に1と2、というように回路の対角線に沿って進みます)。予想通り、reps = 2 と設定するとエンタングルメント層と末尾の SU(2) 層が追加されます。reps = nn 個のエンタングルメント層に対応し、それらの間と両端に SU(2) 層が配置されます。

SU2_ansatz2 = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="linear", reps=2
)
SU2_ansatz2.decompose().draw(output="mpl")

Output of the previous code cell

他にもいくつかのエンタングルメントスキームがあります。特に注目すべきは circular(円形)と full(全結合)です。円形エンタングルメントはリニアエンタングルメントと同一ですが、最初と最後の量子ビットをエンタングルする CX ゲートが追加されます。全結合エンタングルメントスキームでは、すべての量子ビットのペア間に CX ゲートが配置されます。N 量子ビット回路の場合、これは N(N1)/2N(N-1)/2 個の CXCX ゲートになり、計算コストが高くなる可能性があります。

SU2_ansatz3 = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="circular", reps=1
)
SU2_ansatz3.decompose().draw(output="mpl")

Output of the previous code cell

SU2_ansatz4 = efficient_su2(4, su2_gates=["rx", "y", "z"], entanglement="full", reps=1)
SU2_ansatz4.decompose().draw(output="mpl")

Output of the previous code cell

.depth() または場合によっては .decompose().depth() を使って回路の深さを確認できます。

print(SU2_ansatz4.decompose().depth())
11

efficient_su2 を一般化したものが二局所回路(two-local circuit)であり、これ自体は n 局所回路の特殊ケースです。二局所回路も SU(2) ブロック(回転ブロック)とエンタングルメントブロックで構成されています。ここでは、使用するエンタングルメントゲートの種類(例えば CRX ゲート)を自由に指定できます。この例ではすべてのゲートがパラメータを受け取りますが、必ずしもそうである必要はありません。たとえば、Y 回転ゲートと CX エンタングルメントゲートを組み合わせることもできます。

from qiskit.circuit.library import n_local

rotation_blocks = ["ry"]
entanglement_blocks = ["crx"]
two_ansatz = n_local(
4, rotation_blocks, entanglement_blocks, "linear", insert_barriers=True, reps=2
)
two_ansatz.decompose().draw(output="mpl")

Output of the previous code cell

ここで名前を挙げる最後のアンザッツは Pauli-two-design です。この回路は初期回転として RY(π/4)RY(\pi/4) を含み、回転層には X、Y、Z のいずれかがランダムに選ばれる単一量子ビットのパウリ回転が含まれます。エンタングルメント層は、全深さ2のペアごとの CZ ゲートで構成されます。この pauli_two_design と、たとえば efficient_su2 のエンタングルメント(および回路全体)の深さの違いに注目してください。

from qiskit.circuit.library import pauli_two_design

PtwoD_ansatz = pauli_two_design(5, reps=1, seed=10599, insert_barriers=True)
PtwoD_ansatz.decompose().draw(output="mpl")

Output of the previous code cell

これらの既製の変分回路は、望ましいエンタングルメントの度合いを実現しつつ回路深さを抑えるという点で有用なヒューリスティクスです。しかし、それらに特別な魔法があるわけではありません。独自の変分回路を作ることも自由です。実際、対象システムの目標状態のエンタングルメントについて何らかの知識がある場合には、自作の方が有利なこともあります。

独自のアンザッツを作るには、量子回路を構成し、ゲートの一部をパラメータベクトルの要素の関数にするだけです(以下の3量子ビットの例では "theta")。

from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector

n = 3

theta = ParameterVector("θ", length=n)
qc = QuantumCircuit(n)
qc.h(0)
qc.h(2)
for i in range(n - 1):
qc.cx(i, i + 1)
qc.cz(0, n - 1)
qc.barrier()
for i in range(n):
qc.ry(theta[i], i)
qc.barrier()
qc.cz(0, n - 1)
for i in reversed(range(n - 1)):
qc.cx(i, i + 1)
qc.h(0)
qc.h(1)
own_ansatz = qc
print(own_ansatz.depth())
qc.draw("mpl")
9

Output of the previous code cell

一般的に、最良のアンザッツを選ぶことは一種の技術であり、最良のアンザッツとは最小の最適化ステップ数で目標に到達できるアンザッツです。悪いアンザッツを特定する方がむしろ容易です。たとえば、回路深さが大きいほどエラーが蓄積しやすくなります。エラー緩和でこれを軽減できますが、回路深さをできる限り小さく保つことが良い実践です。ただし、必要なエンタングルメントを省略してはいけません。目標状態によっては全結合エンタングルメントスキームが必要な場合もあります。以下に、明らかな理由から良い選択ではないと思われる回路の例を2つ示します。良いアンザッツの選択については、収束テストの文脈で後のセクションで改めて取り上げます。

最初の回路は、最後の量子ビットが他の量子ビットとまったくエンタングルされていないため、良い選択ではない可能性が高いです。実際、最後の量子ビットに対して計算上意味のある操作が行われていません。おそらく、最後の量子ビットは他の量子ビットとエンタングルするか、計算から取り除くべきです。

n = 4

theta = ParameterVector("θ", length=n)
qc = QuantumCircuit(n)
qc.h(0)
qc.h(2)
for i in range(n - 2):
qc.cx(i, i + 1)
qc.cz(0, n - 2)
qc.barrier()
for i in range(n):
qc.ry(theta[i], i)
qc.barrier()
qc.cz(0, n - 2)
for i in reversed(range(n - 2)):
qc.cx(i, i + 1)
qc.h(0)
qc.h(1)
own_ansatz2 = qc
print(own_ansatz2.depth())
qc.draw("mpl")
9

Output of the previous code cell

最後の回路は、ゲート深さが非常に大きく、エンタングルメント層を4回繰り返しても2〜3回の繰り返しと比べて目標状態への一致が大幅に向上するとは考えにくいため、良い選択ではない可能性が高いです。

su2_ansatz_long = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="linear", reps=4
)
print(su2_ansatz_long.decompose().depth())
su2_ansatz_long.decompose().draw(output="mpl")
24

Output of the previous code cell

これらの回路は、多くのゲートに挿入される未知の可変パラメータがまだ残っているという意味で「完成」していません。それらのパラメータは、繰り返し推測を行いながらコスト関数(化学の文脈では通常、基底状態エネルギー)の期待値を下げる方向にパラメータを更新することで選択されます。1次元、あるいは少数の次元であれば、これは簡単な作業です。しかし上記の回路には20個の変分パラメータがあり、最小エネルギーの目標状態を見つけることは20次元の空間を探索することを意味します(これも不要な回路ゲートを含めない理由の一つです)。ここで古典的な最適化アルゴリズムが活躍し、それが次のレッスンのテーマです。