これは トポロジカル符号の実装実験 の記事です。
前回は [5,1,3] 平面符号の論理ビット操作を実験しました。
論理 CNOT ゲートの実験で言及した通り、今回は Lattice Surgery の実験を行います。
Lattice Surgery は日本語では格子手術と訳されます。
本記事では、Lattice Surgery 表記で統一します。
理論的な背景については参考資料の記事[1]、及び論文[2] を参照していただくものとして、本記事ではポイントの説明に留め、実際の挙動を確認する実験手順を中心に扱っていきます。
Lattice Surgery とは、平面上に配置された2個の論理量子ビットを結合(merge)する操作や、論理量子ビットを2つに分割(split)する操作のことを、手術に例えた用語です。
これらの操作によって、平面符号の境界上にある量子ビットだけを操作することで、論理 CNOT 演算を実現できます。
Lattice Surgery による論理 CNOT の手順
CNOT 演算の実施手順は次のようになります。以降、順に解説しながら実行していきます。
- コントロールビットの用意
- コントロールビットの結合
- コントロールビットの分割
- ターゲットビットとコントロールビットの結合
コントロールビットの用意
前回の方法で論理ビットのエンコードを行うことで、任意の状態のコントロールビットを用意します。
今回は $\ket{0_L}$ を $y$ 軸周りに $\pi/3$ 回転した状態とします。変数 sc は第1回のものを使用します。
# コントロールビット |ctrl> を用意
q = QuantumRegister(9)
ctrl = QuantumCircuit(q)
# q0 を欲しい状態で初期化し、q3 と bell 状態を作る
ctrl.ry(np.pi/3, 0)
ctrl.cx(0, 3)
# エンコード
ctrl.append(sc.copy(), q)
# コントロールビットのエンコード結果確認
ctrl2 = QuantumCircuit(9, 5)
ctrl2.append(ctrl.copy(), [0,1,2,3,4,5,6,7,8])
ctrl2.measure([0,1,2,3,4], [0,1,2,3,4])
result = backend_sim.run(transpile(ctrl2.decompose().decompose()), shots=n_shots).result()
counts = result.get_counts(0)
plot_histogram(counts)
頻度の大きい状態と小さい状態に 3 倍程度の開きがあり、$\ket{\mathsf{ctrl_L}} = \cos\frac{\pi}{6}\ket{0_L} + \sin\frac{\pi}{6}\ket{1_L} = \frac{\sqrt{3}}{2}\ket{0_L} + \frac{1}{2}\ket{1_L}$ にエンコードされていそうだと分かります。
また、結合する相手の論理ビット $\ket{\mathsf{temp_L}} = \ket{+_L}$ も用意しておきます。
# 論理ビット |temp> = |+> を作成
q = QuantumRegister(9)
temp = QuantumCircuit(q)
temp.h(0)
temp.cx(0, 3)
temp.append(sc.copy(), q)
コントロールビットの結合
データビット 1 個、アンシラ 2 個を使って、用意した $\ket{\mathsf{ctrl_L}}$ と $\ket{\mathsf{temp_L}}$ を結合します。
以下の図のように、上部に $\ket{\mathsf{ctrl_L}}$ 下部に $\ket{\mathsf{temp_L}}$ を配置して、2 個の論理ビットの間に追加のデータビットとアンシラを並べます。(追加のデータビットは青色、アンシラは赤色で表記)
さらに、データビットの上下に隣接する X スタビライザーを更新、Z スタビライザーを追加します。
データビットは上下の X スタビライザーの測定結果に影響を与えないように $\ket{+}$ 状態にしておきます。
この状態で、$\ket{\mathsf{ctrl_L}}, \ket{\mathsf{temp_L}}$ についてまとめて $ZZ$ 測定を行うことで、2 個の論理ビットを結合できます。このような $Z$ 測定による結合を smooth merge といいます。
この操作は次のように解釈できます。
- 通常、$Z$ 測定は CNOT ゲートを使った回路になります。ここでは CZ ゲートを使った以下の等価な回路で $\ket{\mathsf{ctrl_L}}$ の $Z$ 測定を考えます。
注) シンドローム測定とは異なります。
- CZ ゲートのコントロールビットは H ゲートによって、$\ket{+}, \ket{-}$ のいずれかになっていますが、事前には確定しません。アンシラの測定結果が 0 ならば $\ket{+}$、1 ならば $\ket{-}$ と事後に確定します。
- よって、$q_1$ は CZ ゲートによって $\frac{1}{\sqrt{2}} (\ket{q_1} + (-1)^m Z \ket{q_1}) \quad (m = 0, 1)$ と表記でき、$m$ の値は測定によって決まります。
- 同様に、$q_3$ も $\frac{1}{\sqrt{2}} (\ket{q_3} + (-1)^m Z \ket{q_3})$ となるので、論理ビット $\ket{\mathsf{ctrl_L}}$ 全体を考えると、$\ket{\mathsf{ctrl_L}} = \frac{1}{\sqrt{2}} (\ket{\mathsf{ctrl_L}} + (-1)^m Z \ket{\mathsf{ctrl_L}})$ になります。
- $\ket{\mathsf{temp_L}}$ についても $Z$ 測定をすることによって、$\ket{\mathsf{temp_L}} = \frac{1}{\sqrt{2}} (\ket{\mathsf{temp_L}} + (-1)^m Z \ket{\mathsf{temp_L}}) \quad (m = 0, 1)$ に変化します。
以上より、$\ket{\mathsf{ctrl_L}} = \alpha \ket{0_L} + \beta \ket{1_L}$ と置いて、結合後の大きな論理ビットの状態を整理すると、
$$
\ket{\mathsf{ctrl_L}} \odot \ket{\mathsf{temp_L}} = \frac{1}{2} (\ket{\mathsf {ctrl_L}} \ket{\mathsf{temp_L}} + (-1)^m Z \ket{\mathsf{ctrl_L}} Z \ket{\mathsf{temp_L}})
$$
$$
= \frac{1}{2} (\alpha \ket{0_L} \ket{0_L} + \alpha \ket{0_L} \ket{1_L} + \beta \ket{1_L}\ket{0_L} + \beta \ket{1_L} \ket{1_L} + (-1)^m (\alpha \ket{0_L} \ket{0_L} - \alpha \ket{0_L} \ket{1_L} - \beta \ket{1_L} \ket{0_L} + \beta {\ket{1_L} \ket{1_L}}) )
$$
$$
= \begin{cases}
\alpha \ket{0_L} \ket{0_L} + \beta \ket{1_L}\ket{1_L} & (m = 0) \\
\alpha \ket{0_L} \ket{1_L} + \beta \ket{1_L}\ket{0_L} & (m = 1)
\end{cases}
$$
注) ここで $\odot$ で表記される演算は論理ビットを結合させることを表しています。テンソル積ではありません。
ここまでの操作を実験で確認します。21 個の量子ビットを使用します。
q = QuantumRegister(21)
c = ClassicalRegister(12)
cirq = QuantumCircuit(q, c)
cirq.append(ctrl, q[:9])
cirq.append(temp, q[12:21])
# データビットを + で初期化
cirq.h(9)
# Z 測定によってマージ 実装では CZ でなく CNOT を使う
cirq.cx(3, 10)
cirq.cx(4, 10)
cirq.cx(12, 10)
cirq.cx(13, 10)
cirq.barrier()
cirq.measure([q[10]], [c[10]])
10 個のデータビットと 2 個のアンシラを測定します。
cirq.measure([0,1,2,3,4] + [12,13,14,15,16], range(10))
result = backend_sim.run(transpile(cirq.decompose().decompose()), shots=n_shots).result()
counts = result.get_counts(0)
アンシラの測定結果が 0 $(m = 0)$ の場合に絞ってヒストグラムを確認します。
filtered = {k[2:]: v for k, v in counts.items() if k[:2] == '00'}
plot_histogram(filtered)
測定結果の組み合わせが 32 通りもあって見にくいですが、$\ket{0_L} \ket{0_L}$ と $\ket{1_L} \ket{1_L}$ しか観測されておらず、確かに $\frac{\sqrt{3}}{2} \ket{0_L} \ket{0_L} + \frac{1}{2} \ket{1_L}\ket{1_L}$ になっていそうです。
続いて、アンシラの測定結果が 1 $(m = 1)$ の場合を確認します。
filtered = {k[2:]: v for k, v in counts.items() if k[:2] == '01'}
plot_histogram(filtered)
今度は $\ket{0_L} \ket{1_L}$ と $\ket{1_L} \ket{0_L}$ しか観測されておらず、$\frac{\sqrt{3}}{2} \ket{0_L} \ket{1_L} + \frac{1}{2} \ket{1_L}\ket{0_L}$ になっています。
コントロールビットの分割
結合した論理ビットを再び分割します。
接続部分に追加した青色のデータビットを $X$ 測定することで分割できます。
この測定結果が $\ket{-}$ のとき、$q_1$ と $q_2$ に $Z$ ゲートを作用させて、あたかも分割前に Z スタビライザーを作用させたようにしておきます。これによって切断部分の X スタビライザーの整合性が保たれます。
ここまでの操作によって、実質的にコントロールビットが 2 個に増えたことになります。
ターゲットビットとコントロールビットの結合
分割したコントロールビットの一つとターゲットビットを結合します。
今度は $XX$ 測定によって結合する rough merge を行います。
論文[2] の 式 (2)〜(5) でも rough merge について論じられています。ここで $X \leftrightarrow Z, \ket{0} \leftrightarrow \ket{+}, \ket{1} \leftrightarrow \ket{-}$ と置き換えると、先ほど行った smooth merge と同じになります。
以下の図のように、左側に $\ket{\mathsf{ctrl_L}}$ 右側に $\ket{\mathsf{target_L}}$ を配置して、2 個の論理ビットの間に追加のデータビットとアンシラを並べます。(追加のデータビットは青色、アンシラは赤色で表記)
さらに、データビットの左右に隣接する Z スタビライザーを更新、X スタビライザーを追加します。
データビットは左右の Z スタビライザーの測定結果に影響を与えないように $\ket{0}$ 状態にしておきます。
smooth merge と同様の議論が成り立ち、コントロールビットは $\ket{\mathsf{ctrl_L}} = \frac{1}{\sqrt{2}} (\ket{\mathsf{ctrl_L}} + (-1)^m X \ket{\mathsf{ctrl_L}})$、ターゲットビットは $\ket{\mathsf{target_L}} = \frac{1}{\sqrt{2}} (\ket{\mathsf{target_L}} + (-1)^{m} X \ket{\mathsf{target_L}})$ になります。($X$ と $Z$ が置き換わっています。)
$\ket{\mathsf{ctrl_L}} = \alpha \ket{0_L} + \beta \ket{1_L}, \quad \ket{\mathsf{target_L}} = \alpha' \ket{0_L} + \beta' \ket{1_L}$ と置いて、結合後の大きな論理ビットの状態を整理すると、
$$
\ket{\mathsf{ctrl_L}} \odot \ket{\mathsf{target_L}} = \frac{1}{2} (\ket{\mathsf {ctrl_L}} \ket{\mathsf{target_L}} + (-1)^m X \ket{\mathsf{ctrl_L}} X \ket{\mathsf{target_L}})
$$
$$
= \alpha\alpha' \ket{0_L}\ket{0_L} + \alpha\beta' \ket{0_L}\ket{1_L} + \alpha'\beta \ket{1_L}\ket{0_L} + \beta\beta' \ket{1_L}\ket{1_L}
$$
$$
+(-1)^m ( \beta\beta \ket{0_L}\ket{0_L} + \alpha'\beta \ket{0_L}\ket{1_L} + \alpha\beta' \ket{1_L}\ket{0_L} + \alpha\alpha' \ket{1_L}\ket{1_L} )
$$
$$
= ( \alpha\alpha' + (-1)^m \beta\beta') ( \ket{0_L}\ket{0_L} + (-1)^m \ket{1_L}\ket{1_L} ) \quad + \quad ( \alpha\beta' + (-1)^m \alpha'\beta) ( \ket{0_L}\ket{1_L} + (-1)^m \ket{1_L}\ket{0_L} )
$$
ここで、新たな符号として以下を導入します。
$$
\ket{0_L^{new}} = \ket{0_L}\ket{0_L} + (-1)^m \ket{1_L}\ket{1_L}, \quad \ket{1_L^{new}} = \ket{0_L}\ket{1_L} + (-1)^m \ket{1_L}\ket{0_L}
$$
これを使って結合後の状態を書き直します。
$$
\ket{\mathsf{ctrl_L}} \odot \ket{\mathsf{target_L}} = ( \alpha\alpha' + (-1)^m \beta\beta') \ket{0_L^{new}} \quad + \quad ( \alpha\beta' + (-1)^m \alpha'\beta) \ket{1_L^{new}}
$$
$$
= \alpha ( \alpha' \ket{0_L^{new}} + \beta' \ket{1_L^{new}} ) + (-1)^m \beta ( \beta' \ket{0_L^{new}} + \alpha' \ket{1_L^{new}} )
$$
$$
= \alpha \ket{\mathsf{target_L^{new}}} + (-1)^m \beta X \ket{\mathsf{target_L^{new}}}
$$
$(-1)^m$ が邪魔ですが、これは結合後の新たな符号が、ターゲットビットに CNOT ゲートを作用させた状態に一致していることを示しています。
「最後に全体を通して論理 CNOT の実験を行います」と言いたいところですが、シミュレーションの計算時間が長くなりそうなため、ここではコントロールビットとターゲットビットを予め用意しておき、rough merge を行う部分を実験します。
ターゲットビットは $\ket{1_L}$ にしておきます。
# コントロールビット |ctrl> を用意
q = QuantumRegister(9)
ctrl = QuantumCircuit(q)
# q0 を欲しい状態で初期化し、q3 と bell 状態を作る
ctrl.ry(np.pi/3, 0)
ctrl.cx(0, 3)
# エンコード
ctrl.append(sc.copy(), q)
# ターゲットビット |target> を作成
q = QuantumRegister(9)
target = QuantumCircuit(q)
target.append(sc.copy(), q)
# ターゲットビットを反転させる
target.x(0)
target.x(3)
$XX$ 測定により、結合します。
q = QuantumRegister(21)
c = ClassicalRegister(12)
surgery = QuantumCircuit(q, c)
surgery.append(ctrl.copy(), q[:9])
surgery.append(target.copy(), q[12:])
surgery.barrier()
# XX 測定
surgery.h(10)
surgery.cx(10, 1)
surgery.cx(10, 4)
surgery.cx(10, 12)
surgery.cx(10, 15)
surgery.h(10)
surgery.measure([q[10]], [c[10]])
10 個のデータビットとアンシラを測定します。
# データビットを測定
surgery.measure(q[:5], c[:5])
surgery.measure(q[12:17], c[5:10])
result = backend_sim.run(transpile(surgery.decompose().decompose()), shots=n_shots).result()
counts = result.get_counts(0)
アンシラの測定結果が 0 の場合に絞ってヒストグラムを確認します。
filtered = {k[2:]: v for k, v in counts.items() if k[:2] == '00'}
plot_histogram(filtered)
分かりにくいので、集計・整理します。
logical0 = ["00000", "00111", "11011", "11100"]
logical1 = ["01001", "01110", "10010", "10101"]
code00 = [v for k, v in filtered.items() if k[:5] in logical0 and k[5:10] in logical0]
code01 = [v for k, v in filtered.items() if k[:5] in logical1 and k[5:10] in logical0]
code10 = [v for k, v in filtered.items() if k[:5] in logical0 and k[5:10] in logical1]
code11 = [v for k, v in filtered.items() if k[:5] in logical1 and k[5:10] in logical1]
print("00:", sum(code00))
print("01:", sum(code01))
print("10:", sum(code10))
print("11:", sum(code11))
00: 637
01: 1903
10: 1849
11: 625
ヒストグラムでは位相が見えないため区別が付きませんが、この結果は結合後の新しい符号で $\frac{1}{2} \ket{0_L^{new}} + \frac{\sqrt{3}}{2} \ket{1_L^{new}}$ になっていることを表しています。
課題と考察
表面符号(平面符号)の論理 CNOT 演算が、論理ビットの辺の部分にある量子ビットの操作で実行できることが実験でも確認できました。
平面格子状に量子ビットが規則正しく並んだ超伝導量子コンピュータが存在すれば、誤り耐性のある CNOT 演算が可能です。 しかし本文で触れている通り、符号語が変化してしまうため、さらに演算を継続して行うことは難しく感じました。
全体を通した実験については、改めてアンシラの削減などのプログラム最適化を施してから挑戦しようと思います。
また、以下の実験はいずれ、実機や HPC で試してみたいと考えています。
- 結合されたターゲットビットに対するシンドローム測定
- 結合部分にエラーが発生した時の挙動確認
- 結合されたターゲットビットを再度分割できるか、さらに元の符号語に変換することができるのか
参考資料
[1]: 猫でもわかる Surface Code 量子計算
[2]: Surface code quantum computing by lattice surgery






