これは トポロジカル符号の実装実験 の記事です。
はじめに
前回の記事では、符号距離 $d=5$ の 4.8.8 カラー符号である $[[17,1,5]]$ 符号について、エンコード回路を具体的に構成し、得られた状態がスタビライザーで定義される符号空間に属していることを確認しました。
カラー符号の特徴の一つは、4.8.8 格子において Clifford ゲートをトランスバーサルに実装できる点です[参考1]。一方で、実際に誤り訂正符号として用いるためには、物理量子ビットに生じた誤りをシンドローム測定によって検出し、適切な補正を行う必要があります。この処理は復号、またはデコードと呼ばれます。復号では、スタビライザー測定によって得られたシンドロームから、物理量子ビット上に生じた誤りを推定します。ただし、シンドロームは誤りそのものを直接与えるものではありません。そのため、観測されたシンドロームに対して、どの補正操作を選ぶかが重要になります。
本記事では、ルックアップテーブルに基づくデコーダーを用いて、カラー符号における誤り訂正の流れを実装します。特に、符号化を行わない物理量子ビット、$[[7,1,3]]$ 符号、$[[17,1,5]]$ 符号を比較し、独立なデポラライジングエラーのもとで、符号距離に応じて論理エラー率がどのように抑えられるかを確認します。
カラー符号の誤り訂正の考え方
シンドロームによる誤りの検出
量子誤り訂正では、符号化された量子状態を壊さずに、物理量子ビットに生じた誤りの情報を取り出す必要があります。ここで重要なのは、論理状態そのものを測定するのではなく、誤りに関する情報だけを取り出すことです。この情報をシンドロームと呼び、シンドロームを得るための測定をシンドローム測定と呼びます。
スタビライザー符号では、シンドローム測定はスタビライザー生成元の測定として実現されます。つまり、スタビライザー生成元を測定し、その測定結果を誤り訂正に用いるビット列として記録します。
符号空間 $C$ は、$r$ 個のスタビライザー生成元 $g_1,\dots,g_r$ の $+1$ 固有空間として
$$
C={|\psi\rangle \mid g_i|\psi\rangle=|\psi\rangle,\ i=1,\dots,r}
\tag{1}
$$
で定義されます。つまり、符号空間 $C$ に属する状態では、すべてのスタビライザー生成元 $g_i$ の測定結果が $+1$ になります。
物理量子ビットに誤りが生じると、誤りの種類や位置に応じて、一部の $g_i$ の測定結果が $+1$ から $-1$ に変化します。そこで、各 $g_i$ の測定結果をビット値として記録します。測定結果が $+1$ のとき $s_i=0$、測定結果が $-1$ のとき $s_i=1$ と定めると、測定結果全体は
$$
\mathbf{s}=(s_1,\dots,s_r)
\tag{2}
$$
というビット列で表されます。この $\mathbf{s}$ をシンドロームといいます。
シンドローム $\mathbf{s}$ は、誤りがどの測定結果に影響したかを表します。つまり、$\mathbf{s}$ を見ることで、状態が符号空間からどのように外れたかを知ることができます。
カラー符号におけるデコード
シンドローム測定によって、誤りがどの測定結果に影響したかを知ることができますが、残念ながらシンドロームはエラーそのものを直接与えるわけではありません。異なるエラーが同じシンドロームを与えることがあるため、シンドロームだけを見ても、実際にどのエラーが起きたのかを一意に決められない場合があります。
そのため、量子誤り訂正では、得られたシンドロームから物理量子ビットに生じたエラーを推定する必要があります。この処理をデコードと呼びます。デコードでは、観測されたシンドロームと矛盾しないエラーの候補の中から、実際に補正に用いるエラーまたは補正操作を選びます。
多くの場合、デコードではエラーモデルを仮定し、そのもとで発生確率が最も高いエラーを選びます。つまり、デコードは、シンドローム測定の結果からエラーの候補を絞り込み、その中で最も起こりやすいと考えられるエラーを予測する問題と見ることができます。
ただし、実際の量子デバイスで生じるエラーの分布を完全に知ることは一般には困難です。また、符号の構造によって、シンドロームからエラーを推定する難しさも変わります。そのため、デコードにはさまざまな戦略が考えられます。どの方法を用いるかは、符号の構造、仮定するエラーモデル、計算時間、実装のしやすさなどに依存します。
最も直接的な方法は、ルックアップテーブルによるデコードです。これは、得られるシンドロームのそれぞれに対して、対応するエラーまたは補正操作をあらかじめ決めておく方法です。表を作る段階で、仮定したエラーモデルのもとで最も発生確率が高いエラーを選んでおけば、実際のデコードでは、測定されたシンドロームに対応するエラーを表から読み出すだけで済みます。そのため、小さい符号では実装しやすく、デコードにかかる計算時間も短くできます。
一方で、符号距離 $d$ が大きくなると、取り得るシンドロームやエラーの候補が増えるため、すべてを表として用意する方法は扱いにくくなります。表面符号では、最小重み完全マッチングに基づくデコードがよく用いられますが、カラー符号では同じ方法をそのまま適用することはできません。そのため、カラー符号では、符号の構造に合わせたデコード方法が必要になります。ここでは詳しくは扱いませんが、カラー符号のデコードには、例えば表面符号への射影に基づく方法や、最適化問題として定式化する方法、機械学習を用いる方法も研究されています[参考2,3,4]。
今回扱う $[[17,1,5]]$ 符号は比較的小さい符号なので、これらの発展的なデコーダーは用いず、実装しやすいルックアップテーブルを使って、シンドローム測定、デコード、補正操作、論理エラー率の評価という流れを確認していきます。
誤り訂正の実装
[[17,1,5]]符号のデコード
まずは、$[[17,1,5]]$ 符号に対するルックアップテーブルデコーダーを実装していきます。
ここでは、入力する 1 量子ビット状態を $q_1$ に準備し、前回確認したエンコード回路を使って $[[17,1,5]]$ 符号の論理状態へ符号化します。その後、各物理量子ビットに誤りチャネルを独立に作用させ、シンドローム測定と補正によって、もとの論理状態を回復できるかを確認します。
比較のため、$[[17,1,5]]$ 符号だけでなく、$[[7,1,3]]$ 符号、すなわち Steane 符号についても同様に扱えるようにしておきます。
from qiskit import QuantumCircuit, transpile, QuantumRegister, ClassicalRegister
from qiskit_aer import AerSimulator
from qiskit_aer.noise import pauli_error
from qiskit_aer.noise.errors.base_quantum_error import QuantumChannelInstruction
from qiskit.visualization import plot_histogram
import matplotlib.pyplot as plt
import numpy as np
# %matplotlib inline
def encode_color_code(distance) -> QuantumCircuit:
if distance == 5:
# [[17,1,5]]符号における論理状態の符号化
# データ量子ビットはq1
circ = QuantumCircuit(17)
circ.h(0)
circ.h(4)
circ.h(6)
circ.h(7)
circ.h(8)
circ.h(12)
circ.h(15)
circ.h(16)
circ.cx(1, 2)
circ.cx(0, 3)
circ.cx(0, 1)
circ.cx(6, 14)
circ.cx(6, 2)
circ.cx(6, 3)
circ.cx(8, 13)
circ.cx(15, 10)
circ.cx(15, 11)
circ.cx(15, 14)
circ.cx(16, 15)
circ.cx(2, 5)
circ.cx(5, 9)
circ.cx(7, 6)
circ.cx(16, 7)
circ.cx(6, 10)
circ.cx(7, 11)
circ.cx(4, 0)
circ.cx(0, 2)
circ.cx(8, 4)
circ.cx(4, 5)
circ.cx(12, 8)
circ.cx(8, 9)
circ.cx(9, 13)
return circ.to_instruction(label='encoder_17_1_5')
elif distance == 3:
# [[7,1,3]]符号における論理状態の符号化
# データ量子ビットはq1
circ = QuantumCircuit(7)
circ.h(3)
circ.h(5)
circ.h(6)
circ.cx(1, 2)
circ.cx(1, 4)
circ.cx(3, 1)
circ.cx(3, 2)
circ.cx(3, 0)
circ.cx(5, 1)
circ.cx(5, 4)
circ.cx(5, 0)
circ.cx(6, 2)
circ.cx(6, 4)
circ.cx(6, 0)
return circ.to_instruction(label='encoder_7_1_3')
図1に、$[[7,1,3]]$ 符号と $[[17,1,5]]$ 符号の格子と物理量子ビットの配置を示します。各頂点が物理量子ビットに対応し、各面がシンドローム測定に用いるスタビライザー生成元と対応しています。
ルックアップテーブルの作成
シンドローム測定の結果からエラーを推定するためのルックアップテーブルを作成します。
ルックアップテーブルでは、シンドロームをキーとして、そのシンドロームに対応するエラー候補を登録します。今回の実装では、まず候補となるエラーの位置を列挙し、それぞれのエラーがどのシンドロームを与えるかを計算します。そして、得られたシンドロームとエラー候補の対応を表に保存します。
この計算に用いるのが、図1の量子ビット配置に対応するパリティ検査行列 $H$ です。ここでは、$[[17,1,5]]$ 符号と $[[7,1,3]]$ 符号に対して次の行列を用います。
# [[17,1,5]]符号 のパリティ検査行列
H_17_1_5 = np.array([
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1],
[0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0],
], dtype=int)
# [[7,1,3]]符号(Steane符号)のパリティ検査行列
H_7_1_3 = np.array([
[1, 1, 1, 1, 0, 0, 0],
[1, 1, 0, 0, 1, 1, 0],
[1, 0, 1, 0, 1, 0, 1]
])
また、符号距離に応じて、対応するパリティ検査行列を返す関数を用意しておきます。
def parity_check_matrix(distance):
if distance == 5:
return H_17_1_5
elif distance == 3:
return H_7_1_3
else:
raise ValueError("distance must be 3 or 5")
エラーの位置を表すベクトルを $\mathbf{e}$ とすると、シンドローム $\mathbf{s}$ は
$$
\mathbf{s}=H\mathbf{e} \pmod{2}
\tag{3}
$$
で計算します。つまり、エラーが生じた物理量子ビットに対応する列を取り出し、それらを足し合わせて 2 で割った余りを取ります。
次に、どこまでのエラーをルックアップテーブルに登録するかを決めます。符号距離 $d$ の符号では、
$$
t=\left\lfloor \frac{d-1}{2}\right\rfloor
\tag{4}
$$
個までの物理量子ビットエラーを訂正できます。したがって、ルックアップテーブルを作るときには、1 個のエラーから $t$ 個のエラーまでを列挙します。
$[[17,1,5]]$ 符号では $d=5$ なので $t=2$ です。つまり、1 量子ビットエラーと 2 量子ビットエラーを登録します。一方、$[[7,1,3]]$ 符号では $d=3$ なので $t=1$ であり、1 量子ビットエラーだけを登録します。
この方針に基づいて、次の関数でルックアップテーブルを作成します。
from itertools import combinations
def build_syndrome_lookup(
distance: int,
) -> dict[int, tuple[int, ...]]:
h = parity_check_matrix(distance)
n_qubits = h.shape[1]
max_weight = (distance - 1) // 2
lookup_table = {0: ()}
for w in range(1, max_weight + 1):
for errors in combinations(range(n_qubits), w):
# H[:, errors] の列をまとめて XOR(mod 2)
syn_vec = h[:, errors].sum(axis=1) % 2
# bit列 → int に変換
syn = int(sum(int(b) << i for i, b in enumerate(syn_vec)))
lookup_table.setdefault(syn, errors)
return lookup_table
この関数では、訂正可能な範囲のエラー候補を列挙し、それぞれについて式 (3) に対応するシンドロームを計算しています。得られたシンドロームはビット列 syn_vec として表されますが、Qiskit の if_test で条件として使いやすいように、整数 syn に変換してからルックアップテーブルのキーにしています。
同じシンドロームを与えるエラー候補が複数ある場合もあります。その場合、この実装では最初に見つかった候補を登録します。エラー候補は 1 量子ビットエラー、2 量子ビットエラー、という順番で列挙しているため、同じシンドロームに対しては、より少ない量子ビットに作用するエラーが優先されます。
これは、物理エラー率が十分小さい場合には、少数の量子ビットに生じるエラーほど起こりやすいと考えられるためです。したがって、このルックアップテーブルデコーダーは、観測されたシンドロームに対して、比較的起こりやすいエラー候補を返すように作られています。
エラーチャネルの作成
次に、エラーチャネルを作成します。各物理量子ビットに対して、独立にデポラライジングエラーが生じる場合を考えます。
デポラライジングエラーは、確率的なパウリ誤りとしてモデル化します。具体的には、各物理量子ビットに対して、確率 $1-p$ で何も起こらず、確率 $p$ でパウリ誤りが発生するとします。このとき、生じる誤りは $X$、$Y$、$Z$ のいずれかであり、それぞれ確率 $p/3$ で発生するとします。ここで、$p$ は 1 量子ビットあたりの物理エラー率です。
Qiskit では、pauli_error を用いて次のように定義できます。
def make_depolarizing_error_channel(p_error: float) -> QuantumChannelInstruction:
depolarizing_error = pauli_error([
('I', 1 - p_error),
('X', p_error / 3),
('Y', p_error / 3),
('Z', p_error / 3),
])
return depolarizing_error.to_instruction()
作成したエラーチャネルを、指定した物理量子ビットへ独立に作用させる関数も用意しておきます。
def append_error_channel(
circ: QuantumCircuit,
q_sys: QuantumRegister,
error_channel,
error_qubits,
) -> QuantumCircuit:
if error_qubits is None:
return circ
for q in error_qubits:
circ.append(error_channel, [q_sys[q]])
circ.barrier()
return circ
シンドローム測定と誤り訂正
シンドローム測定と誤り訂正を行う回路を構成します。ここでは、補助量子ビットを 1 つ用意し、それを使い回して各面に対応するシンドロームを順に測定します。
def append_qec(
circ: QuantumCircuit,
q_sys: QuantumRegister,
distance: int,
) -> QuantumCircuit:
h = parity_check_matrix(distance)
lookup_table = build_syndrome_lookup(distance)
n_checks = h.shape[0]
q_anc = QuantumRegister(1, "anc")
c_syn_x = ClassicalRegister(n_checks, "syn_x")
c_syn_z = ClassicalRegister(n_checks, "syn_z")
circ.add_register(q_anc)
circ.add_register(c_syn_x)
circ.add_register(c_syn_z)
# Z 型スタビライザー測定
# X 誤りを検出する
for i in range(n_checks):
circ.reset(q_anc[0])
for j, flag in enumerate(h[i]):
if flag:
circ.cx(q_sys[j], q_anc[0])
circ.measure(q_anc[0], c_syn_z[i])
circ.barrier()
# X 型スタビライザー測定
# Z 誤りを検出する
for i in range(n_checks):
circ.reset(q_anc[0])
circ.h(q_anc[0])
for j, flag in enumerate(h[i]):
if flag:
circ.cx(q_anc[0], q_sys[j])
circ.h(q_anc[0])
circ.measure(q_anc[0], c_syn_x[i])
circ.barrier()
# Z 型スタビライザーのシンドロームから X 補正を行う
for syn, error_qubits in lookup_table.items():
if syn == 0:
continue
with circ.if_test((c_syn_z, syn)):
for q in error_qubits:
circ.x(q_sys[q])
# X 型スタビライザーのシンドロームから Z 補正を行う
for syn, error_qubits in lookup_table.items():
if syn == 0:
continue
with circ.if_test((c_syn_x, syn)):
for q in error_qubits:
circ.z(q_sys[q])
circ.barrier()
return circ
この関数では、まず $Z$ 型スタビライザーに対応するシンドロームを測定します。$Z$ 型スタビライザーは $X$ 誤りと反可換になるため、ここで得られるシンドロームは $X$ 成分のエラーを推定するために使います。測定結果は古典レジスタ c_syn_z に保存します。
次に、$X$ 型スタビライザーに対応するシンドロームを測定します。$X$ 型スタビライザーは $Z$ 誤りと反可換になるため、ここで得られるシンドロームは $Z$ 成分のエラーを推定するために使います。測定結果は古典レジスタ c_syn_x に保存します。
その後、測定されたシンドロームをキーとしてルックアップテーブルを参照し、補正を作用させる物理量子ビットを決めます。c_syn_z の値が syn に一致した場合には、そのシンドロームに対応する量子ビットに $X$ を作用させます。同様に、c_syn_x の値が syn に一致した場合には、そのシンドロームに対応する量子ビットに $Z$ を作用させます。
$Y$ 誤りは $Y=iXZ$ と書けるため、$X$ 成分と $Z$ 成分の両方を含むエラーとして扱えます。そのため、$Y$ 誤りが生じた場合には、$Z$ 型スタビライザー測定と $X$ 型スタビライザー測定の両方にシンドロームが現れ、それぞれに対して補正が行われます。
シンドローム測定と誤り訂正回路の挙動
ここまでで、エラーチャネル、シンドローム測定、デコード、補正操作を行う回路を用意できたので、その挙動を確認します。
ここでは、あえて複数の物理量子ビットにデポラライジングエラーを作用させます。$[[17,1,5]]$ 符号は 2 量子ビットエラーまで訂正できますが、今回は 6 個の物理量子ビットにエラーチャネルを作用させることで、訂正可能な範囲を超えるエラーが発生する状況も含めています。また、誤り訂正に失敗するショットを確認しやすくするために、物理エラー率 p_error をやや高めに設定しています。
distance = 5
num_qubits = 17
p_error = 0.3
error_inst = make_depolarizing_error_channel(p_error)
circ_input = QuantumCircuit(1, name='data')
q_sys = QuantumRegister(num_qubits, name='q_sys')
circ_noise = QuantumCircuit(q_sys)
circ_noise.append(circ_input, [q_sys[1]])
circ_noise.append(encode_color_code(distance), q_sys[:])
circ_noise = append_error_channel(circ_noise, q_sys, error_inst, error_qubits=[0, 1, 2, 3, 4, 5])
circ_noise = append_qec(circ_noise, q_sys, distance)
入力状態を $q_1$ に準備し、エンコード後に指定した量子ビットへエラーチャネルを作用させています。その後、`append_qec` によって、シンドローム測定と補正操作を追加しています。
誤り訂正後の論理状態を簡単に確認するため、最後にエンコード回路の逆操作を作用させ、元の 1 量子ビットへ戻します。
circ_noise_decoded = circ_noise.copy()
circ_noise_decoded.append(encode_color_code(distance).inverse(), q_sys[:])
c_logical = ClassicalRegister(1, "c_logical")
circ_noise_decoded.add_register(c_logical)
circ_noise_decoded.measure(q_sys[1], c_logical[0])
# circ_noise_decoded.draw("mpl")
入力状態を $|0\rangle$ としているため、誤り訂正が成功していれば、復号後の $q_1$ を $Z$ 基底で測定した結果は $0$ になります。一方、補正しきれないエラーが残り、論理ビット反転が生じた場合には、測定結果は $1$ になります。
シミュレーションには、stabilizer バックエンドを用います。
n_shots = 200 # ショット数
backend_sim = AerSimulator(method="stabilizer") # シミュレーターの用意
tcirc = transpile(circ_noise_decoded, backend_sim)
result = backend_sim.run(tcirc, shots=n_shots).result()
counts = result.get_counts()
plot_histogram(counts)
ヒストグラムの横軸には、最後に測定した論理ビットの値に加えて、シンドローム測定で得られた古典ビット列も表示されています。このうち、先頭に表示されているビットが復号後の $q_1$ の測定結果に対応します。
ヒストグラムを見ると、先頭のビットが $0$ のショットが多くを占めています。これは、誤り訂正が成功し、論理状態が $|0\rangle$ として正しく読み出されていることを表しています。一方で、少数ですが先頭のビットが $1$ となるショットも現れており、誤り訂正に失敗し、論理ビット反転が起きていることが分かります。
今回のルックアップテーブルは、符号距離から決まる訂正可能な範囲のエラーだけを登録しています。そのため、訂正可能な範囲を超えるエラーが発生した場合には、正しい補正を選べるとは限りません。このように、ヒストグラムからは、同じ物理エラー率のもとでも誤り訂正が成功するショットと失敗するショットが現れることを確認できます。
error_qubits に指定する量子ビットや、p_error の値を変えると、ヒストグラムの分布も変化します。実際に、error_qubits に含める量子ビットの数や位置、p_error の値を変更して、ヒストグラムがどのように変化するかを確認してみてください。
論理エラー率の評価
符号ごとの誤り訂正の性能を定量的に評価するため、論理エラー率を計算します。
ここでは、すべての物理量子ビットに独立にデポラライジングエラーを作用させます。また、前節のようにエンコード回路の逆操作で 1 量子ビットへ戻すのではなく、最後にすべての物理量子ビットを測定し、測定結果に含まれる $1$ の個数の偶奇によって論理エラーを判定します。今回、入力状態は $|0_L\rangle$ として、復号後に論理ビット反転が生じたかどうかを見ることにします。
各物理量子ビットの測定結果を $b_1,\dots,b_n$ とすると、
$$
b_1+\cdots+b_n = 1 \pmod{2}
\tag{5}
$$
となる場合を論理エラーとして数えます。したがって、論理エラー率は、全ショットのうち測定結果のパリティが奇数になった割合として見積もります。
def error_rate(counts):
n1 = 0
shots = 0
for k, v in counts.items():
bits = k.split()[0]
if bits.count('1') % 2 == 1:
n1 += v
shots += v
return n1 / shots
def calc_error_rate(
circ_input,
p_error: float,
distance : int,
) -> float:
error_inst = make_depolarizing_error_channel(p_error)
if distance != 1:
num_qubits = parity_check_matrix(distance).shape[1]
q_sys = QuantumRegister(num_qubits, name = 'q_Sys')
circ_noise = QuantumCircuit(q_sys)
circ_noise.append(circ_input, [q_sys[1]])
circ_noise.append(encode_color_code(distance), q_sys[:])
circ_noise = append_error_channel(circ_noise, q_sys, error_inst, error_qubits=range(num_qubits))
circ_noise = append_qec(circ_noise, q_sys, distance)
elif distance == 1:
q_sys = QuantumRegister(distance, name = 'q_phys')
circ_noise = QuantumCircuit(q_sys)
circ_noise.append(circ_input, [q_sys[0]])
circ_noise = append_error_channel(circ_noise, q_sys, error_inst, error_qubits=[0])
c_meas = ClassicalRegister(q_sys.size, name='meas')
circ_noise.add_register(c_meas)
circ_noise.measure(q_sys[:], c_meas[:])
tcirc = transpile(circ_noise, backend_sim)
result = backend_sim.run(tcirc, shots=n_shots).result()
counts = result.get_counts()
return error_rate(counts)
distance=3 または distance=5 の場合には、入力状態を $q_1$ に準備し、エンコード回路によってそれぞれ $[[7,1,3]]$ 符号または $[[17,1,5]]$ 符号の論理状態へ符号化します。その後、すべての物理量子ビットにデポラライジングエラーを作用させ、シンドローム測定と補正操作を行います。
一方、distance=1 の場合には、符号化を行わず、1 つの物理量子ビットに直接デポラライジングエラーを作用させています。これを誤り訂正を行わない場合の基準として用います。
物理エラー率と論理エラー率
物理エラー率を変化させながら、論理エラー率を計算します。ここでは、符号化しない物理量子ビット、$[[7,1,3]]$ 符号、$[[17,1,5]]$ 符号の 3 つを比較します。
n_shots = 1_000_000
backend_sim = AerSimulator(
method="stabilizer"
)
error_rate_phys_qubit = []
error_rate_with_7_1_3 = []
error_rate_with_17_1_5 = []
probs = np.logspace(-2.5, -0.5, 7)
circ_input = QuantumCircuit(1, name="data")
for p in probs:
error_rate_phys_qubit.append(calc_error_rate(circ_input, p, 1)) # distance=1は物理量子ビットの誤り率
error_rate_with_7_1_3.append(calc_error_rate(circ_input, p, 3)) # [[7,1,3]] 符号
error_rate_with_17_1_5.append(calc_error_rate(circ_input, p, 5)) # [[17,1,5]] 符号
得られた結果をプロットします。
x_arr = probs
plt.plot(x_arr, error_rate_phys_qubit, 'o-', label='physical qubit')
plt.plot(x_arr, error_rate_with_7_1_3, '^-', label='[[7,1,3]] code')
plt.plot(x_arr, error_rate_with_17_1_5, 's-', label='[[17,1,5]] code')
plt.loglog(probs, 2*probs/3, 'k--', alpha=0.5, label=r'$\frac{2}{3}p$')
plt.loglog(probs, 9 * probs ** 2, 'k--', alpha=0.5, label=r'$9p^2$')
plt.loglog(probs, 200 * probs ** 3, 'k--', alpha=0.5, label=r'$200p^3$')
plt.loglog()
plt.grid(True, which="both", ls="--")
plt.xlabel("physical error rate")
plt.ylabel("logical error rate")
plt.legend()
plt.show()
ショット数を大きくすると統計誤差は小さくなりますが、シミュレーション時間は増加します。特に $[[17,1,5]]$ 符号では、測定と条件付き補正を含む回路が大きくなるため、必要に応じて n_shots を調整してください。
デポラライジングエラーにおける論理エラー率の見積もり
最後に、デポラライジングエラーのもとで論理エラー率がどのようにスケールするかを簡単に考察します。
デポラライジングエラーでは、各物理量子ビットに対して、確率 $p/3$ ずつで $X$、$Y$、$Z$ エラーが生じます。
今回の実装では、初期状態として $|0_L\rangle$ を準備し、測定結果のパリティが奇数になる場合を論理エラーとして考えていました。これは、論理ビット反転が起きたかどうかを見ています。$Z$ 基底での測定を考えると、$X$ エラーはビット値を反転させます。また、$Y$ エラーは $Y=iXZ$ と書けるため、ビット反転成分を含みます。一方、$Z$ エラーは位相を変えるだけなので、$Z$ 基底で見たビット値は反転しません。
したがって、デポラライジングエラーのうち、今回の論理ビット反転として観測される成分は $X$ と $Y$ です。そのため、有効なビット反転確率は
$$
p_{\mathrm{flip}}=\frac{2p}{3}
\tag{6}
$$
となります。
次に、符号距離との関係を見ます。符号距離 $d$ の符号では、式 (4) のように、$t=\left\lfloor (d-1)/2\right\rfloor$ 個までの物理量子ビットエラーを訂正できます。したがって、低い物理エラー率の領域では、論理エラーは主に $t+1$ 個の物理エラーによって生じると考えられます。
物理量子ビット数を $n$ とすると、$t+1$ 個の量子ビットにビット反転成分を含むエラーが生じる確率は、おおよそ
$$
p_L
\simeq
\binom{n}{t+1}
p_{\mathrm{flip}}^{t+1}
\tag{7}
$$
となります。ここでは、$p$ が十分小さいとして、$t+2$ 個以上のエラーが同時に生じる項は無視しています。
具体的に、$[[7,1,3]]$ 符号と $[[17,1,5]]$ 符号について見てみます。
$[[7,1,3]]$ 符号は 1 量子ビットエラーまで訂正できるので、論理エラーは 2 量子ビットエラーが最も寄与していると考えられます。$n=7$ を式 (7) に代入すると、だいたい
$$
p_L
\simeq
9p^2
\tag{8}
$$
となります。
一方、$[[17,1,5]]$ 符号では、2 量子ビットエラーまで訂正できます。したがって、論理エラーは 3 量子ビットエラーが最も寄与していると考えられます。$n=17$ を式 (7) に代入すると、だいたい
$$
p_L
\simeq
200p^3
\tag{9}
$$
となります。つまり、低エラー率領域では、$[[7,1,3]]$ 符号の論理エラー率は $p^2$ に比例し、$[[17,1,5]]$ 符号の論理エラー率は $p^3$ に比例します。
一般に、符号距離 $d$ の符号では、論理エラーを起こすには少なくとも $(d+1)/2$ 個の物理エラーが必要になります。そのため、低エラー率領域では
$$
p_L=O\left(p^{(d+1)/2}\right)
\tag{10}
$$
というスケーリングが期待されます。
エラー率のプロットでも、スケーリングの見積もりと矛盾しない振る舞いが確認できます。物理量子ビットのみの場合は、論理エラー率が物理エラー率 $p$ にほぼ比例して増加しています。一方、$[[7,1,3]]$ 符号や $[[17,1,5]]$ 符号では、おおよそ $p=0.1$ より小さい領域で、符号化によって論理エラー率が抑えられています。これは、物理エラー率が十分小さい領域では、誤りが訂正可能な範囲に収まりやすく、符号化による利得が得られることを表しています。一方、物理エラー率が大きくなると、複数の物理量子ビットに同時にエラーが生じやすくなり、訂正可能な範囲を超えるエラーの寄与が無視できなくなります。そのため、この領域では符号化によってかえって論理エラー率が大きくなってしまうことが見て取れます。
実際の量子コンピュータ上で誤り訂正を有効に働かせるためには、単に符号化すればよいのではなく、物理エラー率を十分に下げたうえで、符号距離を大きくしていくことが重要です。
まとめ
今回は、$[[17,1,5]]$ 符号に対して、シンドローム測定、ルックアップテーブルデコーダー、補正操作を実装し、デポラライジングエラーのもとで論理エラー率を評価しました。物理量子ビット、$[[7,1,3]]$ 符号、$[[17,1,5]]$ 符号の比較から、低エラー率領域では符号距離を大きくするほど論理エラー率が強く抑えられることを確認できました。
これまでの記事で、カラー符号の基本的な構造から始めて、トランスバーサルな Clifford ゲート、$[[17,1,5]]$ 符号のエンコード、そして今回の誤り訂正までを見てきました。カラー符号は、表面符号とは異なる構造を持ちながら、Clifford ゲートをトランスバーサルに実装できるという特徴があり、量子誤り訂正符号の中でも魅力的な選択肢の一つです。さらに近年では、実際の量子コンピュータ上での実装も進んでいます。超伝導量子ビットや中性原子を用いた実験でもカラー符号の実装が報告されており、量子誤り訂正符号の有望な選択肢として研究が進んでいます[参考5,6]。
一方で、本シリーズで扱った内容は、カラー符号の基礎的な部分に限られます。実機上で誤り耐性を考えるためには、シンドローム測定やゲート、読み出しの誤り、より実用的なデコーダーなども含めて考える必要があります。
本シリーズが、カラー符号の構造とその魅力、そしてカラー符号による量子誤り訂正の考え方を理解するための参考になれば幸いです。
謝辞
本記事の作成にあたり、NEDO「ポスト5G情報通信システム基盤強化研究開発事業」における、株式会社 Quemix によるハイエンド量子人材育成プログラムの講義内容を一部参考にしました。
なお、本記事執筆時点では、専門人材育成プログラム第2期の受講者募集も行われています。
参考文献
- Bombin, H. and Martin-Delgado, M. A.: Topological Quantum Distillation, Phys. Rev. Lett., Vol. 97, No. 18, 180501 (2006)
- Delfosse, N.: Decoding color codes by projection onto surface codes, Phys. Rev. A, Vol. 89, No. 1, 012317 (2014)
- Takada, Y., Takeuchi, Y. and Fujii, K.: Ising model formulation for highly accurate topological color codes decoding, Phys. Rev. Research, Vol. 6, No. 1, 013092 (2024)
- Baireuther, P., Caio, M. D., Criger, B., Beenakker, C. W. J. and O’Brien, T. E.: Neural network decoder for topological color codes with circuit level noise, New J. Phys., Vol. 21, No. 1, 013003 (2019)
- Lacroix, N., Bourassa, A., Heras, F. J. H. et al.: Scaling and logic in the colour code on a superconducting quantum processor, Nature, Vol. 645, pp. 614–619 (2025)
- Sales Rodriguez, P., Robinson, J. M., Jepsen, P. N. et al.: Experimental demonstration of logical magic state distillation, Nature, Vol. 645, pp. 620–625 (2025)


