エラーについて以前扱ったので,せっかくなのでエラー訂正についても書いていきたいと思います.参考にしているのはNielsen-Chuang1です.
3量子ビットのビット反転符号
ビット反転エラーに対する符号を考えていきます.ここで,ビット反転エラーが小さい確率$p$で起き,$3$量子ビットあった場合には,$1$量子ビット以下しかエラーが起きない,つまり
$$
\begin{align}
p^3+3(1-p)p^2\ll1
\end{align}
$$
であるとします.
符号化
論理量子ビットを次のように与える符号を考えます.
$$
\begin{align}
\ket{0_L}&=\ket{000}\\
\ket{1_L}&=\ket{111}
\end{align}
$$
これより,論理Xゲートの固有状態は
$$
\begin{align}
\ket{+_L}&=\frac{1}{\sqrt{2}}\left(\ket{000}+\ket{111}\right)\\
\ket{-_L}&=\frac{1}{\sqrt{2}}\left(\ket{000}-\ket{111}\right)
\end{align}
$$
と表されます.論理Zゲートや論理Xゲートは,
$$
\begin{align}
Z_L&=ZZZ\\
X_L&=XXX
\end{align}
$$
を採用すれば辻褄が合うことが分かります.また,符号化するためには
$$
\begin{align}
\operatorname{CNOT}_{12}\left(a\ket{0}+b\ket{1}\right)\otimes\ket{0}&=a\ket{00}+b\ket{11}
\end{align}
$$
より,$CNOT$を2回作用させれば良いことが分かります.
エラー検知
仮定よりエラーは多くともどれか1つにしか起きません.そこで,次のような射影測定$ \{P_i\}_{i=0}^3 $を考えてみます.
$$
\begin{align}
P_0&=\ket{000}\bra{000}+\ket{111}\bra{
111}\\
P_1&=\ket{100}\bra{100}+\ket{011}\bra{011}\\
P_2&=\ket{010}\bra{010}+\ket{101}\bra{101}\\
P_3&=\ket{001}\bra{001}+\ket{110}\bra{110}
\end{align}
$$
例えば,一つ目の量子ビットにエラーが起きた場合,
$$
\begin{align}
\ket{\psi_L}&=a\ket{0_L}+b\ket{1_L}\\
&=a\ket{000}+b\ket{111}\\
&\to a\ket{100}+b\ket{011}=:\ket{\psi'_L}
\end{align}
$$
となりますが,$\bra{\psi'_L}P_1\ket{\psi'_L}=1$より,一つ目の量子ビットにエラーが起きていることを検知することができます.
例えば$ZZI-2IZZ$といった物理量を測定し,その測定値から対応する射影演算子を推測することができます.
しかし,実際の量子コンピュータでは各量子ビットに対して個別に$Z$測定を行うことしか許されず,そのまま測定すると状態が壊れてしまいます.例えば一つ目の量子ビットを$Z$測定して,$+1$が返ってきた場合,
$$
\begin{align}
\ket{\psi'_L}&=a\ket{100}+b\ket{011}\\
&\to \ket{100}
\end{align}
$$
と射影されてしまっており,係数$a,b$の情報が消えてしまいます.そこで,$ZZI$と$IZZ$に関する測定を,状態が壊れないように行うのが肝です.これらの固有値は$\pm1$であり,$ZII$の固有値と等しいことに着目すれば,これらの間のユニタリ変換が存在するはずです.そのユニタリ変換を施した後に,一つ目の量子ビットを$Z$測定すれば良いわけです.例えば$ZZI$について考えれば,一つ目と二つ目の量子ビットのみを取り出すと
$$
\begin{align}
ZZ&=\ket{00}\bra{00}-\ket{10}\bra{10}-\ket{01}\bra{01}+\ket{11}\bra{11}\\
\operatorname{CNOT} _ {21} ZZ\operatorname{CNOT} _ {21}&=\left(I\ket{0}\bra{0}+X\ket{1}\bra{1}\right)ZZ\left(I\ket{0}\bra{0}+X\ket{1}\bra{1}\right)\\
&=\ket{00}\bra{00}-\ket{10}\bra{10}-\ket{11}\bra{11}+\ket{01}\bra{01}\\
&=ZI
\end{align}
$$
が成り立つため,$\operatorname{CNOT} _ {21}$を作用させれば良いことが分かります.実際に状態の変化を追ってみると,
$$
\begin{align}
\operatorname{CNOT} _ {21}I_3\ket{\psi'_L}&=a\ket{100}+b\ket{111}
\end{align}
$$
となるため,一つ目の量子ビットに対する$Z$測定を行うことで,測定結果$-1$を得ることができます.これより,$\ket{\psi'_L}は$$ZZI$の$-1$の固有値に属する固有状態であったことが分かります.よって,一つ目の量子ビットか二つ目の量子ビットにビット反転エラーが生じていることが判明しました.
同様にして$IZZ$測定を行い,エラーが生じている量子ビットを特定します.
状態回復
測定しエラーを特定したら,次にエラーが入る前の状態に戻していきます.これは,エラーの入っている量子ビットにXゲートを作用させ反転させれば良いだけです.
復号
計算の最後に復号し,1物理量子ビットに情報を集約させることを考えます.これは符号化の逆の操作,つまり$CNOT$を2回作用させれば良いです.実際,
$$
\begin{align}
\operatorname{CNOT}_{12}\left(a\ket{00}+b\ket{11}\right)&=\left(a\ket{0}+b\ket{1}\right)\otimes\ket{0}
\end{align}
$$
となり,一番目の量子ビットに情報が集約されていることが分かります.
実装
from qiskit.circuit import QuantumCircuit
from qiskit.visualization import plot_bloch_multivector
from qiskit import Aer, execute
まずは,論理Xゲートを作用させた後に,ビット反転エラーが乗った場合を考えてみます.
circ = QuantumCircuit(3, 2)
# 論理Xゲート
circ.x([0, 1, 2])
# ビット反転エラー
circ.x([0])
simulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
state = result.get_statevector()
plot_bloch_multivector(state)
一番目の量子ビット(qubit 0)のみエラーが起きているのが確認できます.
そこで,エラーを訂正してみましょう.
circ = QuantumCircuit(3, 2)
# 論理Xゲート
circ.x([0, 1, 2])
# ビット反転エラー
circ.x([0])
# 誤り検出
circ.cx(1, 0)
circ.measure(0, 0)
circ.cx(1, 0)
circ.cx(2, 1)
circ.measure(1, 1)
circ.cx(2, 1)
# 古典レジスタの値を確認するために一旦シミュレート
simulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
result.get_counts()
c0_result = int(list(result.get_counts().keys())[0][1])
c1_result = int(list(result.get_counts().keys())[0][0])
# 状態回復
if c0_result == 1 and c1_result == 0:
circ.x(0)
elif c0_result == 1 and c1_result == 1:
circ.x(1)
elif c0_result == 0 and c1_result == 1:
circ.x(2)
# 最終状態確認
mulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
state = result.get_statevector()
plot_bloch_multivector(state)
確かにエラーが訂正されています.
次は,もう少し複雑な回路を実行し,無事復号できるかどうか試してみましょう.論理$RX$ゲートや論理$RZ$ゲートは$RXXX$や$RZZZ$によって与えられることに注意すると2,論理$RX$ゲートや論理$RZ$ゲートを適用した回路は以下のように表されます.
circ = QuantumCircuit(3, 2)
# 論理RXゲート
circ.h([0, 1, 2])
circ.cx(0, 1)
circ.rzz(np.pi / 4, 1, 2)
circ.cx(0, 1)
circ.h([0, 1, 2])
# 論理RZゲート
circ.cx(0, 1)
circ.rzz(3 * np.pi / 4, 1, 2)
circ.cx(0, 1)
# 論理RXゲート
circ.h([0, 1, 2])
circ.cx(0, 1)
circ.rzz(np.pi / 4, 1, 2)
circ.cx(0, 1)
circ.h([0, 1, 2])
# ビット反転エラー
circ.x([0])
simulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
state = result.get_statevector()
plot_bloch_multivector(state)
最後にエラーを乗せたため,一つ目の量子ビットのみ反転していることが分かります.ここで,それぞれの量子ビットが$\ket{0},\ket{1}$の確率混合として表されているのは,
$$
\begin{align}
\operatorname{Tr}_ {23}\ket{\psi_L}\bra{\psi_L}&=|a|^2\ket{0}\bra{0}+|b|^2\ket{1}\bra{1}\\
\operatorname{Tr}_ {23}\ket{\psi_L'}\bra{\psi_L'}&=|a|^2\ket{1}\bra{1}+|b|^2\ket{0}\bra{0}
\end{align}
$$
より理解できます.このエラーを修正し,さらに復号までしてみましょう.
circ = QuantumCircuit(3, 2)
# 論理RXゲート
circ.h([0, 1, 2])
circ.cx(0, 1)
circ.rzz(np.pi / 4, 1, 2)
circ.cx(0, 1)
circ.h([0, 1, 2])
# 論理RZゲート
circ.cx(0, 1)
circ.rzz(3 * np.pi / 4, 1, 2)
circ.cx(0, 1)
# 論理RXゲート
circ.h([0, 1, 2])
circ.cx(0, 1)
circ.rzz(np.pi / 4, 1, 2)
circ.cx(0, 1)
circ.h([0, 1, 2])
# ビット反転エラー
circ.x([0])
# 誤り検出
circ.cx(1, 0)
circ.measure(0, 0)
circ.cx(1, 0)
circ.cx(2, 1)
circ.measure(1, 1)
circ.cx(2, 1)
# 古典レジスタの値を確認するために一旦シミュレート
simulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
result.get_counts()
c0_result = int(list(result.get_counts().keys())[0][1])
c1_result = int(list(result.get_counts().keys())[0][0])
# 状態回復
if c0_result == 1 and c1_result == 0:
circ.x(0)
elif c0_result == 1 and c1_result == 1:
circ.x(1)
elif c0_result == 0 and c1_result == 1:
circ.x(2)
# 復号
circ.cx(0, 1)
circ.cx(0, 2)
# 最終状態確認
mulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
state = result.get_statevector()
plot_bloch_multivector(state)
二つ目と三つ目の量子ビットは初期化され,一つ目の量子ビットのみに情報が戻りました.これが合っているのかどうか,エラーのない1量子ビットでの計算を行なってみると,
circ = QuantumCircuit(1)
# RXゲート
circ.rx(np.pi / 4, 0)
# RZゲート
circ.rz(3 * np.pi / 4, 0)
# RXゲート
circ.rx(np.pi / 4, 0)
# 最終状態確認
mulator = Aer.get_backend('statevector_simulator')
result = execute(circ, simulator).result()
state = result.get_statevector()
plot_bloch_multivector(state)
確かに一致することが分かりました.
Version Information
Software | Version |
---|---|
qiskit | 0.45.0 |
System information | |
---|---|
Python version | 3.10.12 |