表面码模拟:Stim + PyMatching 教程 | QuantumComputingCourses.com
Stim 是 Google 开发的高性能稳定子电路模拟器,专为量子纠错研究而构建。与通用模拟器不同,Stim 将电路表示为稳定子表(stabilizer tableau),并能在 CPU 上每秒模拟数百万次运行(shots),从而能够实际收集所需的统计数据,以测量现实码距下的逻辑错误率。
PyMatching 是表面码的标准最小权重完美匹配(MWPM)解码器。Stim + PyMatching 共同构成了容错量子计算研究的标准模拟栈。
安装
python
pip install stim pymatching numpy matplotlib
什么是稳定子电路?
在编写代码之前,先快速回顾一下 Stim 为何如此快速。表面码在泡利稳定子上操作:即泡利算符 I、X、Y、Z 的张量积。关键性质在于,纠错循环中的所有操作(CNOT 门、Hadamard 门,以及 X 或 Z 基底的测量)都保持稳定子结构。Stim 直接追踪稳定子,而非态矢量,因此内存占用与量子比特数量呈线性关系,而非指数关系。
对于一个码距为 d 的表面码,其物理量子比特数量为 d² + (d-1)²:
- 态矢量模拟器:需要 2ⁿ 个复振幅
- Stim:需要 n + n² 比特用于稳定子表
当 d=3(17 个量子比特)时:态矢量需要 2¹⁷ = 131,072 个振幅,而 Stim 仅需约 289 比特。
在 Stim 中定义表面码
Stim 使用领域特定语言(DSL)来定义电路。以下是生成一个码距为 3 的表面码电路的方法:
python
import stim
# Stim 可以程序化地生成标准编码
circuit = stim.Circuit.generated(
code_task="surface_code:rotated_memory_z",
distance=3,
rounds=3, # 纠错循环次数
after_clifford_depolarization=0.001, # 门错误率
before_round_data_depolarization=0.001,
before_measure_flip_probability=0.001,
)
print(circuit) # 打印完整的 Stim 电路
print(f"Qubits: {circuit.num_qubits}")
print(f"Measurements: {circuit.num_measurements}")
surface_code:rotated_memory_z 任务将单个逻辑量子比特编码在旋转表面码中,并运行综合征测量循环,最终追踪逻辑 Z 可观测量是否发生翻转。
可用的编码任务:
surface_code:rotated_memory_z(Z 基底逻辑量子比特)surface_code:rotated_memory_x(X 基底逻辑量子比特)repetition_code:memory(更简单的 1D 编码,用于测试)
收集综合征数据
Stim 的探测器错误模型(DEM, Detector Error Model)是连接解码器的关键接口。探测器是一组测量值,在无错误时它们的异或(XOR)应为 0。当探测器触发(XOR = 1)时,表示某处发生了错误。
python
import stim
import numpy as np
# 生成码距 d=3 的表面码,3 个循环,错误率 p=1%
circuit = stim.Circuit.generated(
"surface_code:rotated_memory_z",
distance=3,
rounds=3,
after_clifford_depolarization=0.01,
before_round_data_depolarization=0.01,
before_measure_flip_probability=0.01,
)
# 编译快速采样器
sampler = circuit.compile_detector_sampler()
# 采样 100,000 次运行
# detection_events: (n_shots, n_detectors) 布尔数组
# observable_flips: (n_shots, n_observables) 布尔数组(即"答案")
detection_events, observable_flips = sampler.sample(
shots=100_000,
separate_observables=True,
)
print(f"Detection events shape: {detection_events.shape}")
print(f"Observable flips shape: {observable_flips.shape}")
print(f"Fraction of shots with any detection: {detection_events.any(axis=1).mean():.3f}")
print(f"Raw logical error rate (no decoding): {observable_flips[:, 0].mean():.4f}")
使用 PyMatching 解码
检测事件告诉我们发生了 某些错误,但并未告诉我们在哪里。解码器的任务是根据综合征模式推断最可能的纠错方案。最小权重完美匹配(MWPM)寻找与观测综合征一致的最小权重错误集合。
python
import pymatching
# 从电路构建探测器错误模型
# 这编码了 MWPM 解码器所需的图结构
dem = circuit.detector_error_model(decompose_errors=True)
matcher = pymatching.Matching.from_detector_error_model(dem)
# 一次性解码所有运行(向量化,速度快)
predictions = matcher.decode_batch(detection_events)
# 将预测结果与实际可观测量翻转进行比较
# 当解码器的预测与实际翻转不一致时,即发生逻辑错误
num_errors = np.sum(predictions != observable_flips)
logical_error_rate = num_errors / (len(predictions) * observable_flips.shape[1])
print(f"Logical error rate (with MWPM decoding): {logical_error_rate:.5f}")
对于码距为 3 的编码,在 1% 物理错误率下,使用 MWPM 解码后的逻辑错误率通常在 0.1-0.2% 左右,约为物理错误率的 5-10 倍低,这证实了该编码处于阈值以下。
测量编码阈值
阈值是指:当物理错误率低于该值时,增加编码码距可以降低逻辑错误率。在阈值以下,码距 d+2 的逻辑错误率低于码距 d。在阈值以上,则相反。
python
import stim
import pymatching
import numpy as np
import matplotlib.pyplot as plt
distances = [3, 5, 7]
error_rates = np.logspace(-3, -1.3, 12) # 0.001 到 0.05
shots = 50_000
results = {}
for d in distances:
logical_errors = []
for p in error_rates:
circuit = stim.Circuit.generated(
"surface_code:rotated_memory_z",
distance=d,
rounds=d, # 循环次数 = 码距是标准做法
after_clifford_depolarization=p,
before_round_data_depolarization=p,
before_measure_flip_probability=p,
)
dem = circuit.detector_error_model(decompose_errors=True)
matcher = pymatching.Matching.from_detector_model(dem)
sampler = circuit.compile_detector_sampler()
detection_events, observable_flips = sampler.sample(
shots=shots, separate_observables=True
)
predictions = matcher.decode_batch(detection_events)
logical_err = np.sum(predictions != observable_flips) / (shots * observable_flips.shape[1])
logical_errors.append(logical_err)
print(f"d={d}, p={p:.4f}, logical_err={logical_err:.5f}")
results[d] = logical_errors
# 绘制阈值曲线
plt.figure(figsize=(8, 5))
for d in distances:
plt.semilogy(error_rates * 100, results[d], 'o-', label=f'd={d}')
plt.xlabel("Physical error rate (%)")
plt.ylabel("Logical error rate (log scale)")
plt.title("Surface Code Threshold")
plt.legend()
plt.grid(True, alpha=0.3)
plt.axvline(x=1.0, color='gray', linestyle='--', alpha=0.5, label='~1% threshold')
plt.tight_layout()
plt.savefig("surface_code_threshold.png", dpi=150)
plt.show()
# 这些曲线在 p ~ 0.9-1.1% 附近交叉:这就是阈值
# 阈值以下:更高的 d → 更低的逻辑错误率(好!)
# 阈值以上:更高的 d → 更高的逻辑错误率(坏)
这些曲线的交叉点即为阈值。对于退极化噪声模型,表面码的阈值约为 1%。这就是为何实现 99% 以上的双量子比特门保真度,是面向容错操作硬件的关键基准。
逻辑错误率与码距的关系(阈值以下)
在阈值以下,逻辑错误率随码距指数下降。一个有用的近似公式:
logical_error_rate ≈ A * (p / p_threshold)^((d+1)/2)
我们来验证这一标度关系:
python
# 在 p = 0.005(阈值的一半)处拟合亚阈值标度
p_fixed = 0.005
distances_fit = [3, 5, 7, 9, 11]
logical_errs_fit = []
for d in distances_fit:
circuit = stim.Circuit.generated(
"surface_code:rotated_memory_z",
distance=d,
rounds=d,
after_clifford_depolarization=p_fixed,
before_round_data_depolarization=p_fixed,
before_measure_flip_probability=p_fixed,
)
dem = circuit.detector_error_model(decompose_errors=True)
matcher = pymatching.Matching.from_detector_error_model(dem)
sampler = circuit.compile_detector_sampler()
detection_events, observable_flips = sampler.sample(100_000, separate_observables=True)
predictions = matcher.decode_batch(detection_events)
logical_errs_fit.append(np.sum(predictions != observable_flips) / (100_000 * observable_flips.shape[1]))
# 拟合 log(p_L) 与 d 的关系
log_errs = np.log(logical_errs_fit)
coeffs = np.polyfit(distances_fit, log_errs, 1)
print(f"Exponential suppression factor per 2 distance steps: {np.exp(2 * coeffs[0]):.3f}")
# 预期:大致为 (0.005/0.01)^1 = 0.5,每 (d+1)/2 步
加速技巧
用于生产环境:
- 按内存容量分批采样:
sampler.sample(shots=1_000_000)在 8GB 内存下对 d=7 是可行的 pymatching使用 C++ 实现的 MWPM,数百万次运行在数秒内完成- 用于交叉验证时,也可尝试
beliefmatching(置信传播 + MWPM),在高码距下性能可超越纯 MWPM
python
# 运行时间基准测试
import time
circuit = stim.Circuit.generated(
"surface_code:rotated_memory_z",
distance=7,
rounds=7,
after_clifford_depolarization=0.005,
)
dem = circuit.detector_error_model(decompose_errors=True)
matcher = pymatching.Matching.from_detector_error_model(dem)
sampler = circuit.compile_detector_sampler()
t0 = time.time()
detection_events, observable_flips = sampler.sample(1_000_000, separate_observables=True)
predictions = matcher.decode_batch(detection_events)
t1 = time.time()
print(f"1M shots of d=7, decoded in {t1-t0:.1f}s")
# 在笔记本电脑上通常 3-10 秒 ------ Stim 很快
后续进阶
本教程涵盖了在简单退极化噪声模型下模拟表面码的基础。真实的量子纠错研究需要更进一步:
- 关联噪声:包含串扰和泄漏的电路级噪声需要修改解码方案
- 偏置噪声:某些硬件主要存在退相位或弛豫错误;定制编码(如 XZZX 表面码)可利用这一特性
- 容错门:本教程仅涵盖存储实验;实现逻辑门需要横贯操作或编码切换
- 魔法态蒸馏:容错地实现 T 门需要工厂电路,这些电路也可以用 Stim 模拟
- 置信传播解码器:在更高速率编码和近阈值错误率下,性能优于 MWPM
Stim 的文档包含了所有这些内容的工作示例。Stim 论文和 PyMatching 论文是主要参考文献。