Qiskit Intermediate Free 25/61 in series 13 min read

Building Noise Models in Qiskit

Build realistic noise models in Qiskit to simulate quantum hardware behavior: depolarizing errors, T1/T2 decay, readout errors, and importing noise from real IBM backends.

What you'll learn

  • Qiskit
  • noise model
  • AerSimulator
  • decoherence
  • error simulation

Prerequisites

  • Python proficiency
  • Beginner quantum computing concepts (superposition, entanglement)
  • Linear algebra basics

Running a circuit on a noiseless simulator gives you the ideal output. Running it on real hardware gives you noise. Between those two extremes, a calibrated noise model gives you a realistic simulation that helps you understand what will happen on hardware without spending actual quantum compute time or credits.

Noise models are essential for testing error mitigation techniques, benchmarking algorithm robustness, and understanding whether your circuit will survive on a specific device.

Installation

pip install qiskit qiskit-aer qiskit-ibm-runtime

Why Simulate Noise

Before submitting a circuit to a real backend:

  1. Predict outcomes: does your algorithm degrade gracefully as noise increases, or does it fail abruptly?
  2. Test mitigation: verify that zero-noise extrapolation or probabilistic error cancellation improves your results before running the real hardware version.
  3. Understand hardware: the noise model from IBM calibration data captures real error rates for a specific device on a specific day.
  4. Debug faster: local simulation is instant; real hardware has a queue.

AerSimulator with a Noise Model

The AerSimulator accepts a NoiseModel object that it applies during simulation. Without a noise model, Aer simulates ideal unitary evolution. With one, it samples from the noisy quantum channel.

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, depolarizing_error

# Build a simple depolarizing noise model
noise_model = NoiseModel()
p1 = 0.001   # 0.1% single-qubit gate error
p2 = 0.01    # 1% two-qubit gate error

noise_model.add_all_qubit_quantum_error(
    depolarizing_error(p1, 1), ["h", "rx", "ry", "rz", "sx", "x"]
)
noise_model.add_all_qubit_quantum_error(
    depolarizing_error(p2, 2), ["cx", "cz"]
)

sim = AerSimulator(noise_model=noise_model)

qc = QuantumCircuit(2)
qc.h(0)
qc.cx(0, 1)
qc.measure_all()

result = sim.run(transpile(qc, sim), shots=4096).result()
print("Noisy Bell state:", result.get_counts())
# Output: Noisy Bell state: {'00': ~2000, '11': ~2000, '01': ~8, '10': ~8}

Depolarizing Error

The depolarizing channel applies a random Pauli error with probability p. For a single qubit, it applies X, Y, or Z each with probability p/3, and leaves the qubit alone with probability 1-p.

from qiskit_aer.noise import depolarizing_error

# Single-qubit depolarizing: applies X, Y, or Z each with prob p/3
err_1q = depolarizing_error(0.002, 1)
print("Single-qubit depolarizing error:\n", err_1q)

# Two-qubit depolarizing: applies one of 15 non-identity Paulis each with prob p/15
err_2q = depolarizing_error(0.02, 2)
print("Two-qubit depolarizing error:\n", err_2q)

Depolarizing is the canonical noise model for theory because it is symmetric and easy to analyze. Real hardware noise is never perfectly depolarizing, but it is a useful approximation.

T1 and T2 Thermal Relaxation

Thermal relaxation captures the two dominant decoherence mechanisms in superconducting qubits:

  • T1 (amplitude damping): the qubit spontaneously decays from |1> to |0>. Timescale: ~50-300 microseconds on current hardware.
  • T2 (dephasing): the qubit loses phase coherence in the X-Y plane. Always T2 <= 2*T1. Timescale: ~30-200 microseconds.
from qiskit_aer.noise import thermal_relaxation_error, NoiseModel

# Typical superconducting qubit parameters
t1 = 100e3   # 100 microseconds in nanoseconds
t2 = 80e3    # 80 microseconds
gate_time_1q = 35    # single-qubit gate duration in ns
gate_time_2q = 400   # two-qubit (CX) gate duration in ns

# Thermal relaxation error for a single-qubit gate
relax_1q = thermal_relaxation_error(t1, t2, gate_time_1q)

# Thermal relaxation error for a two-qubit gate (applied to each qubit)
relax_2q_q0 = thermal_relaxation_error(t1, t2, gate_time_2q)
relax_2q_q1 = thermal_relaxation_error(t1, t2, gate_time_2q)

# Combine into a 2-qubit error channel (tensor product)
relax_cx = relax_2q_q0.expand(relax_2q_q1)

noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(relax_1q, ["h", "sx", "x", "rz"])
noise_model.add_all_qubit_quantum_error(relax_cx, ["cx"])

For idle qubits (no gate applied), you can add relaxation during the idle time with a dedicated delay instruction, important for circuits with barriers or mid-circuit measurements.

Readout Errors

Readout errors occur during measurement: a qubit in state |0> is sometimes reported as 1, and vice versa. These are characterized by the confusion matrix:

[[P(report 0 | state 0),  P(report 1 | state 0)],
 [P(report 0 | state 1),  P(report 1 | state 1)]]
= [[1 - p0, p0],
   [p1,     1 - p1]]

Typical values on IBM hardware: p0 ~ 0.01 (1% false positive), p1 ~ 0.03 (3% false negative).

from qiskit_aer.noise import ReadoutError, NoiseModel

# p0: probability of reporting 1 when the qubit is in |0>
# p1: probability of reporting 0 when the qubit is in |1>
p0 = 0.01
p1 = 0.03

readout_error = ReadoutError([[1 - p0, p0], [p1, 1 - p1]])

noise_model = NoiseModel()
noise_model.add_all_qubit_readout_error(readout_error)

Readout errors are often the largest source of errors in NISQ circuits. Measurement error mitigation (MEM) uses a calibration matrix to undo the average confusion classically.

Importing a Real Backend Noise Model

The most accurate simulation uses calibration data from the actual device:

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.fake_provider import FakeBrisbane
from qiskit_aer.noise import NoiseModel
from qiskit_aer import AerSimulator
from qiskit import QuantumCircuit, transpile

# Option A: Use a fake backend (bundled calibration data, no account needed)
fake_backend = FakeBrisbane()
noise_model = NoiseModel.from_backend(fake_backend)

# Option B: Use a real backend (requires IBM Quantum account)
# service = QiskitRuntimeService()
# real_backend = service.backend("ibm_brisbane")
# noise_model = NoiseModel.from_backend(real_backend)

sim = AerSimulator(noise_model=noise_model)

qc = QuantumCircuit(3)
qc.h(0); qc.cx(0, 1); qc.cx(1, 2)
qc.measure_all()

# Transpile to the backend's gate set and connectivity
t_qc = transpile(qc, fake_backend, optimization_level=2)
result = sim.run(t_qc, shots=4096).result()
print("Noisy GHZ state (calibrated noise):", result.get_counts())
# Output: Noisy GHZ state (calibrated noise): {'000': ~1968, '111': ~1956, ...} (with error counts)

NoiseModel.from_backend() reads the device’s gate error rates, T1/T2 times, and readout errors from the calibration data and constructs a matching thermal relaxation + depolarizing + readout error model.

Comparing Ideal vs Noisy Output

A common workflow is running both and measuring the degradation:

from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel, depolarizing_error, ReadoutError
from qiskit.quantum_info import hellinger_fidelity

qc = QuantumCircuit(3)
qc.h(0); qc.cx(0, 1); qc.cx(1, 2)
qc.measure_all()

# Ideal run
ideal_sim = AerSimulator()
ideal_result = ideal_sim.run(transpile(qc, ideal_sim), shots=8192).result()
ideal_counts = ideal_result.get_counts()

# Noisy run
nm = NoiseModel()
nm.add_all_qubit_quantum_error(depolarizing_error(0.003, 1), ["h", "sx"])
nm.add_all_qubit_quantum_error(depolarizing_error(0.015, 2), ["cx"])
nm.add_all_qubit_readout_error(ReadoutError([[0.98, 0.02], [0.04, 0.96]]))

noisy_sim = AerSimulator(noise_model=nm)
noisy_result = noisy_sim.run(transpile(qc, noisy_sim), shots=8192).result()
noisy_counts = noisy_result.get_counts()

fidelity = hellinger_fidelity(ideal_counts, noisy_counts)
print(f"Hellinger fidelity (ideal vs noisy): {fidelity:.4f}")
# Output: Hellinger fidelity (ideal vs noisy): ~0.90
print(f"Ideal:  {ideal_counts}")
print(f"Noisy:  {noisy_counts}")

Custom Kraus Operators

For noise channels not covered by the built-in constructors, define any completely positive trace-preserving (CPTP) map using Kraus operators:

import numpy as np
from qiskit_aer.noise import kraus_error, NoiseModel

# Amplitude damping channel: models T1 decay with rate gamma
gamma = 0.05   # damping probability

K0 = np.array([[1, 0], [0, np.sqrt(1 - gamma)]])   # no decay
K1 = np.array([[0, np.sqrt(gamma)], [0, 0]])         # decay to |0>

# Verify Kraus condition: sum(K_i† K_i) = I
check = K0.conj().T @ K0 + K1.conj().T @ K1
assert np.allclose(check, np.eye(2)), "Not a valid Kraus decomposition"

amp_damp = kraus_error([K0, K1])

noise_model = NoiseModel()
noise_model.add_all_qubit_quantum_error(amp_damp, ["id", "u1", "u2", "u3"])

This is useful for modeling crosstalk, leakage to non-computational states, or any noise channel with a known Kraus decomposition from literature.

What to Try Next

  • Combine thermal relaxation and depolarizing errors: compose them with err1.compose(err2) to build a more realistic single-gate error model
  • Use qiskit-experiments to run actual T1, T2, and gate characterization on a real device
  • Implement measurement error mitigation: calibrate a confusion matrix by running |0>^n and |1>^n circuits and invert it to correct noisy counts
  • Read the Qiskit Aer noise documentation for the full error model API

Was this tutorial helpful?