Realistic Noise Models

YAQS ships a library of physically motivated jump operators—relaxation (lowering), excitation (raising), single-qubit Pauli channels, and nearest-neighbor crosstalk (crosstalk_xx, crosstalk_zz, …)—that you assemble into a NoiseModel.

For hardware with static disorder (calibration drift, fabrication spread), each process strength can be a distribution instead of a fixed float. YAQS samples one concrete strength per process when run() starts; all trajectories in that run share the same sampled disorder. The realized model is stored on noise_model.

This page shows:

  1. A typical multi-channel noise model for an analog chain.

  2. Gaussian (bell-curve) strengths and the other built-in distributions.

  3. How sampled disorder changes open-system dynamics compared to a mean-strength baseline.

1. Built-in noise processes

Each process is a dictionary with name, sites, and strength. YAQS fills in the operator matrix (or per-site factors for long-range crosstalk) from NoiseLibrary.

 1from mqt.yaqs import NoiseModel
 2
 3L = 4
 4processes = [
 5    {"name": "lowering", "sites": [i], "strength": 0.05} for i in range(L)
 6] + [
 7    {"name": "pauli_z", "sites": [i], "strength": 0.02} for i in range(L)
 8] + [
 9    {"name": "crosstalk_xx", "sites": [i, i + 1], "strength": 0.01} for i in range(L - 1)
10]
11
12noise_model = NoiseModel(processes)
13print(f"{len(noise_model.processes)} processes:")
14for proc in noise_model.processes:
15    print(f"  {proc['name']:16s} sites={proc['sites']}  strength={proc['strength']}")
11 processes:
  lowering         sites=[0]  strength=0.05
  lowering         sites=[1]  strength=0.05
  lowering         sites=[2]  strength=0.05
  lowering         sites=[3]  strength=0.05
  pauli_z          sites=[0]  strength=0.02
  pauli_z          sites=[1]  strength=0.02
  pauli_z          sites=[2]  strength=0.02
  pauli_z          sites=[3]  strength=0.02
  crosstalk_xx     sites=[0, 1]  strength=0.01
  crosstalk_xx     sites=[1, 2]  strength=0.01
  crosstalk_xx     sites=[2, 3]  strength=0.01

2. Bell-curve (Gaussian) disorder on strengths

Replace a scalar strength with a dict describing the distribution. For a normal (Gaussian) bell curve, set distribution to "normal" and provide mean and std:

 1bell_curve_strength = {"distribution": "normal", "mean": 0.08, "std": 0.02}
 2
 3disordered_processes = [
 4  {
 5      "name": "pauli_z",
 6      "sites": [i],
 7      "strength": bell_curve_strength,
 8  }
 9  for i in range(L)
10]
11
12disordered_model = NoiseModel(disordered_processes)

Other supported distributions:

distribution

Parameters

Use when

"normal"

mean, std

Symmetric spread around a target rate; negatives are clamped to 0.

"truncated_normal"

mean, std

Same shape as normal but sampled only for non-negative strengths.

"lognormal"

mean, std

Log-normal rates (strictly positive; mean/std are the underlying normal parameters).

Sample many independent disorder realizations and plot the bell curve:

 1import matplotlib.pyplot as plt
 2import numpy as np
 3
 4rng = np.random.default_rng(0)
 5samples = [disordered_model.sample(rng=rng).processes[0]["strength"] for _ in range(5000)]
 6
 7fig, ax = plt.subplots(figsize=(6, 3.5))
 8ax.hist(samples, bins=40, density=True, alpha=0.7, color="tab:blue", label="sampled strengths")
 9x = np.linspace(0, max(samples), 200)
10pdf = (
11    1.0 / (bell_curve_strength["std"] * np.sqrt(2 * np.pi))
12    * np.exp(-0.5 * ((x - bell_curve_strength["mean"]) / bell_curve_strength["std"]) ** 2)
13)
14ax.plot(x, pdf, color="black", lw=1.5, label="Gaussian pdf (negatives clamped)")
15ax.set_xlabel("sampled dephasing strength")
16ax.set_ylabel("density")
17ax.set_title("Bell-curve disorder from a normal strength distribution")
18ax.legend()
19ax.grid(alpha=0.3)
20plt.tight_layout()
21plt.show()
Sampled noise strength -0.000463 using 'normal' distribution (mean=0.080000, std=0.020000) was negative and clamped to 0.0.
../_images/32ff2cf4eb7ae9ae15f53b0e43b22633b0055821c691294e54fb798e88b18667.svg

3. Disorder in an analog simulation

We evolve a short Ising chain and compare:

  • Baseline: every site uses the distribution mean (0.08) as a fixed strength.

  • Disordered: strengths are drawn from the bell curve once at the start of each run.

 1from mqt.yaqs import AnalogSimParams, Hamiltonian, Observable, Simulator, State
 2
 3hamiltonian = Hamiltonian.ising(length=L, J=1.0, g=0.5)
 4state = State(L, initial="zeros")
 5z_obs = Observable("z", sites=0)
 6
 7sim_params = AnalogSimParams(
 8    observables=[z_obs],
 9    elapsed_time=2.0,
10    dt=0.1,
11    num_traj=64,
12    max_bond_dim=16,
13    random_seed=7,
14)
15
16baseline_model = NoiseModel([
17    {"name": "pauli_z", "sites": [i], "strength": bell_curve_strength["mean"]} for i in range(L)
18])
19
20sim = Simulator(show_progress=False)
21result_baseline = sim.run(state, hamiltonian, sim_params, baseline_model)
22result_disordered = sim.run(state, hamiltonian, sim_params, disordered_model)
23
24print("Sampled strengths from the disordered run:")
25for proc in result_disordered.noise_model.processes:
26    print(f"  site {proc['sites'][0]}: {proc['strength']:.4f}")
Sampled strengths from the disordered run:
  site 0: 0.0800
  site 1: 0.0860
  site 2: 0.0745
  site 3: 0.0622
 1times = sim_params.times
 2baseline_curve = result_baseline.expectation_values[0]
 3disordered_curve = result_disordered.expectation_values[0]
 4
 5plt.figure(figsize=(7, 4))
 6plt.plot(times, baseline_curve, label="fixed mean strength", color="black", linestyle="--")
 7plt.plot(times, disordered_curve, label="bell-curve disorder (one sample / run)", color="tab:orange")
 8plt.xlabel("time")
 9plt.ylabel(r"$\langle Z_0 \rangle$")
10plt.title("Static disorder shifts open-system decay")
11plt.legend()
12plt.grid(alpha=0.3)
13plt.tight_layout()
14plt.show()

Re-running with the same random_seed reproduces the same sampled strengths and trajectory-averaged curve. Leave random_seed=None for fresh disorder draws in production Monte Carlo studies.

4. Disorder on a noisy circuit

The same distribution syntax works in digital simulation. Below, bit-flip rates on each qubit follow independent bell curves; one sample is drawn per Simulator.run call.

 1from mqt.yaqs import Observable, StrongSimParams
 2from mqt.yaqs.core.libraries.circuit_library import create_ising_circuit
 3
 4num_qubits = 3
 5circuit = create_ising_circuit(L=num_qubits, J=1.0, g=0.5, dt=0.1, timesteps=5)
 6
 7circuit_noise = NoiseModel([
 8    {
 9        "name": "pauli_x",
10        "sites": [i],
11        "strength": {"distribution": "truncated_normal", "mean": 0.05, "std": 0.02},
12    }
13    for i in range(num_qubits)
14])
15
16circuit_params = StrongSimParams(
17    observables=[Observable("z", site) for site in range(num_qubits)],
18    num_traj=32,
19    max_bond_dim=8,
20    random_seed=11,
21)
22
23circuit_result = sim.run(State(num_qubits, initial="zeros"), circuit, circuit_params, circuit_noise)
24sampled = [proc["strength"] for proc in circuit_result.noise_model.processes]
25print(f"truncated-normal bit-flip rates: {[f'{s:.4f}' for s in sampled]}")
26print(f"final <Z_0>: {circuit_result.expectation_values[0][0]:.4f}")
truncated-normal bit-flip rates: ['0.0278', '0.0501', '0.0553']
final <Z_0>: 0.6644+0.0000j

5. Long-range crosstalk

Non-adjacent pairs use the longrange_crosstalk_{ab} naming convention; YAQS attaches per-site Pauli factors automatically:

1lr_model = NoiseModel([
2    {"name": "longrange_crosstalk_xy", "sites": [0, 2], "strength": 0.05},
3])
4sampled = lr_model.sample(rng=0)
5print("sites:", sampled.processes[0]["sites"])
6print("strength:", sampled.processes[0]["strength"])
7print("factors:", [f.shape for f in sampled.processes[0]["factors"]])
sites: [0, 2]
strength: 0.05
factors: [(2, 2), (2, 2)]