Quantum Error Correction under Continuous Noise

The error-correction routines inject a single discrete Pauli error and undo it on the state-vector engine. Real hardware is noisier than that: every physical qubit independently suffers an error channel with some probability p each cycle. This guide covers the exact density-matrix treatment of a code cycle under that continuous noise — no Monte-Carlo sampling.

The 3-qubit bit-flip code against a bit-flip channel

from quantum_debugger.algorithms import bit_flip_code_noisy

r = bit_flip_code_noisy(p=0.1)          # logical |0_L>
print(r["corrected"])    # 0.972  -- logical fidelity after syndrome recovery
print(r["uncorrected"])  # 0.9    -- a lone qubit under the same channel (1 - p)
print(r["analytic"])     # 0.972  -- (1-p)^3 + 3p(1-p)^2

Internally this encodes alpha|0_L> + beta|1_L> as a density matrix, applies an independent bit_flip(p) channel to each of the three physical qubits, and then applies the exact CPTP recovery map

R(rho) = sum_s  C_s P_s rho P_s C_s†

where P_s projects onto syndrome subspace s (the joint eigenspace of the stabilizers Z0Z1, Z1Z2) and C_s is the matching single-qubit X correction. Because the projectors partition the Hilbert space and each correction is unitary, R is completely positive and trace-preserving — a faithful model of the full measure-and-correct cycle.

The recovered fidelity of a computational-basis codeword is exactly

F_corrected(p) = (1 - p)^3 + 3 p (1 - p)^2

— the probability that 0 or 1 of the 3 qubits flips. It beats the un-encoded 1 - p for every p < 1/2 (the code threshold) and is worse above it, because majority voting then amplifies the error:

bit_flip_code_noisy(0.6)["corrected"] < bit_flip_code_noisy(0.6)["uncorrected"]  # True

A |+_L> codeword is even better off: a logical-X failure maps (|000> + |111>)/√2 to itself, so it stays perfectly protected (F = 1) at any p.

Phase-flip code

The phase-flip code is the bit-flip code conjugated by Hadamards, so it protects against a phase_flip(p) channel with the identical logical fidelity:

from quantum_debugger.algorithms import phase_flip_code_noisy
phase_flip_code_noisy(0.1)["corrected"]   # 0.972

Distance scaling and the threshold

For a distance-d (odd) repetition code with majority-vote decoding, the exact logical error rate is

P_L(p, d) = sum_{k > d/2} C(d, k) p^k (1 - p)^(d - k)
from quantum_debugger.algorithms import repetition_code_logical_error

repetition_code_logical_error(0.1, 3)   # 0.028
repetition_code_logical_error(0.1, 5)   # 0.00856
repetition_code_logical_error(0.1, 7)   # 0.00270

Below threshold, adding qubits suppresses the logical error — the defining feature of a good code. At exactly p = 1/2 every distance gives P_L = 1/2: the code neither helps nor hurts. This complements the Monte-Carlo repetition_code_error_rate with a closed-form curve.

The physical syndrome-extraction circuit

bit_flip_code_noisy applies an abstract CPTP recovery. syndrome_extraction_cycle instead runs the measured-ancilla circuit — the way a real device corrects errors:

from quantum_debugger.algorithms import syndrome_extraction_cycle

r = syndrome_extraction_cycle(p=0.1)
r["corrected"]   # 0.972 == (1-p)^3 + 3p(1-p)^2

The cycle, on 3 data + 2 ancilla qubits (density matrix):

  1. encode the logical qubit, ancillas in |0>;

  2. hit each data qubit with an independent bit-flip channel of strength p;

  3. extract the syndrome with CNOTs — ancilla 0 records parity(d0, d1) (stabilizer Z0Z1), ancilla 1 records parity(d1, d2) (Z1Z2);

  4. measure the ancillas and apply the X correction their outcome selects, summed over outcomes as a CPTP measurement channel;

  5. discard the ancillas (partial trace) and read out the logical fidelity.

This reproduces the ideal-recovery fidelity (1-p)^3 + 3p(1-p)^2 exactly — a direct check that the physical circuit implements the code, not just the abstract map.

Protecting qubits without QEC: decoherence-free subspaces

Full error correction is not the only defense. When the noise has a symmetry — e.g. a fluctuating global field that adds the same random Z phase to every qubit — encoding in a symmetric subspace gives perfect protection with no syndrome measurements at all:

from quantum_debugger.algorithms import dfs_protection

r = dfs_protection(sigma=0.8)     # collective Gaussian dephasing, strength sigma
r["dfs_fidelity"]                 # 1.0     -- a|01> + b|10> is untouched, ANY sigma
r["bare_coherence"]               # 0.726   == exp(-sigma^2/2)  (a lone |+> qubit)
r["antidfs_coherence"]            # 0.278   == exp(-2 sigma^2)  (a|00> + b|11>: worse!)

Under U(phi) = exp(-i phi/2 sum Z_q) a basis state’s phase depends only on its excitation number, so the equal-excitation subspace {|01>, |10>} picks up a single global phase — invariant for every realization of the noise. The channel is computed as a genuine ensemble average of unitaries (Gauss-Hermite quadrature), and all three behaviors match their closed forms exactly. Trapped-ion experiments use exactly this encoding against collective magnetic-field noise.

Dynamical decoupling: the Hahn spin echo

The DFS exploited a spatial symmetry; the spin echo exploits a temporal one — noise that stays correlated across one shot:

from quantum_debugger.algorithms import spin_echo

r = spin_echo(sigma=1.5, static=True)   # quasi-static random detuning
r["no_echo"]    # 0.325 == exp(-sigma^2/2) -- free evolution dephases
r["echo"]       # 1.0   -- X pulse at mid-time refocuses the phase EXACTLY

r = spin_echo(sigma=1.5, static=False)  # noise re-randomizes mid-shot
r["echo"]       # 0.325 -- no advantage: nothing to refocus

Per shot, U(phi/2) X U(phi/2) = X for every realization phi — the phase accumulated after the flip cancels the phase before it, so the ensemble average is perfectly coherent. The failure case is just as instructive: when the two half-evolutions carry independent phases, the echoed coherence equals the free one exactly. Noise correlation is the resource — which is why real devices characterize their noise spectrum before choosing a decoupling sequence.

The quantum Zeno effect

The third protection mechanism is the strangest: measurement itself. A watched qubit cannot Rabi-flip:

import numpy as np
from quantum_debugger.algorithms import quantum_zeno, zeno_postselected

quantum_zeno(np.pi, 1)["survival"]      # 0.0   -- a free pi-pulse fully inverts
quantum_zeno(np.pi, 50)["survival"]     # 0.953 -- 50 unread measurements freeze it
quantum_zeno(np.pi, 2000)["survival"]   # 0.9988 -- N -> infinity: never leaves |0>

r = zeno_postselected(np.pi / 2, 100)   # demand outcome 0 at every check
r["survival_probability"]               # 0.9938 == cos^200(pi/400)
r["state_fidelity"]                     # 1.0 -- the survivor is still exactly |0>

Each unread measurement is the exact channel rho -> P0 rho P0 + P1 rho P1; the final |0> population is exactly 1/2 + cos^N(wT/N)/2. Post-selecting outcome 0 each time survives with probability cos^{2N}(wT/2N) -> 1. Together with the DFS (symmetry) and the spin echo (correlation), this completes the trio of ways to protect a qubit without the overhead of full error correction.

Distance 3 in action: the Steane code vs depolarizing noise

The 3-qubit codes above correct only one error type. Running the full Steane [[7,1,3]] code against independent depolarizing noise on all 7 qubits (exact 64-syndrome CPTP recovery on the density matrix) shows what distance 3 buys:

from quantum_debugger.algorithms import steane_code_noisy

r = steane_code_noisy(p=0.002)
1 - r["corrected"]      # 4.2e-5  -- logical error
1 - r["uncorrected"]    # 1.0e-3  -- bare qubit: 24x worse
r["weight1_bound"]      # (1-p)^7 + 7p(1-p)^6, always exceeded

# Quadratic suppression: doubling p quadruples the logical error.
(1 - steane_code_noisy(0.004)["corrected"]) / (1 - steane_code_noisy(0.002)["corrected"])
# 3.98  ~= 4

1 - F ~ O(p^2) versus O(p) bare — the hallmark of a distance-3 code, since any single-qubit error is corrected and only (some) weight-2 errors cause logical failure. The pseudo-threshold sits near p ~ 0.05: below it encoding helps, at p = 0.25 it hurts. This is the number a fault-tolerant architecture must engineer its physical error rate below.

The payoff: logical qubit lifetime

Run the full loop — noise, syndrome extraction, recovery, repeat — and watch the encoded qubit outlive a bare one:

from quantum_debugger.algorithms import repeated_qec_cycles

r = repeated_qec_cycles(p=0.01, cycles=10)
r["logical_flip_probability"]   # 2.98e-4 == 3p^2 - 2p^3, vs p = 0.01 bare
r["lifetime_gain"]              # 33.9    ~= 1/(3p)
r["fidelities"][-1]             # matches (1 + (1-2q)^10)/2 exactly

The exact effective dynamics: one cycle = a logical bit-flip channel with q = 3p^2 - 2p^3, because every weight-2 or weight-3 physical error decodes to exactly X_L. So the k-cycle fidelity has the closed form (1 + (1-2q)^k)/2 — and the simulation reproduces it to machine precision at every cycle. The gain exceeds 1 for every p < 1/2 (the fixed point sits exactly at threshold), and a |+_L> codeword, being logical-X-invariant, never decays at all.

Detect and discard: the [[4,2,2]] code

Correction is expensive; detection is cheap. The [[4,2,2]] code stores TWO logical qubits in four physical ones with just two stabilizers (XXXX, ZZZZ), and simply throws away any run whose syndrome trips:

from quantum_debugger.algorithms import detect_single_errors, postselected_memory

detect_single_errors()["all_detected"]      # True -- all 12 single-qubit errors

r = postselected_memory(p=0.002)
1 - r["postselected_fidelity"]   # 3.0e-6 -- vs 1.0e-3 bare: 330x better
r["acceptance_probability"]      # 0.994  -- the price: ~0.6% of runs discarded

Distance 2 cannot tell which qubit erred, so it cannot correct — but every weight-1 error is caught, leaving only O(p^2) weight-2 events (some of which are exactly logical operators, e.g. X0 X1 = X_L1). Post-selection buys quadratic error suppression for linear rejection cost, which is why the [[4,2,2]] code anchors so many early fault-tolerance demonstrations.

When there’s no perfect code: the Petz recovery map

Amplitude damping and most real noise cannot be perfectly corrected by any code. The Petz map is the canonical near-optimal recovery — and it degrades gracefully from exact to approximate:

import numpy as np
from quantum_debugger.algorithms import petz_code_recovery

z0 = np.zeros(8); z0[0] = 1        # |0_L> = |000>
z1 = np.zeros(8); z1[7] = 1        # |1_L> = |111>

# `noise` is any list of Kraus operators on the 3 qubits (8x8 matrices).
# Correctable (bit-flip) errors: Petz recovers PERFECTLY -- it *is* the decoder.
petz_code_recovery(bit_flip_noise, [z0, z1], 0.6, 0.8)["recovered_fidelity"]  # 1.0

# Amplitude damping on the 3 qubits: not correctable, but Petz still helps.
r = petz_code_recovery(amp_damping_noise, [z0, z1], 0.6, 0.8)
r["noisy_fidelity"]      # 0.82  -- no correction
r["recovered_fidelity"]  # 0.93  -- approximate recovery

R_sigma(rho) = sigma^{1/2} N^dagger(N(sigma)^{-1/2} rho N(sigma)^{-1/2}) sigma^{1/2}, with reference sigma the maximally mixed code state. When the Knill-Laflamme conditions hold it reproduces the exact syndrome decoder; when they don’t it gives the best approximate correction available — the same map that underlies approximate-QEC and quantum-memory theory. It also perfectly inverts the channel on its reference state (R_sigma(N(sigma)) = sigma).