4. 模拟环境搭建与工具链
在进入纠错阈值模拟之前,先把“算得动、跑得对、结果可复现”的环境搭好,是后续所有实验的前提。本节围绕两条主线展开:一是以 NumPy、SciPy 为核心的 Python 科学计算栈,负责矩阵运算、稀疏线性代数与随机数控制;二是以 Stim、PyMatching 为代表的纠错模拟库,负责稳定子电路的采样与解码。理解它们各自的分工,比记住安装命令更重要。
一、工具链的分工与数据流
纠错阈值模拟的典型链路是:先构造一个带噪声的稳定子电路(例如旋转表面码的记忆实验),再由采样器生成大量含测量错误的探测事件(detector events),然后由解码器根据探测事件推断最可能的错误链,最后统计逻辑错误率。Stim 承担“电路构造 + 快速采样 + 探测事件生成”,PyMatching 承担“基于匹配图的最小权完美匹配解码”,NumPy/SciPy 则承担参数扫描、稀疏矩阵组装、统计分析与绘图。下图给出这条链路的对象与数据流向。
二、科学计算栈的安装与版本约束
建议使用独立虚拟环境,避免与系统 Python 冲突。NumPy 与 SciPy 是 Stim、PyMatching 的底层依赖,版本过旧可能导致 wheel 不匹配。一个可用的安装流程如下(示例命令,实际版本以官方发布为准):
python -m venv qec_env
source qec_env/bin/activate # Windows: qec_env\Scripts\activate
python -m pip install --upgrade pip
pip install numpy scipy
pip install stim pymatching
python -c "import stim, pymatching, numpy, scipy; print(stim.__version__, pymatching.__version__)"
最后一行用于验证导入是否成功并打印版本号。若在无网络或受限环境,可提前下载对应平台的 wheel 文件离线安装。NumPy 的随机数生成器建议显式指定种子(如 np.random.default_rng(2024)),以保证参数扫描结果可复现;Stim 采样器本身也支持传入 seed 参数。
三、用 Stim 构造电路并采样探测事件
Stim 提供内置的电路生成器,可直接生成旋转表面码的记忆实验电路。下面以距离 d=3、物理错误率 p=0.001 为例,构造电路并采样 10000 次:
import stim
circuit = stim.Circuit.generated(
"surface_code:rotated_memory_z",
distance=3,
rounds=3,
after_clifford_depolarization=0.001,
before_measure_flip_probability=0.001,
after_reset_flip_probability=0.001,
)
sampler = circuit.compile_detector_sampler(seed=2024)
dets, obs = sampler.sample(shots=10000, separate_observables=True)
print(dets.shape, obs.shape) # (10000, 探测器数), (10000, 1)
这里 dets 是布尔型探测事件矩阵,每一行对应一次采样,列对应一个探测器是否被触发;obs 是真实逻辑观测值。三个噪声参数分别刻画 Clifford 门后的去极化噪声、测量前翻转与复位后翻转,是阈值模拟中最常用的噪声模型组合。注意 rounds 与 distance 共同决定探测器数量,d 增大时探测器数近似按 d² 增长,采样成本随之上升。
四、用 PyMatching 解码并统计逻辑错误率
PyMatching 可直接从 Stim 电路提取解码图,无需手工构造匹配图:
import pymatching
import numpy as np
matching = pymatching.Matching.from_detector_error_model(
circuit.detector_error_model(decompose_errors=True)
)
predictions = matching.decode_batch(dets)
logical_errors = np.sum(np.any(predictions != obs, axis=1))
print("逻辑错误率:", logical_errors / 10000)
decode_batch 返回预测的逻辑翻转,与真实 obs 逐行比较,任一位不一致即记为一次逻辑错误。这个“错误数 / shots”就是该参数点下的逻辑错误率估计。由于是统计量,shots 较小时波动明显,通常每个参数点取 10⁴~10⁶ 次采样,并在报告中给出置信区间或标准误(如 sqrt(p(1-p)/N))。
五、参数扫描的组织方式
把上述流程封装成函数,用 NumPy 生成参数网格,即可得到阈值曲线所需的数据。示例结构如下:
def logical_error_rate(d, p, shots=20000, seed=2024):
c = stim.Circuit.generated(
"surface_code:rotated_memory_z",
distance=d, rounds=d,
after_clifford_depolarization=p,
before_measure_flip_probability=p,
after_reset_flip_probability=p,
)
dets, obs = c.compile_detector_sampler(seed=seed).sample(
shots=shots, separate_observables=True)
m = pymatching.Matching.from_detector_error_model(
c.detector_error_model(decompose_errors=True))
pred = m.decode_batch(dets)
return float(np.mean(np.any(pred != obs, axis=1)))
for d in [3, 5, 7]:
for p in np.logspace(-3.5, -2, 8):
print(d, p, logical_error_rate(d, p))
运行后把 (p, 错误率) 按 d 分组绘制在半对数坐标上,不同 d 的曲线会在阈值附近交叉:p 低于阈值时,d 越大错误率越低;p 高于阈值时,d 越大错误率反而越高。这正是后续章节判读阈值位置的依据。环境搭建阶段不必追求大规模数据,先用小 d、少 shots 跑通全链路,确认形状、导入与统计逻辑无误,再扩展规模。
from_detector_error_model 报错,应升级到相互兼容的版本;二是忘记 separate_observables=True,导致观测值与探测事件混在同一数组;三是 shots 过小使低错误率点全为 0,误判为“无错误”。完成本节后,你应当拥有一条可复现的最小链路:虚拟环境中装好 NumPy、SciPy、Stim、PyMatching,能用内置生成器构造带噪声的表面码电路,采样探测事件,用匹配解码得到逻辑错误率,并以函数形式组织参数扫描。这条链路是后续阈值模拟与物理错误率扫描实验的直接基础。