Chapter 5: NISQ Reality and Outlook

βš›οΈ Noise You Can Simulate, Mitigation You Can Measure, and an Assessment You Can Defend

πŸ“– Reading Time: 40-45 minutes πŸ“Š Difficulty: Advanced πŸ’» Code Examples: 5 πŸ“ Exercises: 6

🌐 EN | πŸ‡―πŸ‡΅ JP | Last sync: 2026-08-12

Fundamental Mathematics Dojo > Introduction to Quantum Computing > Chapter 5

Every number in Chapters 1 through 4 came from a perfect quantum computer. The state vector stayed normalized, gates were exact, and expectation values were computed to machine precision. Real devices are none of these things, and the gap between the two is not a detail to be patched later β€” it is the single fact that determines what quantum computing can do for materials research today, next year, and in the decade after that.

This chapter closes the gap in three steps. First we build noise into the same state-vector simulator we have used all along, using the trajectory method, and validate it against exact density-matrix evolution. Then we measure how quickly a circuit's fidelity decays with depth at realistic error rates, and apply zero-noise extrapolation to the very VQE state computed in Chapter 4 to see how much of the noise-induced bias is recoverable and at what cost. Finally we put the resulting budgets β€” depth, width, measurements β€” next to what quantum error correction demands, and state as plainly as we can what near-term hardware can and cannot deliver.

That last section is the point of the chapter. It is deliberately unexciting. A researcher who reads it should come away able to look at a quantum computing claim, a vendor roadmap or a press release, and decide within a few minutes whether it bears on their own work. That skill is more useful than any algorithm in this series.

Learning Objectives

After completing this chapter, you will be able to:


5.1 The Physics of Noise

What actually goes wrong

A qubit is a two-level subspace of a much larger physical system β€” a superconducting circuit, a trapped ion, a spin in silicon β€” and the environment does not respect the abstraction. Four failure modes account for nearly everything:

Mechanism Physical origin Timescale symbol Effect on the state
Energy relaxation Spontaneous emission into the environment $T_1$ $\lvert 1 \rangle \to \lvert 0 \rangle$; population decays
Dephasing Fluctuating energy splitting (flux, charge, magnetic noise) $T_\phi$ Relative phase randomizes; coherence decays
Gate error Imperfect calibration, pulse distortion, crosstalk per-gate $p$ A slightly wrong unitary is applied
Readout error Finite measurement fidelity, discrimination overlap per-shot Measured bit differs from the true one

The two coherence times combine as

$$ \frac{1}{T_2} = \frac{1}{2T_1} + \frac{1}{T_\phi} $$

so $T_2 \le 2T_1$ always. Relaxation destroys phase as a side effect; pure dephasing destroys phase without moving population. A device datasheet quoting $T_1$ and $T_2$ has told you $T_\phi$ too.

What matters for algorithms is not the times themselves but their ratio to gate duration. A two-qubit gate lasting $\tau_g$ on a device with coherence time $T$ has an error floor of order $\tau_g / T$, and a circuit of $N_g$ sequential gates accumulates roughly $N_g \tau_g / T$ worth of error. This is why "coherence time" and "gate fidelity" are two views of one number.

Density matrices, minimally

A pure state $\lvert \psi \rangle$ cannot represent a system that has become correlated with its environment. The minimal extension is the density matrix

$$ \rho = \sum_k p_k \lvert \psi_k \rangle \langle \psi_k \rvert $$

with $\mathrm{Tr}\,\rho = 1$, $\rho = \rho^\dagger$, $\rho \succeq 0$. Expectation values become $\langle A \rangle = \mathrm{Tr}(\rho A)$, and the purity $\mathrm{Tr}(\rho^2)$ equals 1 for a pure state and $1/2^n$ for the maximally mixed state.

Noise processes are quantum channels, written in Kraus form:

$$ \rho \mapsto \mathcal{E}(\rho) = \sum_k K_k \rho K_k^\dagger, \qquad \sum_k K_k^\dagger K_k = I $$

Three channels cover most of what we need.

Depolarizing channel β€” the workhorse model, in which an unspecified error occurs with probability $p$ and is equally likely to be $X$, $Y$ or $Z$:

$$ \mathcal{E}_{\text{dep}}(\rho) = (1-p)\rho + \frac{p}{3}\left(X\rho X + Y\rho Y + Z\rho Z\right) $$

Amplitude damping β€” $T_1$ relaxation, with $\gamma = 1 - e^{-t/T_1}$:

$$ K_0 = \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1-\gamma} \end{pmatrix}, \qquad K_1 = \begin{pmatrix} 0 & \sqrt{\gamma} \\ 0 & 0 \end{pmatrix} $$

Phase damping β€” pure dephasing, with $\lambda$ set by $T_\phi$:

$$ K_0 = \begin{pmatrix} 1 & 0 \\ 0 & \sqrt{1-\lambda} \end{pmatrix}, \qquad K_1 = \begin{pmatrix} 0 & 0 \\ 0 & \sqrt{\lambda} \end{pmatrix} $$

The trajectory method

Simulating a density matrix costs $4^n$ numbers instead of $2^n$, which halves the reachable qubit count. The trajectory (or quantum-jump, or Monte Carlo wavefunction) method avoids that: keep a pure state, apply a randomly chosen error at each noisy location, and average observables over many independent runs. In the limit of many trajectories the average reproduces the density matrix exactly, because

$$ \rho = \mathbb{E}\left[\lvert \psi_{\text{traj}} \rangle \langle \psi_{\text{traj}} \rvert\right] $$

is precisely the statement that the channel is a probabilistic mixture of pure-state maps. For the depolarizing channel the recipe is immediate: with probability $p$ apply a uniformly random Pauli. The cost is statistical error falling as $1/\sqrt{N_{\text{traj}}}$ β€” and, conveniently, it mirrors what a real device does, since a real device also gives you one sample at a time.

The subtlety is that not every channel's trajectory form is obvious. Amplitude damping requires a state-dependent jump probability and a renormalization of the no-jump branch. Phase damping needs care with the kick probability: a random $Z$ applied with probability $q$ multiplies the off-diagonal element by $(1-2q)$, while the Kraus channel multiplies it by $\sqrt{1-\lambda}$, so $q = (1 - \sqrt{1-\lambda})/2$ β€” not $\lambda/2$. Getting this wrong produces a plausible-looking decay with the wrong rate, so the first thing to do with any noise model is check it against the exact channel.

Code Example 1: Noise Channels, Trajectory Method vs Exact Density Matrix

"""Chapter 5, Example 1: noise channels by the trajectory method, checked
against the exact density-matrix evolution."""
import numpy as np

I2 = np.eye(2, dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)


def bloch(rho):
    """Bloch vector (x, y, z) of a single-qubit density matrix."""
    return np.array([np.real(np.trace(rho @ P)) for P in (X, Y, Z)])


# ---------------------------------------------------------------------
# Exact channels, written with Kraus operators
# ---------------------------------------------------------------------

def kraus_apply(rho, kraus):
    return sum(K @ rho @ K.conj().T for K in kraus)


def depolarizing_kraus(p):
    """rho -> (1-p) rho + (p/3)(X rho X + Y rho Y + Z rho Z)."""
    return [np.sqrt(1 - p) * I2,
            np.sqrt(p / 3) * X, np.sqrt(p / 3) * Y, np.sqrt(p / 3) * Z]


def amplitude_damping_kraus(gamma):
    """Energy relaxation (T1): |1> decays to |0> with probability gamma."""
    return [np.array([[1, 0], [0, np.sqrt(1 - gamma)]], dtype=complex),
            np.array([[0, np.sqrt(gamma)], [0, 0]], dtype=complex)]


def phase_damping_kraus(lam):
    """Pure dephasing: destroys coherence without moving population."""
    return [np.array([[1, 0], [0, np.sqrt(1 - lam)]], dtype=complex),
            np.array([[0, 0], [0, np.sqrt(lam)]], dtype=complex)]


# ---------------------------------------------------------------------
# Trajectory ("quantum jump") realisation: one pure state per shot
# ---------------------------------------------------------------------

def depolarizing_trajectory(psi, p, rng):
    """With probability p, apply one uniformly chosen Pauli error."""
    if rng.random() < p:
        return (X, Y, Z)[rng.integers(3)] @ psi
    return psi


def amplitude_damping_trajectory(psi, gamma, rng):
    """Jump |1> -> |0> with probability gamma |<1|psi>|^2; otherwise
    apply the no-jump operator and renormalize."""
    if rng.random() < gamma * abs(psi[1]) ** 2:
        return np.array([1.0 + 0j, 0.0 + 0j])
    out = np.array([psi[0], np.sqrt(1 - gamma) * psi[1]])
    return out / np.linalg.norm(out)


def phase_damping_trajectory(psi, lam, rng):
    """A random Z kick reproduces pure dephasing. A kick with probability q
    multiplies the off-diagonal element by (1 - 2q), while the Kraus channel
    multiplies it by sqrt(1 - lam), so q = (1 - sqrt(1 - lam)) / 2."""
    q = (1 - np.sqrt(1 - lam)) / 2
    if rng.random() < q:
        return Z @ psi
    return psi


def trajectory_average(psi0, step, arg, trials, seed):
    """Monte Carlo average of |psi><psi| over independent trajectories."""
    rng = np.random.default_rng(seed)
    acc = np.zeros((2, 2), dtype=complex)
    for _ in range(trials):
        psi = step(psi0.copy(), arg, rng)
        acc += np.outer(psi, psi.conj())
    return acc / trials


# =====================================================================
np.set_printoptions(precision=5, suppress=True)
plus = H @ np.array([1.0 + 0j, 0.0 + 0j])          # |+>
trials = 200_000

print("Trajectory method vs exact density matrix, initial state |+>")
print("=" * 74)

for name, exact_kraus, traj_step, arg in (
        ("depolarizing, p = 0.15", depolarizing_kraus(0.15),
         depolarizing_trajectory, 0.15),
        ("amplitude damping, gamma = 0.30", amplitude_damping_kraus(0.30),
         amplitude_damping_trajectory, 0.30),
        ("phase damping, lambda = 0.40", phase_damping_kraus(0.40),
         phase_damping_trajectory, 0.40)):
    rho_exact = kraus_apply(np.outer(plus, plus.conj()), exact_kraus)
    rho_traj = trajectory_average(plus, traj_step, arg, trials, seed=7)
    print(f"\n  {name}")
    print(f"    exact Bloch vector      = {bloch(rho_exact)}")
    print(f"    trajectory Bloch vector = {bloch(rho_traj)}")
    print(f"    max |rho_exact - rho_traj| = "
          f"{np.abs(rho_exact - rho_traj).max():.5f}")
    print(f"    purity Tr(rho^2): exact "
          f"{np.real(np.trace(rho_exact @ rho_exact)):.5f}"
          f"   trajectory {np.real(np.trace(rho_traj @ rho_traj)):.5f}")

print()
print("Convergence of the trajectory average (depolarizing, p = 0.15)")
print("-" * 74)
print("  (mean over 8 independent runs; Monte Carlo error falls as 1/sqrt(N))")
rho_exact = kraus_apply(np.outer(plus, plus.conj()), depolarizing_kraus(0.15))
print(f"  {'trials':>10} {'mean max error':>16} {'1/sqrt(N)':>12}")
for n_tr in (100, 1_000, 10_000, 100_000):
    errs = [np.abs(trajectory_average(plus, depolarizing_trajectory,
                                      0.15, n_tr, seed=s) - rho_exact).max()
            for s in range(8)]
    print(f"  {n_tr:10,d} {np.mean(errs):16.6f} {1/np.sqrt(n_tr):12.6f}")

print()
print("Free decay: T1 and T2 as repeated weak channels")
print("-" * 74)
T1, T2 = 100.0, 60.0                      # microseconds, illustrative values
dt = 1.0
gamma = 1 - np.exp(-dt / T1)              # per-step relaxation probability
rate_phi = 1 / T2 - 1 / (2 * T1)          # 1/T2 = 1/(2 T1) + 1/T_phi
lam = 1 - np.exp(-2 * dt * rate_phi)
print(f"  T1 = {T1} us, T2 = {T2} us -> per-step gamma = {gamma:.5f},"
      f" lambda = {lam:.5f}")
print(f"  pure-dephasing time T_phi = {1/rate_phi:.2f} us")
print(f"  {'t (us)':>8} {'population':>13} {'exp(-t/T1)':>12} "
      f"{'coherence':>11} {'exp(-t/T2)':>12}")
rho_e = np.array([[0, 0], [0, 1]], dtype=complex)      # excited state |1>
rho_p = np.outer(plus, plus.conj())                    # superposition |+>
for step in range(0, 201):
    if step % 40 == 0:
        t = step * dt
        print(f"  {t:8.0f} {np.real(rho_e[1, 1]):13.6f} {np.exp(-t/T1):12.6f} "
              f"{2*abs(rho_p[0, 1]):11.6f} {np.exp(-t/T2):12.6f}")
    rho_e = kraus_apply(kraus_apply(rho_e, amplitude_damping_kraus(gamma)),
                        phase_damping_kraus(lam))
    rho_p = kraus_apply(kraus_apply(rho_p, amplitude_damping_kraus(gamma)),
                        phase_damping_kraus(lam))
Trajectory method vs exact density matrix, initial state |+>
==========================================================================

  depolarizing, p = 0.15
    exact Bloch vector      = [0.8 0.  0. ]
    trajectory Bloch vector = [0.7999 0.     0.    ]
    max |rho_exact - rho_traj| = 0.00005
    purity Tr(rho^2): exact 0.82000   trajectory 0.81992

  amplitude damping, gamma = 0.30
    exact Bloch vector      = [0.83666 0.      0.3    ]
    trajectory Bloch vector = [0.83758 0.      0.29923]
    max |rho_exact - rho_traj| = 0.00046
    purity Tr(rho^2): exact 0.89500   trajectory 0.89554

  phase damping, lambda = 0.40
    exact Bloch vector      = [0.7746 0.     0.    ]
    trajectory Bloch vector = [0.77739 0.      0.     ]
    max |rho_exact - rho_traj| = 0.00140
    purity Tr(rho^2): exact 0.80000   trajectory 0.80217

Convergence of the trajectory average (depolarizing, p = 0.15)
--------------------------------------------------------------------------
  (mean over 8 independent runs; Monte Carlo error falls as 1/sqrt(N))
      trials   mean max error    1/sqrt(N)
         100         0.016250     0.100000
       1,000         0.006500     0.031623
      10,000         0.001962     0.010000
     100,000         0.000623     0.003162

Free decay: T1 and T2 as repeated weak channels
--------------------------------------------------------------------------
  T1 = 100.0 us, T2 = 60.0 us -> per-step gamma = 0.00995, lambda = 0.02306
  pure-dephasing time T_phi = 85.71 us
    t (us)    population   exp(-t/T1)   coherence   exp(-t/T2)
         0      1.000000     1.000000    1.000000     1.000000
        40      0.670320     0.670320    0.513417     0.513417
        80      0.449329     0.449329    0.263597     0.263597
       120      0.301194     0.301194    0.135335     0.135335
       160      0.201897     0.201897    0.069483     0.069483
       200      0.135335     0.135335    0.035674     0.035674

What to notice. The first block is the validation that licenses everything after it. For all three channels the trajectory average reproduces the exact density matrix to within the Monte Carlo error, and β€” importantly β€” it reproduces the purity as well. That is the nontrivial check: any wrong trajectory rule would still give a trace-1 matrix, but the purity is sensitive to how much genuine mixing occurred. The depolarizing channel on $\lvert + \rangle$ shrinks the Bloch vector from 1 to $1 - 4p/3 = 0.8$, which is exactly what the exact and trajectory columns both show.

The convergence table shows the price. The error falls from 0.016 at 100 trajectories to 0.0006 at 100,000 β€” a factor of 26 for a factor of 1000 more work, i.e. $1/\sqrt{N}$ as expected. There is no way around this: statistical sampling is what a real quantum computer does too, and Section 5.4 shows that the shot budget, not the qubit count, is what usually kills a proposed calculation.

The last block is the $T_1$/$T_2$ picture assembled from repeated weak channels. Because the per-step parameters were chosen as $\gamma = 1 - e^{-\Delta t/T_1}$ and $\lambda = 1 - e^{-2\Delta t/T_\phi}$, the discrete evolution reproduces $e^{-t/T_1}$ and $e^{-t/T_2}$ exactly at every printed time. With $T_1 = 100\ \mu\text{s}$ and $T_2 = 60\ \mu\text{s}$, the pure-dephasing time is $T_\phi = 85.7\ \mu\text{s}$, and coherence is gone (down to 3.6%) after 200 $\mu\text{s}$. Compare that with the duration of a circuit: at a few hundred nanoseconds per two-qubit gate, 200 $\mu\text{s}$ buys a few hundred sequential gates. That single comparison is the whole NISQ constraint.


5.2 Simulating a Noisy Circuit

Where the noise goes

We model a circuit as layers of gates and place one depolarizing kick after every gate, on every qubit that gate touched. A layer of the hardware-efficient ansatz from Chapters 3 and 4 has $n$ single-qubit rotations and $n-1$ CNOTs, so the number of noise sites per layer is

$$ N_{\text{sites}} = n + 2(n-1) $$

For $n = 4$ that is 10. Note the factor 2 on the CNOTs: the model places an independent kick on each qubit a two-qubit gate touches, so the effective two-qubit gate error in this model is $2p$, and a device whose quoted two-qubit error is $p_{2Q}$ corresponds to $p = p_{2Q}/2$ here. Throughout this chapter $p$ is a per-qubit-per-gate rate, and the "gate budget" it buys is counted in noisy gate locations, not in gates. This is a deliberately simple model β€” real devices have different single- and two-qubit error rates, correlated errors, crosstalk and leakage β€” but it captures the one feature that matters: error accumulates with the number of gate applications, and a circuit's usable depth is set by that accumulation.

The natural figure of merit is the state fidelity between the ideal and noisy states,

$$ F(d) = \mathbb{E}_{\text{traj}}\left[\left\lvert \langle \psi_{\text{ideal}}(d) \mid \psi_{\text{noisy}}(d) \rangle \right\rvert^2\right] $$

which starts at 1 and decays toward the fully depolarized value $1/2^n$.

Code Example 2: Fidelity Versus Circuit Depth

"""Chapter 5, Example 2: fidelity vs circuit depth on a noisy state-vector simulator.

This block is the toolbox for the rest of the chapter: run it first, then
Examples 3 and 4 in the same session (or paste everything into one file).
"""
import numpy as np

# =====================================================================
# Mini state-vector simulator (Chapters 1-2 API, big-endian:
# qubit 0 = leftmost bit = most significant bit, index = sum_i q_i 2^(n-1-i))
# =====================================================================
# ---- single-qubit gates -------------------------------------------------
I2 = np.eye(2, dtype=complex)
X = np.array([[0, 1], [1, 0]], dtype=complex)
Y = np.array([[0, -1j], [1j, 0]], dtype=complex)
Z = np.array([[1, 0], [0, -1]], dtype=complex)
H = np.array([[1, 1], [1, -1]], dtype=complex) / np.sqrt(2)
S = np.array([[1, 0], [0, 1j]], dtype=complex)
T = np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex)


def rx(theta):
    c, s = np.cos(theta / 2), np.sin(theta / 2)
    return np.array([[c, -1j * s], [-1j * s, c]], dtype=complex)


def ry(theta):
    c, s = np.cos(theta / 2), np.sin(theta / 2)
    return np.array([[c, -s], [s, c]], dtype=complex)


def rz(theta):
    e = np.exp(-1j * theta / 2)
    return np.array([[e, 0], [0, np.conj(e)]], dtype=complex)


# ---- states -------------------------------------------------------------
def ket(bits: str) -> np.ndarray:
    """'01' -> the 4-dimensional basis state |01> (big-endian)."""
    n = len(bits)
    psi = np.zeros(2 ** n, dtype=complex)
    psi[int(bits, 2)] = 1.0
    return psi


def apply_gate(state, U, targets, n):
    """Apply the 2^k x 2^k unitary U to the listed target qubits of an n-qubit state."""
    k = len(targets)
    psi = state.reshape([2] * n)          # 1. view as an n-index tensor
    psi = np.moveaxis(psi, targets, range(k))   # 2. bring targets to the front
    rest = psi.shape[k:]
    psi = psi.reshape(2 ** k, -1)         # 3. flatten and multiply
    psi = U @ psi
    psi = psi.reshape(list((2,) * k) + list(rest))
    psi = np.moveaxis(psi, range(k), targets)   # 4. put the axes back
    return psi.reshape(-1)


CNOT4 = np.array([[1, 0, 0, 0],
                  [0, 1, 0, 0],
                  [0, 0, 0, 1],
                  [0, 0, 1, 0]], dtype=complex)


def cnot(state, control, target, n):
    """CNOT with the given control and target; any pair of qubits, any order."""
    return apply_gate(state, CNOT4, [control, target], n)


def probs(state):
    """Born-rule probabilities of all 2^n outcomes."""
    return np.abs(state) ** 2


def sample(state, shots, seed=None):
    """Simulated measurement: {bitstring: count}."""
    n = int(np.log2(state.size))
    rng = np.random.default_rng(seed)
    idx = rng.choice(state.size, size=shots, p=probs(state))
    out = {}
    for i in idx:
        b = format(i, f'0{n}b')
        out[b] = out.get(b, 0) + 1
    return dict(sorted(out.items()))


PAULI = {'I': I2, 'X': X, 'Y': Y, 'Z': Z}


def expval(state, pauli, coeff_map=None):
    """Expectation value of a Pauli string such as 'ZZ', 'XI' (one character per qubit).

    If coeff_map is given, the result is multiplied by coeff_map[pauli], so that a
    whole Hamiltonian is one line:  sum(expval(psi, p, terms) for p in terms).
    """
    n = len(pauli)
    phi = state.copy()
    for q, ch in enumerate(pauli):
        if ch != 'I':
            phi = apply_gate(phi, PAULI[ch], [q], n)
    val = np.vdot(state, phi).real
    if coeff_map is not None:
        val *= coeff_map.get(pauli, 1.0)
    return val


# =====================================================================
# Trajectory noise: one random Pauli kick per noisy gate location
# =====================================================================

def depol_kick(state, q, n, p, rng):
    """Depolarizing channel on qubit q, trajectory realisation."""
    if p and rng.random() < p:
        return apply_gate(state, (X, Y, Z)[rng.integers(3)], [q], n)
    return state


def noisy_layer(state, n, thetas, p, rng):
    """One hardware-efficient layer: Ry on every qubit, then a CNOT ladder.
    Each gate is followed by a depolarizing kick on every qubit it touched."""
    for q in range(n):
        state = apply_gate(state, ry(thetas[q]), [q], n)
        state = depol_kick(state, q, n, p, rng)
    for q in range(n - 1):
        state = cnot(state, q, q + 1, n)
        state = depol_kick(state, q, n, p, rng)
        state = depol_kick(state, q + 1, n, p, rng)
    return state


def noise_sites_per_layer(n):
    """n single-qubit gates + (n-1) two-qubit gates, each two-qubit gate
    contributing a kick on both of its qubits."""
    return n + 2 * (n - 1)


def fidelity_curve(n, max_depth, angles, p, trajectories, seed):
    """F(d) = E_traj |<psi_ideal(d) | psi_noisy(d)>|^2 for every depth d.

    One pass per trajectory records all depths, so the whole curve costs
    about as much as a single run of the deepest circuit.
    """
    ideal, st = [], ket('0' * n)
    for d in range(max_depth):
        st = noisy_layer(st, n, angles[d], 0.0, None)
        ideal.append(st.copy())

    rng = np.random.default_rng(seed)
    acc = np.zeros(max_depth)
    for _ in range(trajectories):
        st = ket('0' * n)
        for d in range(max_depth):
            st = noisy_layer(st, n, angles[d], p, rng)
            acc[d] += abs(np.vdot(ideal[d], st)) ** 2
    return acc / trajectories


# =====================================================================
n, max_depth, trajectories = 4, 24, 2000
angles = np.random.default_rng(2).uniform(0, 2 * np.pi, size=(max_depth, n))
sites = noise_sites_per_layer(n)
depths = np.arange(1, max_depth + 1)

print(f"n = {n} qubits, {sites} noise sites per layer, "
      f"{trajectories} trajectories per point")
print(f"fully depolarized floor 1/2^n = {1/2**n:.4f}")

for p in (0.001, 0.005, 0.01):
    F = fidelity_curve(n, max_depth, angles, p, trajectories, seed=17)
    gamma = -np.polyfit(depths, np.log(F), 1)[0]
    print(f"\nper-gate depolarizing probability p = {p}")
    print(f"  {'depth':>6} {'gates':>6} {'F (measured)':>13} "
          f"{'(1-p)^gates':>13} {'ratio':>7}")
    for i in range(0, max_depth, 2):
        n_sites = sites * depths[i]
        survive = (1 - p) ** n_sites
        print(f"  {depths[i]:6d} {n_sites:6d} {F[i]:13.4f} {survive:13.4f} "
              f"{F[i]/survive:7.3f}")
    print(f"  exponential fit F ~ exp(-gamma d): gamma = {gamma:.5f}")
    print(f"  per-layer survival exp(-gamma) = {np.exp(-gamma):.5f}")
    print(f"  depth where F = 0.5: {np.log(2)/gamma:.1f} layers"
          f"  ({np.log(2)/gamma*sites:.0f} noisy gates)")

print("\nHow deep can we go before the state is meaningless?")
print("-" * 70)
print(f"  {'p':>8} {'F=0.9 depth':>12} {'F=0.5 depth':>12} {'gate budget':>12}")
for p in (0.02, 0.01, 0.005, 0.002, 0.001, 0.0005):
    F = fidelity_curve(n, max_depth, angles, p, trajectories, seed=17)
    gamma = -np.polyfit(depths, np.log(F), 1)[0]
    d90, d50 = np.log(1 / 0.9) / gamma, np.log(2) / gamma
    print(f"  {p:8.4f} {d90:12.1f} {d50:12.1f} {d50*sites:12.0f}")
n = 4 qubits, 10 noise sites per layer, 2000 trajectories per point
fully depolarized floor 1/2^n = 0.0625

per-gate depolarizing probability p = 0.001
   depth  gates  F (measured)   (1-p)^gates   ratio
       1     10        0.9927        0.9900   1.003
       3     30        0.9698        0.9704   0.999
       5     50        0.9513        0.9512   1.000
       7     70        0.9360        0.9324   1.004
       9     90        0.9219        0.9139   1.009
      11    110        0.9064        0.8958   1.012
      13    130        0.8856        0.8780   1.009
      15    150        0.8660        0.8606   1.006
      17    170        0.8495        0.8436   1.007
      19    190        0.8308        0.8269   1.005
      21    210        0.8115        0.8105   1.001
      23    230        0.7985        0.7944   1.005
  exponential fit F ~ exp(-gamma d): gamma = 0.00983
  per-layer survival exp(-gamma) = 0.99022
  depth where F = 0.5: 70.5 layers  (705 noisy gates)

per-gate depolarizing probability p = 0.005
   depth  gates  F (measured)   (1-p)^gates   ratio
       1     10        0.9581        0.9511   1.007
       3     30        0.8718        0.8604   1.013
       5     50        0.7897        0.7783   1.015
       7     70        0.7169        0.7041   1.018
       9     90        0.6598        0.6369   1.036
      11    110        0.5961        0.5762   1.035
      13    130        0.5461        0.5212   1.048
      15    150        0.4918        0.4715   1.043
      17    170        0.4460        0.4265   1.046
      19    190        0.4028        0.3858   1.044
      21    210        0.3720        0.3490   1.066
      23    230        0.3431        0.3157   1.087
  exponential fit F ~ exp(-gamma d): gamma = 0.04698
  per-layer survival exp(-gamma) = 0.95410
  depth where F = 0.5: 14.8 layers  (148 noisy gates)

per-gate depolarizing probability p = 0.01
   depth  gates  F (measured)   (1-p)^gates   ratio
       1     10        0.9253        0.9044   1.023
       3     30        0.7842        0.7397   1.060
       5     50        0.6433        0.6050   1.063
       7     70        0.5235        0.4948   1.058
       9     90        0.4401        0.4047   1.087
      11    110        0.3714        0.3310   1.122
      13    130        0.3124        0.2708   1.154
      15    150        0.2649        0.2215   1.196
      17    170        0.2305        0.1811   1.273
      19    190        0.2018        0.1481   1.362
      21    210        0.1738        0.1212   1.434
      23    230        0.1520        0.0991   1.534
  exponential fit F ~ exp(-gamma d): gamma = 0.08229
  per-layer survival exp(-gamma) = 0.92100
  depth where F = 0.5: 8.4 layers  (84 noisy gates)

How deep can we go before the state is meaningless?
----------------------------------------------------------------------
         p  F=0.9 depth  F=0.5 depth  gate budget
    0.0200          0.9          6.1           61
    0.0100          1.3          8.4           84
    0.0050          2.2         14.8          148
    0.0020          5.4         35.6          356
    0.0010         10.7         70.5          705
    0.0005         20.3        133.3         1333

What to notice. The decay is exponential in depth, and the rate is what a back-of-the-envelope argument predicts. The naive model β€” "the circuit works only if no error occurs anywhere" β€” gives survival probability $(1-p)^{N_{\text{gates}}}$, and the measured fidelity tracks it with a ratio between 1.00 and 1.09 at $p = 0.001$ and $p = 0.005$. The ratio exceeds 1 because some Pauli errors are harmless on the particular state that happens to be present, so the true fidelity is slightly better than "no error at all". At $p = 0.01$ the ratio climbs to 1.53 by depth 23, because the fidelity is approaching the $1/2^n = 0.0625$ floor and the naive model keeps falling past it.

The final table is the one to remember. It converts a per-gate error rate into a gate budget β€” the number of noisy gate applications you can afford before the state is half wrong:

Per-gate error Usable gates at $F = 0.5$ Usable gates at $F = 0.9$
$2 \times 10^{-2}$ 61 9
$1 \times 10^{-2}$ 84 13
$5 \times 10^{-3}$ 148 22
$1 \times 10^{-3}$ 705 107
$5 \times 10^{-4}$ 1333 203

The budget scales as $1/p$, as it must. Note the second column: a useful calculation needs high fidelity, not 50%, and the $F = 0.9$ budget is roughly seven times smaller. Remember what $p$ means in this model: it is a per-qubit-per-gate rate, so $p = 10^{-3}$ describes a device whose two-qubit gate error is $2\times10^{-3}$, and such a device supports of order a hundred noisy gate locations at 90% fidelity. Chapter 4's Trotter analysis needed $3.4 \times 10^4$ Pauli rotations for $10^{-3}$ accuracy on a four-qubit toy model. Against a budget of 107, that is a factor of about 300 β€” two and a half orders of magnitude β€” and no amount of software cleverness closes two and a half orders of magnitude.

Plotting the decay

The same data plotted linearly and logarithmically makes the two regimes visible at once: a pure exponential over most of the range, and the saturation floor at $1/2^n$.

Code Example 3: The Fidelity-Decay Curve

"""Chapter 5, Example 3: the fidelity-decay curve, plotted.
Continues from Example 2 (same session)."""
import matplotlib.pyplot as plt

n, max_depth, trajectories = 4, 30, 1500
angles = np.random.default_rng(2).uniform(0, 2 * np.pi, size=(max_depth, n))
depths = np.arange(1, max_depth + 1)
sites = noise_sites_per_layer(n)
rates = (0.0005, 0.001, 0.002, 0.005, 0.01, 0.02)

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.8))
summary = []
for p in rates:
    F = fidelity_curve(n, max_depth, angles, p, trajectories, seed=17)
    mask = F > 0.3            # fit only where the decay is still exponential
    gamma = -np.polyfit(depths[mask], np.log(F[mask]), 1)[0]
    summary.append((p, gamma, np.log(2) / gamma))
    ax1.plot(depths, F, 'o-', ms=3.5, lw=1.4, label=f'p = {p}')
    ax2.semilogy(depths, F, 'o-', ms=3.5, lw=1.4, label=f'p = {p}')

for ax in (ax1, ax2):
    ax.axhline(1 / 2 ** n, color='k', ls=':', lw=1.2)
    ax.axhline(0.5, color='gray', ls='--', lw=1.0)
    ax.set_xlabel('circuit depth (layers)')
    ax.set_ylabel('state fidelity $F(d)$')
    ax.grid(alpha=0.3)
    ax.legend(fontsize=8, ncol=2)
ax1.set_title(f'Fidelity vs depth, {n} qubits, {sites} noise sites per layer')
ax2.set_title('Same data, log scale: the decay is a pure exponential')
ax2.text(0.5, 1 / 2 ** n * 1.15, '$1/2^n$ floor', fontsize=8)
plt.tight_layout()
plt.show()

print(f"{'p':>8} {'gamma (per layer)':>19} {'F=0.5 depth':>13} "
      f"{'gamma/(sites*p)':>17}")
for p, gamma, d50 in summary:
    print(f"{p:8.4f} {gamma:19.5f} {d50:13.1f} {gamma/(sites*p):17.4f}")
print("\nThe last column is close to 1: the decay rate per layer is")
print("(noise sites per layer) x (error probability), with a prefactor")
print("slightly below 1 because some Pauli errors leave the state unchanged.")
       p   gamma (per layer)   F=0.5 depth   gamma/(sites*p)
  0.0005             0.00524         132.4            1.0471
  0.0010             0.00990          70.0            0.9901
  0.0020             0.02000          34.7            1.0000
  0.0050             0.04599          15.1            0.9198
  0.0100             0.09297           7.5            0.9297
  0.0200             0.17561           3.9            0.8781

The last column is close to 1: the decay rate per layer is
(noise sites per layer) x (error probability), with a prefactor
slightly below 1 because some Pauli errors leave the state unchanged.

What to notice. The last column collapses six curves onto one number. Across a factor of 40 in error rate, $\gamma / (N_{\text{sites}} p)$ stays between 0.88 and 1.05, which says the decay rate per layer is simply

$$ \gamma \approx N_{\text{sites}}\, p \qquad \Longrightarrow \qquad F(d) \approx e^{-N_{\text{sites}} p \, d} $$

This is worth committing to memory, because it lets you estimate a circuit's fidelity without any simulation at all: count the gates, multiply by the error rate, exponentiate. A 40-qubit circuit of depth 100 has roughly $40 + 2\times39 = 118$ noise sites per layer, so $1.2 \times 10^4$ gate applications; at $p = 10^{-3}$ the fidelity is $e^{-12} \approx 6 \times 10^{-6}$. The circuit produces noise.

The log-scale panel shows why this simple rule works and where it fails: the decay is a straight line β€” a genuine exponential β€” until the fidelity approaches $1/2^n$, at which point the state is essentially the maximally mixed state and cannot get any worse.


5.3 Error Mitigation and Error Correction

Two entirely different responses to noise exist, and conflating them is a common source of confusion.

Error mitigation accepts the noise and corrects the statistics. It requires no extra qubits, works today, and reduces bias in expectation values β€” but it does not restore the quantum state, and its sampling cost grows rapidly with circuit size. It is a NISQ-era technique.

Error correction removes the noise from the computation by encoding one logical qubit in many physical qubits and continuously measuring syndromes. It restores arbitrary-depth computation, but demands physical error rates below a threshold and an overhead of hundreds to thousands of physical qubits per logical qubit. It is the fault-tolerant era.

Zero-noise extrapolation

The most widely used mitigation technique is zero-noise extrapolation (ZNE). Deliberately increase the noise by a known factor $\lambda$, measure the observable at several $\lambda$, fit a curve, and extrapolate back to $\lambda = 0$:

$$ \langle A \rangle_{\lambda} \approx \langle A \rangle_0 + c_1 \lambda + c_2\lambda^2 + \cdots \quad\Longrightarrow\quad \langle A \rangle_0 \approx \sum_i w_i \langle A \rangle_{\lambda_i} $$

In practice $\lambda$ is scaled by stretching gate pulses or by inserting pairs of gates that cancel (unitary folding, $U \to U U^\dagger U$). Here we scale the error probability directly, which is the idealized version of the same idea.

The other common techniques, in one line each:

Technique Idea Extra qubits Sampling overhead What it fixes
Zero-noise extrapolation Measure at amplified noise, extrapolate to zero None $\sim 10$ Bias in expectation values
Probabilistic error cancellation Sample from a quasi-probability inverse of the noise None $\sim 10^2\text{-}10^4$ Bias, more rigorously
Readout-error correction Invert the measured confusion matrix None $\sim 1$ Measurement errors only
Symmetry verification Discard shots violating particle number or spin None $\sim 1\text{-}10$ Errors that break a symmetry
Dynamical decoupling Pulse sequences that refocus dephasing during idles None $\sim 1$ Idle-time dephasing
Purification / virtual distillation Use $M$ copies to suppress incoherent error $\times M$ $\sim 10^2$ Incoherent error, not coherent

Every entry in the "extra qubits" column is None or a small multiple, and every entry in the sampling column is a multiplier on an already-large shot budget. That is the essential trade: mitigation buys accuracy with samples.

Code Example 4: Zero-Noise Extrapolation on a Noisy VQE Energy

"""Chapter 5, Example 4: zero-noise extrapolation of a noisy VQE energy.
Continues from Example 2 (same session)."""


def noisy_ansatz(theta, n, layers, p=0.0, rng=None):
    """The Chapter 3-4 hardware-efficient ansatz with depolarizing kicks
    after every gate. p = 0 reproduces the noiseless circuit exactly."""
    psi, k = ket('0' * n), 0
    for q in range(n):
        psi = apply_gate(psi, ry(theta[k]), [q], n)
        k += 1
        psi = depol_kick(psi, q, n, p, rng)
    for _ in range(layers):
        for q in range(n - 1):
            psi = cnot(psi, q, q + 1, n)
            psi = depol_kick(psi, q, n, p, rng)
            psi = depol_kick(psi, q + 1, n, p, rng)
        for q in range(n):
            psi = apply_gate(psi, ry(theta[k]), [q], n)
            k += 1
            psi = depol_kick(psi, q, n, p, rng)
    return psi


def tfim_hamiltonian(N, J, h):
    terms = {}
    for i in range(N - 1):
        s = 'I' * i + 'ZZ' + 'I' * (N - i - 2)
        terms[s] = terms.get(s, 0.0) - J
    for i in range(N):
        s = 'I' * i + 'X' + 'I' * (N - i - 1)
        terms[s] = terms.get(s, 0.0) - h
    return terms


def exact_ground_energy(terms):
    n = len(next(iter(terms)))
    M = np.zeros((2 ** n, 2 ** n), dtype=complex)
    for s, c in terms.items():
        A = np.array([[1.0 + 0j]])
        for ch in s:
            A = np.kron(A, PAULI[ch])
        M += c * A
    return float(np.linalg.eigvalsh(M)[0])


def energy(theta, terms, n, layers):
    psi = noisy_ansatz(theta, n, layers)
    return sum(expval(psi, s, terms) for s in terms)


def gradient(theta, terms, n, layers):
    g = np.zeros_like(theta)
    for i in range(len(theta)):
        tp, tm = theta.copy(), theta.copy()
        tp[i] += np.pi / 2
        tm[i] -= np.pi / 2
        g[i] = 0.5 * (energy(tp, terms, n, layers)
                      - energy(tm, terms, n, layers))
    return g


def noisy_energy(theta, terms, n, layers, p, trajectories, rng):
    """Trajectory-averaged <H> as a noisy device would report it."""
    tot = 0.0
    for _ in range(trajectories):
        psi = noisy_ansatz(theta, n, layers, p=p, rng=rng)
        tot += sum(expval(psi, s, terms) for s in terms)
    return tot / trajectories


n, layers = 4, 3
terms = tfim_hamiltonian(n, 1.0, 1.0)
E_exact = exact_ground_energy(terms)

# noiseless VQE first, so we know the target the noisy device should reproduce
theta = np.random.default_rng(1).normal(0.0, 0.3, size=n * (layers + 1))
for _ in range(800):
    theta -= 0.3 * gradient(theta, terms, n, layers)
E_clean = energy(theta, terms, n, layers)

print("Zero-noise extrapolation, 4-qubit transverse-field Ising chain")
print("=" * 74)
print(f"  exact ground state       E0 = {E_exact:+.6f}")
print(f"  noiseless VQE (3 layers) E  = {E_clean:+.6f}"
      f"   (ansatz error {E_clean - E_exact:+.2e})")
print(f"  circuit: {n*(layers+1)} Ry gates, {layers*(n-1)} CNOTs,"
      f" {n*(layers+1) + 2*layers*(n-1)} noise sites")

trajectories = 6000
for p0 in (0.002, 0.005):
    print(f"\n  base error rate p0 = {p0}"
          f"   ({trajectories} trajectories per noise scale)")
    lams = np.array([1.0, 2.0, 3.0])
    rng = np.random.default_rng(101)
    Es = []
    for lam in lams:
        E = noisy_energy(theta, terms, n, layers, p0 * lam, trajectories, rng)
        Es.append(E)
        print(f"    lambda = {lam:.0f}  (p = {p0*lam:.3f}):"
              f"  <H> = {E:+.6f}   bias = {E - E_clean:+.6f}")
    Es = np.array(Es)
    lin = np.polyval(np.polyfit(lams, Es, 1), 0.0)
    quad = np.polyval(np.polyfit(lams, Es, 2), 0.0)
    print(f"    unmitigated (lambda = 1) : {Es[0]:+.6f}"
          f"   residual bias {Es[0]-E_clean:+.6f}")
    print(f"    linear extrapolation     : {lin:+.6f}"
          f"   residual bias {lin-E_clean:+.6f}")
    print(f"    quadratic extrapolation  : {quad:+.6f}"
          f"   residual bias {quad-E_clean:+.6f}")
    print(f"    bias reduction (linear)  : "
          f"{abs(Es[0]-E_clean)/abs(lin-E_clean):.1f}x")
    # One run is one seed.  Replicate to separate genuine bias from
    # sampling fluctuation: the two fits differ in variance, not in bias.
    lin_b, quad_b = [], []
    for seed in range(201, 207):
        rng_s = np.random.default_rng(seed)
        Es_s = np.array([noisy_energy(theta, terms, n, layers, p0 * lam,
                                      trajectories, rng_s) for lam in lams])
        lin_b.append(np.polyval(np.polyfit(lams, Es_s, 1), 0.0) - E_clean)
        quad_b.append(np.polyval(np.polyfit(lams, Es_s, 2), 0.0) - E_clean)
    lin_b, quad_b = np.array(lin_b), np.array(quad_b)
    print("    the printed run is ONE seed; over 6 independent seeds:")
    print(f"      linear    residual bias = {lin_b.mean():+.4f}"
          f"  +/- {lin_b.std(ddof=1):.4f}")
    print(f"      quadratic residual bias = {quad_b.mean():+.4f}"
          f"  +/- {quad_b.std(ddof=1):.4f}")

print("\n  Why the bias has this sign: depolarizing noise pulls the state")
print("  towards the maximally mixed state, whose energy is Tr(H)/2^n = 0,")
print("  so a negative ground-state energy is systematically raised.")

print("\n  The cost of the mitigation")
print("  " + "-" * 66)
print("  Richardson extrapolation is E(0) = sum_i w_i E(lambda_i), and the")
print("  weights alternate in sign and grow with the number of noise scales:")
print(f"    {'noise scales':<24} {'weights w_i':<24} {'||w||_2':>8} {'sum|w_i|':>9}")
for lams_try in ([1.0, 2.0], [1.0, 2.0, 3.0], [1.0, 2.0, 3.0, 4.0]):
    V = np.vander(np.array(lams_try), len(lams_try), increasing=True)
    w = np.linalg.inv(V)[0]
    print(f"    {str(lams_try):<24} {str(np.round(w, 2)):<24} "
          f"{np.linalg.norm(w):8.2f} {np.abs(w).sum():9.2f}")
print("  Those are the weights of an EXACT interpolation through m points, which\n"
      "  is what the quadratic fit above is for m = 3.  The linear fit above is a\n"
      "  least-squares line through the same three points, and its weights are\n"
      "  much gentler:")
w_lin = np.linalg.pinv(np.vander(lams, 2, increasing=True))[0]
print(f"    {str([float(x) for x in lams]):<24} {str(np.round(w_lin, 3)):<24} "
      f"{np.linalg.norm(w_lin):8.2f} {np.abs(w_lin).sum():9.2f}")
print("  With m noise scales the shot budget must grow by roughly ||w||^2 to")
print("  hold the statistical error fixed: mitigation buys bias with variance.")
Zero-noise extrapolation, 4-qubit transverse-field Ising chain
==========================================================================
  exact ground state       E0 = -4.758770
  noiseless VQE (3 layers) E  = -4.749403   (ansatz error +9.37e-03)
  circuit: 16 Ry gates, 9 CNOTs, 34 noise sites

  base error rate p0 = 0.002   (6000 trajectories per noise scale)
    lambda = 1  (p = 0.002):  <H> = -4.569866   bias = +0.179537
    lambda = 2  (p = 0.004):  <H> = -4.406701   bias = +0.342703
    lambda = 3  (p = 0.006):  <H> = -4.237564   bias = +0.511840
    unmitigated (lambda = 1) : -4.569866   residual bias +0.179537
    linear extrapolation     : -4.737013   residual bias +0.012390
    quadratic extrapolation  : -4.727061   residual bias +0.022343
    bias reduction (linear)  : 14.5x
    the printed run is ONE seed; over 6 independent seeds:
      linear    residual bias = +0.0127  +/- 0.0098
      quadratic residual bias = +0.0124  +/- 0.0341

  base error rate p0 = 0.005   (6000 trajectories per noise scale)
    lambda = 1  (p = 0.005):  <H> = -4.301924   bias = +0.447479
    lambda = 2  (p = 0.010):  <H> = -3.950008   bias = +0.799395
    lambda = 3  (p = 0.015):  <H> = -3.569222   bias = +1.180181
    unmitigated (lambda = 1) : -4.301924   residual bias +0.447479
    linear extrapolation     : -4.673087   residual bias +0.076316
    quadratic extrapolation  : -4.624970   residual bias +0.124434
    bias reduction (linear)  : 5.9x
    the printed run is ONE seed; over 6 independent seeds:
      linear    residual bias = +0.0585  +/- 0.0124
      quadratic residual bias = +0.0282  +/- 0.0770

  Why the bias has this sign: depolarizing noise pulls the state
  towards the maximally mixed state, whose energy is Tr(H)/2^n = 0,
  so a negative ground-state energy is systematically raised.

  The cost of the mitigation
  ------------------------------------------------------------------
  Richardson extrapolation is E(0) = sum_i w_i E(lambda_i), and the
  weights alternate in sign and grow with the number of noise scales:
    noise scales             weights w_i               ||w||_2  sum|w_i|
    [1.0, 2.0]               [ 2. -1.]                    2.24      3.00
    [1.0, 2.0, 3.0]          [ 3. -3.  1.]                4.36      7.00
    [1.0, 2.0, 3.0, 4.0]     [ 4. -6.  4. -1.]            8.31     15.00
  Those are the weights of an EXACT interpolation through m points, which
  is what the quadratic fit above is for m = 3.  The linear fit above is a
  least-squares line through the same three points, and its weights are
  much gentler:
    [1.0, 2.0, 3.0]          [ 1.333  0.333 -0.667]       1.53      2.33
  With m noise scales the shot budget must grow by roughly ||w||^2 to
  hold the statistical error fixed: mitigation buys bias with variance.

What to notice. Four observations, in increasing order of importance.

ZNE works, and by a useful factor. At $p = 0.002$ the raw noisy energy is biased by $+0.180$ and linear extrapolation brings it to $+0.012$ β€” a 14.5-fold reduction. At $p = 0.005$ the reduction is 5.9-fold. The technique is real, not cosmetic.

The bias is much larger than the ansatz error, and has the opposite significance. The three-layer ansatz misses the exact ground state by $+9.4 \times 10^{-3}$. Noise at $p = 0.002$ adds a bias of $+0.18$, nineteen times larger. Every effort spent on ansatz design is wasted until the noise bias is brought below the ansatz error. This ordering β€” noise first, then algorithm β€” is the correct priority for near-term work, and it is frequently reversed in the literature.

Higher-order extrapolation is not worse in bias β€” it is noisier, and one run cannot tell you which. In the printed run the quadratic fit is worse than the linear one in both cases ($+0.022$ vs $+0.012$, and $+0.124$ vs $+0.076$), and it would be easy to draw the wrong conclusion from that. The wrong conclusion is that a quadratic "has a spare degree of freedom and spends it fitting noise": a quadratic through three points has zero spare degrees of freedom, it interpolates them exactly, so there is nothing to overfit. What it does instead is amplify the noise. The six-seed replication printed underneath makes the real behaviour visible: at $p_0 = 0.002$ the two are indistinguishable in mean bias ($+0.013$ linear, $+0.012$ quadratic), and at $p_0 = 0.005$ the quadratic is the less biased of the two ($+0.059$ against $+0.028$) β€” as it should be, since it cancels the $\lambda$ and $\lambda^2$ terms of the expansion rather than just the $\lambda$ term. But its scatter is three to six times larger ($\pm 0.034$ and $\pm 0.077$ against $\pm 0.010$ and $\pm 0.012$), which is exactly the variance penalty of the weights printed at the end of the block: exact interpolation through three points has $\lVert w \rVert^2 = 19$ against $2.33$ for the least-squares line, a factor of 8 in variance, $2.9$ in standard deviation. The single printed run is one seed on which the quadratic's larger scatter went the wrong way. The lesson is about variance, not about degrees of freedom β€” and about never drawing a methodological conclusion from one Monte Carlo run.

The variance cost is explicit. The Richardson weights are $(2, -1)$, $(3, -3, 1)$, $(4, -6, 4, -1)$ for two, three and four noise scales, with $\lVert w \rVert_2 = 2.24, 4.36, 8.31$. Since independent estimates with variance $\sigma^2$ combine to variance $\sigma^2 \lVert w \rVert^2$, going from two to four noise scales multiplies the required shot count by $(8.31/2.24)^2 \approx 14$. And this sits on top of the fact that a noisier circuit has a smaller signal, so each $\langle A \rangle_{\lambda_i}$ is itself harder to estimate. Mitigation converts a bias problem into a sampling problem, and Section 5.4 shows that the sampling problem was already the binding one.

Error correction, and the threshold

Error correction encodes a logical qubit redundantly and measures syndromes β€” observables that reveal whether an error occurred without revealing the encoded state. The surface code is the leading candidate for superconducting hardware: physical qubits on a 2D lattice with nearest-neighbour parity checks, a code distance $d$ that can be increased by making the patch larger, and a decoder that infers the most likely error from the syndrome history.

The central fact is the threshold theorem. Below a critical physical error rate $p_{\text{th}}$, the logical error rate falls exponentially in the code distance:

$$ p_L \approx A\left(\frac{p}{p_{\text{th}}}\right)^{(d+1)/2} $$

Above threshold, adding qubits makes things worse, because each added qubit contributes more errors than the code can correct. The threshold for the surface code under standard circuit-level noise models is of order $10^{-2}$; the prefactor $A$ and the exact exponent depend on the code, the decoder and the noise model, so everything that follows is an order-of-magnitude statement and should be treated as one.

The rotated surface code uses $2d^2 - 1$ physical qubits per logical qubit.

Code Example 5: Correction, Depth and Measurement Budgets

"""Chapter 5, Example 5: order-of-magnitude budgets for correction, depth
and measurement. Self-contained: only arithmetic, no simulator needed."""
import numpy as np

P_THRESHOLD = 1e-2      # representative surface-code threshold, order of magnitude
A_PREFACTOR = 0.1       # dimensionless prefactor, order of magnitude


def logical_error(p_phys, d, p_th=P_THRESHOLD, A=A_PREFACTOR):
    """Surface-code scaling p_L ~ A (p/p_th)^((d+1)/2).
    Order of magnitude only: the prefactor and the threshold are
    code-, decoder- and noise-model dependent."""
    return A * (p_phys / p_th) ** ((d + 1) / 2)


def required_distance(p_phys, target, p_th=P_THRESHOLD, A=A_PREFACTOR):
    """Smallest odd code distance reaching a target logical error rate.

    The comparison carries a relative tolerance because p_L is a ratio raised
    to a large power: a distance that meets the target *exactly* lands a few
    ulps above it in binary floating point (0.1 * 0.1**5 evaluates to
    1.0000000000000004e-06), and a bare `<= target` would reject it and return
    the next distance up."""
    if p_phys >= p_th:
        return None                # at or above threshold, more qubits do not help
    for d in range(3, 201, 2):
        if logical_error(p_phys, d, p_th, A) <= target * (1 + 1e-9):
            return d
    return None


def physical_per_logical(d):
    """Rotated surface code: 2 d^2 - 1 physical qubits per logical qubit."""
    return 2 * d * d - 1


print("A. Where the error-correction threshold bites")
print("=" * 74)
print(f"  assumed threshold p_th = {P_THRESHOLD:.0e}, prefactor A = {A_PREFACTOR}")
print(f"\n  {'p_phys':>9} {'d = 3':>10} {'d = 7':>10} {'d = 11':>10} "
      f"{'d = 21':>10} {'d = 31':>10}")
for p_phys in (2e-2, 1e-2, 5e-3, 1e-3, 3e-4, 1e-4):
    row = "  ".join(f"{logical_error(p_phys, d):10.2e}" for d in (3, 7, 11, 21, 31))
    print(f"  {p_phys:9.0e} {row}")
print("\n  Above threshold, increasing d makes the logical error WORSE.")
print("  Below threshold it falls exponentially in d. That is the whole game.")

print("\nB. Qubit overhead for a target logical error rate")
print("=" * 74)
print(f"  {'p_phys':>9} {'target p_L':>12} {'distance d':>11} "
      f"{'physical/logical':>17} {'100 logical qubits':>19}")
for p_phys in (1e-3, 3e-4, 1e-4):
    for target in (1e-6, 1e-10, 1e-15):
        d = required_distance(p_phys, target)
        if d is None:
            print(f"  {p_phys:9.0e} {target:12.0e} {'unreachable':>11}")
            continue
        per = physical_per_logical(d)
        print(f"  {p_phys:9.0e} {target:12.0e} {d:11d} {per:17,d} {100*per:19,d}")

print("\nC. Gate budget without error correction")
print("=" * 74)
print("  A circuit carries information only while (gates) x (error rate) << 1.")
print(f"  {'per-gate error':>15} {'gates at error 1':>18} "
      f"{'gates at error 0.1':>20}")
for p in (1e-2, 1e-3, 1e-4, 1e-5, 1e-6, 1e-10, 1e-12):
    print(f"  {p:15.0e} {1/p:18,.0f} {0.1/p:20,.0f}")

print("\n  Circuit sizes that materials problems actually ask for:")
for label, gates in (("2-site Hubbard, one Trotter step", 1e1),
                     ("2-site Hubbard, phase estimation to 1e-3", 3.4e7),
                     ("20-orbital active space, VQE ansatz", 1e4),
                     ("50-orbital active space, phase estimation", 1e11),
                     ("FeMoco-scale phase estimation, order of mag", 1e11)):
    print(f"    {label:45s}: ~{gates:8.0e} gates  -> needs p < {0.1/gates:.0e}")

print("\nD. Measurement cost of chemical accuracy")
print("=" * 74)
target = 1.6e-3          # Hartree; 1 kcal/mol, the usual 'chemical accuracy'
print(f"  target precision = {target:.1e} Ha (1 kcal/mol)")
print("  shots ~ (sum of term variances) / epsilon^2, variance ~ 1 per term.")
print("  This is the BEST case: it assumes the terms are perfectly grouped into")
print("  one commuting family.  Measuring each Pauli term in its own circuit")
print("  costs (sum_j |c_j| sigma_j)^2 / epsilon^2 instead, which is larger.")
print(f"\n  {'orbitals':>9} {'Pauli terms ~n^4':>17} {'shots (best case)':>18} "
      f"{'time at 1e4/s':>16}")
for n_orb in (4, 10, 20, 50, 100):
    n_terms = n_orb ** 4
    shots = n_terms / target ** 2
    seconds = shots / 1e4
    years = seconds / 3.156e7
    t = f"{years:.2e} yr" if years > 1 else f"{seconds:.2e} s"
    print(f"  {n_orb:9d} {n_terms:17,d} {shots:18.3e} {t:>16}")

print("\n  Precision is quadratically expensive:")
for eps in (1e-1, 1e-2, 1.6e-3, 1e-4):
    print(f"    epsilon = {eps:8.1e} Ha  ->  shots x {(1/eps)**2:12.3e} per term")

print("\nE. The three budgets side by side")
print("=" * 74)
print("  A NISQ calculation must satisfy all three at once:")
print("    (1) depth    : gates x error rate << 1")
print("    (2) width    : qubits <= device size, with no correction overhead")
print("    (3) sampling : shots x circuit time <= available wall-clock time")
print("\n  Worked case: 20-orbital active space (40 qubits), VQE")
n_orb, gates = 20, 1e4
n_terms = n_orb ** 4
shots = n_terms / (1.6e-3) ** 2
print(f"    qubits               : {2*n_orb}")
print(f"    Pauli terms          : {n_terms:,}")
print(f"    circuit gates        : {gates:.0e}  -> needs p < {0.1/gates:.0e}")
print(f"    shots for 1 kcal/mol : {shots:.2e}  (best case, perfect grouping)")
print(f"    at 1e4 circuits/s    : {shots/1e4/3.156e7:.2e} years"
      f" for ONE energy evaluation")
print(f"    a geometry optimization needs ~1e2 evaluations:"
      f" {1e2*shots/1e4/3.156e7:.2e} years")
A. Where the error-correction threshold bites
==========================================================================
  assumed threshold p_th = 1e-02, prefactor A = 0.1

     p_phys      d = 3      d = 7     d = 11     d = 21     d = 31
      2e-02   4.00e-01    1.60e+00    6.40e+00    2.05e+02    6.55e+03
      1e-02   1.00e-01    1.00e-01    1.00e-01    1.00e-01    1.00e-01
      5e-03   2.50e-02    6.25e-03    1.56e-03    4.88e-05    1.53e-06
      1e-03   1.00e-03    1.00e-05    1.00e-07    1.00e-12    1.00e-17
      3e-04   9.00e-05    8.10e-08    7.29e-11    1.77e-18    4.30e-26
      1e-04   1.00e-05    1.00e-09    1.00e-13    1.00e-23    1.00e-33

  Above threshold, increasing d makes the logical error WORSE.
  Below threshold it falls exponentially in d. That is the whole game.

B. Qubit overhead for a target logical error rate
==========================================================================
     p_phys   target p_L  distance d  physical/logical  100 logical qubits
      1e-03        1e-06           9               161              16,100
      1e-03        1e-10          17               577              57,700
      1e-03        1e-15          27             1,457             145,700
      3e-04        1e-06           7                97               9,700
      3e-04        1e-10          11               241              24,100
      3e-04        1e-15          19               721              72,100
      1e-04        1e-06           5                49               4,900
      1e-04        1e-10           9               161              16,100
      1e-04        1e-15          13               337              33,700

C. Gate budget without error correction
==========================================================================
  A circuit carries information only while (gates) x (error rate) << 1.
   per-gate error   gates at error 1   gates at error 0.1
            1e-02                100                   10
            1e-03              1,000                  100
            1e-04             10,000                1,000
            1e-05            100,000               10,000
            1e-06          1,000,000              100,000
            1e-10     10,000,000,000        1,000,000,000
            1e-12  1,000,000,000,000      100,000,000,000

  Circuit sizes that materials problems actually ask for:
    2-site Hubbard, one Trotter step             : ~   1e+01 gates  -> needs p < 1e-02
    2-site Hubbard, phase estimation to 1e-3     : ~   3e+07 gates  -> needs p < 3e-09
    20-orbital active space, VQE ansatz          : ~   1e+04 gates  -> needs p < 1e-05
    50-orbital active space, phase estimation    : ~   1e+11 gates  -> needs p < 1e-12
    FeMoco-scale phase estimation, order of mag  : ~   1e+11 gates  -> needs p < 1e-12

D. Measurement cost of chemical accuracy
==========================================================================
  target precision = 1.6e-03 Ha (1 kcal/mol)
  shots ~ (sum of term variances) / epsilon^2, variance ~ 1 per term.
  This is the BEST case: it assumes the terms are perfectly grouped into
  one commuting family.  Measuring each Pauli term in its own circuit
  costs (sum_j |c_j| sigma_j)^2 / epsilon^2 instead, which is larger.

   orbitals  Pauli terms ~n^4  shots (best case)    time at 1e4/s
          4               256          1.000e+08       1.00e+04 s
         10            10,000          3.906e+09       3.91e+05 s
         20           160,000          6.250e+10       6.25e+06 s
         50         6,250,000          2.441e+12      7.74e+00 yr
        100       100,000,000          3.906e+13      1.24e+02 yr

  Precision is quadratically expensive:
    epsilon =  1.0e-01 Ha  ->  shots x    1.000e+02 per term
    epsilon =  1.0e-02 Ha  ->  shots x    1.000e+04 per term
    epsilon =  1.6e-03 Ha  ->  shots x    3.906e+05 per term
    epsilon =  1.0e-04 Ha  ->  shots x    1.000e+08 per term

E. The three budgets side by side
==========================================================================
  A NISQ calculation must satisfy all three at once:
    (1) depth    : gates x error rate << 1
    (2) width    : qubits <= device size, with no correction overhead
    (3) sampling : shots x circuit time <= available wall-clock time

  Worked case: 20-orbital active space (40 qubits), VQE
    qubits               : 40
    Pauli terms          : 160,000
    circuit gates        : 1e+04  -> needs p < 1e-05
    shots for 1 kcal/mol : 6.25e+10  (best case, perfect grouping)
    at 1e4 circuits/s    : 1.98e-01 years for ONE energy evaluation
    a geometry optimization needs ~1e2 evaluations: 1.98e+01 years

What to notice. Part A contains the whole logic of fault tolerance in one table. The $p = 10^{-2}$ row is flat at $0.1$: exactly at threshold, code distance does nothing. The $p = 2\times10^{-2}$ row rises with $d$, reaching 6550 at $d = 31$ β€” above threshold, a bigger code is a worse code. The $p = 10^{-4}$ row falls to $10^{-33}$ at $d = 31$. Being below threshold is not a quantitative improvement; it is a qualitative change of regime.

Part B prices it. At $p = 10^{-3}$, a logical error rate of $10^{-10}$ needs distance 17, which is 577 physical qubits per logical qubit β€” so a modest 100-logical-qubit machine needs 57,700 physical qubits. Improving the physical error rate to $3\times10^{-4}$ cuts that to 24,100. This is why hardware groups chase gate fidelity so hard: every factor of 3 in physical error rate saves roughly a factor of 3 in qubit count, compounding.

Part C is the sentence to quote when someone claims a near-term application. A circuit is meaningful while (gates) Γ— (error rate) is well below 1. Phase estimation on the two-site Hubbard model needs $3.4 \times 10^7$ gates β€” Chapter 4, Example 3, on that chapter's own optimistic accounting β€” hence $p < 3\times10^{-9}$, six orders of magnitude beyond uncorrected hardware. A 50-orbital active space needs $p < 10^{-12}$, and FeMoco-scale estimates land in the same place: gate counts of order $10^{10}$ to $10^{11}$ need $p \lesssim 10^{-12}$ as well. Those numbers are reachable only with error correction, which is exactly why FeMoco is a fault-tolerance argument. Treat every exponent in that list as an order of magnitude; the published FeMoco estimates have already moved by several.

Part D is the constraint people forget. Even with a perfect noiseless quantum computer, and even granting perfect grouping of the Pauli terms into one commuting family β€” the best case, which the printed table assumes β€” a VQE on a 20-orbital active space needs $6\times10^{10}$ circuit executions to reach chemical accuracy, which at $10^4$ circuits per second is 2.4 months of continuous running for one energy. A geometry optimization needing a hundred energies takes twenty years. The measurement cost is a property of the algorithm, not of the hardware: it follows from $\varepsilon \propto 1/\sqrt{N}$ and the $O(M^4)$ term count. Better measurement strategies (grouping commuting terms, classical shadows, low-rank factorizations) reduce the prefactor substantially, but the $1/\varepsilon^2$ scaling is a law.

Part E puts the three together, and the conclusion is uncomfortable: even setting depth aside, VQE on a chemically interesting active space is not merely hard on today's hardware β€” it is hard on any hardware that estimates expectation values by sampling. This is one of the strongest arguments for phase estimation, whose precision cost is $1/\varepsilon$ rather than $1/\varepsilon^2$ β€” at a circuit depth that also grows as $1/\varepsilon$ β€” and therefore one of the strongest arguments for pursuing fault tolerance rather than optimizing NISQ algorithms.


5.4 A Sober Assessment

This section is the centre of the chapter. Everything above was measurement; this is judgement, and it is stated as plainly as possible.

What NISQ devices can do for materials research today

What NISQ devices cannot do

How to read a quantum advantage claim

The pattern of the last several years is consistent: a quantum experiment claims to have performed a task beyond classical reach, and within months a classical algorithm β€” often a tensor-network method exploiting the specific structure of the sampled circuit β€” reproduces the result. This is not scandal; it is how the field establishes where the boundary actually lies. But it means claims must be read carefully.

Questions to ask, in order:

  1. Is the task useful, or constructed? Random-circuit sampling and boson sampling are designed to be hard for classical computers and are not useful for anything else. Demonstrating them is a legitimate physics milestone and tells you nothing about chemistry.
  2. What is the classical baseline, and who computed it? A comparison against a naive classical algorithm is not a comparison. Ask whether the best known classical method was used, whether it was given comparable engineering effort, and whether the authors of the classical baseline agree with the framing.
  3. Was the quantum result verified? If the answer cannot be checked classically, how is correctness established? Extrapolation from smaller verifiable instances is the usual approach, and it is an assumption, not a proof.
  4. What accuracy was achieved? A quantum energy accurate to 0.1 Hartree is not a chemistry result; chemical accuracy is $1.6\times10^{-3}$.
  5. What was mitigated, and at what cost? Heavy post-processing can produce a number close to the right answer while the underlying quantum state has negligible fidelity. Ask for the raw result and the shot count.
  6. Does the method scale? Many demonstrations rely on symmetry, small size or problem-specific tricks that vanish at larger scale. Ask what the resource count is at twice the size.

Principled criteria, not announcements

Device announcements age badly, and any assessment tied to a qubit count is obsolete when it is published. A better approach is to ask three quantitative questions about any proposed quantum calculation, all of which can be answered from the physics of the problem:

Question Quantity to compute Threshold for plausibility
Is the circuit shallow enough? (gate count) Γ— (per-gate error) Well below 1, ideally below 0.1
Is the sampling affordable? (Pauli terms) / $\varepsilon^2$ Γ— (circuit time), best case Below available wall-clock time
Is it classically hard? Best classical method's cost and accuracy Classical method must fail, not merely be slow

If a proposal fails any of the three, no hardware improvement of the kind announced in a press release will rescue it; what is needed is a change of algorithm or a change of era. Conversely, a proposal that passes all three deserves serious attention regardless of who is making it.

What would change the picture

To be clear about what progress looks like, here are the developments that would genuinely alter the assessment above:

The first two are engineering problems with clear paths. The third may be impossible. The fourth is the actual goal. The fifth is where theorists can contribute most.


5.5 The Ecosystem

You will not write your own simulator for production work. Three open-source frameworks dominate, and they differ more in philosophy than in capability. We describe their positioning rather than their APIs, which change between versions.

Framework Origin Philosophy Strongest for
Qiskit IBM Circuit-centric, hardware-oriented, large ecosystem Running on IBM hardware, transpilation, error mitigation modules
Cirq Google Explicit control of gate scheduling and device topology Hardware-aware circuit construction, NISQ experiments
PennyLane Xanadu Differentiable programming, autodiff integration Variational algorithms, quantum machine learning, hybrid gradients

Around them sit specialized tools worth knowing about by category: quantum chemistry interfaces that produce fermionic Hamiltonians and apply qubit mappings (the role played by Example 2 of Chapter 4); high-performance state-vector and tensor-network simulators, which are what you should compare against before claiming hardware is needed; and error-mitigation libraries implementing ZNE and probabilistic error cancellation.

Two practical recommendations. First, learn one framework properly rather than three superficially; the concepts transfer, the APIs do not. Second, always run the classical simulator first. If a 30-qubit state-vector simulation answers your question, hardware adds noise and nothing else.

References and further reading

Category Suggested entry points
Textbooks Nielsen & Chuang, Quantum Computation and Quantum Information (the standard reference); Preskill's lecture notes on quantum computation (freely available)
NISQ framing Preskill, "Quantum Computing in the NISQ era and beyond" (2018) β€” the paper that named the era and stated its limits
Quantum chemistry on quantum computers Cao et al., Chemical Reviews review of quantum chemistry in the age of quantum computing; McArdle et al., Reviews of Modern Physics review of quantum computational chemistry
Variational algorithms Cerezo et al., Nature Reviews Physics review of variational quantum algorithms; the barren-plateau literature starting from McClean et al. (2018)
Error correction Fowler et al., "Surface codes: towards practical large-scale quantum computation"; Terhal's review of quantum error correction for memories
Error mitigation Cai et al., review of quantum error mitigation; the Mitiq software paper for implementations
Classical competition SchollwΓΆck's DMRG review; the literature on classical simulation of quantum supremacy experiments, which is where the boundary is actually being drawn
Resource estimation The successive FeMoco resource-estimate papers, read in chronological order β€” the best available education in what dominates fault-tolerant cost

5.6 Series Wrap-Up and Learning Roadmap

What this series covered

Chapter Content What you can now do
1 Qubits, superposition, measurement, tensor products Represent and sample from a multi-qubit state; explain why $2^n$ is both resource and curse
2 Gates, circuits, entanglement, universality, the simulator Apply arbitrary unitaries to arbitrary qubits; quantify entanglement; compile Pauli exponentials
3 Variational quantum eigensolver Build an ansatz, measure a Pauli-decomposed observable, run a full VQE with parameter-shift gradients
4 Second quantization, Jordan-Wigner, model Hamiltonians Map a fermionic problem onto qubits and verify it; diagonalize Ising and Hubbard models exactly; compare VQE against exact answers
5 Noise, mitigation, correction, assessment Simulate noisy circuits; measure fidelity decay; apply ZNE; budget depth, width and shots; evaluate a claim

The mini-simulator you built in Chapters 1-2 carried every subsequent calculation. That is the point of building it: ninety-nine lines of NumPy are enough to reproduce, verify and understand every quantum algorithm in this series, and anything you cannot reproduce that way you probably do not understand yet.

Where to go next

Three routes, depending on what you want.

If you are a materials researcher who wants to keep an eye on the field. You are done with the essentials. The highest-value follow-up is classical methods for strongly correlated systems, because that is what any quantum result must beat: DMRG and matrix product states, quantum Monte Carlo and the sign problem, dynamical mean-field theory, and embedding schemes. Read the annual reviews rather than the preprints, and apply the three criteria of Section 5.4 to anything that looks exciting.

If you want to do quantum algorithm research. Deepen the theory: quantum phase estimation and its modern descendants (qubitization, quantum signal processing); Hamiltonian simulation beyond Trotter; the barren-plateau literature and what it says about trainability; measurement-reduction strategies including classical shadows; and quantum error correction proper. The prerequisites in this Dojo β€” Linear Algebra and Tensors, Introduction to Quantum Mechanics, Introduction to Quantum Field Theory β€” are where the mathematics lives.

If you want to build things. Pick one framework, implement a VQE on a molecule of your choosing from integrals you generate yourself, run it on real hardware, and compare against your own exact diagonalization. Then implement ZNE and measure how much it helps. The gap between the simulator result and the hardware result, measured by you on a problem you chose, teaches more than any review article.

A recommended sequence

Stage Focus Rough effort
1 Reproduce every code example in this series from scratch, without looking 2-3 weeks
2 Extend the simulator: density matrices, a second noise model, a better optimizer 2-3 weeks
3 One classical strong-correlation method (DMRG on a spin chain is ideal) 1-2 months
4 One framework, one real molecule, one hardware run 1-2 months
5 Read the fault-tolerant resource-estimation literature chronologically ongoing

Stage 3 is the one people skip and should not. Understanding why DMRG solves 1D problems essentially exactly, and why it fails in 2D, is the single best preparation for judging where quantum computing can contribute.

A closing note

Quantum computing for materials science is, right now, a field with excellent physics, real engineering progress, a clear long-term target, and no near-term applications. All four of those statements are true simultaneously, and holding them together is the mark of someone who understands the field rather than either its marketing or its dismissal.

The useful posture is neither enthusiasm nor scepticism but literacy: the ability to compute the three budgets, identify the classical baseline, and reach your own conclusion. If you can do that β€” and after this chapter you can β€” you will be able to evaluate this field's claims for the rest of your career, including the ones that turn out to be true.


Exercises

Work through these with the code from this chapter in front of you. Solutions follow each question.

Exercise 1: Coherence Times

A device reports $T_1 = 80\ \mu\text{s}$ and $T_2 = 120\ \mu\text{s}$. (a) Is this report internally consistent? State the bound that applies, and say why it is easy to misremember. (b) For $T_1 = 80\ \mu\text{s}$ and $T_2 = 40\ \mu\text{s}$, find $T_\phi$. (c) If a two-qubit gate takes 300 ns, roughly how many sequential gates fit within $T_2$, and how does that compare with the gate budget of Section 5.2?

Solution

(a) Yes, it is consistent β€” and that is the point of the question. The bound is \(T_2 \le 2T_1\), which follows from \(1/T_2 = 1/(2T_1) + 1/T_\phi\) with \(T_\phi > 0\). Here \(2T_1 = 160\ \mu\mathrm{s}\) and \(T_2 = 120\ \mu\mathrm{s} < 160\ \mu\mathrm{s}\), so nothing is wrong. The trap is misremembering the bound as \(T_2 \le T_1\): \(T_2 > T_1\) is perfectly physical and is what a relaxation-limited device with very little pure dephasing looks like. Here \(1/T_\phi = 1/120 - 1/160\), i.e. \(T_\phi = 480\ \mu\mathrm{s}\), four times \(T_1\) β€” dephasing is nearly absent.

(b) \(1/T_\phi = 1/T_2 - 1/(2T_1) = 1/40 - 1/160 = 0.025 - 0.00625 = 0.01875\ \mu\mathrm{s}^{-1}\), so \(T_\phi = 53.3\ \mu\mathrm{s}\). Dephasing dominates.

(c) \(40\ \mu\mathrm{s} / 300\ \mathrm{ns} \approx 133\) gates fit inside \(T_2\). Fitting inside \(T_2\) is not the same as being usable, and the comparison has to be made against the right row of Section 5.2. This device's coherence-limited per-gate error is \(\tau_g/T_2 = 300\ \mathrm{ns}/40\ \mu\mathrm{s} = 7.5\times10^{-3}\), which sits between the \(p = 5\times10^{-3}\) row (148 locations at \(F = 0.5\), 22 at \(F = 0.9\)) and the \(p = 10^{-2}\) row (84 and 13) β€” interpolating, about 92 at \(F = 0.5\) and 14 at \(F = 0.9\). So the \(T_2\) count and the fidelity budget agree at the 50% level (133 against ~92), while the usable depth is ten times smaller than either. Quoting the \(p = 10^{-3}\) row (705 and 107) for this device would be wrong by an order of magnitude: that row describes a device with ten times better gates.

Exercise 2: A Wrong Trajectory Rule

In Code Example 1, replace phase_damping_trajectory with a version that applies $Z$ with probability $\lambda/2$ instead of $(1-\sqrt{1-\lambda})/2$. (a) What does the trajectory Bloch vector become for $\lambda = 0.4$? (b) Which quantity in the printed output reveals the error most clearly? (c) Why is this bug particularly dangerous?

Solution

(a) A \(Z\) kick with probability \(q\) multiplies the off-diagonal element by \((1-2q)\). With \(q = \lambda/2 = 0.2\) the coherence becomes \(1 - 0.4 = 0.6\), so the Bloch vector is \((0.60, 0, 0)\) instead of the correct \(\sqrt{1-\lambda} = 0.7746\).

(b) The purity. The correct channel gives \(\mathrm{Tr}(\rho^2) = 0.800\); the wrong one gives \(0.68\). The Bloch vector also differs, but purity is the sharper diagnostic because it is quadratic in the state and therefore doubly sensitive to over-mixing.

(c) Because the wrong model still produces exponential decay with a plausible rate. Every qualitative feature survives β€” coherence decays, populations are untouched, the channel is trace-preserving β€” and only the numerical rate is wrong β€” but wrong by a factor of exactly 2, for every \(\lambda\). The correct rule multiplies the coherence by \(\sqrt{1-\lambda}\) per application, the wrong one by \((1-\lambda)\), and \(\ln(1-\lambda) = 2\ln\sqrt{1-\lambda}\) identically, so the extracted dephasing rate is exactly twice too large regardless of \(\lambda\). (The \(\sim\)23% figure is the error in the single-application coherence, 0.60 against 0.7746; the rate is off by 100%.) A factor of two propagates straight into any \(T_2\) read off a decay curve, or any fidelity estimate. This is why Code Example 1 exists: a noise model must be validated against the exact channel before it is used for anything.

Exercise 3: Predicting a Fidelity Without Simulating

Using the rule $F \approx \exp(-N_{\text{sites}} p\, d)$ from Code Example 3: (a) estimate the fidelity of a 10-qubit, 20-layer hardware-efficient circuit at $p = 2\times10^{-3}$; (b) how deep a circuit can 10 qubits support at $F = 0.9$? (c) At what $n$ does the $1/2^n$ floor stop mattering for the estimate?

Solution

(a) \(N_{\mathrm{sites}} = n + 2(n-1) = 10 + 18 = 28\) per layer. Total noise sites \(= 28 \times 20 = 560\). \(F \approx e^{-560 \times 2\times10^{-3}} = e^{-1.12} = 0.33\). Roughly a third of the amplitude survives β€” already marginal.

(b) \(F = 0.9\) needs \(N_{\mathrm{sites}} p\, d = \ln(1/0.9) = 0.105\), so \(d = 0.105/(28 \times 2\times10^{-3}) = 1.9\) layers. Two layers. This is the practical meaning of "NISQ": a ten-qubit device at \(2\times10^{-3}\) error supports a two-layer circuit at useful fidelity.

(c) The floor matters when \(e^{-N_{\mathrm{sites}} p d}\) approaches \(2^{-n}\), i.e. when \(N_{\mathrm{sites}} p d \gtrsim n \ln 2\). For \(n = 4\) that is \(2.8\), and Code Example 2 indeed shows the ratio departing from 1 near that point at \(p = 0.01\). For larger \(n\) the floor is exponentially lower, so the simple exponential rule holds over a much wider range β€” the floor becomes irrelevant, and the honest reading is that large noisy circuits are not saturating at a floor, they are simply useless.

Exercise 4: When Is Mitigation Worth It?

From Code Example 4: at $p_0 = 0.002$ linear ZNE reduced the bias 14.5-fold using three noise scales. (a) By what factor did the shot budget have to grow? (b) Suppose you had instead spent the same total shots on the unmitigated circuit. What would the statistical error have been, and would that have been a better trade? (c) Under what circumstances is ZNE clearly not worth it?

Solution

(a) Three noise scales means three separate expectation-value estimates, so 3Γ— the shots even before accounting for the extrapolation weights. Then use the weights of the estimator actually used: the linear extrapolation is a least-squares line through \(\lambda = 1, 2, 3\), whose intercept weights are \((4/3, 1/3, -2/3)\) with \(\lVert w \rVert^2 = 2.33\) β€” the last row printed by Code Example 4. Holding the statistical error of the extrapolated value fixed therefore costs another factor of 2.33 per point: about 7Γ— in total. (The Richardson weights \((3, -3, 1)\) with \(\lVert w \rVert^2 = 19\) apply to the quadratic extrapolation, which is an exact interpolation through the three points; that one would cost about 57Γ—.)

(b) Spending 7Γ— the shots on the unmitigated circuit reduces its statistical error by \(\sqrt{7} = 2.6\), but does nothing to its bias of \(+0.180\). Bias does not average away. So the trade is worth it precisely when the bias exceeds the statistical error, which is the usual situation for a shallow circuit with many shots. Mitigation attacks the error that sampling cannot.

(c) Three cases. (i) When the statistical error already dominates the bias β€” then more shots on the raw circuit are better. (ii) When the noise is so strong that the extrapolation is unreliable: at \(p_0 = 0.005\) the residual bias was \(+0.076\), still 8Γ— the ansatz error, and the fit quality degrades as the \(\lambda = 3\) point approaches the depolarized floor. (iii) When the observable's bias is not smooth in \(\lambda\), which happens with coherent (non-depolarizing) errors β€” ZNE assumes an analytic dependence on noise strength that coherent errors need not satisfy.

Exercise 5: Error-Correction Arithmetic

Using Code Example 5: (a) at $p = 5\times10^{-3}$, what code distance reaches $p_L = 10^{-9}$, and what is the qubit overhead? (b) A useful algorithm needs $10^{12}$ logical gates. What logical error rate does it require, and what physical error rate and distance would supply it? (c) Why does the assumed prefactor $A$ matter less than the ratio $p/p_{\text{th}}$?

Solution

(a) From part A of the output, \(p = 5\times10^{-3}\) gives \(p_L = 1.53\times10^{-6}\) at \(d = 31\). Reaching \(10^{-9}\) needs \((p/p_\mathrm{th})^{(d+1)/2} = 10^{-8}\) with \(p/p_\mathrm{th} = 0.5\), i.e. \((d+1)/2 = 8/\log_{10}2 = 26.6\), so \(d = 53\) and \(2d^2 - 1 = 5{,}617\) physical qubits per logical qubit. At half the threshold, error correction technically works and is ruinously expensive. Operating within a factor of 2 of threshold is not a viable engineering point.

(b) \(10^{12}\) logical gates need \(p_L \lesssim 10^{-13}\) for the whole computation to have a reasonable chance of being correct (more conservatively \(10^{-15}\)). From part B, \(p_L = 10^{-15}\) needs \(d = 27\) at \(p = 10^{-3}\) (1,457 physical per logical), \(d = 19\) at \(3\times10^{-4}\) (721), or \(d = 13\) at \(10^{-4}\) (337).

(c) Because \(A\) enters linearly while \(p/p_\mathrm{th}\) enters to the power \((d+1)/2\). Changing \(A\) from 0.1 to 0.01 shifts the required distance by about 2; changing \(p/p_\mathrm{th}\) from 0.1 to 0.03 changes it by a factor. This is why all realistic estimates emphasize the physical error rate relative to threshold and treat prefactors as noise β€” and why every result in Example 5 is presented as an order of magnitude.

Exercise 6: Assess a Claim

A preprint reports: "Using a 127-qubit superconducting processor with zero-noise extrapolation, we compute the ground-state energy of a 20-site Heisenberg chain to within 2% of the exact value, a calculation intractable for classical computers." Apply the six questions of Section 5.4.

Solution

1. Useful or constructed? A 20-site Heisenberg chain is a well-studied model with no unknown physics. The calculation is a benchmark, not a discovery. Legitimate as such.

2. Classical baseline? The claim of classical intractability is false. A 20-site spin-1/2 chain is \(2^{20} = 10^6\) dimensional β€” exact diagonalization runs on a laptop in seconds, and DMRG handles hundreds of sites to near machine precision. The word "intractable" appears to refer to brute-force full-state simulation of the circuit, which is a different claim and not a relevant one.

3. Verified? Yes, implicitly β€” the authors compare against the exact value, which is why they can quote 2%. Good practice, and it simultaneously refutes the intractability claim.

4. Accuracy? 2% of a ground-state energy is far from chemical accuracy and far from what DMRG delivers (\(10^{-10}\) relative or better for a 1D chain). For extracting physics β€” critical exponents, correlation functions β€” 2% on the energy is not usable.

5. What was mitigated? ZNE, so the raw fidelity should be requested. If the unmitigated result was 30% off and the post-processing brought it to 2%, the quantum state had little to do with the final number. Also ask for the shot count and the noise scales used.

6. Does it scale? A Heisenberg chain maps to qubits without Jordan-Wigner strings and has \(O(N)\) local terms β€” the easiest possible case. It says nothing about a fermionic Hamiltonian with \(O(M^4)\) terms and non-local strings.

Verdict. A respectable hardware benchmark with an unsupportable framing. The correct summary would be: "a 127-qubit processor with error mitigation reproduces a classically exact result to 2%, demonstrating improved device performance." That is genuine progress and worth publishing; the intractability claim is not.


Summary

Key Takeaways

1. Noise has a small number of causes and a simple model

2. Fidelity decays exponentially, and the rate is predictable without simulation

3. Mitigation reduces bias and pays in variance

4. Error correction is a change of regime, not an improvement

5. Three budgets must be satisfied at once, and depth is not always the binding one

6. The honest assessment

Practical implications

Where This Leads

You have reached the end of the series. You built a quantum simulator from an empty file, used it to run a variational eigensolver, mapped a fermionic Hamiltonian onto qubits and verified the mapping, diagonalized model Hamiltonians whose physics you can now read off the numbers, added noise and watched fidelity decay, mitigated that noise and measured the cost, and priced the resources that a real calculation would need.

What remains is to apply it. The next quantum computing claim you encounter β€” in a seminar, a proposal, a funding call or a press release β€” is one you can now evaluate quantitatively in a few minutes. Do that, and keep doing it, and you will be among the people who can tell the difference when something genuinely changes. For the classical methods that any quantum result must beat, continue with Computational Statistical Mechanics and the strong-correlation literature; for the theory beneath the algorithms, continue with Introduction to Quantum Field Theory.

← Chapter 4: Quantum Computing for Chemistry and Materials Back to Series Index β†’

Disclaimer