Qiskitの動的回路を利用して誤り訂正符号を実装する

# はじめに

Qiskitの動的回路[[1]](#参考文献)は、量子回路の実行途中に観測を行い、その観測結果に応じて、回路の操作を動的に変化させることができるものです。

量子コンピュータにおける誤り訂正符号の実装では、シンドローム測定を行い、この結果に応じて、エラーを修正するようなゲートを作用させる必要があります。

今回は、動的回路の機能を用いて、Steane符号の実装を行います。

以前、[Steane符号の実装を試みた記事](https://qiita.com/kazuo_watanabe/items/75d902ec1e8c0ecb9029)では、マルチコントロールゲートを用いることで、誤り訂正を実装していました。

シミュレータの上で誤り訂正符号の挙動を確認するだけであれば、この実装でも大きな問題はないのですが、実機での活用を考慮すると、マルチコントロールゲートの実装はコストが高く、これ自体もエラーの原因になります。このため、シンドローム測定を用いたエラー訂正のほうが現実的になります。

本記事の内容は、東京大学「量子ソフトウェア」寄付講座[[2]](#参考文献)内で実施された第5回量子ソフトウェアハンズオン(産学協働ゼミ)[[3]](#参考文献)の演習資料を元に作成しています。

# 動的回路の実装について

まずは、簡単な回路を用いて、動的回路の利用方法を確認します。

今回の実行環境は以下の通りです。

```
Python 3.11.3
qiskit 0.45.2
qiskit_aer 0.12.0
```

最初に必要なライブラリのインポートを行います。

```Python
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit.visualization import plot_histogram
```

次に、以下のような回路を用意します。

```Python
qr = QuantumRegister(2)
cr = ClassicalRegister(2)
circ = QuantumCircuit(qr, cr)

# ベル状態の用意
circ.h(0)
circ.cx(0, 1)

# 0番のqubitの測定
circ.measure(0, 0)

# 0番のqubitの値に応じて、1番のqubitに処理を加える
with circ.if_test((cr[0], 0b1)):
    circ.x(1)
    
# 1番のqubitの測定
circ.measure(1, 1)
```

![回路図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670216/7a0ecaa1-aabb-cbf8-edd4-ee625753c980.png)


最初に、ベル状態を作成します。このときの状態は$\lvert 00 \rangle$と$\lvert 11 \rangle$のどちらかです。

次に、0番のqubitを観測し、この値に応じて、動的回路の機能を用いて1番のqubitにゲートを適用します。ここでは、0番のqubitが1のときに、1番のqubitにXゲートを適用させます。

したがって、$\lvert 00 \rangle$のときには何も起こらず、$\lvert 11 \rangle$のときのみ、1番のqubitにXゲートがかかり、$\lvert 10 \rangle$になります。

実際に結果がそのようになっていることを確認します。

```Python
n_shots = 1000 # シミュレーターでのサンプリング回数
backend_sim = AerSimulator() # シミュレーターの用意

result = backend_sim.run(circ, shots=n_shots).result()
plot_histogram(result.get_counts(0))
```

![出力ヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670216/81f3c76b-e250-ef63-abad-0bd203e980c3.png)


たしかに、$\lvert 00 \rangle$と$\lvert 10 \rangle$の二つの結果が得られました。これが動的回路の基本的な使い方になります。

最初に述べたように、この結果をただ実現するだけであれば、動的回路を使わずとも、CNOTゲートを使うことでも実現可能です。この回路では、条件判定を1量子ビットのみでおこなっていますが、条件に用いる量子ビットが複数になるのであれば、マルチコントロールゲートを使わずに済むことが利点になります。ただし、観測した状態は壊れてしまうので、用途は適切に考える必要があります。

# Steane符号の実装

それでは、本題であるSteane符号の実装に移ります。

今回実装するSteane符号のエラーシンドロームは以下のようになっています。

```math
\begin{align}
M_0 = Z_0 Z_4 Z_5 Z_6 \\
M_1 = Z_1 Z_3 Z_5 Z_6 \\
M_2 = Z_2 Z_3 Z_4 Z_6 \\

N_0 = X_0 X_4 X_5 X_6 \\
N_1 = X_1 X_3 X_5 X_6 \\
N_2 = X_2 X_3 X_4 X_6 \\
\end{align}
```

これを実現する回路は以下のように実装できます。

```Python
def steane_code(noise_channel: list[int] = [], p_error: float = 0.1) -> QuantumCircuit:
    # noise_channel : ノイズをかけたいチャネル(int)を入れたリスト
    # p_error : エラーの発生確率
    
    # エラーの定義
    # 確率p_errorでビット反転エラーと位相反転エラーが独立に発生する
    bit_flip = pauli_error([('X', p_error), ('I', 1 - p_error)])
    phase_flip = pauli_error([('Z', p_error), ('I', 1 - p_error)])

    bitphase_flip = bit_flip.compose(phase_flip)
    
    # 回路の記述
    n_qubits = 7 + 6
    qr = QuantumRegister(n_qubits)
    cr = ClassicalRegister(1) # デコード後の結果読み出し用
    ancilla_1 = ClassicalRegister(3) # ビット反転の検知用
    ancilla_2 = ClassicalRegister(3) # 位相反転の検知用
    circ = QuantumCircuit(qr, cr, ancilla_1, ancilla_2)

    # 符号化
    circ.h(0)
    circ.h(1)
    circ.h(2)
    
    circ.cx(3, 4)
    circ.cx(3, 5)
    
    circ.cx(2, 3)
    circ.cx(2, 4)
    circ.cx(2, 6)
    
    circ.cx(1, 3)
    circ.cx(1, 5)
    circ.cx(1, 6)
    
    circ.cx(0, 4)
    circ.cx(0, 5)
    circ.cx(0, 6)
    
    circ.barrier()

    # エラーが発生する部分
    for i in noise_channel:
        assert (0 <= i) and (i < 13)
        circ.append(bitphase_flip, [i])

    circ.barrier()

    # エラー訂正
    
    # アンシラに情報を送る
    for i in range(6):
        circ.reset(i + 7)
        circ.h(i + 7)
        
    circ.cz(7, 0)
    circ.cz(7, 4)
    circ.cz(7, 5)
    circ.cz(7, 6)
    
    circ.cz(8, 1)
    circ.cz(8, 3)
    circ.cz(8, 5)
    circ.cz(8, 6)

    circ.cz(9, 2)
    circ.cz(9, 3)
    circ.cz(9, 4)
    circ.cz(9, 6)
    
    circ.cx(10, 0)
    circ.cx(10, 4)
    circ.cx(10, 5)
    circ.cx(10, 6)

    circ.cx(11, 1)
    circ.cx(11, 3)
    circ.cx(11, 5)
    circ.cx(11, 6)
    
    circ.cx(12, 2)
    circ.cx(12, 3)
    circ.cx(12, 4)
    circ.cx(12, 6)
    
    for i in range(6):
        circ.h(i + 7)
    
    circ.barrier()
    
    # シンドローム測定
    circ.measure(qr[7:10], ancilla_1)
    circ.measure(qr[10:], ancilla_2)
    
    # ビット反転 
    with circ.if_test((ancilla_1, 0b001)):
        circ.x(0)
        
    with circ.if_test((ancilla_1, 0b010)):
        circ.x(1)
    
    with circ.if_test((ancilla_1, 0b100)):
        circ.x(2)
    
    with circ.if_test((ancilla_1, 0b110)):
        circ.x(3)
        
    with circ.if_test((ancilla_1, 0b101)):
        circ.x(4)
        
    with circ.if_test((ancilla_1, 0b011)):
        circ.x(5)
    
    with circ.if_test((ancilla_1, 0b111)):
        circ.x(6)
    
    circ.barrier()
    
    # 位相反転
    with circ.if_test((ancilla_2, 0b001)):
        circ.z(0)
        
    with circ.if_test((ancilla_2, 0b010)):
        circ.z(1)
    
    with circ.if_test((ancilla_2, 0b100)):
        circ.z(2)
        
    with circ.if_test((ancilla_2, 0b110)):
        circ.z(3)
    
    with circ.if_test((ancilla_2, 0b101)):
        circ.z(4)
    
    with circ.if_test((ancilla_2, 0b011)):
        circ.z(5)
        
    with circ.if_test((ancilla_2, 0b111)):
        circ.z(6)
    
    circ.barrier()
    
    # 復号
    circ.cx(0, 4)
    circ.cx(0, 5)
    circ.cx(0, 6)
    
    circ.cx(1, 3)
    circ.cx(1, 5)
    circ.cx(1, 6)
    
    circ.cx(2, 3)
    circ.cx(2, 4)
    circ.cx(2, 6)
    
    circ.cx(3, 4)
    circ.cx(3, 5)
    
    circ.h(0)
    circ.h(1)
    circ.h(2)

    circ.measure(qr[3], cr)

    return circ
```

今回の実装では、アンシラが6個使われており、3個がビット反転、残りの3個が位相反転を検知するのに使われます。

したがって、ビット反転の訂正では、3ビットの測定結果を用いた動的回路を使っています。たとえば、一つ目の修正部分について着目してみると、以下のようになっています。

```Python
with circ.if_test((ancilla_1, 0b001)):
    circ.x(0)
```

これは、測定結果が$\lvert 100 \rangle$の場合に、0番のビットにXゲートを適用することになります。エラーシンドロームを確認してみると、0番が反転した際に影響を受けるのは、$M_0$のみとなっています。

## 回路の実行

作成したSteane符号の回路を動かしてみます。まずは、エラーを載せない状態で実行してみます。

```Python
circ = steane_code()
result = backend_sim.run(circ, shots=n_shots).result()
plot_histogram(result.get_counts(0))
```

![出力ヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670216/13b79727-15ad-21c9-97b4-62d79e016e1f.png)

ヒストグラムには、アンシラの測定結果も含めたものが出てきます。こちらの結果において、最上位ビットが観測したい目的のビット、次の3ビットがビット反転検出用のアンシラ、最後の3ビットが位相反転検出用のアンシラになります。

今回の結果ではすべて0となっています。元の状態が0かつ、エラーが発生していないため、このような結果になります。

## 1量子ビットのエラー
次に、エラーを加えてみます。ここでは、0番目のビットにエラーを加えることにします。

```Python
# 0番のビットに、ビット反転エラーと位相反転エラーを等確率で発生させる
circ = steane_code(noise_channel=[0]) 
result = backend_sim.run(circ, shots=n_shots).result()
plot_histogram(result.get_counts(0))
```

![出力ヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670216/5e8f0c2e-a96b-8ba6-92a7-8a279744567c.png)


複数の結果が出てきたので、順番に見ていきます。

まず出現頻度が一番高い左の結果ですが、すべてのビットが0になっています。これは、エラーが一切起こらなかった場合に相当します。

次に、左から2番目の結果ですが、最上位ビットが0、次のアンシラが100、最後のアンシラが000となっています。これはビット反転エラーが発生したものなりますが、エラーが正しく修正され、最上位ビットの結果は正しく0になっています。

同様に3番目については、位相反転エラーが発生した場合のものになります。

一番出現頻度の低い4番目の結果は、ビット反転エラー、位相反転エラーの両方が発生したものになります。この場合についても、一つの物理量子ビットに発生したエラーなので、問題なく修正できています。

試しにほかのビットにもエラーを乗せてみます。

```Python
circ = steane_code(noise_channel=[3])
result = backend_sim.run(circ, shots=n_shots).result()
plot_histogram(result.get_counts(0))
```

![出力ヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670216/38b2e552-9829-2cc4-bfb0-b5726172f794.png)


今回は、3番の量子ビットにエラーを載せました。エラーシンドロームを改めて確認すると、このときはアンシラの後ろ二つが反応することがわかります。

結果の解釈については、先ほどと同様です。

## 2量子ビットのエラー

最後に、複数の量子ビットにエラーを乗せてみます。Steane符号は、1ビットまでの誤りを検出・訂正できる符号ですので、訂正できないことが期待値になります。

ここでは、先ほど実験した、0番と3番にエラーをかけてみます。

```Python
circ = steane_code(noise_channel=[0, 3])
result = backend_sim.run(circ, shots=n_shots).result()
plot_histogram(result.get_counts(0))
```

![出力ヒストグラム](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2670216/9c7209cc-fca8-cd88-6026-93da2eee7561.png)


多くの結果が出てきましたが、注目していただきたいのは、最上位ビットが1になってしまっているケースがあることです。これは、誤りが訂正できていないことになります。

例えば、左から4番目の結果を見ると、最初のアンシラが111となっています。これは、0番と3番に同時にビット反転が起きてしまったために、このような結果になっています。

一方で、誤り訂正のアルゴリズムとしては、1量子ビットまでしか誤りが起きていないと考えているため、このシンドローム測定の結果から、6番のビットにエラーがあるものと判断して、訂正してしまいます。というのも、改めてエラーシンドロームを見ていただければわかる通り、6番のビットのみがビット反転した場合も、アンシラは111という結果になるのです。

```math
\begin{align}
M_0=Z_0 Z_4 Z_5 Z_6 \\
M_1=Z_1 Z_3 Z_5 Z_6 \\
M_2=Z_2 Z_3 Z_4 Z_6 \\
\end{align}
```

今回、サンプリングによるヒストグラムで確認しているため、位相反転のエラーについては、誤りが訂正しきれなくても、結果的には問題ないように見えてしまっている点には注意が必要です。先ほどの結果のヒストグラムの右から三つの結果がこれに相当します。


# まとめ

今回は、動的回路の機能を用いて、Steane符号の実装を行いました。

理論上、静的回路と動的回路で実現可能な計算に変わりはありませんが、ハードウェアの能力を効率的に活用する上では有効な手段です。ただし、動的回路を利用するためには、ハードウェア側もリアルタイムにデータのやり取りを行いながら、実行内容が制御できるものである必要があります。IBMにおいては、2022年後半に実機上でも動的回路が実行できるようになっています。

マルチコントロールゲートを使った実装と比べ、条件の記述部分のコードの可読性が高いこと、アンシラの観測結果と合わせて確認することで、誤り訂正の動作について理解しやすいということも、学習上の観点では嬉しいポイントかもしれません。

# 参考文献

1. [Qiskit Runtimeで動的回路を最大限に活用する | Think Blog Japan](https://www.ibm.com/blogs/think/jp-ja/quantum-dynamic-circuits/) (アクセス日：2024/3/18)

2. [東京大学「量子ソフトウェア」寄付講座](http://qsw.phys.s.u-tokyo.ac.jp/)

3. [東京大学「量子ソフトウェア」寄付講座 - 第5回量子ソフトウェアハンズオン(産学協働ゼミ)](http://qsw.phys.s.u-tokyo.ac.jp/handson202403)