3. 三量子比特重复码的纠错解码与恢复操作仿真
解码器设计:从症状到恢复操作的映射
三量子比特重复码的编码空间由 |000⟩ 与 |111⟩ 张成,单比特翻转错误会把逻辑态推出编码空间。症状提取电路给出两个症状比特 s₀、s₁,其取值与错误位置的对应关系是查表式解码器的全部依据。本例在已获得症状结果的前提下,实现解码与恢复,并用仿真验证恢复后态重新落回编码空间。
约定:数据比特按 q₀、q₁、q₂ 排列,症状比特定义为 s₀ = q₀ ⊕ q₁、s₁ = q₁ ⊕ q₂(均为模 2 加)。由此得到无歧义的解码表:
| s₀ | s₁ | 推断错误位置 | 恢复操作 |
|---|---|---|---|
| 0 | 0 | 无错误 | I(不操作) |
| 1 | 0 | q₀ 翻转 | X(q₀) |
| 1 | 1 | q₁ 翻转 | X(q₁) |
| 0 | 1 | q₂ 翻转 | X(q₂) |
注意 (s₀,s₁)=(1,0) 与 (0,1) 分别指向两端比特,而 (1,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) 会互换,导致恢复作用到错误的比特上。