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:
- Predict how algorithms perform on real hardware
- Develop noise-resilient algorithms
- Characterize and calibrate quantum devices
- Verify error correction schemes
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:
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: FalseAdding Gate Errors
Add quantum errors to specific gates and qubits:
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:
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
where
1. Pauli X Error (Bit Flip)
The bit flip channel randomly applies the X (NOT) gate with probability
Kraus operators:
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
2. Pauli Y Error
The Pauli Y error applies the Y gate with probability
Kraus operators:
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
Kraus operators:
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
4. Depolarizing Error
The depolarizing channel replaces the state with the maximally mixed state
For a single qubit (
Kraus operators:
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
Kraus operators:
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):
Kraus operators:
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:
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
| Error | Physical Model | Kraus Operators | Parameter |
|---|---|---|---|
pauli_x_error(p) | Bit flip | ||
pauli_y_error(p) | Bit+Phase flip | ||
pauli_z_error(p) | Phase flip | ||
depolarizing_error(p) | Generic noise | 4 operators | |
amplitude_damping_error(γ) | T1 decay | 2 operators | |
phase_damping_error(λ) | T2 dephasing | 2 operators | |
decoherence_error(T1, T2, t_gate) | T1+T2 combined | Multiple | T1, T2 (us), t_gate (ns) |
QuantumError Class
The QuantumError class wraps noise data and provides composition operations:
Creating Quantum Errors
from pyqpanda3 import core
import numpy as np
# Method 1: From Kraus matrices (list of numpy arrays)
kraus = [
np.sqrt(0.99) * np.eye(2),
np.sqrt(0.01) * np.array([[0, 1], [1, 0]])
]
error = core.QuantumError(kraus)
# Method 2: From noise map (Pauli probabilities)
noise_map = {"X": 0.01, "Y": 0.005, "Z": 0.005}
error2 = core.QuantumError(noise_map)Error Properties
from pyqpanda3 import core
# Get number of qubits affected
print(f"Qubits affected: {error.qubit_num()}")
# Get the error type
print(f"Error type: {error.error_type()}")
# Get Kraus matrices
karus = error.error_karus()
print(f"Kraus matrices: {karus}")Error Composition
from pyqpanda3 import core
# Tensor product of two errors (independent errors on different qubits)
combined = error.tensor(error2)
# Expansion to a larger system
expanded = error.expand(error2)
# Sequential composition (error applied after another)
composed = error.compose(error2)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
Channel Comparison Table
The table below compares all seven error channels on key characteristics:
| Channel | Preserves Energy? | Preserves Phase? | CPTP? | Unital? | Typical Physical Source |
|---|---|---|---|---|---|
| Pauli X | No | Yes | Yes | Yes | Spin relaxation |
| Pauli Y | No | No | Yes | Yes | Combined bit+phase flip |
| Pauli Z | Yes | No | Yes | Yes | Magnetic flux noise |
| Depolarizing | No | No | Yes | Yes | Generic gate error |
| Amplitude Damping | No (irreversible) | Yes | Yes | No | T1 energy decay |
| Phase Damping | Yes | No | Yes | Yes | T2 pure dephasing |
| Decoherence | No | No | Yes | No | Combined T1+T2 |
A channel is unital if it maps the identity to the identity:
. Unital channels drive states toward the maximally mixed state .
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.
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}")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
and . - 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
toward , so gets more counts than .
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
where
The output fidelity (overlap with the ideal state) follows an exponential decay:
where
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.
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)}")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:
where
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 distributionGateType Enum
When adding errors to a noise model, specify the gate type using the GateType enum:
# 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
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())With DensityMatrixSimulator
from pyqpanda3 import core
# Build circuit (no measurement needed)
prog = core.QProg()
prog << core.H(0) << core.CNOT(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 exact noise simulation
dm_sim = core.DensityMatrixSimulator()
dm_sim.run(prog, noise)
# Get noisy state probabilities
print(f"Probabilities: {dm_sim.state_probs()}")
print(f"Density matrix:\n{dm_sim.density_matrix()}")Expected output:
Probabilities: {0: 0.475, 1: 0.025, 2: 0.025, 3: 0.475}
Density matrix:
[[0.475 0. 0. 0.475]
[0. 0.025 0.025 0. ]
[0. 0.025 0.025 0. ]
[0.475 0. 0. 0.475]]With Stabilizer
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())Practical Examples
Example 1: Modeling a Real Quantum Device
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⟩Example 2: Comparing Noise Levels
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}")Example 3: Per-Qubit Noise Configuration
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())Example 4: GHZ State Under Noise
The Greenberger-Horne-Zeilinger (GHZ) state
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)}")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.
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}")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.
from pyqpanda3 import core
import numpy as np
# Build a Bell state circuit without measurement
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
# Reference: ideal density matrix
dm_sim = core.DensityMatrixSimulator()
dm_sim.run(prog)
ideal_dm = dm_sim.density_matrix()
ideal_probs = dm_sim.state_probs()
print("=== Ideal Bell State ===")
print(f"Probabilities: {ideal_probs}")
print(f"Purity: {np.trace(np.dot(ideal_dm, ideal_dm)):.6f}")
# Ideal Bell state has purity = 1.0
# Analyze each noise channel's effect on the density matrix
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)
dm_sim.run(prog, noise)
noisy_dm = dm_sim.density_matrix()
noisy_probs = dm_sim.state_probs()
# Compute purity: Tr(rho^2)
purity = np.trace(np.dot(noisy_dm, noisy_dm))
# Compute fidelity with ideal state
# F(rho, sigma) = [Tr(sqrt(sqrt(rho) * sigma * sqrt(rho)))]^2
# For pure ideal state |psi>: F = <psi|rho|psi>
fidelity = np.real(ideal_dm[0, 0] * noisy_dm[0, 0]
+ ideal_dm[0, 3] * noisy_dm[3, 0]
+ ideal_dm[3, 0] * noisy_dm[0, 3]
+ ideal_dm[3, 3] * noisy_dm[3, 3])
print(f"\n{name}:")
print(f" P(00)={noisy_probs.get(0, 0):.4f} "
f"P(11)={noisy_probs.get(3, 0):.4f} "
f"P(01)={noisy_probs.get(1, 0):.4f} "
f"P(10)={noisy_probs.get(2, 0):.4f}")
print(f" Purity={purity:.4f} Bell Fidelity={fidelity:.4f}")Key takeaways from the density matrix analysis:
- Purity drops below 1.0 for all noise channels, quantifying how much mixedness the noise introduces.
- Pauli Z noise reduces purity but does not change the computational-basis probabilities, since
and . The noise only affects off-diagonal elements (coherences). - Amplitude damping breaks the symmetry between
and , pushing probability toward . - Depolarizing noise evenly distributes probability leakage across all non-ideal outcomes.
NoiseOpType Enum
The NoiseOpType enum specifies the internal representation of quantum errors:
# Kraus matrix representation
core.NoiseOpType.KARUS_MATRIICES
# Noise operation representation
core.NoiseOpType.NOISEGate-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
Why Gate-Dependent Noise Matters
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:
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()}")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:
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()}")Per-Gate Noise Summary
| Gate Category | Typical Error Rate | Recommended Channel |
|---|---|---|
| Clifford single-qubit (H, S, X) | Depolarizing | |
| Non-Clifford single-qubit (T) | Depolarizing | |
| Parametric rotations (RX, RY, RZ) | Depolarizing | |
| Two-qubit entangling (CNOT, CZ) | Depolarizing + Amplitude Damping | |
| SWAP (native or decomposed) | Depolarizing | |
| Measurement | Readout 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
Deriving Amplitude Damping Rate from T1
The amplitude damping rate
For short gate times (
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)Deriving Phase Damping Rate from T2
The phase damping rate
Note that
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)Building a Calibrated Noise Model
The following function demonstrates how to assemble a full noise model from a set of hardware calibration parameters:
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()}")Noise Simulation Workflow
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
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.
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 noise2. 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.
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 used3. 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
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}")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
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 measurementMitigation Strategy Selection Guide
| Strategy | Overhead | Effectiveness | When to Use |
|---|---|---|---|
| Minimize depth | None (compile-time) | Moderate | Always apply first |
| Native gate set | None (compile-time) | Moderate | Targeting specific hardware |
| Readout mitigation | High for measurement-heavy circuits | When readout error dominates | |
| Zero-noise extrapolation | High for moderate-depth circuits | General-purpose mitigation | |
| Probabilistic error cancellation | Exponential in circuit depth | High but costly | Small circuits (< 20 qubits) |
Best Practices
1. Match Noise to Hardware
Use error rates that match your target device. Typical values for superconducting devices:
| Error Type | Typical Rate |
|---|---|
| Single-qubit gate | |
| Two-qubit gate | |
| Readout | |
| T1 time | 20-100 μs |
2. Use DensityMatrixSimulator for Small Circuits
For circuits with fewer than ~10 qubits, the DensityMatrixSimulator provides exact noise evolution:
from pyqpanda3 import core
# Exact noise simulation for small circuits
prog = core.QProg()
prog << core.H(0) << core.CNOT(0, 1)
noise_model = core.NoiseModel()
noise_model.add_all_qubit_quantum_error(
core.depolarizing_error(0.01), core.GateType.CNOT
)
dm_sim = core.DensityMatrixSimulator()
dm_sim.run(prog, noise_model)
exact_probs = dm_sim.state_probs()
print(f"Exact probabilities: {exact_probs}")3. Use Sufficient Shots
The statistical error decreases as
- 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:
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}")Next Steps
- Simulation - Overview of all simulators
- Quantum Information - Distance metrics for comparing ideal vs noisy states
- Cloud Computing - Running on real noisy hardware