3. 三量子比特重复码的纠错解码与恢复操作仿真

解码器设计:从症状到恢复操作的映射

三量子比特重复码的编码空间由 |000⟩ 与 |111⟩ 张成,单比特翻转错误会把逻辑态推出编码空间。症状提取电路给出两个症状比特 s₀、s₁,其取值与错误位置的对应关系是查表式解码器的全部依据。本例在已获得症状结果的前提下,实现解码与恢复,并用仿真验证恢复后态重新落回编码空间。

约定:数据比特按 q₀、q₁、q₂ 排列,症状比特定义为 s₀ = q₀ ⊕ q₁、s₁ = q₁ ⊕ q₂(均为模 2 加)。由此得到无歧义的解码表:

s₀s₁推断错误位置恢复操作
00无错误I(不操作)
10q₀ 翻转X(q₀)
11q₁ 翻转X(q₁)
01q₂ 翻转X(q₂)

注意 (s₀,s₁)=(1,0) 与 (0,1) 分别指向两端比特,而 (1,1) 指向中间比特——这是重复码症状映射的关键特征,也是查表实现中最容易写反的地方。

症状比特 → 错误位置 → 恢复操作(查表解码) 症状 (s₀,s₁) (0, 0) (1, 0) (1, 1) (0, 1) 错误位置(q₀ q₁ q₂) q₀ q₁ q₂ 无翻转 q₀ q₁ q₂ q₀ 翻转 q₀ q₁ q₂ q₁ 翻转 q₀ q₁ q₂ q₂ 翻转 恢复操作 I(不操作) X(q₀) X(q₁) X(q₂) 判据:恢复后重新测量症状应为 (0,0),且态与编码空间投影的保真度 ≈ 1; 若恢复后症状仍非零,说明查表映射写错或症状比特顺序颠倒。

查表解码器的实现

下面用 Qiskit 实现一个最小可运行流程:编码 → 注入指定位置的单比特翻转 → 提取症状 → 查表解码 → 施加恢复 → 验证。解码器本身是一个纯字典查表函数,与量子线路解耦,便于单独测试。

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile
from qiskit_aer import AerSimulator
from qiskit.quantum_info import Statevector, state_fidelity

# 症状 -> 恢复比特索引;None 表示无错误
DECODE_TABLE = {
    (0, 0): None,
    (1, 0): 0,
    (1, 1): 1,
    (0, 1): 2,
}

def build_syndrome_circuit(error_qubit=None, logical_one=False):
    """编码 + 可选错误注入 + 症状提取,返回线路与症状经典比特位置。"""
    q = QuantumRegister(3, 'q')
    s = ClassicalRegister(2, 's')
    qc = QuantumCircuit(q, s)

    # 编码:|000> -> (|000>+|111>)/sqrt2
    qc.h(0)
    qc.cx(0, 1)
    qc.cx(0, 2)

    # 注入单比特翻转错误
    if error_qubit is not None:
        qc.x(error_qubit)

    # 症状提取:s0 = q0 xor q1, s1 = q1 xor q2
    qc.cx(0, 1)
    qc.cx(1, 2)
    qc.measure(1, s[0])
    qc.measure(2, s[1])
    return qc

def decode_and_recover(syndrome_bits):
    """查表解码:输入 (s0, s1),返回需施加 X 的比特索引或 None。"""
    return DECODE_TABLE[tuple(syndrome_bits)]

def run_case(error_qubit, shots=2000):
    qc = build_syndrome_circuit(error_qubit=error_qubit)
    sim = AerSimulator()
    result = sim.run(transpile(qc, sim), shots=shots).result()
    counts = result.get_counts()
    # 取出现次数最多的症状组合
    top = max(counts, key=counts.get)
    s0, s1 = int(top[1]), int(top[0])  # 注意比特串顺序
    target = decode_and_recover((s0, s1))
    return (s0, s1), target, counts

for eq in [None, 0, 1, 2]:
    syn, target, counts = run_case(eq)
    print(f"注入错误 q{eq} -> 症状 {syn}, 恢复目标 {target}, 计数 {counts}")

运行后应观察到:注入 q₀ 时症状为 (1,0)、恢复目标 0;注入 q₁ 时症状为 (1,1)、恢复目标 1;注入 q₂ 时症状为 (0,1)、恢复目标 2;无错误时症状 (0,0)、不操作。若某一行症状与预期不符,优先检查测量比特与经典比特的对应顺序——Qiskit 的比特串是高位在左,s[0] 对应字符串末位。

恢复后是否回到编码空间

症状正确只说明错误被定位,还需验证恢复后态确实回到编码空间。做法是:在恢复操作后不再测量,直接用 Statevector 计算与理想编码态的保真度,并检查两个稳定子 Z₀Z₁、Z₁Z₂ 的期望值是否都为 +1。

def recover_statevector(error_qubit):
    qc = QuantumCircuit(3)
    qc.h(0); qc.cx(0, 1); qc.cx(0, 2)      # 编码
    if error_qubit is not None:
        qc.x(error_qubit)                   # 注入错误
    # 症状提取(不测量,用辅助逻辑等价地推断)
    # 这里直接复用查表结果,模拟解码器输出
    syn = {None: (0,0), 0: (1,0), 1: (1,1), 2: (0,1)}[error_qubit]
    target = DECODE_TABLE[syn]
    if target is not None:
        qc.x(target)                        # 恢复
    return Statevector.from_instruction(qc)

ideal = Statevector.from_instruction(
    QuantumCircuit(3).compose(
        QuantumCircuit(3).compose(
            QuantumCircuit(3).h(0), [0]
        )
    )
)
# 更稳妥:直接构造理想编码态
from qiskit.quantum_info import Statevector as SV
import numpy as np
bell = np.zeros(8, dtype=complex)
bell[0] = 1/np.sqrt(2)   # |000>
bell[7] = 1/np.sqrt(2)   # |111>
ideal = SV(bell)

for eq in [None, 0, 1, 2]:
    psi = recover_statevector(eq)
    fid = state_fidelity(psi, ideal)
    z01 = psi.expectation_value(__import__('qiskit.quantum_info', fromlist=['Pauli']).Pauli('ZZI'))
    z12 = psi.expectation_value(__import__('qiskit.quantum_info', fromlist=['Pauli']).Pauli('IZZ'))
    print(f"错误 q{eq}: 保真度={fid:.4f}, <Z0Z1>={z01.real:.3f}, <Z1Z2>={z12.real:.3f}")

预期输出中保真度均为 1.0000,两个稳定子期望值均为 +1.000。这构成“恢复成功”的判据:保真度≈1 且稳定子全为 +1。若保真度为 1 但稳定子异常,说明态虽相同但相位约定有误;若保真度明显小于 1,说明恢复比特选错。

不同错误位置下的纠错成功率统计

把上述流程封装成批量统计:对每个错误位置重复采样,统计“症状正确识别 + 恢复后保真度≈1”的比例。由于本例是无噪声的确定性注入,成功率应全部为 100%;这一基线值正是后续引入比特翻转噪声后对比的参照。

def correction_success(error_qubit, shots=1000, tol=1e-6):
    syn, target, _ = run_case(error_qubit, shots=shots)
    expected = {None: (0,0), 0: (1,0), 1: (1,1), 2: (0,1)}[error_qubit]
    syn_ok = (syn == expected)
    psi = recover_statevector(error_qubit)
    fid_ok = abs(state_fidelity(psi, ideal) - 1.0) < tol
    return syn_ok and fid_ok

stats = {}
for eq in [None, 0, 1, 2]:
    ok = sum(correction_success(eq) for _ in range(20)) / 20
    stats[eq] = ok
    print(f"错误位置 q{eq}: 纠错成功率 = {ok:.0%}")

统计结果应显示四个位置的成功率均为 100%。若某个位置低于 100%,按以下顺序排查:症状比特顺序是否颠倒、DECODE_TABLE 中 (1,0) 与 (0,1) 是否写反、恢复操作是否作用在正确的比特索引上。

查表解码器的可测试性来自“解码逻辑与线路分离”。把 DECODE_TABLE 单独拿出来做单元测试(输入 4 种症状,断言输出比特索引),比在完整线路里调试快得多。
症状比特的经典寄存器顺序容易出错:measure(1, s[0]) 与 measure(2, s[1]) 决定了 s[0] 是 q₀⊕q₁ 的结果。读取 counts 时比特串高位在左,必须按 top[1]、top[0] 的顺序还原,否则 (1,0) 与 (0,1) 会互换,导致恢复作用到错误的比特上。