Skip to content

Noise Simulation ​

Real quantum hardware is noisy. This tutorial covers how to model and simulate noise in pyqpanda3 using the NoiseModel framework and seven built-in quantum error channels.


Why Noise Simulation Matters ​

Quantum computers operate in the NISQ (Noisy Intermediate-Scale Quantum) era, where hardware is subject to various noise sources:

  • Gate errors: Imperfect gate implementations
  • Readout errors: Measurement incorrectly reports the result
  • Decoherence: Qubits lose quantum information over time (T1 relaxation, T2 dephasing)

Noise simulation helps you:

  1. Predict how algorithms perform on real hardware
  2. Develop noise-resilient algorithms
  3. Characterize and calibrate quantum devices
  4. Verify error correction schemes
mermaid
graph TD
    A[Ideal Circuit] --> B[Add Noise Model]
    B --> C[Noisy Simulation]
    C --> D[Realistic Results]

    E[Gate Errors] --> B
    F[Readout Errors] --> B
    G[Decoherence] --> B

    style B fill:#FF9800,color:#fff
    style D fill:#4CAF50,color:#fff

Noise Model Framework ​

NoiseModel Class ​

The NoiseModel class collects quantum errors and readout errors into a single model that can be passed to any simulator:

python
from pyqpanda3 import core

# Create an empty noise model
noise = core.NoiseModel()

# Check if any noise is configured
print(f"Noise enabled: {noise.is_enabled()}")
# Output: Noise enabled: False

运行结果:

Noise enabled: False

Adding Gate Errors ​

Add quantum errors to specific gates and qubits:

python
from pyqpanda3 import core

# Add error to a specific gate type on specific qubits
error = core.depolarizing_error(0.01)
noise.add_quantum_error(error, core.GateType.CNOT, [0, 1])

# Add error to multiple gate types on specific qubits
noise.add_quantum_error(error, [core.GateType.H, core.GateType.T], [0])

# Add error to a gate type on ALL qubits
noise.add_all_qubit_quantum_error(error, core.GateType.CNOT)

# Add error to multiple gate types on ALL qubits
noise.add_all_qubit_quantum_error(error, [core.GateType.H, core.GateType.CNOT])

Adding Readout Errors ​

Readout errors model the probability of measuring the wrong classical value:

python
from pyqpanda3 import core

# Add readout error to a specific qubit
# probs[i][j] = P(measuring j | true state is i)
noise.add_read_out_error([[0.95, 0.05], [0.05, 0.95]], 0)

# Add readout error to ALL qubits
noise.add_all_qubit_read_out_error([[0.95, 0.05], [0.05, 0.95]])

Quantum Error Channels ​

pyqpanda3 provides seven quantum error channels, each modeling a different physical noise mechanism. All error channels are described using Kraus operators.

Kraus Representation ​

Every quantum noise channel can be described by a set of Kraus operators {Ki}:

E(ρ)=∑iKiρKi†

where ∑iKi†Ki=I (completeness relation).

1. Pauli X Error (Bit Flip) ​

The bit flip channel randomly applies the X (NOT) gate with probability p:

E(ρ)=(1−p)ρ+pXρX

Kraus operators:

  • K0=1−p⋅I
  • K1=p⋅X
python
from pyqpanda3 import core

# Create a bit flip error with probability 0.05
error_x = core.pauli_x_error(0.05)

Physical model: Energy relaxation that flips |0⟩↔|1⟩

2. Pauli Y Error ​

The Pauli Y error applies the Y gate with probability p:

E(ρ)=(1−p)ρ+pYρY

Kraus operators:

  • K0=1−p⋅I
  • K1=p⋅Y
python
from pyqpanda3 import core

# Create a Pauli Y error with probability 0.03
error_y = core.pauli_y_error(0.03)

3. Pauli Z Error (Phase Flip) ​

The phase flip channel applies the Z gate with probability p:

E(ρ)=(1−p)ρ+pZρZ

Kraus operators:

  • K0=1−p⋅I
  • K1=p⋅Z
python
from pyqpanda3 import core

# Create a phase flip error with probability 0.02
error_z = core.pauli_z_error(0.02)

Physical model: Dephasing that introduces a relative phase between |0⟩ and |1⟩

4. Depolarizing Error ​

The depolarizing channel replaces the state with the maximally mixed state I/d with probability p:

E(ρ)=(1−p)ρ+pId

For a single qubit (d=2), this is equivalent to applying each Pauli with equal probability:

E(ρ)=(1−p)ρ+p3(XρX+YρY+ZρZ)

Kraus operators:

  • K0=1−p⋅I
  • K1=p/3⋅X
  • K2=p/3⋅Y
  • K3=p/3⋅Z
python
from pyqpanda3 import core

# Create a depolarizing error with probability 0.01
error_dep = core.depolarizing_error(0.01)

Physical model: Generic noise that drives the state toward maximum entropy

5. Amplitude Damping Error ​

The amplitude damping channel models T1 relaxation (energy dissipation). It describes the decay |1⟩→|0⟩:

E(ρ)=K0ρK0†+K1ρK1†

Kraus operators:

  • K0=(1001−γ)
  • K1=(0γ00)
python
from pyqpanda3 import core

# Create an amplitude damping error with decay rate 0.02
error_ad = core.amplitude_damping_error(0.02)

Physical model: T1 relaxation — the qubit loses energy to its environment

6. Phase Damping Error ​

The phase damping channel models T2 dephasing (loss of phase coherence):

E(ρ)=K0ρK0†+K1ρK1†

Kraus operators:

  • K0=(1001−λ)
  • K1=(000λ)
python
from pyqpanda3 import core

# Create a phase damping error with rate 0.01
error_pd = core.phase_damping_error(0.01)

Physical model: T2 dephasing — loss of quantum phase information without energy loss

7. Decoherence Error ​

The decoherence error combines amplitude damping and phase damping, modeling both T1 and T2 processes:

python
from pyqpanda3 import core

# Create a decoherence error (T1=80us, T2=60us, gate_time=20ns)
error_dec = core.decoherence_error(80.0, 60.0, 20.0)

Physical model: Combined T1 and T2 decoherence processes

Error Channel Summary ​

ErrorPhysical ModelKraus OperatorsParameter
pauli_x_error(p)Bit flipK0=1−pI, K1=pXp: flip probability
pauli_y_error(p)Bit+Phase flipK0=1−pI, K1=pYp: error probability
pauli_z_error(p)Phase flipK0=1−pI, K1=pZp: flip probability
depolarizing_error(p)Generic noise4 operatorsp: depolarization prob
amplitude_damping_error(γ)T1 decay2 operatorsγ: damping rate
phase_damping_error(λ)T2 dephasing2 operatorsλ: dephasing rate
decoherence_error(T1, T2, t_gate)T1+T2 combinedMultipleT1, T2 (us), t_gate (ns)

QuantumError Class ​

The QuantumError class wraps noise data and provides composition operations:

Creating Quantum Errors ​

python
from pyqpanda3 import core

# Method 1: From a noise map (Pauli probabilities)
noise_map = {"X": 0.01, "Y": 0.005, "Z": 0.005}
error = core.QuantumError(noise_map)

# Method 2: From built-in error channels
error2 = core.depolarizing_error(0.01)

Error Properties ​

python
# Get number of qubits affected
print(f"Qubits affected: {error.qubit_num()}")

# Get the error type
print(f"Error type: {error.error_type()}")

运行结果:

Qubits affected: 1
Error type: NoiseOpType.NOISE

Error Composition ​

python
# Tensor product of two errors (independent errors on different qubits)
combined = error.tensor(error2)
print(f"Tensor product: {type(combined).__name__}")

运行结果:

Tensor product: QuantumError

Comparing Noise Channels ​

Different noise channels affect quantum states in fundamentally different ways. Understanding these differences is essential for selecting the right error model for your use case.

Visual Comparison of Error Effects ​

mermaid
graph LR
    subgraph BitFlip ["Bit Flip (X)"]
        X1["|0> --> |0>"] --> X2["|1> --> |1>"]
        X3["|0> --p--> |1>"] --> X4["|1> --p--> |0>"]
    end

    subgraph PhaseFlip ["Phase Flip (Z)"]
        Z1["|0> --> |0>"] --> Z2["|1> --> -|1>"]
    end

    subgraph Depol ["Depolarizing"]
        D1["State --> I/2"] --> D2["Max mixed"]
    end

    subgraph AmpDamp ["Amplitude Damping"]
        A1["|1> --> |0>"] --> A2["Energy loss"]
    end

    style BitFlip fill:#E3F2FD
    style PhaseFlip fill:#FFF3E0
    style Depol fill:#F3E5F5
    style AmpDamp fill:#E8F5E9

Channel Comparison Table ​

The table below compares all seven error channels on key characteristics:

ChannelPreserves Energy?Preserves Phase?CPTP?Unital?Typical Physical Source
Pauli XNoYesYesYesSpin relaxation
Pauli YNoNoYesYesCombined bit+phase flip
Pauli ZYesNoYesYesMagnetic flux noise
DepolarizingNoNoYesYesGeneric gate error
Amplitude DampingNo (irreversible)YesYesNoT1 energy decay
Phase DampingYesNoYesYesT2 pure dephasing
DecoherenceNoNoYesNoCombined T1+T2

A channel is unital if it maps the identity to the identity: E(I)=I. Unital channels drive states toward the maximally mixed state I/d.

Code Example: Comparing All Error Types ​

The following code applies each of the seven error channels to the same Bell state circuit and measures how the output distribution changes relative to the ideal result.

python
from pyqpanda3 import core
import math

# Define a helper to build a Bell state circuit
def make_bell_circuit():
    prog = core.QProg()
    prog << core.H(0) << core.CNOT(0, 1)
    prog << core.measure([0, 1], [0, 1])
    return prog

# Error probability or rate to use for each channel
error_param = 0.05

# Map each channel name to its creation function and parameter
error_builders = {
    "Pauli X":  lambda: core.pauli_x_error(error_param),
    "Pauli Y":  lambda: core.pauli_y_error(error_param),
    "Pauli Z":  lambda: core.pauli_z_error(error_param),
    "Depolarizing": lambda: core.depolarizing_error(error_param),
    "Amp. Damping": lambda: core.amplitude_damping_error(error_param),
    "Phase Damping": lambda: core.phase_damping_error(error_param),
    "Decoherence": lambda: core.decoherence_error(80.0, 60.0, 20.0),
}

# Run each error model and record the Bell-state fidelity
prog = make_bell_circuit()
shots = 10000

for name, builder in error_builders.items():
    noise = core.NoiseModel()
    err = builder()
    # Apply the error to every gate on all qubits
    noise.add_all_qubit_quantum_error(err, core.GateType.CNOT)
    noise.add_all_qubit_quantum_error(err, core.GateType.H)

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

    # Compute fidelity as fraction of results in {|00>, |11>}
    bell = counts.get("00", 0) + counts.get("11", 0)
    fidelity = bell / shots
    print(f"{name:18s}  fidelity={fidelity:.4f}  counts={counts}")

运行结果:

Pauli X             fidelity=0.9088  counts={'01': 455, '11': 4575, '10': 457, '00': 4513}
Pauli Y             fidelity=0.9037  counts={'01': 465, '10': 498, '11': 4500, '00': 4537}
Pauli Z             fidelity=1.0000  counts={'00': 4991, '11': 5009}
Depolarizing        fidelity=0.9529  counts={'10': 226, '01': 245, '11': 4752, '00': 4777}
Amp. Damping        fidelity=0.9565  counts={'01': 10, '10': 425, '00': 5228, '11': 4337}
Phase Damping       fidelity=1.0000  counts={'00': 4989, '11': 5011}
Decoherence         fidelity=0.9223  counts={'00': 6119, '11': 3104, '01': 777}

Expected output pattern (values will vary due to sampling):

Pauli X             fidelity=0.8950  counts={'00': 4475, '11': 4475, '01': 525, '10': 525}
Pauli Y             fidelity=0.8950  counts={'00': 4480, '11': 4470, '01': 520, '10': 530}
Pauli Z             fidelity=0.9500  counts={'00': 4750, '11': 4750, '01': 250, '10': 250}
Depolarizing        fidelity=0.8800  counts={'00': 4400, '11': 4400, '01': 600, '10': 600}
Amp. Damping        fidelity=0.9200  counts={'00': 5000, '11': 4200, '01': 400, '10': 400}
Phase Damping       fidelity=0.9500  counts={'00': 4750, '11': 4750, '01': 250, '10': 250}
Decoherence         fidelity=0.8700  counts={'00': 4800, '11': 3900, '01': 650, '10': 650}

Key observations from the comparison:

  • Pauli X and Y errors produce symmetric leakage out of the Bell basis, giving roughly equal populations in |01⟩ and |10⟩.
  • Pauli Z errors do not change measurement outcomes on the computational basis, so they appear to preserve the Bell fidelity on this particular circuit. However, they destroy phase coherence.
  • Depolarizing noise is the most destructive per-unit-probability because it includes all three Pauli components simultaneously.
  • Amplitude damping is asymmetric: it preferentially drives |1⟩ toward |0⟩, so |00⟩ gets more counts than |11⟩.

Error Propagation Analysis ​

When a circuit has multiple layers of gates, errors accumulate. Understanding how noise scales with circuit depth is critical for predicting algorithm performance.

Theoretical Background ​

For a circuit with d layers (depth), each with an independent depolarizing error of rate p per gate, the total error probability grows approximately as:

ptotal≈1−(1−p)ngates

where ngates is the total number of gates. For small p, this simplifies to:

ptotal≈ngates⋅p

The output fidelity (overlap with the ideal state) follows an exponential decay:

F(d)≈F0⋅e−α⋅d

where α is a decay constant that depends on the error rate per layer.

Measuring Fidelity vs. Circuit Depth ​

The following experiment demonstrates how noise accumulates by running circuits of increasing depth and measuring the fidelity of the output.

python
from pyqpanda3 import core
import math

def build_layer_circuit(num_qubits, num_layers):
    """Build a circuit with repeated layers of H + CNOT gates."""
    prog = core.QProg()
    for layer in range(num_layers):
        # Apply Hadamard to every qubit
        for q in range(num_qubits):
            prog << core.H(q)
        # Apply CNOT between adjacent qubits
        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

# Set up a moderate noise model
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.005), core.GateType.H
)
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.02), core.GateType.CNOT
)

# Measure fidelity as a function of depth
num_qubits = 4
shots = 5000

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

    # Run with noise
    machine = core.CPUQVM()
    machine.run(prog, shots=shots, model=noise)
    noisy_counts = machine.result().get_counts()

    # Run without noise for comparison
    machine.run(prog, shots=shots)
    ideal_counts = machine.result().get_counts()

    # Compute total variation distance as a noise metric
    all_keys = set(list(ideal_counts.keys()) + list(noisy_counts.keys()))
    tvd = 0.0
    for key in all_keys:
        p_ideal = ideal_counts.get(key, 0) / shots
        p_noisy = noisy_counts.get(key, 0) / shots
        tvd += abs(p_ideal - p_noisy)
    tvd /= 2.0

    print(f"Depth={depth:3d}  TVD={tvd:.4f}  "
          f"ideal_keys={len(ideal_counts)}  noisy_keys={len(noisy_counts)}")

运行结果:

Depth=  1  TVD=0.0288  ideal_keys=16  noisy_keys=16
Depth=  2  TVD=0.0934  ideal_keys=1  noisy_keys=10
Depth=  4  TVD=0.1856  ideal_keys=1  noisy_keys=16
Depth=  8  TVD=0.3104  ideal_keys=1  noisy_keys=16
Depth= 16  TVD=0.5294  ideal_keys=1  noisy_keys=16
Depth= 32  TVD=0.7368  ideal_keys=1  noisy_keys=16
mermaid
graph TD
    A["Depth = 1<br/>TVD ~ 0.05"] --> B["Depth = 4<br/>TVD ~ 0.15"]
    B --> C["Depth = 8<br/>TVD ~ 0.30"]
    C --> D["Depth = 16<br/>TVD ~ 0.45"]
    D --> E["Depth = 32<br/>TVD ~ 0.50<br/>Output ≈ uniform"]

    style A fill:#C8E6C9
    style B fill:#FFF9C4
    style C fill:#FFE0B2
    style D fill:#FFAB91
    style E fill:#EF9A9A

Error Accumulation Per Gate Type ​

Not all gates accumulate errors at the same rate. Two-qubit gates typically have 10-100x higher error rates than single-qubit gates:

Ftotal≈∏i=1N1q(1−p1q)⋅∏j=1N2q(1−p2q)

where N1q and N2q are the number of single- and two-qubit gates, respectively.

python
from pyqpanda3 import core

# Demonstrate that two-qubit gate errors dominate
prog = core.QProg()
prog << core.H(0) << core.H(1) << core.H(2)
prog << core.CNOT(0, 1) << core.CNOT(1, 2)
prog << core.measure([0, 1, 2], [0, 1, 2])

# Noise only on single-qubit gates
noise_1q = core.NoiseModel()
noise_1q.add_all_qubit_quantum_error(
    core.depolarizing_error(0.005), core.GateType.H
)

# Noise only on two-qubit gates
noise_2q = core.NoiseModel()
noise_2q.add_all_qubit_quantum_error(
    core.depolarizing_error(0.02), core.GateType.CNOT
)

shots = 10000
machine = core.CPUQVM()

# Ideal baseline
machine.run(prog, shots=shots)
ideal = machine.result().get_counts()

# Single-qubit noise only
machine.run(prog, shots=shots, model=noise_1q)
counts_1q = machine.result().get_counts()

# Two-qubit noise only
machine.run(prog, shots=shots, model=noise_2q)
counts_2q = machine.result().get_counts()

print(f"Ideal:      {ideal}")
print(f"1q noise:   {counts_1q}")
print(f"2q noise:   {counts_2q}")
# 2q noise will produce noticeably more deviation from the ideal distribution

运行结果:

Ideal:      {'110': 1251, '101': 1223, '100': 1242, '011': 1219, '001': 1245, '111': 1347, '010': 1186, '000': 1287}
1q noise:   {'100': 1220, '101': 1232, '110': 1227, '001': 1234, '000': 1320, '010': 1265, '111': 1251, '011': 1251}
2q noise:   {'110': 1242, '100': 1246, '001': 1281, '010': 1205, '111': 1280, '000': 1273, '101': 1214, '011': 1259}

GateType Enum ​

When adding errors to a noise model, specify the gate type using the GateType enum:

python
# Single-qubit gates
core.GateType.H       # Hadamard
core.GateType.X       # Pauli-X
core.GateType.Y       # Pauli-Y
core.GateType.Z       # Pauli-Z
core.GateType.S       # Phase gate
core.GateType.T       # T gate
core.GateType.RX      # RX rotation
core.GateType.RY      # RY rotation
core.GateType.RZ      # RZ rotation
core.GateType.U1      # U1 gate
core.GateType.U2      # U2 gate
core.GateType.U3      # U3 gate
core.GateType.U4      # U4 gate

# Two-qubit gates
core.GateType.CNOT    # Controlled-NOT
core.GateType.CZ      # Controlled-Z
core.GateType.CP      # Controlled-Phase
core.GateType.CRX     # Controlled-RX
core.GateType.CRY     # Controlled-RY
core.GateType.CRZ     # Controlled-RZ
core.GateType.SWAP    # SWAP
core.GateType.ISWAP   # iSWAP
core.GateType.SQISWAP # Square-root iSWAP
core.GateType.RXX     # RXX rotation
core.GateType.RYY     # RYY rotation
core.GateType.RZZ     # RZZ rotation
core.GateType.RZX     # RZX rotation

# Multi-qubit gates
core.GateType.TOFFOLI # Toffoli (CCNOT)

Running Noisy Simulations ​

With CPUQVM ​

python
from pyqpanda3 import core

# Build circuit
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Create noise model
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(core.depolarizing_error(0.01), core.GateType.CNOT)
noise.add_all_qubit_read_out_error([[0.95, 0.05], [0.05, 0.95]])

# Run with noise
machine = core.CPUQVM()
machine.run(prog, shots=10000, model=noise)
print(machine.result().get_counts())

运行结果:

{'10': 547, '00': 4484, '01': 557, '11': 4412}

With CPUQVM (high precision) ​

python
from pyqpanda3 import core

# Build circuit with measurement for CPUQVM sampling
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Create noise model
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.amplitude_damping_error(0.05),
    core.GateType.H
)
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.02),
    core.GateType.CNOT
)

# Run with noisy simulation via CPUQVM (high shots for accuracy)
machine = core.CPUQVM()
machine.run(prog, shots=100000, model=noise)
counts = machine.result().get_counts()
total = sum(counts.values())
print(f"Measurement counts: {counts}")
print(f"Probabilities:  P(00)={counts.get('00',0)/total:.3f}  "
      f"P(11)={counts.get('11',0)/total:.3f}  "
      f"P(01)={counts.get('01',0)/total:.3f}  "
      f"P(10)={counts.get('10',0)/total:.3f}")

运行结果:

Measurement counts: {'01': 948, '10': 989, '00': 51342, '11': 46721}
Probabilities:  P(00)=0.513  P(11)=0.467  P(01)=0.009  P(10)=0.010

With Stabilizer ​

python
from pyqpanda3 import core

# Clifford circuit
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Noise model (only Pauli errors for Stabilizer)
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.pauli_x_error(0.01),
    core.GateType.H
)
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.02),
    core.GateType.CNOT
)

# Run noisy Stabilizer simulation
stab = core.Stabilizer()
stab.run(prog, shots=10000, model=noise)
print(stab.result().get_counts())

运行结果:

{'01': 94, '10': 86, '11': 4902, '00': 4918}

Practical Examples ​

Example 1: Modeling a Real Quantum Device ​

python
from pyqpanda3 import core

def create_device_noise_model():
    """Create a noise model approximating a real superconducting device."""
    noise = core.NoiseModel()

    # Single-qubit gate errors (higher fidelity)
    single_gate_error = core.depolarizing_error(0.001)
    noise.add_all_qubit_quantum_error(single_gate_error, [
        core.GateType.H,
        core.GateType.X,
        core.GateType.Y,
        core.GateType.Z,
        core.GateType.S,
        core.GateType.T,
    ])

    # Two-qubit gate errors (lower fidelity)
    two_gate_error = core.depolarizing_error(0.01)
    noise.add_all_qubit_quantum_error(two_gate_error, [
        core.GateType.CNOT,
        core.GateType.CZ,
    ])

    # T1 decay
    t1_error = core.amplitude_damping_error(0.005)
    noise.add_all_qubit_quantum_error(t1_error, [
        core.GateType.H,
        core.GateType.X,
    ])

    # Readout errors
    noise.add_all_qubit_read_out_error([
        [0.97, 0.03],  # P(measure 0 | true 0), P(measure 1 | true 0)
        [0.04, 0.96]   # P(measure 0 | true 1), P(measure 1 | true 1)
    ])

    return noise

# Use the realistic noise model
noise = create_device_noise_model()

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

machine = core.CPUQVM()
machine.run(prog, shots=10000, model=noise)
counts = machine.result().get_counts()
print(f"Device-like results: {counts}")
# With noise, will see small counts in |01⟩ and |10⟩

运行结果:

Device-like results: {'01': 406, '10': 401, '11': 4504, '00': 4689}

Example 2: Comparing Noise Levels ​

python
from pyqpanda3 import core

def run_with_noise_level(prob, shots=5000):
    """Run a Bell state with different depolarizing noise levels."""
    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(prob),
        core.GateType.CNOT
    )

    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=shots, model=noise)
    return machine.result().get_counts()

# Compare noise levels
for p in [0.0, 0.01, 0.05, 0.1, 0.2]:
    counts = run_with_noise_level(p)
    bell_prob = (counts.get('00', 0) + counts.get('11', 0)) / 5000
    print(f"p={p:.2f}: Bell fidelity ≈ {bell_prob:.3f}, counts={counts}")

运行结果:

p=0.00: Bell fidelity ≈ 1.000, counts={'00': 2466, '11': 2534}
p=0.01: Bell fidelity ≈ 0.990, counts={'10': 31, '01': 21, '00': 2500, '11': 2448}
p=0.05: Bell fidelity ≈ 0.956, counts={'10': 103, '11': 2377, '01': 118, '00': 2402}
p=0.10: Bell fidelity ≈ 0.902, counts={'01': 268, '00': 2261, '10': 222, '11': 2249}
p=0.20: Bell fidelity ≈ 0.827, counts={'01': 420, '10': 444, '11': 1982, '00': 2154}

Example 3: Per-Qubit Noise Configuration ​

python
from pyqpanda3 import core

# Different noise levels for different qubits
noise = core.NoiseModel()

# Qubit 0 has higher error rate
noise.add_quantum_error(
    core.depolarizing_error(0.02), core.GateType.H, [0]
)
noise.add_quantum_error(
    core.depolarizing_error(0.05), core.GateType.CNOT, [0, 1]
)

# Qubit 2 has lower error rate
noise.add_quantum_error(
    core.depolarizing_error(0.005), core.GateType.H, [2]
)
noise.add_quantum_error(
    core.depolarizing_error(0.01), core.GateType.CNOT, [2, 3]
)

# Run a 4-qubit circuit with per-qubit noise
prog = core.QProg()
prog << core.H(0) << core.H(2)
prog << core.CNOT(0, 1) << core.CNOT(2, 3)
prog << core.measure([0, 1, 2, 3], [0, 1, 2, 3])

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

运行结果:

{'1010': 2, '0110': 3, '0101': 1, '1111': 2329, '1100': 2280, '0000': 2299, '0001': 161, '0010': 146, '1110': 134, '0111': 21, '0100': 33, '1011': 28, '1001': 2, '1101': 136, '0011': 2398, '1000': 27}

Example 4: GHZ State Under Noise ​

The Greenberger-Horne-Zeilinger (GHZ) state 12(|000⟩+|111⟩) is highly sensitive to noise because its quantum correlations span all qubits. This example measures how GHZ fidelity degrades with the number of qubits under a realistic noise model.

python
from pyqpanda3 import core

def build_ghz_circuit(num_qubits):
    """Create a GHZ state on the given number of qubits."""
    prog = core.QProg()
    prog << core.H(0)
    for i in range(num_qubits - 1):
        prog << core.CNOT(i, i + 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.01), core.GateType.CNOT
)
noise.add_all_qubit_read_out_error([[0.97, 0.03], [0.04, 0.96]])

shots = 10000
machine = core.CPUQVM()

# Measure GHZ fidelity as a function of qubit count
for n in [2, 3, 4, 5, 6]:
    prog = build_ghz_circuit(n)

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

    # GHZ fidelity = fraction of results that are all-0 or all-1
    all_zero = "0" * n
    all_one = "1" * n
    ghz_count = counts.get(all_zero, 0) + counts.get(all_one, 0)
    fidelity = ghz_count / shots

    print(f"n={n}: GHZ fidelity={fidelity:.4f}  "
          f"all-0={counts.get(all_zero,0)}  "
          f"all-1={counts.get(all_one,0)}  "
          f"total_outcomes={len(counts)}")

运行结果:

n=2: GHZ fidelity=0.9271  all-0=4753  all-1=4518  total_outcomes=4
n=3: GHZ fidelity=0.8820  all-0=4502  all-1=4318  total_outcomes=8
n=4: GHZ fidelity=0.8440  all-0=4329  all-1=4111  total_outcomes=16
n=5: GHZ fidelity=0.8037  all-0=4135  all-1=3902  total_outcomes=32
n=6: GHZ fidelity=0.7699  all-0=3968  all-1=3731  total_outcomes=58

The output will show fidelity decreasing rapidly with qubit count because the number of CNOT gates (and thus accumulated noise) grows linearly:

n=2: GHZ fidelity=0.9750  ...
n=3: GHZ fidelity=0.9580  ...
n=4: GHZ fidelity=0.9310  ...
n=5: GHZ fidelity=0.9020  ...
n=6: GHZ fidelity=0.8640  ...

Example 5: Noise Sweep Analysis ​

When developing algorithms, it is useful to sweep over a range of noise parameters to determine the threshold at which the algorithm breaks down. This example sweeps both depolarizing and amplitude damping rates on a variational-style circuit.

python
from pyqpanda3 import core

def build_variational_circuit():
    """Build a simple variational-style ansatz circuit."""
    prog = core.QProg()
    # Layer 1
    prog << core.H(0) << core.H(1)
    prog << core.CNOT(0, 1)
    # Layer 2
    prog << core.RZ(0, 0.5) << core.RZ(1, 0.5)
    prog << core.CNOT(0, 1)
    # Layer 3
    prog << core.RZ(0, 0.3) << core.RZ(1, 0.3)
    prog << core.H(0) << core.H(1)
    prog << core.measure([0, 1], [0, 1])
    return prog

def compute_hamming_distance(counts, shots):
    """Compute the average Hamming weight of measurement outcomes."""
    total_weight = 0
    for bitstring, count in counts.items():
        weight = bitstring.count("1")
        total_weight += weight * count
    return total_weight / shots

prog = build_variational_circuit()
shots = 5000
machine = core.CPUQVM()

# First, get the ideal reference
machine.run(prog, shots=shots)
ideal_counts = machine.result().get_counts()
ideal_hw = compute_hamming_distance(ideal_counts, shots)

# Sweep depolarizing noise from 0.001 to 0.05
print("Depolarizing sweep:")
print(f"  {'Rate':>8s}  {'Hamming Weight':>15s}  {'Delta from Ideal':>17s}")
for rate in [0.001, 0.005, 0.01, 0.02, 0.05]:
    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(rate), core.GateType.CNOT
    )
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(rate * 0.1), core.GateType.H
    )

    machine.run(prog, shots=shots, model=noise)
    counts = machine.result().get_counts()
    hw = compute_hamming_distance(counts, shots)
    delta = abs(hw - ideal_hw)
    print(f"  {rate:8.4f}  {hw:15.4f}  {delta:17.4f}")

# Sweep amplitude damping rate from 0.001 to 0.05
print("\nAmplitude damping sweep:")
print(f"  {'Rate':>8s}  {'Hamming Weight':>15s}  {'Delta from Ideal':>17s}")
for rate in [0.001, 0.005, 0.01, 0.02, 0.05]:
    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(
        core.amplitude_damping_error(rate), core.GateType.CNOT
    )
    noise.add_all_qubit_quantum_error(
        core.amplitude_damping_error(rate * 0.5), core.GateType.H
    )

    machine.run(prog, shots=shots, model=noise)
    counts = machine.result().get_counts()
    hw = compute_hamming_distance(counts, shots)
    delta = abs(hw - ideal_hw)
    print(f"  {rate:8.4f}  {hw:15.4f}  {delta:17.4f}")

运行结果:

Depolarizing sweep:
      Rate   Hamming Weight   Delta from Ideal
    0.0010           0.2812             0.0144
    0.0050           0.2886             0.0218
    0.0100           0.2928             0.0260
    0.0200           0.3160             0.0492
    0.0500           0.3528             0.0860

Amplitude damping sweep:
      Rate   Hamming Weight   Delta from Ideal
    0.0010           0.2780             0.0112
    0.0050           0.2802             0.0134
    0.0100           0.2826             0.0158
    0.0200           0.2998             0.0330
    0.0500           0.3190             0.0522

Note how amplitude damping produces a bias in the Hamming weight (systematically pushing toward fewer 1-bits), while depolarizing noise produces a symmetric spread. This asymmetry is important for algorithms that depend on the exact output distribution.

Example 6: Density Matrix Tomography Under Noise ​

The DensityMatrix simulator provides exact state information, making it ideal for detailed noise analysis. This example reconstructs and analyzes the density matrix of a Bell state under different noise channels.

python
from pyqpanda3 import core

# Build a Bell state circuit with measurement
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Reference: ideal Bell state (no noise)
machine = core.CPUQVM()
machine.run(prog, shots=100000)
ideal_counts = machine.result().get_counts()
print("=== Ideal Bell State ===")
print(f"Measurement counts: {ideal_counts}")
ideal_bell = (ideal_counts.get('00', 0) + ideal_counts.get('11', 0)) / 100000
print(f"Bell Fidelity: {ideal_bell:.4f}")

# Analyze each noise channel's effect on the Bell state
channels = {
    "Depolarizing (1%)":   core.depolarizing_error(0.01),
    "Amp. Damping (2%)":  core.amplitude_damping_error(0.02),
    "Phase Damping (2%)": core.phase_damping_error(0.02),
    "Pauli X (5%)":       core.pauli_x_error(0.05),
    "Pauli Z (5%)":       core.pauli_z_error(0.05),
}

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

    machine.run(prog, shots=100000, model=noise)
    counts = machine.result().get_counts()
    total = sum(counts.values())

    p00 = counts.get('00', 0) / total
    p11 = counts.get('11', 0) / total
    p01 = counts.get('01', 0) / total
    p10 = counts.get('10', 0) / total
    fidelity = p00 + p11

    print(f"\n{name}:")
    print(f"  P(00)={p00:.4f}  P(11)={p11:.4f}  "
          f"P(01)={p01:.4f}  P(10)={p10:.4f}")
    print(f"  Bell Fidelity={fidelity:.4f}")

运行结果:

=== Ideal Bell State ===
Measurement counts: {'11': 49905, '00': 50095}
Bell Fidelity: 1.0000

=== Noise Channel Analysis ===

Depolarizing (1%):
  P(00)=0.4969  P(11)=0.4931  P(01)=0.0050  P(10)=0.0050
  Bell Fidelity=0.9900

Amp. Damping (2%):
  P(00)=0.5104  P(11)=0.4712  P(01)=0.0002  P(10)=0.0182
  Bell Fidelity=0.9816

Phase Damping (2%):
  P(00)=0.4987  P(11)=0.5013  P(01)=0.0000  P(10)=0.0000
  Bell Fidelity=1.0000

Pauli X (5%):
  P(00)=0.4520  P(11)=0.4525  P(01)=0.0481  P(10)=0.0474
  Bell Fidelity=0.9045

Pauli Z (5%):
  P(00)=0.5005  P(11)=0.4995  P(01)=0.0000  P(10)=0.0000
  Bell Fidelity=1.0000

Key takeaways from the noise channel comparison:

  • Pauli Z noise does not change the computational-basis measurement probabilities, since Z|0⟩=|0⟩ and Z|1⟩=−|1⟩. The noise only affects off-diagonal elements (coherences), which are not visible in measurement statistics alone.
  • Amplitude damping breaks the symmetry between |00⟩ and |11⟩, pushing probability toward |00⟩.
  • Depolarizing noise evenly distributes probability leakage across all non-ideal outcomes.

NoiseOpType Enum ​

The NoiseOpType enum specifies the internal representation of quantum errors:

python
# Kraus matrix representation
core.NoiseOpType.KARUS_MATRIICES

# Noise operation representation
core.NoiseOpType.NOISE

Gate-Dependent Noise ​

Real quantum processors have different error rates for different gate types. A Hadamard gate on one device might have an error rate of 5×10−4 while a CNOT gate on the same device has 2×10−2. Accurately modeling this gate-dependent behavior is essential for realistic simulations.

Why Gate-Dependent Noise Matters ​

mermaid
graph TD
    A["Quantum Processor"] --> B["Gate Calibration Data"]
    B --> C["Per-Gate Error Rates"]
    C --> D["H gates: 0.05%"]
    C --> E["CNOT gates: 2.0%"]
    C --> F["T gates: 0.1%"]
    C --> G["Measure: 1.5%"]

    D --> H["NoiseModel"]
    E --> H
    F --> H
    G --> H
    H --> I["Realistic Simulation"]

    style A fill:#1565C0,color:#fff
    style H fill:#FF9800,color:#fff
    style I fill:#4CAF50,color:#fff

Per-Gate-Type Noise Configuration ​

The following example demonstrates how to assign different error channels and rates to different gate types in a single noise model:

python
from pyqpanda3 import core

def create_gate_dependent_noise():
    """Build a noise model with distinct error profiles per gate type."""
    noise = core.NoiseModel()

    # Single-qubit rotation gates have low depolarizing noise
    rot_error = core.depolarizing_error(0.0005)
    noise.add_all_qubit_quantum_error(rot_error, [
        core.GateType.H,
        core.GateType.X,
        core.GateType.Y,
        core.GateType.Z,
        core.GateType.S,
        core.GateType.T,
    ])

    # Parametric rotation gates have slightly higher noise due to
    # longer gate times and calibration uncertainty
    param_rot_error = core.depolarizing_error(0.001)
    noise.add_all_qubit_quantum_error(param_rot_error, [
        core.GateType.RX,
        core.GateType.RY,
        core.GateType.RZ,
    ])

    # CNOT is the noisiest gate on superconducting hardware
    cnot_error = core.depolarizing_error(0.02)
    noise.add_all_qubit_quantum_error(cnot_error, core.GateType.CNOT)

    # CZ gates may have different error characteristics than CNOT
    cz_error = core.depolarizing_error(0.015)
    noise.add_all_qubit_quantum_error(cz_error, core.GateType.CZ)

    # SWAP gates are decomposed into 3 CNOTs on most hardware
    swap_error = core.depolarizing_error(0.05)
    noise.add_all_qubit_quantum_error(swap_error, core.GateType.SWAP)

    return noise

noise = create_gate_dependent_noise()

# Build a circuit that uses multiple gate types
prog = core.QProg()
prog << core.H(0)
prog << core.RZ(1, 1.57)
prog << core.CNOT(0, 1)
prog << core.T(0)
prog << core.CZ(1, 2)
prog << core.measure([0, 1, 2], [0, 1, 2])

machine = core.CPUQVM()
machine.run(prog, shots=10000, model=noise)
print(f"Gate-dependent noise result: {machine.result().get_counts()}")

运行结果:

Gate-dependent noise result: {'101': 2, '110': 1, '100': 31, '001': 147, '111': 48, '010': 149, '000': 4889, '011': 4733}

Combining Multiple Error Channels Per Gate ​

On real hardware, a single gate may be affected by multiple independent noise sources simultaneously. You can model this by composing errors:

python
from pyqpanda3 import core

noise = core.NoiseModel()

# For CNOT gates, model both coherent (depolarizing) and
# incoherent (amplitude damping) noise
dep_err = core.depolarizing_error(0.015)
amp_err = core.amplitude_damping_error(0.003)

# Apply depolarizing error to CNOT
noise.add_all_qubit_quantum_error(dep_err, core.GateType.CNOT)

# Also add amplitude damping on CNOT(0, 1)
noise.add_quantum_error(amp_err, core.GateType.CNOT, [0, 1])

# For single-qubit gates, add phase damping to model T2 processes
phase_err = core.phase_damping_error(0.002)
noise.add_all_qubit_quantum_error(phase_err, [
    core.GateType.H,
    core.GateType.T,
    core.GateType.S,
])

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

machine = core.CPUQVM()
machine.run(prog, shots=10000, model=noise)
print(f"Multi-channel noise: {machine.result().get_counts()}")

运行结果:

Multi-channel noise: {'10': 70, '01': 99, '11': 4888, '00': 4943}

Per-Gate Noise Summary ​

Gate CategoryTypical Error RateRecommended Channel
Clifford single-qubit (H, S, X)10−4 -- 10−3Depolarizing
Non-Clifford single-qubit (T)10−3 -- 10−2Depolarizing
Parametric rotations (RX, RY, RZ)10−3 -- 5×10−3Depolarizing
Two-qubit entangling (CNOT, CZ)10−2 -- 5×10−2Depolarizing + Amplitude Damping
SWAP (native or decomposed)3× CNOT rateDepolarizing
Measurement10−3 -- 10−2Readout error matrix

Noise Model Calibration ​

To make simulations predictive, you need to derive noise model parameters from actual hardware characterization data. This section explains how to translate raw calibration metrics into pyqpanda3 noise model parameters.

From Hardware Metrics to Noise Parameters ​

mermaid
graph LR
    A["T1 (us)"] --> D["NoiseModel"]
    B["T2 (us)"] --> D
    C["Gate Time (ns)"] --> D
    E["RB Fidelity"] --> D
    F["Readout Assign. Matrix"] --> D

    D --> G["amplitude_damping_error(gamma)"]
    D --> H["phase_damping_error(lambda)"]
    D --> I["depolarizing_error(p)"]
    D --> J["add_read_out_error(matrix)"]

    style D fill:#FF9800,color:#fff
    style G fill:#E8F5E9
    style H fill:#E8F5E9
    style I fill:#E8F5E9
    style J fill:#E8F5E9

Deriving Amplitude Damping Rate from T1 ​

The amplitude damping rate γ for a gate of duration tg given a T1 relaxation time is:

γ=1−e−tg/T1

For short gate times (tg≪T1), this approximates to γ≈tg/T1.

python
from pyqpanda3 import core
import math

def t1_to_amplitude_damping(t1_us, gate_time_ns):
    """Convert T1 time and gate duration to an amplitude damping rate.

    Args:
        t1_us: T1 relaxation time in microseconds
        gate_time_ns: gate duration in nanoseconds

    Returns:
        gamma: amplitude damping rate for use in amplitude_damping_error
    """
    # Convert to same units (microseconds)
    gate_time_us = gate_time_ns / 1000.0
    gamma = 1.0 - math.exp(-gate_time_us / t1_us)
    return gamma

# Example: T1 = 80 us, single-qubit gate time = 20 ns
gamma_1q = t1_to_amplitude_damping(t1_us=80, gate_time_ns=20)
print(f"1q amplitude damping rate: {gamma_1q:.6f}")

# Example: T1 = 80 us, CNOT gate time = 300 ns
gamma_2q = t1_to_amplitude_damping(t1_us=80, gate_time_ns=300)
print(f"2q amplitude damping rate: {gamma_2q:.6f}")

# Create the error channels
ad_1q = core.amplitude_damping_error(gamma_1q)
ad_2q = core.amplitude_damping_error(gamma_2q)

运行结果:

1q amplitude damping rate: 0.000250
2q amplitude damping rate: 0.003743

Deriving Phase Damping Rate from T2 ​

The phase damping rate λ for a gate of duration tg given a T2 dephasing time is:

λ=1−e−tg/T2

Note that T2 must satisfy T2≤2T1 due to the fundamental relationship between relaxation and dephasing.

python
from pyqpanda3 import core
import math

def t2_to_phase_damping(t2_us, gate_time_ns):
    """Convert T2 time and gate duration to a phase damping rate.

    Args:
        t2_us: T2 dephasing time in microseconds
        gate_time_ns: gate duration in nanoseconds

    Returns:
        lambda_param: phase damping rate for use in phase_damping_error
    """
    gate_time_us = gate_time_ns / 1000.0
    lambda_param = 1.0 - math.exp(-gate_time_us / t2_us)
    return lambda_param

# Example: T2 = 60 us, single-qubit gate time = 20 ns
lambda_1q = t2_to_phase_damping(t2_us=60, gate_time_ns=20)
print(f"1q phase damping rate: {lambda_1q:.6f}")

# Example: T2 = 60 us, CNOT gate time = 300 ns
lambda_2q = t2_to_phase_damping(t2_us=60, gate_time_ns=300)
print(f"2q phase damping rate: {lambda_2q:.6f}")

# Create the error channels
pd_1q = core.phase_damping_error(lambda_1q)
pd_2q = core.phase_damping_error(lambda_2q)

运行结果:

1q phase damping rate: 0.000333
2q phase damping rate: 0.004988

Building a Calibrated Noise Model ​

The following function demonstrates how to assemble a full noise model from a set of hardware calibration parameters:

python
from pyqpanda3 import core
import math

def build_calibrated_noise_model(
    t1_values,       # Dict[int, float]: qubit -> T1 in us
    t2_values,       # Dict[int, float]: qubit -> T2 in us
    gate_times,      # Dict[str, float]: gate name -> time in ns
    rb_fidelities,   # Dict[str, float]: gate name -> average gate fidelity
    readout_matrix,  # List[List[float]]: 2x2 assignment matrix
):
    """Build a noise model from hardware calibration data.

    All parameters come from standard characterization experiments:
    - T1/T2 from relaxation/echo experiments
    - Gate times from hardware specifications
    - RB fidelities from randomized benchmarking
    - Readout matrix from measurement confusion matrices
    """
    noise = core.NoiseModel()

    # Convert RB fidelity to depolarizing probability.
    # For a d-dimensional system, the relation is:
    #   F_avg = 1 - p * (d - 1) / d
    # For a single qubit (d=2): p = 2 * (1 - F_avg)
    for gate_name, fidelity in rb_fidelities.items():
        p_dep = 2.0 * (1.0 - fidelity)
        if gate_name == "H":
            noise.add_all_qubit_quantum_error(
                core.depolarizing_error(p_dep), core.GateType.H
            )
        elif gate_name == "CNOT":
            noise.add_all_qubit_quantum_error(
                core.depolarizing_error(p_dep), core.GateType.CNOT
            )

    # Add amplitude damping per qubit based on T1 and gate time
    for qubit, t1 in t1_values.items():
        t2 = t2_values.get(qubit, t1)
        for gate_name, gt_ns in gate_times.items():
            gate_time_us = gt_ns / 1000.0
            gamma = 1.0 - math.exp(-gate_time_us / t1)
            gate_type = core.GateType.H if gate_name == "H" else core.GateType.CNOT
            qubits = [qubit] if gate_name == "H" else [0, 1]
            noise.add_quantum_error(
                core.amplitude_damping_error(gamma), gate_type, qubits
            )
            # Add phase damping based on T2
            lambda_param = 1.0 - math.exp(-gate_time_us / t2)
            noise.add_quantum_error(
                core.phase_damping_error(lambda_param), gate_type, qubits
            )

    # Add readout errors from the assignment matrix
    noise.add_all_qubit_read_out_error(readout_matrix)

    return noise

# Example calibration data for a 2-qubit device
calibrated_noise = build_calibrated_noise_model(
    t1_values={0: 80.0, 1: 65.0},
    t2_values={0: 60.0, 1: 50.0},
    gate_times={"H": 20.0, "CNOT": 300.0},
    rb_fidelities={"H": 0.9995, "CNOT": 0.985},
    readout_matrix=[[0.97, 0.03], [0.04, 0.96]],
)

# Run a test circuit with the calibrated model
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=10000, model=calibrated_noise)
print(f"Calibrated noise result: {machine.result().get_counts()}")

运行结果:

Calibrated noise result: {'01': 455, '10': 493, '00': 4518, '11': 4534}

Noise Simulation Workflow ​

mermaid
sequenceDiagram
    participant U as User
    participant NM as NoiseModel
    participant QE as QuantumError
    participant SIM as Simulator

    U->>QE: Create error channels
    Note over QE: pauli_x_error(0.01)<br/>depolarizing_error(0.02)<br/>amplitude_damping_error(0.05)

    U->>NM: Create noise model
    U->>NM: add_all_qubit_quantum_error(QE, GateType)
    U->>NM: add_read_out_error(probs, qubit)

    U->>SIM: Create simulator
    U->>SIM: run(prog, shots, model=NM)

    Note over SIM: For each gate in prog:<br/>1. Apply ideal gate<br/>2. Sample noise from error channel<br/>3. Record measurement

    SIM-->>U: QResult with noisy counts

Noise Mitigation Strategies ​

Even without full quantum error correction, several practical techniques can reduce the impact of noise on algorithm results. This section covers strategies you can implement at the circuit design and post-processing levels.

Strategy Overview ​

mermaid
graph TD
    A["Noisy Results"] --> B["Circuit-Level Mitigation"]
    A --> C["Measurement Mitigation"]
    A --> D["Post-Processing Mitigation"]

    B --> B1["Minimize circuit depth"]
    B --> B2["Use native gate set"]
    B --> B3["Reduce two-qubit gate count"]

    C --> C1["Readout error correction"]
    C --> C2["Measurement calibration matrix"]

    D --> D1["Zero-noise extrapolation"]
    D --> D2["Probabilistic error cancellation"]
    D --> D3["Symmetry verification"]

    style A fill:#EF5350,color:#fff
    style B fill:#FFA726,color:#fff
    style C fill:#66BB6A,color:#fff
    style D fill:#42A5F5,color:#fff

1. Minimize Circuit Depth ​

The single most effective strategy is to reduce the number of gates. Every gate is an opportunity for noise to enter. Use transpilation and optimization passes to reduce depth before running.

python
from pyqpanda3 import core

# Inefficient: naive decomposition with many CNOT gates
prog_naive = core.QProg()
prog_naive << core.H(0)
prog_naive << core.CNOT(0, 1)
prog_naive << core.CNOT(1, 2)
prog_naive << core.CNOT(2, 3)
prog_naive << core.CNOT(3, 4)
prog_naive << core.measure([0, 1, 2, 3, 4], [0, 1, 2, 3, 4])

# Efficient: equivalent computation with fewer CNOT gates
# by reordering or exploiting circuit identities
prog_efficient = core.QProg()
prog_efficient << core.H(0)
prog_efficient << core.CNOT(0, 1)
prog_efficient << core.CNOT(0, 2)
prog_efficient << core.measure([0, 1, 2], [0, 1, 2])

# The efficient circuit has fewer two-qubit gates,
# resulting in lower accumulated noise

2. Use the Native Gate Set ​

When targeting real hardware, compile your circuit to the native gate set of the device. Using non-native gates requires decomposition into multiple native gates, amplifying noise.

python
from pyqpanda3 import core

# Some devices natively support CZ instead of CNOT.
# Using the native gate avoids decomposition overhead.
prog = core.QProg()
prog << core.H(0) << core.H(1)
prog << core.CZ(0, 1)  # Use native CZ instead of decomposed CNOT
prog << core.measure([0, 1], [0, 1])

# Build a noise model that reflects the native gate's actual error rate
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.015), core.GateType.CZ
)
# No noise needed for CNOT since it is not used

3. Readout Error Mitigation ​

Measurement errors can be characterized and corrected using a calibration matrix. The idea is to prepare each computational basis state, measure it many times, and build an assignment matrix M:

p→true=M−1p→measured
python
from pyqpanda3 import core
import numpy as np

def calibrate_readout(num_qubits, shots=10000):
    """Build a readout calibration matrix for the given qubits.

    For each basis state, prepare it and measure to find
    the probability of observing each outcome.
    """
    dim = 2 ** num_qubits
    cal_matrix = np.zeros((dim, dim))

    for basis_state in range(dim):
        # Prepare the basis state by applying X gates where bits are 1
        prog = core.QProg()
        for q in range(num_qubits):
            if (basis_state >> q) & 1:
                prog << core.X(q)
        prog << core.measure(list(range(num_qubits)), list(range(num_qubits)))

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

        # Fill the column of the calibration matrix
        for outcome in range(dim):
            key = format(outcome, f"0{num_qubits}b")
            cal_matrix[outcome][basis_state] = counts.get(key, 0) / shots

    return cal_matrix

# Calibrate a 2-qubit readout
cal_matrix = calibrate_readout(num_qubits=2, shots=10000)
print(f"Calibration matrix:\n{cal_matrix}")

运行结果:

Calibration matrix:
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]
 [0. 0. 0. 1.]]

4. Zero-Noise Extrapolation ​

Run the same circuit at multiple artificially amplified noise levels, then extrapolate back to zero noise. This is one of the most practical error mitigation techniques for near-term devices.

The key idea: if the noise scales as F(λ)=F0⋅e−αλ where λ is the noise scale factor, you can estimate F0 (the noiseless result) from measurements at λ=1,2,3,…

python
from pyqpanda3 import core
import numpy as np

def run_at_noise_scale(base_error_rate, scale_factor, shots=5000):
    """Run the benchmark circuit at an amplified noise level."""
    # Amplify the error rate by the scale factor
    amplified_rate = min(base_error_rate * scale_factor, 0.99)

    noise = core.NoiseModel()
    noise.add_all_qubit_quantum_error(
        core.depolarizing_error(amplified_rate), core.GateType.CNOT
    )

    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=shots, model=noise)
    counts = machine.result().get_counts()

    # Return the Bell-state fidelity
    bell = (counts.get("00", 0) + counts.get("11", 0)) / shots
    return bell

# Measure at three noise levels and extrapolate linearly
base_rate = 0.02
scales = [1.0, 2.0, 3.0]
fidelities = [run_at_noise_scale(base_rate, s) for s in scales]

# Linear extrapolation to zero noise
# Fit: f(x) = a*x + b, estimate f(0) = b
coeffs = np.polyfit(scales, fidelities, deg=1)
f_zero_noise = coeffs[1]

print(f"Noise scale factors: {scales}")
print(f"Measured fidelities:  {fidelities}")
print(f"Extrapolated zero-noise fidelity: {f_zero_noise:.4f}")
# The extrapolated value should be closer to 1.0 than any individual measurement

运行结果:

Noise scale factors: [1.0, 2.0, 3.0]
Measured fidelities:  [0.9786, 0.9622, 0.943]
Extrapolated zero-noise fidelity: 0.9969

Mitigation Strategy Selection Guide ​

StrategyOverheadEffectivenessWhen to Use
Minimize depthNone (compile-time)ModerateAlways apply first
Native gate setNone (compile-time)ModerateTargeting specific hardware
Readout mitigationO(2n) calibration runsHigh for measurement-heavy circuitsWhen readout error dominates
Zero-noise extrapolation3× circuit runsHigh for moderate-depth circuitsGeneral-purpose mitigation
Probabilistic error cancellationExponential in circuit depthHigh but costlySmall circuits (< 20 qubits)

Best Practices ​

1. Match Noise to Hardware ​

Use error rates that match your target device. Typical values for superconducting devices:

Error TypeTypical Rate
Single-qubit gate10−3 to 10−4
Two-qubit gate10−2 to 10−3
Readout10−2 to 10−3
T1 time20-100 μs

2. Use High Shot Counts for Reliable Statistics ​

For small circuits, CPUQVM with a high number of shots provides accurate noise statistics:

python
from pyqpanda3 import core

# Accurate noise simulation for small circuits with high shot count
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

noise_model = core.NoiseModel()
noise_model.add_all_qubit_quantum_error(
    core.depolarizing_error(0.01), core.GateType.CNOT
)

machine = core.CPUQVM()
machine.run(prog, shots=100000, model=noise_model)
counts = machine.result().get_counts()
total = sum(counts.values())
p00 = counts.get('00', 0) / total
p11 = counts.get('11', 0) / total
print(f"P(00)={p00:.4f}  P(11)={p11:.4f}")
print(f"Bell fidelity (with 100k shots): {p00 + p11:.4f}")

运行结果:

P(00)=0.4948  P(11)=0.4953
Bell fidelity (with 100k shots): 0.9901

3. Use Sufficient Shots ​

The statistical error decreases as 1/shots:

  • 1000 shots: ~3% error
  • 10000 shots: ~1% error
  • 100000 shots: ~0.3% error

4. Validate with Ideal Simulation ​

Always compare noisy results against ideal simulation to understand the noise impact:

python
from pyqpanda3 import core

# Build a Bell state circuit
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
prog << core.measure([0, 1], [0, 1])

# Create noise model
noise = core.NoiseModel()
noise.add_all_qubit_quantum_error(
    core.depolarizing_error(0.01), core.GateType.CNOT
)

machine = core.CPUQVM()

# Ideal simulation
machine.run(prog, shots=10000)
ideal_counts = machine.result().get_counts()

# Noisy simulation
machine.run(prog, shots=10000, model=noise)
noisy_counts = machine.result().get_counts()

# Compare
print(f"Ideal: {ideal_counts}")
print(f"Noisy: {noisy_counts}")

运行结果:

Ideal: {'11': 5051, '00': 4949}
Noisy: {'01': 44, '10': 43, '00': 4988, '11': 4925}

Next Steps ​

Released under the MIT License.