3. 仿真环境搭建与工具链选型

量子纠错循环仿真的第一步不是写纠错算法,而是把“谁来算、算什么、怎么算得快”这三件事定下来。量子纠错的核心对象是稳定子测量与错误传播,仿真框架需要同时处理两类任务:一是生成带噪声的测量结果(采样),二是根据测量结果推断最可能的错误(解码)。Stim、PyMatching、qiskit-qec 分别覆盖了这两类任务的不同环节,理解它们的分工比记住安装命令更重要。

三个框架的定位与分工

Stim 是一个专注于 Clifford 电路采样的高性能模拟器。它不直接做通用量子态演化,而是利用稳定子形式化,把“测量结果”以极快速度批量生成。对于重复码、表面码这类以稳定子测量为主的纠错电路,Stim 的采样速度通常比通用态矢量模拟器快几个数量级。PyMatching 则是一个最小权完美匹配(MWPM)解码器,输入是探测事件(detector events)和它们之间的图结构,输出是最可能的错误链。qiskit-qec 是 Qiskit 生态中的纠错模块,提供码的定义、综合、解码接口,适合与 Qiskit 的电路层和噪声模型配合使用,但在大规模采样上通常不如 Stim 直接。

一个实用的组合是:用 Stim 生成带噪声的探测事件,用 PyMatching 做解码,再用统计脚本计算逻辑错误率。qiskit-qec 更适合教学演示和与 Qiskit 后端联调。下面这张图展示了三者在一次重复码循环仿真中的信号链关系。

重复码循环仿真信号链:噪声采样 → 探测事件 → 解码 → 逻辑错误统计 Stim Clifford 电路采样 输出:测量比特串 探测事件提取 相邻稳定子测量异或 输出:detector events PyMatching MWPM 解码 输出:修正链 统计 逻辑错误率 p_L vs p 重复码 d=3 循环结构(每轮 2 个稳定子测量) 数据 q0 数据 q1 数据 q2 S1 = Z0Z1 S2 = Z1Z2 每轮测量 S1、S2,与上一轮比较得到探测事件 若某轮 S1 翻转,说明 q0 或 q1 发生错误 PyMatching 根据事件图找最短错误链并修正 统计多轮后逻辑比特是否出错,得到 p_L

安装与最小配置

推荐使用 Python 3.10 或 3.11 的虚拟环境,避免与系统包冲突。三个框架的安装命令如下:

python -m venv qec-env
source qec-env/bin/activate        # Windows 用 qec-env\Scripts\activate
pip install stim pymatching
pip install qiskit qiskit-qec      # qiskit-qec 视版本可能需从源码或指定索引安装

安装完成后,用三行代码验证 Stim 是否可用,并确认 PyMatching 能正常导入:

import stim, pymatching
c = stim.Circuit.generated("repetition_code:memory", distance=3, rounds=3,
                           before_round_data_depolarization=0.05)
print(c.num_detectors, c.num_observables)
m = pymatching.Matching.from_detector_error_model(c.detector_error_model())
print(m.num_detectors)

这里 before_round_data_depolarization=0.05 表示每轮数据比特上以 5% 概率发生去极化错误,是教学示例参数,不是实测值。运行后应看到探测器数量和观测量数量,说明电路与解码图已正确构建。

完成第一个重复码循环仿真

下面用 Stim 采样、PyMatching 解码,完成一次完整的重复码记忆实验。重复码 d=3 使用 3 个数据比特和 2 个稳定子测量,逻辑观测量取 q0 的 Z 值。噪声模型采用每轮数据去极化错误,错误率 p 从 0.01 到 0.10 扫描。

import stim, pymatching, numpy as np

def run_rep_code(d=3, rounds=3, p=0.05, shots=20000):
    circuit = stim.Circuit.generated(
        "repetition_code:memory",
        distance=d, rounds=rounds,
        before_round_data_depolarization=p,
        before_measure_flip_probability=p,
        after_reset_flip_probability=p)
    dem = circuit.detector_error_model(decompose_errors=True)
    matching = pymatching.Matching.from_detector_error_model(dem)
    sampler = circuit.compile_detector_sampler()
    dets, obs = sampler.sample(shots, separate_observables=True)
    pred = matching.decode_batch(dets)
    logical_errors = np.sum(np.any(pred != obs, axis=1))
    return logical_errors / shots

for p in [0.01, 0.03, 0.05, 0.08, 0.10]:
    print(f"p={p:.2f}  p_L={run_rep_code(p=p):.4f}")

这段代码的关键点有三处。第一,detector_error_model(decompose_errors=True) 把电路级噪声转换成解码器可用的图模型,分解错误是为了让 MWPM 能处理超边。第二,compile_detector_sampler() 返回的采样器直接输出探测事件矩阵,形状为 (shots, num_detectors),不需要手动从测量比特串做异或。第三,decode_batch 批量解码,返回预测的观测量翻转,与真实 obs 比较即可统计逻辑错误率。

一次典型运行会得到类似下面的输出(示例数据,非实测):

p=0.01  p_L=0.0021
p=0.03  p_L=0.0087
p=0.05  p_L=0.0196
p=0.08  p_L=0.0412
p=0.10  p_L=0.0635

判断仿真是否正确的依据是:随着物理错误率 p 增大,逻辑错误率 p_L 应单调上升;在 p 较小时,p_L 应显著低于 p,说明纠错在起作用。如果 p_L 与 p 接近甚至更高,通常说明解码图构建有误、噪声模型过强,或者观测量与解码输出没有对齐。

性能对比与选型建议

在相同 d=3、rounds=3、shots=20000 的条件下,Stim + PyMatching 的采样与解码通常在秒级完成。qiskit-qec 若走 Qiskit 的 Aer 模拟器,采样速度会明显慢于 Stim,因为后者不模拟完整量子态,只追踪稳定子。下表给出的是量级上的定性对比,不是精确基准。

框架主要任务采样速度解码能力适用场景
StimClifford 电路采样极快不内置解码大规模重复码/表面码采样
PyMatchingMWPM 解码—强与 Stim 配合做解码
qiskit-qec码定义与综合中等接口层教学、与 Qiskit 后端联调

选型建议很直接:如果目标是快速扫描参数、评估逻辑错误率曲线,用 Stim + PyMatching;如果目标是演示码的结构、与 Qiskit 电路层交互,用 qiskit-qec;如果两者都需要,可以用 qiskit-qec 定义码,再导出为 Stim 电路做加速采样。不要一开始就追求最大距离或最复杂噪声模型,先用 d=3、rounds=3 跑通闭环,确认探测事件数量、解码输出维度和逻辑错误率趋势都符合预期,再逐步增加距离和轮数。

第一次跑通后,建议固定随机种子或增加 shots,观察 p_L 的统计波动。shots=20000 时,p_L 在 0.01 附近的相对误差约 7%,若需要更平滑的曲线可提高到 100000 以上。