Skip to content

噪声表征(Noise Characterization)

使用 pyqpanda3 模拟器和 NoiseModel 框架测量、识别和量化量子硬件上的噪声源。


问题

真实量子处理器以可预测的方式偏离理想行为。在对真实硬件进行模拟或应用误差缓解之前,你需要对设备上的噪声进行表征(Characterization)——即测量其类型、幅度以及对量子比特、门和电路深度的依赖关系。

为什么需要表征噪声?

  1. 理解设备性能。 原始校准数据(T1、T2、门保真度)是抽象的。运行针对性的表征电路可以告诉你噪声在设备实际运行中的表现。

  2. 校准噪声模型。 噪声模型只有在其参数与硬件匹配时才有用。表征实验提供了用准确错误率填充 NoiseModel 对象所需的数据。

  3. 启用误差缓解。 读出误差校正、零噪声外推和概率误差消除等技术都需要已校准的噪声参数。

NISQ 设备上的噪声类型

噪声源物理起源对计算的影响
门误差(Gate Errors)不完美的控制脉冲、校准漂移每个门之后施加错误的酉操作
读出误差(Readout Errors)放大器噪声、态分辨误差测量的比特串与真实态不同
串扰(Crosstalk)量子比特之间的非预期耦合取决于同时门操作的误差
退相干 T1(Decoherence)向环境的能量弛豫|1|0 随时间衰减
退相位 T2(Dephasing)环境引起的随机相位扰动叠加态相干性丢失

下图展示了这些噪声源如何进入模拟管道:


方案

表征分四个阶段进行。每个阶段隔离不同的噪声机制并产生一个参数输入 NoiseModel

步骤 1:测量读出误差概率

制备每个计算基态(单量子比特的 |0|1),多次测量,记录混淆矩阵(Confusion Matrix):

Mij=P(测量 i真实态 j)

步骤 2:表征单量子比特门误差

施加一长串随机单量子比特 Clifford 门后跟其逆,测量存活概率。指数衰减率给出平均门误差。这就是随机基准测试(Randomized Benchmarking, RB)

步骤 3:表征双量子比特门误差

使用交错双量子比特门(CNOT 或 CZ)运行相同的 RB 协议。与单量子比特 RB 相比,额外的衰减率隔离了双量子比特门误差。

步骤 4:构建 NoiseModel

将测量的读出矩阵、去极化误差率和退相干参数输入 NoiseModel 对象,并用独立测试电路验证。


代码

1. 测量读出误差概率

以下代码在单个量子比特上制备 |0|1,在已知读出噪声模型下多次测量,并估计混淆矩阵条目。

python
from pyqpanda3 import core

def measure_readout_confusion(qubit, shots=10000):
    """Estimate the 2x2 readout confusion matrix for one qubit.

    Returns a list of lists: probs[i][j] = P(measure j | prepared i).
    """
    confusion = [[0.0, 0.0], [0.0, 0.0]]

    for prepared in [0, 1]:
        prog = core.QProg()
        if prepared == 1:
            prog << core.X(qubit)
        prog << core.measure([qubit], [0])

        machine = core.CPUQVM()
        machine.run(prog, shots=shots)
        counts = machine.result().get_counts()

        p0 = counts.get("0", 0) / shots
        p1 = counts.get("1", 0) / shots
        confusion[prepared][0] = p0
        confusion[prepared][1] = p1

    return confusion

# Calibrate on qubit 0
matrix = measure_readout_confusion(qubit=0, shots=20000)
print("Readout confusion matrix (rows=prepared, cols=measured):")
print(f"  |0> -> |0>: {matrix[0][0]:.4f}   |0> -> |1>: {matrix[0][1]:.4f}")
print(f"  |1> -> |0>: {matrix[1][0]:.4f}   |1> -> |1>: {matrix[1][1]:.4f}")

2. 在已知噪声模型下校准读出

为验证校准过程,注入已知读出误差并验证测量的混淆矩阵是否符合预期。

python
from pyqpanda3 import core

# Define the ground-truth readout error matrix
true_probs = [[0.96, 0.04],   # P(measure 0|0)=0.96, P(measure 1|0)=0.04
              [0.03, 0.97]]   # P(measure 0|1)=0.03, P(measure 1|1)=0.97

# Build a noise model that contains ONLY readout error
noise = core.NoiseModel()
noise.add_read_out_error(true_probs, 0)

# Run the calibration experiment under noise
shots = 50000
confusion_measured = [[0.0, 0.0], [0.0, 0.0]]

for prepared in [0, 1]:
    prog = core.QProg()
    if prepared == 1:
        prog << core.X(0)
    prog << core.measure([0], [0])

    machine = core.CPUQVM()
    machine.run(prog, shots=shots, model=noise)
    counts = machine.result().get_counts()

    confusion_measured[prepared][0] = counts.get("0", 0) / shots
    confusion_measured[prepared][1] = counts.get("1", 0) / shots

print("True matrix:        ", true_probs)
print("Measured matrix:    ", confusion_measured)
# The measured values should closely match the true values

3. 通过比较理想与噪声电路估计门误差率

估计去极化误差率的一种直接方法是分别在有噪声和无噪声条件下运行相同电路,然后计算两个输出分布之间的全变差距离(Total Variation Distance, TVD)。

python
from pyqpanda3 import core

def total_variation_distance(counts_a, counts_b, shots):
    """Compute the total variation distance between two count distributions."""
    all_keys = set(list(counts_a.keys()) + list(counts_b.keys()))
    tvd = 0.0
    for key in all_keys:
        p_a = counts_a.get(key, 0) / shots
        p_b = counts_b.get(key, 0) / shots
        tvd += abs(p_a - p_b)
    return tvd / 2.0

def estimate_error_rate(num_layers, error_prob, shots=5000):
    """Build a random-looking circuit, run with and without depolarizing noise,
    and return the total variation distance."""
    prog = core.QProg()
    for layer in range(num_layers):
        prog << core.H(0)
        prog << core.CNOT(0, 1)
    prog << core.measure([0, 1], [0, 1])

    # Ideal run
    machine = core.CPUQVM()
    machine.run(prog, shots=shots)
    ideal_counts = machine.result().get_counts()

    # Noisy run
    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.H
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.CNOT
    )

    machine.run(prog, shots=shots, model=noise)
    noisy_counts = machine.result().get_counts()

    tvd = total_variation_distance(ideal_counts, noisy_counts, shots)
    return tvd

# Sweep error probabilities and observe the resulting TVD
for p in [0.001, 0.005, 0.01, 0.02, 0.05]:
    tvd = estimate_error_rate(num_layers=4, error_prob=p, shots=10000)
    print(f"Error prob {p:.3f} -> TVD = {tvd:.4f}")

4. 使用随机 Clifford 电路运行 RB 实验

随机基准测试(Randomized Benchmarking)通过施加递增长度的随机门序列并观察存活概率的指数衰减来测量门误差累积速度。衰减常数直接与平均门保真度(Average Gate Fidelity)相关。

python
import random
from pyqpanda3 import core

# Clifford gate set: gates whose compositions are also Clifford
CLIFFORD_GATES = [
    (core.GateType.H,  lambda q: core.H(q)),
    (core.GateType.X,  lambda q: core.X(q)),
    (core.GateType.Y,  lambda q: core.Y(q)),
    (core.GateType.Z,  lambda q: core.Z(q)),
    (core.GateType.S,  lambda q: core.S(q)),
]

def build_rb_circuit(qubit, num_cliffords):
    """Build a simplified RB sequence: random Clifford gates followed by
    a corrective gate that attempts to return the qubit to |0>.

    For this example we use a heuristic inverse (apply the same gates
    in reverse order with dagger where applicable) which is approximately
    correct for short sequences.
    """
    prog = core.QProg()
    gates_applied = []

    for _ in range(num_cliffords):
        _, gate_fn = random.choice(CLIFFORD_GATES)
        prog << gate_fn(qubit)
        gates_applied.append(gate_fn)

    # Apply gates in reverse to approximately invert
    for gate_fn in reversed(gates_applied):
        prog << gate_fn(qubit)

    prog << core.measure([qubit], [0])
    return prog

def run_rb_experiment(error_prob, sequence_lengths, shots=5000):
    """Run RB-like experiments at a given depolarizing error rate and
    return the survival probability for each sequence length."""
    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.H
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.X
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.Y
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.Z
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(error_prob), core.GateType.S
    )

    results = {}
    for length in sequence_lengths:
        survival_count = 0
        for trial in range(10):
            random.seed(trial)
            prog = build_rb_circuit(qubit=0, num_cliffords=length)

            machine = core.CPUQVM()
            machine.run(prog, shots=shots // 10, model=noise)
            counts = machine.result().get_counts()
            survival_count += counts.get("0", 0)

        survival_prob = survival_count / shots
        results[length] = survival_prob

    return results

# Run RB at two different error rates
for err in [0.01, 0.05]:
    results = run_rb_experiment(
        error_prob=err,
        sequence_lengths=[1, 3, 5, 10, 20, 40],
        shots=10000
    )
    print(f"\nError rate = {err}")
    for length, prob in sorted(results.items()):
        print(f"  Length {length:3d}: survival = {prob:.4f}")

5. 扫描电路深度以测量误差累积

此实验量化误差随电路深度增加如何累积。它是完整 RB 的简化替代方案,直接展示噪声对真实电路的影响。

python
from pyqpanda3 import core

def build_depth_sweep_circuit(num_qubits, depth):
    """Build a layered circuit of given depth using H and CNOT gates."""
    prog = core.QProg()
    for layer in range(depth):
        for q in range(num_qubits):
            prog << core.H(q)
        for q in range(0, num_qubits - 1, 2):
            prog << core.CNOT(q, q + 1)
    prog << core.measure(
        list(range(num_qubits)), list(range(num_qubits))
    )
    return prog

# Define a realistic noise model
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.002), core.GateType.H
)
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.015), core.GateType.CNOT
)
noise.add_all_qubit_read_out_error([[0.97, 0.03], [0.04, 0.96]])

# Sweep depth and record fidelity
num_qubits = 3
shots = 8000

print(f"{'Depth':>6s}  {'Bell-like Fidelity':>20s}  {'TVD':>8s}  {'Outcome Count':>14s}")
for depth in [1, 2, 4, 8, 16, 32, 64]:
    prog = build_depth_sweep_circuit(num_qubits, depth)

    # Ideal reference
    machine = core.CPUQVM()
    machine.run(prog, shots=shots)
    ideal = machine.result().get_counts()

    # Noisy
    machine.run(prog, shots=shots, model=noise)
    noisy = machine.result().get_counts()

    # Total variation distance
    all_keys = set(list(ideal.keys()) + list(noisy.keys()))
    tvd = sum(
        abs(ideal.get(k, 0) / shots - noisy.get(k, 0) / shots)
        for k in all_keys
    ) / 2.0

    # Fraction of outcomes matching ideal distribution keys
    ideal_fraction = sum(
        noisy.get(k, 0) for k in ideal.keys()
    ) / shots

    print(f"{depth:6d}  {ideal_fraction:20.4f}  {tvd:8.4f}  {len(noisy):14d}")

6. 从表征数据构建 NoiseModel

收集校准数据后,将其组装到单个 NoiseModel 对象中。此示例展示从原始指标到已验证模型的完整端到端管道。

python
from pyqpanda3 import core
import math

def build_noise_model_from_calibration(
    single_qubit_gate_error,
    two_qubit_gate_error,
    readout_error_matrix,
    t1_us=None,
    t2_us=None,
    gate_time_1q_ns=20,
    gate_time_2q_ns=300,
):
    """Construct a NoiseModel from hardware characterization data.

    Parameters
    ----------
    single_qubit_gate_error : float
        Average depolarizing error probability for single-qubit gates,
        typically obtained from single-qubit RB.
    two_qubit_gate_error : float
        Average depolarizing error probability for two-qubit gates,
        typically obtained from two-qubit interleaved RB.
    readout_error_matrix : list[list[float]]
        2x2 confusion matrix where entry [i][j] is
        P(measure j | prepared i).
    t1_us : float, optional
        T1 relaxation time in microseconds. If provided, amplitude
        damping errors are added.
    t2_us : float, optional
        T2 dephasing time in microseconds. If provided, phase
        damping errors are added.
    gate_time_1q_ns : float
        Single-qubit gate duration in nanoseconds.
    gate_time_2q_ns : float
        Two-qubit gate duration in nanoseconds.
    """
    noise = core.NoiseModel()

    # --- Gate errors from RB ---
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(single_qubit_gate_error), [
            core.GateType.H,
            core.GateType.X,
            core.GateType.Y,
            core.GateType.Z,
            core.GateType.S,
            core.GateType.T,
        ]
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(two_qubit_gate_error),
        core.GateType.CNOT
    )

    # --- Decoherence from T1 (amplitude damping) ---
    if t1_us is not None:
        gamma_1q = 1.0 - math.exp(-(gate_time_1q_ns / 1000.0) / t1_us)
        gamma_2q = 1.0 - math.exp(-(gate_time_2q_ns / 1000.0) / t1_us)

        ad_1q = core.amplitude_damping_error(gamma_1q)
        ad_2q = core.amplitude_damping_error(gamma_2q)

        noise.add_all_qubit_quantum_error(ad_1q, [
            core.GateType.H, core.GateType.X
        ])
        noise.add_all_qubit_quantum_error(
            ad_2q, core.GateType.CNOT
        )

    # --- Dephasing from T2 (phase damping) ---
    if t2_us is not None:
        lam_1q = 1.0 - math.exp(-(gate_time_1q_ns / 1000.0) / t2_us)
        lam_2q = 1.0 - math.exp(-(gate_time_2q_ns / 1000.0) / t2_us)

        pd_1q = core.phase_damping_error(lam_1q)
        pd_2q = core.phase_damping_error(lam_2q)

        noise.add_all_qubit_quantum_error(pd_1q, [
            core.GateType.H, core.GateType.T
        ])
        noise.add_all_qubit_quantum_error(
            pd_2q, core.GateType.CNOT
        )

    # --- Readout errors ---
    noise.add_all_qubit_read_out_error(readout_error_matrix)

    return noise

# Build a model using typical superconducting device parameters
noise_model = build_noise_model_from_calibration(
    single_qubit_gate_error=0.001,
    two_qubit_gate_error=0.015,
    readout_error_matrix=[[0.97, 0.03], [0.04, 0.96]],
    t1_us=80.0,
    t2_us=60.0,
    gate_time_1q_ns=20,
    gate_time_2q_ns=300,
)

# Validate: run a Bell state and check the output distribution
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

machine = core.CPUQVM()
machine.run(prog, shots=20000, model=noise_model)
counts = machine.result().get_counts()

total = sum(counts.values())
bell_fraction = (counts.get("00", 0) + counts.get("11", 0)) / total
print(f"Bell state fidelity under noise: {bell_fraction:.4f}")
print(f"Full distribution: {counts}")

7. 噪声影响的密度矩阵分析

对于小电路,DensityMatrixSimulator 在噪声下给出精确概率,避免采样噪声。这对于高精度验证表征结果很有用。

python
from pyqpanda3 import core
import numpy as np

# Prepare a Bell state circuit (no measurement)
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)

# Ideal density matrix
dm_sim = core.DensityMatrixSimulator()
dm_sim.run(prog)
ideal_probs = dm_sim.state_probs()
ideal_dm = dm_sim.density_matrix()

print("=== Ideal Bell State ===")
print(f"Probabilities: {ideal_probs}")
purity_ideal = np.trace(np.dot(ideal_dm, ideal_dm)).real
print(f"Purity: {purity_ideal:.6f}")

# Analyze each noise channel independently
channels = [
    ("Depolarizing 1%",    core.depolarizing_error(0.01)),
    ("Amplitude Damping 2%", core.amplitude_damping_error(0.02)),
    ("Phase Damping 2%",   core.phase_damping_error(0.02)),
    ("Pauli X 3%",         core.pauli_x_error(0.03)),
    ("Pauli Z 3%",         core.pauli_z_error(0.03)),
]

print("\n=== Noise Channel Comparison ===")
for name, err in channels:
    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(err, core.GateType.H)
    noise.add_all_qubit_quantum_error(err, core.GateType.CNOT)

    dm_sim.run(prog, noise)
    noisy_dm = dm_sim.density_matrix()
    noisy_probs = dm_sim.state_probs()
    purity = np.trace(np.dot(noisy_dm, noisy_dm)).real

    print(f"\n{name}:")
    print(f"  Probabilities: {noisy_probs}")
    print(f"  Purity: {purity:.6f}")
    print(f"  Purity loss: {purity_ideal - purity:.6f}")

解析

读出误差矩阵校准

读出误差是最容易表征的噪声之一,因为不需要纠缠门。流程如下:

  1. 制备 |0(不做任何操作)并测量 N 次。
  2. 制备 |1(施加 X)并测量 N 次。
  3. 构建 2×2 赋值矩阵 M,其中 Mij=P(测量 i制备 j)

对于 n 个量子比特,完整矩阵为 2n×2n,但实际中单量子比特矩阵独立测量并假设可因式分解:

Mfull=M0M1Mn1

这种因式分解是一种简化——某些硬件上确实存在关联读出误差——但它是标准的起点。

一旦已知 M,你可以通过在观测概率向量上求混淆矩阵的逆来缓解(Mitigate)读出误差:

pcorrected=M1pobserved

随机基准测试理论

随机基准测试(Randomized Benchmarking, RB)是估计平均门保真度(Average Gate Fidelity)的标准协议,无需完整态层析。关键洞察是:

  • 施加 m 个随机 Clifford 门序列 C1,C2,,Cm
  • 计算逆 Cinv=(CmC2C1) 并附加。
  • 在无噪声设置下,量子比特返回初始态。
  • 有噪声时,存活概率(Survival Probability)指数衰减:
F(m)=Apm+B

其中:

  • F(m)m 个 Clifford 门后测量到初始态的概率。
  • p去极化参数(Depolarizing Parameter),与平均门保真度的关系为 Favg=pd+1d+1,其中 d=2n
  • AB 是由态制备和测量(SPAM)误差决定的常数。
  • 每个 Clifford 的有效误差率为 r=(1p)(d1)/d

指数形式成立是因为每个 Clifford 门从 Clifford 群中均匀随机抽取,因此 m 个门后的平均噪声信道是平均噪声信道的 m 重复合。对于去极化映射形式的任何信道,这种复合产生指数。

从指数衰减提取误差率

给定 RB 数据 {(mi,Fi)},使用最小二乘回归拟合模型 F(m)=Apm+B。从拟合的 p 计算:

r=(1p)(d1)d

对于单量子比特(d=2),这简化为 r=(1p)/2

core.depolarizing_error(p_dep) 中使用的去极化误差概率与平均门保真度的关系为:

Favg=123pdep

对于单量子比特。因此,给定 RB 得到的 Favg

pdep=32(1Favg)

构建设备特定噪声模型

实用的噪声模型组合三个独立表征的误差源:

输入每个误差信道的参数来自:

参数来源公式
pdep1q单量子比特 RB 保真度 F1qpdep=32(1F1q)
pdep2q双量子比特交错 RB 保真度 F2qpdep=1516(1F2q)d=4
γT1 + 门时间 tgγ=1etg/T1
λT2 + 门时间 tgλ=1etg/T2
Mij读出校准直接测量

简单表征方法的局限性

上述方法做了几个值得理解的简化假设:

  1. 去极化假设。 真实门噪声不是完美去极化的。它可能是相干的(系统性过旋转)、关联的(多量子比特串扰)或非马尔可夫的。去极化模型将所有这些平均为单一参数,对性能预测有用,但可能遗漏结构化误差。

  2. 独立量子比特误差。 标准 NoiseModel 对每个量子比特独立施加误差。在真实硬件上,同时门操作可能产生此模型不捕获的关联误差。

  3. 门无关噪声。 对所有单量子比特门施加相同去极化误差率忽略了不同门具有不同持续时间和敏感性。有条件时应使用逐门误差率。

  4. 态依赖读出。 2×2 读出矩阵假设测量误差仅依赖于制备态,不依赖于邻近量子比特态。某些设备表现出态依赖读出串扰。

  5. RB 的 SPAM 鲁棒性。 标准 RB 设计为对态制备和测量(SPAM)误差不敏感,因为它们只影响常数 AB,不影响衰减率 p。然而,非常大的 SPAM 误差可能偏置拟合,特别是在短序列长度时。

  6. 模拟中的简化 RB。 本指南中的代码示例使用近似逆电路,而非计算精确 Clifford 逆。对于定量误差率提取,需要具有精确逆运算的正规 Clifford 群实现。

对于真实硬件的生产级表征,考虑使用交错 RB、交叉熵基准测试(Cross-Entropy Benchmarking, XEB)或门集层析(Gate Set Tomography, GST),它们以额外测量开销为代价提供更详细的噪声模型。

8. 交叉熵基准测试

交叉熵基准测试(Cross-Entropy Benchmarking, XEB)是一种通过比较有噪声执行与理想分布的输出来估计量子电路保真度的技术。与随机基准测试不同,XEB 适用于随机非 Clifford 电路并可扩展到多量子比特。线性交叉熵保真度(Linear Cross-Entropy Fidelity)定义为:

FXEB=2nxPideal(x)Pnoisy(x)1

其中 n 是量子比特数,Pideal(x) 是结果 x 的理想概率,Pnoisy(x) 是噪声下的观测频率。保真度为 1.0 表示完美一致,0.0 对应均匀随机分布。

问题

你想量化给定噪声模型对随机量子电路输出的退化程度,需要一个捕获完整分布——而不仅是单个基态概率——的度量。

方案

在理想模拟器和噪声模拟器上运行相同的随机电路,计算两个输出分布之间的线性交叉熵,并将结果解释为电路级保真度。

代码

python
import random
import math
from pyqpanda3 import core

def build_random_circuit(num_qubits, depth, seed=42):
    """Build a random circuit with single-qubit rotations and CNOT layers.

    Uses Rz and Rx rotations with random angles to produce a circuit
    whose ideal output is not concentrated on a few basis states.
    """
    random.seed(seed)
    prog = core.QProg()
    for layer in range(depth):
        for q in range(num_qubits):
            angle_h = random.uniform(0, 2 * math.pi)
            angle_z = random.uniform(0, 2 * math.pi)
            prog << core.H(q)
            prog << core.RZ(q, angle_z)
        for q in range(0, num_qubits - 1):
            prog << core.CNOT(q, q + 1)
    prog << core.measure(
        list(range(num_qubits)), list(range(num_qubits))
    )
    return prog


def counts_to_probs(counts, shots):
    """Convert a counts dictionary to a probability dict."""
    return {k: v / shots for k, v in counts.items()}


def linear_cross_entropy(ideal_probs, noisy_probs, num_qubits):
    """Compute the linear cross-entropy fidelity.

    F_xeb = 2^n * sum_x P_ideal(x) * P_noisy(x) - 1

    Returns a value in [0, 1] for valid fidelities (may be slightly
    negative due to statistical fluctuations).
    """
    dim = 2 ** num_qubits
    weighted_sum = 0.0
    for bitstring, p_noisy in noisy_probs.items():
        p_ideal = ideal_probs.get(bitstring, 0.0)
        weighted_sum += p_ideal * p_noisy
    fidelity = dim * weighted_sum - 1.0
    return max(fidelity, 0.0)


# Define a noise model with realistic parameters
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.003), core.GateType.H
)
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.02), core.GateType.CNOT
)

num_qubits = 3
shots = 20000

print(f"{'Depth':>6s}  {'XEB Fidelity':>14s}  {'TVD':>8s}")
print("-" * 34)

for depth in [1, 2, 4, 8, 16, 32]:
    prog = build_random_circuit(num_qubits, depth, seed=depth)

    # Ideal execution
    machine = core.CPUQVM()
    machine.run(prog, shots=shots)
    ideal_counts = machine.result().get_counts()
    ideal_probs = counts_to_probs(ideal_counts, shots)

    # Noisy execution
    machine.run(prog, shots=shots, model=noise)
    noisy_counts = machine.result().get_counts()
    noisy_probs = counts_to_probs(noisy_counts, shots)

    # XEB fidelity
    xeb_f = linear_cross_entropy(ideal_probs, noisy_probs, num_qubits)

    # Total variation distance for comparison
    all_keys = set(list(ideal_counts.keys()) + list(noisy_counts.keys()))
    tvd = sum(
        abs(ideal_counts.get(k, 0) / shots - noisy_counts.get(k, 0) / shots)
        for k in all_keys
    ) / 2.0

    print(f"{depth:6d}  {xeb_f:14.4f}  {tvd:8.4f}")

解析

线性交叉熵保真度测量理想分布在噪声下保留了多少信息。与全变差距离等更简单度量相比,其优势在于:

  1. 捕获关联结构。 两个分布可能有相同的 TVD 但非常不同的 XEB 保真度,取决于噪声输出是否集中在与理想分布相同的高概率态上。

  2. 对电路质量敏感。 XEB 被谷歌用于量子霸权实验中,验证其随机电路产生远离均匀的输出,这正是高保真量子计算的标志。

  3. 高效扩展。 与从密度矩阵计算保真度(需要 4n 个矩阵元素)不同,XEB 只需将采样比特串与理想分布比较,对多量子比特电路实用。

关键限制是计算 Pideal(x) 需要理想模拟,在大约 50 个量子比特以上变得不可行。对于更大系统,需要近似 XEB 技术或专用硬件验证方法。

9. 噪声感知电路设计

同一逻辑操作的不同电路实现可能具有截然不同的噪声敏感性。双量子比特门(特别是 CNOT)的数量通常是主导因素,因为超导硬件上双量子比特门误差通常比单量子比特门误差大 10~50 倍。通过选择最小化 CNOT 数量的实现,可以在真实噪声下显著提高输出质量。

问题

你有同一酉变换的两种等效电路实现。需要确定哪种在硬件噪声下降解更少并量化差异。

方案

构建两种电路实现,在相同噪声模型下运行,并使用全变差距离或交叉熵保真度等保真度度量比较其输出分布与理想结果的差异。

代码

python
from pyqpanda3 import core

def build_circuit_a(qubits):
    """Implementation A: SWAP via three CNOTs (standard decomposition).

    This is the textbook SWAP decomposition:
      CNOT(q0, q1) -> CNOT(q1, q0) -> CNOT(q0, q1)

    Total: 3 CNOT gates
    """
    prog = core.QProg()
    q0, q1 = qubits[0], qubits[1]
    prog << core.CNOT(q0, q1)
    prog << core.CNOT(q1, q0)
    prog << core.CNOT(q0, q1)
    prog << core.measure(list(qubits), list(range(len(qubits))))
    return prog


def build_circuit_b(qubits):
    """Implementation B: SWAP preceded by single-qubit state preparation.

    Uses the same SWAP decomposition but adds extra single-qubit gates
    that cancel out, testing whether single-qubit gate overhead matters.
    Total: 3 CNOT gates + 4 single-qubit gates (H before and after each CNOT)
    """
    prog = core.QProg()
    q0, q1 = qubits[0], qubits[1]
    # Extra single-qubit gates (these cancel in the ideal case)
    prog << core.H(q0)
    prog << core.H(q0)
    prog << core.CNOT(q0, q1)
    prog << core.H(q1)
    prog << core.H(q1)
    prog << core.CNOT(q1, q0)
    prog << core.H(q0)
    prog << core.H(q0)
    prog << core.CNOT(q0, q1)
    prog << core.measure(list(qubits), list(range(len(qubits))))
    return prog


def build_circuit_c(qubits):
    """Implementation C: Equivalent operation using fewer CNOTs.

    Uses only 1 CNOT plus single-qubit gates, which is possible
    for certain input states. Demonstrates that minimizing CNOT
    count reduces noise impact.
    Total: 1 CNOT gate + 2 H gates
    """
    prog = core.QProg()
    q0, q1 = qubits[0], qubits[1]
    prog << core.H(q0)
    prog << core.CNOT(q0, q1)
    prog << core.H(q0)
    prog << core.measure(list(qubits), list(range(len(qubits))))
    return prog


def compute_tvd(counts_a, counts_b, shots):
    """Total variation distance between two count distributions."""
    all_keys = set(list(counts_a.keys()) + list(counts_b.keys()))
    return sum(
        abs(counts_a.get(k, 0) / shots - counts_b.get(k, 0) / shots)
        for k in all_keys
    ) / 2.0


# Noise model with asymmetry: CNOT errors are much larger
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.002), core.GateType.H
)
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.025), core.GateType.CNOT
)
noise.add_all_qubit_read_out_error([[0.96, 0.04], [0.05, 0.95]])

qubits = [0, 1]
shots = 30000

# Prepare an initial superposition state
# so the SWAP has a non-trivial effect
def with_initial_state(builder, qubits):
    """Wrap a circuit builder to add an initial H on qubit 0."""
    prog = core.QProg()
    prog << core.H(qubits[0])
    inner = builder(qubits)
    # Rebuild the inner program without its own measurements
    prog_bare = core.QProg()
    prog_bare << core.H(qubits[0])
    return builder(qubits)

# Build circuits (each starts with H on qubit 0)
circuit_a = core.QProg()
circuit_a << core.H(0) << core.CNOT(0, 1) << core.CNOT(1, 0) << core.CNOT(0, 1)
circuit_a << core.measure([0, 1], [0, 1])

circuit_b = core.QProg()
circuit_b << core.H(0)
circuit_b << core.H(0) << core.H(0)
circuit_b << core.CNOT(0, 1) << core.H(1) << core.H(1)
circuit_b << core.CNOT(1, 0) << core.H(0) << core.H(0)
circuit_b << core.CNOT(0, 1)
circuit_b << core.measure([0, 1], [0, 1])

circuit_c = core.QProg()
circuit_c << core.H(0) << core.H(0) << core.CNOT(0, 1) << core.H(0)
circuit_c << core.measure([0, 1], [0, 1])

# Run all three circuits
machine = core.CPUQVM()
results = {}
for label, circuit in [("A (3 CNOT)", circuit_a),
                        ("B (3 CNOT + 4 H)", circuit_b),
                        ("C (1 CNOT + 3 H)", circuit_c)]:
    # Ideal
    machine.run(circuit, shots=shots)
    ideal = machine.result().get_counts()

    # Noisy
    machine.run(circuit, shots=shots, model=noise)
    noisy = machine.result().get_counts()

    tvd = compute_tvd(ideal, noisy, shots)

    # Count the Bell-state fraction (00 + 11) for reference
    bell_noisy = (noisy.get("00", 0) + noisy.get("11", 0)) / shots
    bell_ideal = (ideal.get("00", 0) + ideal.get("11", 0)) / shots

    results[label] = {
        "tvd": tvd,
        "bell_ideal": bell_ideal,
        "bell_noisy": bell_noisy,
        "noisy_counts": noisy,
    }

print(f"{'Implementation':<22s}  {'TVD':>8s}  {'Bell(ideal)':>12s}  {'Bell(noisy)':>12s}")
print("-" * 60)
for label, data in results.items():
    print(f"{label:<22s}  {data['tvd']:8.4f}  {data['bell_ideal']:12.4f}  {data['bell_noisy']:12.4f}")

解析

结果将显示明确的层级:使用更少 CNOT 门的电路(实现 C)在噪声下将具有更低的 TVD 和更高的输出保真度,比使用三个 CNOT 的实现更好,尽管所有三种在理想情况下产生相同的逻辑结果。实现 B 添加了不必要的单量子比特门,通常比实现 A 略差,因为额外的门层允许更多误差累积。

噪声感知电路设计的实用要点:

  1. 最小化双量子比特门。 CNOT 和 CZ 误差在当前硬件上通常为 1~5%,而单量子比特门为 0.01~0.1%。消除每个 CNOT 可以带来不成比例的输出质量提升。

  2. 优先选择更短的电路深度。 即使 CNOT 数量固定,重新排列门以减少整体电路深度也可以限制退相干(T1/T2)误差的可用时间。

  3. 用模拟验证。 在硬件上运行之前,在校准到目标设备的噪声模型下比较候选电路实现。实现之间 TVD 或 XEB 保真度的差异是哪个表现更好的可靠预测。

  4. 考虑量子比特拓扑。 在连接性有限的设备上,添加 SWAP 门来路由量子比特可能显著增加 CNOT 数量。利用原生连接性的电路优化可以显著降低有效误差率。


后续步骤

Released under the MIT License.