アンザッツ
Victoria Lipinska がアンザッツとは何か、そして変分量子固有値ソルバー(VQE)の文脈でなぜそれが重要なのかを説明します。
参考文献
上記の動画で参照されている論文は以下の通りです。
- The theory of variational hybrid quantum-classical algorithms, McClean, et al.
- Quantum Chemistry in the Age of Quantum Computing, Cao, et al.
- Noisy intermediate-scale quantum (NISQ) algorithms, Bharti, et al.
- The Variational Quantum Eigensolver: A review of methods and best practices, Tilly, et al.
- Hardware-efficient Variational Quantum Eigensolver for Small Molecules and Quantum Magnets, Kandala, et al.
- Quantum computational chemistry, McArdle, et al.
アンザッツのコード
前のレッスンでは、対象分子のエネルギーを記述する__ハミルトニアン__を作成し、量子コンピュータで利用できる形式にマッピングしました。VQE は 変分回路 を使って量子状態を準備します。その状態を用いてハミルトニアンの期待値(エネルギー)を求めます。変分回路のパラメーターは、計算が期待値の最小値に収束するまで繰り返し更新されます。量子化学の文脈では、この最小値が基底状態エネルギーに対応します。このレッスンでは、変分回路(ドイツ語で「アプローチ」や「手法」を意味する__アンザッツ__とも呼ばれます)に焦点を当てます。 このレッスンでは以下を学びます
- 回路ライブラリに用意されている既製アンザッツのセット
- アンザッツの特徴を指定または変更する方法
- 独自のアンザッツを構築する方法
- 良いアンザッツと悪いアンザッツの例
Qiskit の回路ライブラリには、アンザッツとして使用できる回路が数多くのカテゴリーに分類されています。ここでは、two-local 回路(一度に最大2つのキュービットに作用するゲートで構成される回路)に議論を限定します。Efficient SU2 はよく使われるアンザッツです。
efficient_su_2回路は、SU(2)(Pauliの回転ゲートのような、次数2の特殊ユニタリ群)にまたがる単一キュービット演算の層とCXエンタングルメントの層で構成されます。これは、VQEのような変分量子アルゴリズムや、量子機械学習(QML)における分類回路で有用となり得るヒューリスティックなパターンです。
まず、2 種類の SU(2) ゲート(例えば rx と y)を持つ 4 Qubit の efficient_su2 回路の例から始めます。エンタングルメント方式と繰り返し回数も指定します。単純に回路を .draw() すると、かなり抽象的な表現になります。.decompose().draw() を使うとより分かりやすい回路図が得られ、ここでは `output = "mpl"' を使用します。
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"qiskit": "qiskit"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 ├
└──────────┘└───┘ └───┘ └──────────┘└───┘
SU(2)ゲートは、su2_gates = [...]で指定された順序と要素で最初と最後に現れます。エンタングルメント方式linearは、CXゲートが番号付けされたキュービットを順に処理し、0と1、次に1と2というように、回路の中で対角線状にエンタングルさせることを意味します。予想通り、reps = 2と設定すると、エンタングルメント層と最後のSU(2)層が1つ追加されます。設定reps = nは、その間と両端にSU(2)層を持つn個のエンタングルメント層に対応します。
SU2_ansatz2 = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="linear", reps=2
)
SU2_ansatz2.decompose().draw(output="mpl")
他にもいくつかのエンタングルメント方式があります。特筆すべき2つはcircularとfullです。円形エンタングルメントは線形エンタングルメントと同一ですが、最初と最後のキュービットをエンタングルさせる追加のCXゲートがあります。全結合エンタングルメント方式は、すべてのキュービットのペア間にCXゲートを含みます。Nキュービットの回路の場合、これは個のゲートとなり、計算コストが高くなる可能性があることに注意してください。
SU2_ansatz3 = efficient_su2(
4, su2_gates=["rx", "y", "z"], entanglement="circular", reps=1
)
SU2_ansatz3.decompose().draw(output="mpl")
SU2_ansatz4 = efficient_su2(4, su2_gates=["rx", "y", "z"], entanglement="full", reps=1)
SU2_ansatz4.decompose().draw(output="mpl")
.depth()、または時には.decompose().depth()を使用することで、回路の深さを監視できます。
print(SU2_ansatz4.decompose().depth())
11
efficient_su2の一般化がtwo-local回路であり、これ自体はn-local回路の特殊なケースです。two-local回路にも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")
名前を挙げて説明する最後のアンザッツは、Pauli-two-designです。この回路にはの初期回転が含まれ、回転層には単一キュービットのPauli回転が含まれ、軸は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")
これらの既製の変分回路は、望ましいレベルのエンタングルメントを達成し、回路の深さを制限するという点の両方で有用なヒューリスティックです。しかし、それらには何ら魔法のようなものはありません。独自の変分回路を自由に作成することができます。実際、システムのターゲット状態のエンタングルメントについて何か知っている場合には、それが有利になることがあります。
独自のアンザッツを作成するには、一部のゲートがパラメーターベクトル(以下の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
一般に、最適なアンザッツを選択することは技芸(アート)です。最良のアンザッツとは、最少の最適化ステップでターゲットに到達するのに役立つアンザッツです。悪くなりそうなアンザッツを特定する方が容易です。例えば、回路の深さが大きいほど、エラーが蓄積する傾向があります。エラー緩和はこれに役立ちますが、回路の深さは合理的な範囲でできるだけ低く保つことが良い実践です。しかし、必要なエンタングルメントを省いてはいけません。システムのターゲット状態が全結合エンタングルメント方式を必要とする場合もあります。以下に、かなり明白な理由から悪い選択となる可能性が高い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
この最後の回路はおそらく悪い選択です。なぜなら、ゲートの深さが非常に大きく、エンタングルメント層を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
これらの回路は、多くのゲートに未知の可変パラメーターがまだ挿入される必要があるという意味で「完成」していません。それらのパラメーターは、逐次的な推測を行い、コスト関数(化学の文脈では通常、基底状態のエネルギー)の期待値を下げるようにパラメーターを更新することで選ばれます。1次元、あるいは数次元であれば、これは自明です。しかし、上記の回路には20個の変分パラメーターがあり、最小エネルギーでターゲット状態を見つけることは20次元の状態空間を探索することを意味します(不要な回路ゲートを含めるべきではないもう1つの理由です)。ここで古典的な最適化アルゴリズムが登場し、それが次のレッスンの主題です。