これは トポロジカル符号の実装実験 の記事です。
表面符号(Surface Code)は量子コンピュータにおける誤り訂正のための有望な手法の一つと考えられています。
2 次元格子状に配置された量子ビットに対して、スタビライザーと呼ばれる演算子によって誤りを検出・訂正する符号になっているため、特に超伝導方式の量子コンピュータでの活用が期待されています。
ここでは平面格子状に量子ビットが配置されている平面符号 (Planar Surface Code) を扱います。
平面符号
- 白い丸:データビット
- 小さい黒丸:シンドロームのための補助ビット
- 黄緑の領域:X スタビライザー
- 茶色の領域:Z スタビライザー
平面符号は、平面上にこれら 4 種類が規則正しく並んだものになっています。
図は 13 個のデータビットを使って 1 個の論理量子ビットを構成する符号を示しています。
量子ビットを縦横にさらに追加することで、より大きい符号を構成することができます。
[5,1,3] 平面符号
実装実験を進めていくために、ここから先は以下のような小さい平面符号を使用します。
X スタビライザー $S_1$ はデータビット $q_0, q_1, q_2$ をスタビライズしていて、これら 3 ビットを反転させても、論理ビットは変化しません。
同様に Z スタビライザー $S_3$ はデータビット $q_0, q_2, q_3$ をスタビライズしていて、これら 3 ビットの位相を反転させても、論理ビットの位相は変化しません。表にまとめると以下のようになります。
| $q_0$ | $q_1$ | $q_2$ | $q_3$ | $q_4$ | |
|---|---|---|---|---|---|
| $S_1$ | $X$ | $X$ | $X$ | ||
| $S_2$ | $X$ | $X$ | $X$ | ||
| $S_3$ | $Z$ | $Z$ | $Z$ | ||
| $S_4$ | $Z$ | $Z$ | $Z$ |
この表から論理量子ビットの符号語を導出できます。[^1]
$$
\ket{0_L} = \frac{1}{2} (\ket{00000} + \ket{00111} + \ket{11011} + \ket{11100})
$$
$$
\ket{1_L} = \frac{1}{2} (\ket{01001} + \ket{01110} + \ket{10010} + \ket{10101})
$$
この符号は 5 個のデータビットで 1 論理ビットを作り、2 回のパウリ操作で符号反転できるから符号距離 $d = 2$ であり、
これらを並べて、[5,1,2] 符号と表記されます。また、$d/2 = 1$ 個のエラーを訂正できます。
エラー検出
表面符号では、各補助ビットについてシンドローム測定を行うことで、
その補助ビットに隣り合うデータビットでビット反転や位相反転のエラーが発生したかどうかを診断することができます。
- Z スタビライザー $S_2$ のシンドローム測定
- X スタビライザー $S_1$ のシンドローム測定
エンコード
表面符号はすべてのスタビライザーについて測定を行う(実際に測定までは不要)ことでエンコードできます。エンコードの後に、補助ビットをリセットしておきます。
エンコードとシンドローム測定の実験
[5,1,2] 符号でここまでの話を実験していきます。まずは論理ビットのエンコードを行います。
Qiskit で実装する場合の Python コードは以下のようになります。
# 前準備
import numpy as np
from qiskit import QuantumCircuit, transpile, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
n_shots = 10_000 # サンプリング回数
if 'GPU' in AerSimulator().available_devices():
device='GPU'
else:
device='CPU'
backend_sim = AerSimulator(device=device) # シミュレーターの用意
backend_sim.set_option('method', 'statevector')
Qubit 0〜4 は $q_0$〜$q_4$ に対応、Qubit 5〜8 は $S_1$〜$S_4$ に対応します。
# 論理ビットを部品化
circ = QuantumCircuit(9)
circ.h(5)
circ.cx(5, 0)
circ.cx(5, 1)
circ.cx(5, 2)
circ.h(5)
circ.h(8)
circ.cx(8, 2)
circ.cx(8, 3)
circ.cx(8, 4)
circ.h(8)
circ.cx(0, 6)
circ.cx(2, 6)
circ.cx(3, 6)
circ.cx(2, 7)
circ.cx(1, 7)
circ.cx(4, 7)
circ.reset(5)
circ.reset(6)
circ.reset(7)
circ.reset(8)
# 以降の実験のためにエンコード回路を変数に保存
sc = circ.to_instruction(label='sc512')
q = QuantumRegister(9)
c = ClassicalRegister(5)
circ = QuantumCircuit(q, c)
circ.append(sc.copy(), q)
circ.measure(q[:5], c)
result = backend_sim.run(transpile(circ.decompose()), shots=n_shots).result()
counts = result.get_counts(0)
plot_histogram(counts)
符号語 $\ket{0_L}$ に対応した結果が得られました。
次に、$S_1$ と $S_2$ のシンドローム測定を行います。
q = QuantumRegister(9)
c = ClassicalRegister(2)
circ = QuantumCircuit(q, c)
circ.append(sc.copy(), q)
circ.h(5)
circ.cx(5, 0)
circ.cx(5, 1)
circ.cx(5, 2)
circ.h(5)
circ.measure([5], [0])
circ.reset(5)
circ.cx(0, 6)
circ.cx(2, 6)
circ.cx(3, 6)
circ.measure([6], [1])
circ.reset(6)
result = backend_sim.run(transpile(circ.decompose()), shots=n_shots).result()
counts = result.get_counts(0)
plot_histogram(counts)
$S_2$ は常に 0 が観測されましたが、$S_1$ は 0 と 1 がほぼ等確率で観測されました。
シミュレータを使用しているので、エラーは発生していないはずです。
これは、最初に $S_1$ に H ゲートを作用させたことで、$S_1$ の状態が不定になっていることが原因だと思われます。
その後、CNOT ゲートによって状態がスタビライズされるので、$S_1$ の状態が変化することはありません。
つまり、X スタビライザーのシンドローム測定は 「状態が変化したかどうか」を確認するための測定 を行い、変化していればエラーが発生したと判定することになります。
確認のために、$S_1$ を 9 回測定した後で位相反転エラーを発生させ、再度 1 回測定を行います。
q = QuantumRegister(9)
c = ClassicalRegister(10)
circ = QuantumCircuit(q, c)
circ.append(sc.copy(), q)
# X スタビライザーは値が初期状態から「変化しない」ことでエラー検出をする
for i in range(9):
circ.h(5)
circ.cx(5, 0)
circ.cx(5, 1)
circ.cx(5, 2)
circ.h(5)
circ.measure([5], [i])
circ.reset(5)
# 位相反転エラーを起こす
circ.z(1)
# 再度測定
circ.h(5)
circ.cx(5, 0)
circ.cx(5, 1)
circ.cx(5, 2)
circ.h(5)
circ.measure([5], [9])
circ.reset(5)
result = backend_sim.run(transpile(circ.decompose()), shots=n_shots).result()
counts = result.get_counts(0)
plot_histogram(counts)
期待通り、9 回連続で同じ値が観測されて、エラー発生後に測定値が反転しました。
エンコード回路の改善
シンドローム測定ができることが実験できたとはいえ、「状態が変化したかどうか」を確認するのは実装が面倒なので、2 通りのエンコード回路の改善を試みました。
- 中間測定 (mid-circuit measurements) によって初期状態を固定させる方法
エンコード後に $S_1, S_4$ を測定し、1 であればビット反転させることで常に $\ket{0}$ 状態にすることができます。Qiskit の if_test を使います。
circ = QuantumCircuit(9, 9)
circ.h(5)
circ.cx(5, 0)
circ.cx(5, 1)
circ.cx(5, 2)
circ.h(5)
circ.measure([5], [5])
circ.h(8)
circ.cx(8, 2)
circ.cx(8, 3)
circ.cx(8, 4)
circ.h(8)
circ.measure([8], [8])
circ.cx(0, 6)
circ.cx(2, 6)
circ.cx(3, 6)
circ.measure([6], [6])
circ.cx(2, 7)
circ.cx(1, 7)
circ.cx(4, 7)
circ.measure([7], [7])
# 測定値に応じてビット反転
with circ.if_test((circ.clbits[5], 1)):
circ.x(5)
with circ.if_test((circ.clbits[8], 1)):
circ.x(8)
circ.reset(5)
circ.reset(6)
circ.reset(7)
circ.reset(8)
- 根本的改善
エンコード回路に CNOT ゲートを追加して、$S_1, S_4$ の初期状態が常に $\ket{0}$ になるような回路を設計しました。
しかし、[5,1,2] 符号に依存したコードになっているため、表面符号の大きさが変われば再度設計しなければなりません。
circ = QuantumCircuit(9)
circ.h(5)
circ.cx(5, 0)
circ.cx(5, 1)
circ.cx(5, 2)
circ.h(8)
circ.cx(8, 2)
circ.cx(8, 3)
circ.cx(8, 4)
circ.cx(0, 6)
circ.cx(2, 6)
circ.cx(3, 6)
circ.cx(2, 7)
circ.cx(1, 7)
circ.cx(4, 7)
# 追加の CNOT
circ.cx(0, 5)
circ.cx(4, 8)
StateVector を確認した結果、期待通りにエンコードできたことが分かりました。
circ.save_statevector()
result = backend_sim.run(transpile(circ)).result()
sv = np.array(result.get_statevector())
states = sv.nonzero()[0]
for b in states:
print(format(b, "09b"))
000000000
000000111
000011011
000011100
補足
Qiskit はバージョン 1 以降で破壊的な仕様変更が行われており、公式のサンプルコードでさえ動かないことがあります。
ここに掲載したコードは Google Colab で以下のコマンドを実行してから動作確認をしています。
pip install qiskit==1.4.4 qiskit-aer==0.15.1
pip install pylatexenc
pip install matplotlib
pip install qiskit[visualization]
関連文献
- 量子情報理論の基本:量子誤り訂正(スタビライザー符号:1)
- Michael A. Nielsen, Isaac L. Chuang, Quantum Computation and Quantum Information: 10th Anniversary Edition