Qiskit 3量子ビットのビット反転符号

エラーについて以前扱ったので，せっかくなのでエラー訂正についても書いていきたいと思います．参考にしているのはNielsen-Chuang[^0]です．

# 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回作用させれば良いことが分かります．

## エラー検知

仮定よりエラーは多くともどれか１つにしか起きません．そこで，次のような射影測定$ \\{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ゲートを作用させ反転させれば良いだけです．

## 復号
計算の最後に復号し，１物理量子ビットに情報を集約させることを考えます．これは符号化の逆の操作，つまり$CNOT$を２回作用させれば良いです．実際，

$$
\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}
$$

となり，一番目の量子ビットに情報が集約されていることが分かります．

## 実装

```python
from qiskit.circuit import QuantumCircuit
from qiskit.visualization import plot_bloch_multivector
from qiskit import Aer, execute
```

まずは，論理Xゲートを作用させた後に，ビット反転エラーが乗った場合を考えてみます．
```python
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)
```
![biterror_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/5f141a3c-2b9c-8335-279c-52a7db696389.png)

一番目の量子ビット(qubit 0)のみエラーが起きているのが確認できます．
そこで，エラーを訂正してみましょう．
```python
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)
```
![bitflip_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/f68c3e30-d367-7fb1-897c-27b875e2dc35.png)

確かにエラーが訂正されています．

次は，もう少し複雑な回路を実行し，無事復号できるかどうか試してみましょう．論理$RX$ゲートや論理$RZ$ゲートは$RXXX$や$RZZZ$によって与えられることに注意すると[^1]，論理$RX$ゲートや論理$RZ$ゲートを適用した回路は以下のように表されます．

```python
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)
```
![bitflip_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/dca4b142-48d5-77a1-63cd-ee3cc6d5703d.png)

最後にエラーを乗せたため，一つ目の量子ビットのみ反転していることが分かります．ここで，それぞれの量子ビットが$\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}
$$

より理解できます．このエラーを修正し，さらに復号までしてみましょう．

```python
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)
```
![bitflip_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/4f2c2c3a-cb8c-293a-90f5-ca159b474407.png)

二つ目と三つ目の量子ビットは初期化され，一つ目の量子ビットのみに情報が戻りました．これが合っているのかどうか，エラーのない１量子ビットでの計算を行なってみると，

```python
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)
```
![bitflip_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/887541/c0324eaa-7693-167d-590a-828043d05c7a.png)

確かに一致することが分かりました．



#### Version Information
|Software|Version|
|---|---|
|qiskit|0.45.0|


||System information|
|---|---|
|Python version	|3.10.12|


[^0]:Nielsen, Michael A., and Isaac L. Chuang. Quantum computation and quantum information. Cambridge university press, 2010.

[^1]:この実装については，[以前の記事](https://qiita.com/tare_/items/0753a977de9d8d3348a2)で紹介しました．