Week 12 | describing a code by the operators that leave it alone, and building quantum codes from pairs of classical ones | reading: Watrous Ch. 14–15.1; Roffe §4–5
Lecture 21 described codes by their code words. That stops working quickly: a code on 50 qubits has code words with \(2^{50}\) amplitudes. This lecture describes codes by the operators that leave every code word unchanged. By the end you should be able to decide whether two Pauli strings commute at a glance, read the parameters \([[n,k,d]]\) of a stabilizer code from its generators, compute the syndrome of any Pauli error, find logical operators, build a quantum code from two classical codes (the CSS construction), and decode single errors in the Steane code.
The \(n\)-qubit Pauli group \(\mathcal P_n\) consists of all tensor products \(P_1\otimes\cdots\otimes P_n\) with \(P_j\in\{I,X,Y,Z\}\), times a phase \(\pm1,\pm i\). We write these as strings, \(XZZXI\) or \(X_1Z_2Z_3X_4\), with qubits numbered from 1 on the left. The weight of a Pauli is the number of non-identity positions.
Any two elements of \(\mathcal P_n\) either commute or anticommute. They anticommute exactly when the number of positions where both are non-identity and different is odd.
Proof. Single-qubit Paulis commute when equal or when one is \(I\), and anticommute otherwise (\(XZ=-ZX\), etc.). Moving one string past the other position by position collects a factor \(-1\) for each anticommuting position, so \(PQ=(-1)^mQP\) with \(m\) that number.
\(X_1X_2\) and \(Z_2Z_3\) , while \(X_1X_2\) and \(Z_1Z_2\) .
For computation, write a Pauli (ignoring phase) as two bit vectors \((x\,|\,z)\): \(x_j=1\) if position \(j\) is \(X\) or \(Y\), \(z_j=1\) if it is \(Z\) or \(Y\). Then \(P\) and \(Q\) commute iff \(x_P\cdot z_Q+z_P\cdot x_Q\equiv0\pmod2\) (the symplectic inner product), and multiplying Paulis adds their vectors mod 2. Stabilizer codes become linear algebra over \(\mathbb F_2\), like the classical codes of Lecture 21.
In the \((x\,|\,z)\) notation, the three-qubit Pauli \(XYZ\) is
A stabilizer group \(S\) is an abelian subgroup of \(\mathcal P_n\) that does not contain \(-I\). Its code space is
$$\mathcal C(S)=\{|\psi\rangle:\ g|\psi\rangle=|\psi\rangle\ \text{for all }g\in S\}.$$\(S\) is described by a list of independent generators \(g_1,\dots,g_{n-k}\) (no generator is a product of the others); every element of \(S\) is a product of generators.
Why abelian and why not \(-I\)? If \(g,h\in S\) anticommuted, a common \(+1\) eigenvector would satisfy \(|\psi\rangle=gh|\psi\rangle=-hg|\psi\rangle=-|\psi\rangle\), so \(|\psi\rangle=0\). If \(-I\in S\), then \(|\psi\rangle=-|\psi\rangle\) directly. Either way the code space would be empty.
Tick every list of generators that defines a valid stabilizer group (abelian, without \(-I\)).
If \(S\) has \(n-k\) independent generators, then \(\mathcal C(S)\) has dimension \(2^k\): the code encodes \(k\) logical qubits in \(n\) physical ones. We call it an \([[n,k]]\) code.
Proof sketch. The projector onto \(\mathcal C(S)\) is \(\Pi=\prod_{i}\tfrac12(I+g_i)=\frac1{2^{n-k}}\sum_{g\in S}g\). Every \(g\ne I\) in \(S\) is a Pauli string with zero trace, so \(\dim\mathcal C(S)=\operatorname{tr}\Pi=2^n/2^{n-k}=2^k\). (verify_L22.py computes \(\operatorname{tr}\Pi\) for every code in this lecture.) Each independent generator cuts the space in half.
A stabilizer group on \(n=10\) qubits has 7 independent generators. How many logical qubits \(k\) does the code encode, and what is the dimension of its code space?
(a) Show that the bit-flip code of Lecture 21 is the stabilizer code of \(S=\langle Z_1Z_2,Z_2Z_3\rangle\). (b) Find \(n\) and \(k\) for \(S=\langle XXXX,ZZZZ\rangle\), and check that it is a valid stabilizer group.
(a) \(n=3\) and two generators, so \(k=1\). The \(+1\) eigenspace of \(Z_1Z_2\) is spanned by basis states with \(q_1=q_2\), and of \(Z_2Z_3\) by those with \(q_2=q_3\); together, \(|000\rangle\) and \(|111\rangle\). The group is \(S=\{III,ZZI,IZZ,ZIZ\}\).
(b) \(XXXX\) and \(ZZZZ\) differ at all four positions: an even number, so they commute. Their product is \((XZ)^{\otimes4}=(-iY)^{\otimes4}=(-i)^4\,YYYY=YYYY\), so \(S=\{IIII,XXXX,ZZZZ,YYYY\}\) and \(-I\notin S\). \(n=4\), two generators, \(k=2\): this is the \([[4,2]]\) code, which encodes two qubits in four.
Let \(|\psi\rangle\in\mathcal C(S)\) and let \(E\) be a Pauli error. For each generator \(g_i\), \(E|\psi\rangle\) is an eigenvector of \(g_i\) with eigenvalue \(+1\) if \(E\) commutes with \(g_i\) and \(-1\) if it anticommutes. The syndrome of \(E\) is the bit string \(s_i=1\) iff \(E\) anticommutes with \(g_i\); it does not depend on \(|\psi\rangle\).
Proof: two Paulis either commute or anticommute (Proposition 22.1); HW11 Problem 4 works out what this means for a measurement of \(g_i\).
The \([[5,1,3]]\) code of Example 22.3 below has generators \(XZZXI\), \(IXZZX\), \(XIXZZ\), \(ZXIXZ\). Find the syndrome bits of the error \(Z_4\) (1 means "anticommutes").
Measuring any \(g\in S\) on a code state returns \(+1\) with certainty and leaves the state unchanged; on \(E|\psi\rangle\) it returns the syndrome bit with certainty, again leaving the state unchanged. (Proof: HW11 Problem 4.)
So stabilizer measurements reveal the error and nothing about the encoded data (HW11 Problem 4 explains why this does not contradict "measurement disturbs"). Syndromes are linear: the syndrome of \(E_1E_2\) is the sum mod 2 of the syndromes of \(E_1\) and \(E_2\). In the \((x|z)\) picture, the syndrome is a matrix–vector product over \(\mathbb F_2\), just like the classical \(Hr\).
A logical operator is a Pauli that commutes with every element of \(S\) but is not itself in \(S\) (up to phase). It maps the code space to itself and acts non-trivially on it. The distance \(d\) is the smallest weight of a logical operator, and the code is an \([[n,k,d]]\) code.
An error that is itself a logical operator has trivial syndrome and silently changes the encoded data: that is a logical error. Since the smallest such error has weight \(d\), a distance-\(d\) code detects any error of weight up to \(d-1\) and corrects any error of weight up to \(\lfloor(d-1)/2\rfloor\) (two errors of that weight have different syndromes, or else their product, of weight \(\le d-1\), would be a logical operator or a stabilizer, and in the second case they act identically).
A code has distance \(d=4\). It detects every error of weight up to and corrects every error of weight up to .
For \(S=\langle XXXX,ZZZZ\rangle\): find logical operators for the two encoded qubits, the distance, and the syndromes of \(X_1\) and \(X_2\).
A Pauli commutes with \(ZZZZ\) iff it has an even number of \(X\)/\(Y\) positions, and with \(XXXX\) iff it has an even number of \(Z\)/\(Y\) positions. So weight-1 errors are all detected, and weight-2 operators like \(X_1X_2\) commute with \(S\) without being in it. One choice of logical operators: \(\bar X_1=X_1X_2\), \(\bar Z_1=Z_1Z_3\), \(\bar X_2=X_1X_3\), \(\bar Z_2=Z_1Z_2\) (each \(\bar X_i\) anticommutes with its \(\bar Z_i\), one overlapping \(X\)/\(Z\) position, and commutes with the other pair, two positions). Distance 2: \([[4,2,2]]\). \(X_1\) and \(X_2\) both anticommute with \(ZZZZ\) only: the same syndrome \((0,1)\). The code detects a single error but cannot tell where it is, which is why "correct up to \(\lfloor(d-1)/2\rfloor=0\)".
The generators are \(XZZXI\) and its cyclic shifts, \(IXZZX\), \(XIXZZ\), \(ZXIXZ\). Check that the first two commute, and count syndromes against single-qubit errors.
\(XZZXI\) and \(IXZZX\) are both non-identity at positions 2, 3, 4, and differ at positions 2 (\(Z\) vs \(X\)) and 4 (\(X\) vs \(Z\)); at position 3 both are \(Z\). Two anticommuting positions: they commute. (All six pairs commute; the code in Section 8 checks them.) There are \(5\times3=15\) single-qubit Paulis and \(2^4=16\) syndromes. All 15 errors have distinct nonzero syndromes, so the code corrects any single-qubit error, with no syndrome left over: it is perfect, the smallest code that protects one qubit against an arbitrary single-qubit error. \(XXXXX\) and \(ZZZZZ\) serve as \(\bar X\) and \(\bar Z\).
The cleanest way to get a stabilizer code is to take one classical code for the \(X\) errors and one for the \(Z\) errors. The construction is due to Calderbank, Shor and Steane (1996).
Let \(H_X\) and \(H_Z\) be binary matrices with \(n\) columns and with \(H_XH_Z^{\mathsf T}\equiv0\pmod2\). Take one \(X\)-type generator \(X(r)\) for each row \(r\) of \(H_X\) (\(X\) on the positions where \(r\) has a 1) and one \(Z\)-type generator \(Z(r)\) for each row of \(H_Z\). These generators commute and define a stabilizer code, with \(k=n-\operatorname{rank}H_X-\operatorname{rank}H_Z\). The \(Z\)-type checks detect \(X\) errors exactly as the classical code with parity-check matrix \(H_Z\) detects bit flips, and the \(X\)-type checks detect \(Z\) errors as the code with parity-check matrix \(H_X\) does.
Proof sketch. That the generators commute follows from Proposition 22.1 (HW11 Problem 3 carries it out for the Steane code). An \(X\) error \(X(e)\) anticommutes with \(Z(r)\) iff \(r\cdot e=1\), so the \(Z\)-syndrome is \(H_Ze\), the classical syndrome. Same for \(Z\) errors. The count of \(k\) is Proposition 22.2.
A CSS code on \(n=15\) qubits has \(\operatorname{rank}H_X=4\) and \(\operatorname{rank}H_Z=10\) (it satisfies \(H_XH_Z^{\mathsf T}\equiv0\)). How many logical qubits does it encode?
In the language of codes: if \(C_X=\ker H_X\) and \(C_Z=\ker H_Z\), the condition says that the row space of \(H_X\) lies inside \(C_Z\). The most common case uses one classical code \(C\) with parity-check matrix \(H\) that contains its own dual (\(C^\perp\subseteq C\)), and sets \(H_X=H_Z=H\); HW11 Problem 3 shows why this satisfies the condition of Theorem 22.5.
Take \(H_X=H_Z=H\), the Hamming parity-check matrix of Lecture 21. Each of the three rows of \(H\) gives one \(X\)-type and one \(Z\)-type generator, drawn as the plaquettes of the figure below. HW11 Problem 3 writes the six out, checks that they commute, and checks that \(\bar X=X^{\otimes7}\) and \(\bar Z=Z^{\otimes7}\) are logical operators. With \(n=7\) and \(\operatorname{rank}H=3\), \(k=7-3-3=1\).
Decoding is the Hamming decoder, run twice. An \(X\) error on qubit \(j\) anticommutes with the \(Z\)-type checks whose plaquettes contain \(j\); read as a binary number \((s_{h_1}s_{h_2}s_{h_3})\), the \(Z\)-syndrome is \(j\). A \(Z\) error gives the same pattern on the \(X\)-type checks. A \(Y\) error gives \(j\) on both. So the Steane code corrects any single-qubit error: it is a \([[7,1,3]]\) code (its smallest logical operators, such as \(X_1X_2X_3\), have weight 3; verify_L22.py finds them by search).
A \(Y\) error hits qubit 6 of the Steane code. The \(Z\)-checks \((h_1h_2h_3)\) read and the \(X\)-checks read .
| error | \(Z\)-checks \((h_1h_2h_3)\) | \(X\)-checks \((h_1h_2h_3)\) | decoder |
|---|---|---|---|
| none | 000 | 000 | nothing |
| \(X_j\) | \(j\) in binary | 000 | apply \(X_j\) |
| \(Z_j\) | 000 | \(j\) in binary | apply \(Z_j\) |
| \(Y_j\) | \(j\) in binary | \(j\) in binary | apply \(Y_j\) |
The Clifford gates (\(H\), \(S\), CNOT, and everything built from them) map Paulis to Paulis under conjugation: \(HXH=Z\), \(\mathrm{CNOT}\,(X\otimes I)\,\mathrm{CNOT}=X\otimes X\), and so on (Lecture 4). So a stabilizer state, described by \(n\) generators, stays a stabilizer state under Clifford gates, and updating the description costs \(O(n)\) per gate. Measurements of Paulis can be handled the same way. This is the Gottesman–Knill theorem (Lecture 7): circuits of Clifford gates, Pauli measurements and Pauli noise can be simulated efficiently on a classical computer, using \(O(n^2)\) bits instead of \(2^n\) amplitudes.
Tick every 100-qubit circuit that the Gottesman–Knill theorem lets a laptop simulate efficiently.
Two consequences. First, quantum advantage needs non-Clifford gates, such as \(T\) (which is why Lecture 23's "magic states" matter). Second, error-correction experiments, whose circuits are almost entirely Clifford, can be simulated at scale. Stim (Gidney, 2021) handles circuits with thousands of qubits, and samples small circuits at millions of shots per second, and is the tool of Lab 6.
import stim
gens = [stim.PauliString(s) for s in ("XZZX_", "_XZZX", "X_XZZ", "ZX_XZ")] # the [[5,1,3]] code
print("generators commute pairwise:", all(g.commutes(h) for g in gens for h in gens))
table = {}
for q in range(5):
for p in "XYZ":
e = stim.PauliString(5); e[q] = p
table[f"{p}{q+1}"] = "".join(str(int(not e.commutes(g))) for g in gens)
print(table)
print("15 errors, distinct nonzero syndromes:", len(set(table.values())) == 15 and "0000" not in table.values())
XL, ZL = stim.PauliString("XXXXX"), stim.PauliString("ZZZZZ")
print("XXXXX and ZZZZZ commute with S:", all(XL.commutes(g) and ZL.commutes(g) for g in gens), "; anticommute with each other:", not XL.commutes(ZL))
generators commute pairwise: True
{'X1': '0001', 'Y1': '1011', 'Z1': '1010', 'X2': '1000', 'Y2': '1101', 'Z2': '0101', 'X3': '1100', 'Y3': '1110', 'Z3': '0010', 'X4': '0110', 'Y4': '1111', 'Z4': '1001', 'X5': '0011', 'Y5': '0111', 'Z5': '0100'}
15 errors, distinct nonzero syndromes: True
XXXXX and ZZZZZ commute with S: True ; anticommute with each other: True
Example 22.3: the syndrome table of the \([[5,1,3]]\) code. stim.PauliString supports products (*), commutes, and indexing by qubit (from 0 on the left).
import time, numpy as np, stim # measure the four stabilizers of the [[5,1,3]] code twice, with noise in between; compare the rounds gens = "X0*Z1*Z2*X3 X1*Z2*Z3*X4 X0*X2*Z3*Z4 Z0*X1*X3*Z4" c = stim.Circuit(f""" MPP {gens} DEPOLARIZE1(0.01) 0 1 2 3 4 MPP {gens} DETECTOR rec[-4] rec[-8] DETECTOR rec[-3] rec[-7] DETECTOR rec[-2] rec[-6] DETECTOR rec[-1] rec[-5] """) t = time.time() det = c.compile_detector_sampler(seed=22).sample(1_000_000) print(f"1,000,000 shots in {time.time() - t:.2f} s; fraction with a nonzero syndrome: {det.any(axis=1).mean():.4f}") # stim's DEPOLARIZE1(p) applies X, Y or Z with probability p/3 each: p is the total error probability # (in Lecture 18's parametrization this is lambda = 4p/3, whose error probability is 3*lambda/4). Every single error has # a nonzero syndrome, so expect: print(f"P(at least one qubit hit) = 1 - 0.99^5 = {1 - 0.99**5:.4f}")
1,000,000 shots in 0.06 s; fraction with a nonzero syndrome: 0.0490 P(at least one qubit hit) = 1 - 0.99^5 = 0.0490
MPP measures a Pauli product directly; a DETECTOR declares a parity of measurement results that should be deterministic without noise (here: the same stabilizer measured twice). A million shots take a fraction of a second. Note Stim's convention: DEPOLARIZE1(p) applies \(X\), \(Y\) or \(Z\) with probability \(p/3\) each, so \(p\) is the total error probability.
Next time: surface codes, thresholds and decoders, the codes running on today's hardware, and the launch of Lab 6. HW11 (Lectures 21–22) is due at the start of Week 13.
all numbers and figures in these notes are computed in python (verify_L22.py) and follow the page theme