2. 单量子比特门保真度基准测试

实验对象与测量原理

随机基准测试(Randomized Benchmarking, RB)的核心思想是:用一组从 Clifford 群中随机抽取的单量子比特门构成序列,序列末尾追加一个“逆门”使整体理论作用为恒等操作,然后测量初态 |0⟩ 的存活概率。随着序列长度 m 增加,含噪声后端的存活概率按指数衰减,衰减率直接给出平均门保真度。

单量子比特 Clifford 群共 24 个元素,可由 {I, X, Y, Z, H, S} 生成。RB 的拟合模型为:

P(m) = A · p^m + B

其中 p 为去极化参数,平均门保真度 F_avg = 1 - (1 - p) · (d - 1) / d,单量子比特 d = 2,故 F_avg = 1 - (1 - p)/2 = (1 + p)/2。A、B 吸收制备与测量误差,不影响 p 的估计。

实验设计:序列长度、采样数与后端

为让衰减曲线可拟合且对比有意义,采用如下参数(示例设定,非实测):

  • 序列长度 m ∈ {1, 2, 4, 8, 16, 32, 64, 128, 256},共 9 个点;
  • 每个长度采样 20 条独立随机序列,每条序列执行 1024 shots;
  • 后端一:理想模拟器(无噪声),预期 p ≈ 1;
  • 后端二:含噪声模拟器,单量子比特去极化噪声参数 p_depol = 0.002(每门),并叠加读出误判率 0.01。

关键点:逆门本身也计入序列长度,但标准做法是在拟合时把 m 定义为随机门数量,逆门作为编译产物不单独计数;两种后端必须使用同一组随机种子生成的序列,才能保证曲线差异只来自噪声。

RB 信号链(单量子比特) 初态 |0⟩ 态制备 随机 Clifford C₁ C₂ … Cₘ m = 1…256 逆门 C⁻¹ 理论恒等 测量 Z 基 1024 shots P(m) 存活率 拟合 A·pᵐ+B 噪声注入点 去极化 p=0.002 读出误判 0.01 存活概率衰减曲线对比 序列长度 m(对数刻度) P(m) 1 4 16 64 256 0 0.25 0.5 0.75 1.0 理想模拟器 p≈1 含噪模拟器 p≈0.998 F_avg = (1 + p) / 2

可运行实现:同一随机种子下的双后端 RB

下面代码使用 Qiskit 的 qiskit_experiments 随机基准测试框架,但为了展示“同一序列、双后端”的对比逻辑,这里直接手写 Clifford 序列生成与拟合,避免依赖实验库的自动流程。代码自洽可运行,输出两条曲线的拟合参数。

import numpy as np
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, depolarizing_error, ReadoutError
from scipy.optimize import curve_fit

# ---------- 1. 单量子比特 Clifford 群(24 个元素,用 2x2 矩阵表示) ----------
I = np.eye(2, dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
S = np.array([[1, 0], [0, 1j]], dtype=complex)

def clifford_group():
    """生成 24 个单量子比特 Clifford 矩阵(去重)。"""
    gens = [I, X, Y, Z, H, S]
    group = set()
    frontier = [I]
    while frontier:
        new_frontier = []
        for g in frontier:
            for h in gens:
                prod = h @ g
                key = tuple(np.round(prod.flatten(), 8))
                if key not in group:
                    group.add(key)
                    new_frontier.append(prod)
        frontier = new_frontier
    return [np.array(k).reshape(2, 2) for k in group]

CLIFFORDS = clifford_group()
assert len(CLIFFORDS) == 24, f"Clifford 群大小应为 24,实际 {len(CLIFFORDS)}"

# ---------- 2. 把矩阵转成 Qiskit 线路(用 U 门精确实现) ----------
def matrix_to_gate(mat):
    qc = QuantumCircuit(1)
    qc.unitary(mat, [0], label="C")
    return qc

# ---------- 3. 生成一条 RB 序列:m 个随机 Clifford + 逆门 ----------
def build_rb_sequence(m, rng):
    mats = [CLIFFORDS[rng.integers(0, 24)] for _ in range(m)]
    total = np.eye(2, dtype=complex)
    for mat in mats:
        total = mat @ total
    inverse = total.conj().T  # Clifford 是酉矩阵,逆 = 共轭转置
    qc = QuantumCircuit(1, 1)
    for mat in mats:
        qc.compose(matrix_to_gate(mat), inplace=True)
    qc.compose(matrix_to_gate(inverse), inplace=True)
    qc.measure(0, 0)
    return qc

# ---------- 4. 两个后端 ----------
ideal_sim = AerSimulator()

noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(
    depolarizing_error(0.002, 1), ["u1", "u2", "u3", "unitary"])
noise_model.add_all_qubit_readout_error(
    ReadoutError([[0.99, 0.01], [0.01, 0.99]]))
noisy_sim = AerSimulator(noise_model=noise_model)

# ---------- 5. 采集存活概率 ----------
def run_rb(backend, lengths, n_seqs=20, shots=1024, seed=2024):
    rng = np.random.default_rng(seed)
    result = {}
    for m in lengths:
        probs = []
        for _ in range(n_seqs):
            qc = build_rb_sequence(m, rng)
            tqc = transpile(qc, backend, optimization_level=0)
            counts = backend.run(tqc, shots=shots).result().get_counts()
            probs.append(counts.get("0", 0) / shots)
        result[m] = float(np.mean(probs))
    return result

lengths = [1, 2, 4, 8, 16, 32, 64, 128, 256]
ideal_data = run_rb(ideal_sim, lengths)
noisy_data = run_rb(noisy_sim, lengths)

# ---------- 6. 拟合 P(m) = A * p^m + B ----------
def rb_model(m, A, p, B):
    return A * (p ** m) + B

def fit_rb(data):
    m = np.array(list(data.keys()), dtype=float)
    y = np.array(list(data.values()), dtype=float)
    popt, _ = curve_fit(rb_model, m, y, p0=[0.5, 0.99, 0.5],
                        bounds=([0, 0, 0], [1, 1, 1]), maxfev=20000)
    A, p, B = popt
    f_avg = (1 + p) / 2
    return p, f_avg, A, B

p_ideal, f_ideal, _, _ = fit_rb(ideal_data)
p_noisy, f_noisy, _, _ = fit_rb(noisy_data)

print(f"理想后端: p = {p_ideal:.5f}, F_avg = {f_ideal:.5f}")
print(f"含噪后端: p = {p_noisy:.5f}, F_avg = {f_noisy:.5f}")
print(f"保真度差: {f_ideal - f_noisy:.5f}")

结果判读与常见陷阱

预期现象(示例设定,实际数值随随机种子与 shots 波动):理想后端 p 接近 1,F_avg 约 0.999 以上,曲线几乎水平;含噪后端 p 约 0.998,F_avg 约 0.999,但曲线在 m = 256 处已明显下降到 0.6 附近。注意:单门保真度很高不代表长序列存活率高,因为误差随门数累积,这正是 RB 用长序列放大微小误差的原因。

三个高频陷阱:

  • 逆门计数不一致:若把逆门也计入 m,拟合出的 p 会系统性偏低。固定一种约定并在两后端间保持一致。
  • 序列未共享随机种子:两后端若用不同随机序列,曲线差异混入了序列本身的统计涨落,无法归因于噪声。必须用同一 seed 生成序列。
  • transpile 优化改变门数:optimization_level 大于 0 时可能合并或消去门,导致实际作用门数与 m 不符。RB 场景应固定 optimization_level=0。

快速复查清单

检查项通过条件不通过时的处理
Clifford 群完整性去重后恰好 24 个元素检查生成元与去重精度(round 8 位)
逆门正确性序列总矩阵 ≈ 单位阵确认逆门用 total.conj().T
双后端序列一致同一 seed 下序列逐门相同把 rng 的创建移到循环外并复用
拟合收敛p ∈ (0, 1],残差无明显系统偏差增加序列长度点或 shots,检查初值
保真度换算F_avg = (1 + p) / 2确认维度 d = 2 的公式