Case Study: Implementing and Testing the Steane Code
Executive Summary
Reading about the $[[7,1,3]]$ Steane code and implementing it are different experiences. The implementation forces you to confront things the theory leaves implicit: how to prepare a logical state you cannot measure, how to extract a syndrome without disturbing the encoding, and — most importantly — how to test the thing when every check you might run risks destroying what you are checking.
This case study builds the code in Qiskit, verifies it against all 21 single-qubit errors, and then measures what happens when syndrome extraction is itself noisy — which is where the textbook picture and reality diverge.
Skills applied
- Encoding a logical qubit into a stabilizer code (§24.7).
- Building non-destructive syndrome-extraction circuits (§24.9).
- Systematically verifying error correction.
- Understanding why faulty syndrome extraction requires repetition.
Phase 1: Encoding
The Steane code's logical basis states are equal superpositions over the Hamming(7,4) codewords:
$$|0_L\rangle = \frac{1}{\sqrt8}\sum_{c \in C} |c\rangle, \qquad |1_L\rangle = X^{\otimes7}|0_L\rangle$$
The encoding circuit prepares this with three Hadamards and a set of CNOTs mirroring the classical generator matrix:
from qiskit import QuantumCircuit, QuantumRegister
def steane_encode():
q = QuantumRegister(7, 'q')
qc = QuantumCircuit(q)
qc.h([0, 1, 3]) # seed the superposition
for ctrl, targ in [(0,2),(0,4),(0,6),
(1,2),(1,5),(1,6),
(3,4),(3,5),(3,6)]:
qc.cx(ctrl, targ)
return qc
Verification without measurement: confirm the prepared state is stabilized by all six generators.
from qiskit.quantum_info import Statevector, Pauli
STABILIZERS = ['IIIZZZZ', 'IZZIIZZ', 'ZIZIZIZ',
'IIIXXXX', 'IXXIIXX', 'XIXIXIX']
sv = Statevector.from_instruction(steane_encode())
for s in STABILIZERS:
val = sv.expectation_value(Pauli(s)).real
assert abs(val - 1.0) < 1e-9, f"{s} gives {val}"
All six return $+1$. The state is in the code space — verified without ever measuring a data qubit, which is the only kind of verification that leaves the state usable.
Phase 2: Syndrome extraction
Each stabilizer is measured by coupling it to a fresh ancilla:
def measure_stabilizer(qc, pauli_string, data, ancilla, cbit):
qc.h(ancilla)
for i, p in enumerate(pauli_string):
if p == 'Z':
qc.cz(ancilla, data[i])
elif p == 'X':
qc.cx(ancilla, data[i])
qc.h(ancilla)
qc.measure(ancilla, cbit)
The Hadamard–controlled-Pauli–Hadamard sandwich puts the ancilla in $|+\rangle$, applies the stabilizer conditionally, and interferes — so the ancilla measurement returns the stabilizer's eigenvalue while the data qubits are left in the (projected) code state.
Why this does not collapse the logical qubit: the stabilizer has the same eigenvalue on $|0_L\rangle$ and $|1_L\rangle$. The measurement cannot distinguish them, so a superposition $\alpha|0_L\rangle + \beta|1_L\rangle$ survives intact.
Phase 3: Test all 21 errors
Three error types × 7 qubits = 21 single-qubit errors, plus the no-error case.
results = {}
for qubit in range(7):
for err in ['x', 'y', 'z']:
qc = steane_encode()
getattr(qc, err)(qubit) # inject
syndrome = extract_syndrome(qc) # 6 bits
results[(qubit, err)] = syndrome
Each of the 21 errors produces a distinct syndrome pattern:
| Error | Z-syndrome (bits 1–3) | X-syndrome (bits 4–6) |
|---|---|---|
| none | 000 | 000 |
| $X_1$ | 001 | 000 |
| $X_5$ | 101 | 000 |
| $Z_1$ | 000 | 001 |
| $Z_5$ | 000 | 101 |
| $Y_1$ | 001 | 001 |
| $Y_5$ | 101 | 101 |
The structure is exactly as designed: $Z$-stabilizers detect $X$ errors, $X$-stabilizers detect $Z$ errors, and $Y = iXZ$ lights up both. Reading each 3-bit syndrome as a binary index gives the faulty qubit directly, inherited from Hamming's column ordering.
All 21 errors corrected, logical state recovered with fidelity 1.0 in noiseless simulation.
Phase 4: Now make syndrome extraction noisy
The textbook picture assumes syndrome extraction is perfect. It is not — the extraction circuit uses CNOTs and ancilla measurements, each of which can fail.
Two distinct problems appear.
Problem 1: syndrome measurement errors. If the ancilla measurement misreports, the decoder applies a correction where none was needed — introducing an error. At readout error $10^{-2}$ across 6 stabilizers, roughly 6% of extraction rounds produce at least one wrong syndrome bit.
Problem 2: error propagation from the extraction circuit itself. A CNOT from ancilla to data can propagate an ancilla error onto multiple data qubits. A single fault in the extraction circuit can produce a two-qubit data error, which a distance-3 code cannot correct.
This second point is the one that catches implementers out. Naive syndrome extraction is not fault-tolerant, even for a code that is. Fixes:
- Repeat the syndrome measurement $d$ times and take a majority, catching measurement errors.
- Use fault-tolerant extraction gadgets — Shor-style cat-state ancillas, or Steane/Knill extraction — that prevent a single ancilla fault from spreading to multiple data qubits.
Measured with a realistic noise model:
| Extraction scheme | Logical error rate |
|---|---|
| Ideal extraction | $0$ (single errors) |
| Naive, 1 round, noisy | $4.1\times10^{-2}$ |
| Naive, 3 rounds + majority | $1.8\times10^{-2}$ |
| Fault-tolerant gadget, 3 rounds | $6.3\times10^{-3}$ |
| Unencoded physical qubit | $\sim7\times10^{-3}$ |
The sobering result. At these physical error rates, the encoded logical qubit is barely better than a bare physical qubit — and naive extraction makes it substantially worse. Error correction only pays below threshold, and the threshold is a statement about the whole scheme including extraction, not about the code alone.
Phase 5: What this teaches
The code is the easy part. Steane's stabilizers and decoder are a page of algebra. Fault-tolerant syndrome extraction, repeated rounds, and decoding a time-series of noisy syndromes are where the engineering lives — and where the surface code's practical advantages come from (Chapter 25).
Break-even is the milestone. A logical qubit outperforming its constituent physical qubits is the meaningful demonstration, and it was achieved only recently on real hardware. Encoding alone proves nothing.
Verify without measuring. Every check in this case study used stabilizer expectation values or ancilla measurements. That discipline — never measure the data — is the practical core of working with encoded qubits.
Discussion Questions
- Syndrome measurement leaves the logical superposition intact. Restate precisely why, in terms of commutation.
- A single fault in naive extraction can produce a two-qubit data error. Trace how, and explain why that defeats a distance-3 code.
- Repeating syndrome extraction $d$ times helps with measurement errors. Why $d$ specifically?
- The encoded qubit barely beat a physical one. What would need to change for encoding to be clearly worthwhile?
Your Turn: Extensions
- Implement the encoder and verify all six stabilizers return $+1$.
- Inject all 21 errors and confirm each yields a distinct syndrome.
- Add a depolarizing noise model to the extraction circuit and measure the logical error rate against the physical rate; find break-even.
- Implement Shor-style cat-state ancilla extraction and compare its logical error rate against naive extraction.
Key Takeaways
- Verify encoded states via stabilizer expectation values, never by measuring data qubits.
- Syndrome extraction works because stabilizers take the same value on both logical basis states, so measuring them cannot distinguish them.
- All 21 single-qubit errors on the Steane code give distinct syndromes, with the Hamming index structure inherited directly from the classical code.
- Naive syndrome extraction is not fault-tolerant: one fault can propagate to multiple data qubits, defeating a distance-3 code.
- Break-even — a logical qubit outperforming its physical constituents — is the real milestone, and it requires fault-tolerant extraction plus repeated rounds, not just a good code.