Quantum Computation & Cryptography · Lecture 22

Lecture 22
The Stabilizer Formalism and CSS Codes

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

Cartoon: the guest listA stick figure stands at a door labelled code space, holding a short list with four lines on it. A long queue of states waits outside. A speech bubble says: I don't list who gets in. I list what they all ignore. codespace I don't list who gets in.I list what they allignore.
a stabilizer code is defined by a few commuting Pauli operators that fix every code state: \(n-k\) operators instead of \(2^k\) amplitudes
In this lecture
  1. The Pauli group
  2. Stabilizer codes
  3. Syndromes
  4. Logical operators and distance
  5. CSS codes
  6. The Steane code
  7. Why stabilizer circuits are easy to simulate
  8. Try it in code
  9. Summary and exercises

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.

1 — The Pauli group

definition 22.1 — the n-qubit Pauli group

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.

proposition 22.1 — two Paulis commute or anticommute

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\) .

Use Proposition 22.1: count the positions where both strings are non-identity and different, then look at the parity of that count.
\(X_1X_2\) and \(Z_2Z_3\) clash only at qubit 2 (one position: anticommute); \(X_1X_2\) and \(Z_1Z_2\) clash at qubits 1 and 2 (two positions: commute). The rule is: count, then check the parity.

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

\(x_j=1\) where the string has \(X\) or \(Y\); \(z_j=1\) where it has \(Z\) or \(Y\).
\(X\) and \(Y\) sit at positions 1 and 2, so \(x=110\); \(Y\) and \(Z\) sit at positions 2 and 3, so \(z=011\).

2 — Stabilizer codes

definition 22.2 — stabilizer group and stabilizer code

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\)).

For each pair, check commutation by counting clashing positions, and multiply the generators to see whether \(-I\) appears.
\(XX,ZZ\) and \(XX,YY\) each clash at two positions, so they commute; \(XX\cdot YY=-ZZ\), not \(-I\). \(XI,ZI\) anticommute, and \(ZZ\cdot(-ZZ)=-I\).
proposition 22.2 — counting logical qubits

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?

Proposition 22.2: \(n-k\) is the number of independent generators, and the dimension is \(2^k\).
\(k=10-7=3\), and the code space has dimension \(2^{10}/2^7=2^3=8\): each generator halves the space.
example 22.1 — two codes in stabilizer language

(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.

3 — Syndromes

proposition 22.3 — the syndrome of a Pauli error

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").

Only position 4 matters. \(Z\) anticommutes with \(X\) and \(Y\) and commutes with \(I\) and \(Z\): read off the fourth letter of each generator.
The fourth letters are \(X,Z,Z,X\), so \(Z_4\) anticommutes with the first and last generators: syndrome 1001.
proposition 22.4 — measuring stabilizers is harmless

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\).

4 — Logical operators and distance

definition 22.3 — logical operators; distance; [[n,k,d]]

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 .

Detect up to \(d-1\); correct up to \(\lfloor(d-1)/2\rfloor\).
It detects up to weight 3 and corrects up to weight \(\lfloor3/2\rfloor=1\). An even distance buys extra detection but no extra correction.
example 22.2 — the [[4,2,2]] code detects but cannot correct

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\)".

example 22.3 — the perfect [[5,1,3]] code

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\).

5 — CSS codes

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).

theorem 22.5 — the CSS construction

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?

Each independent row gives one independent generator: \(k=n-\operatorname{rank}H_X-\operatorname{rank}H_Z\).
\(k=15-4-10=1\). (This is the 15-qubit quantum Reed–Muller code, which reappears with magic states.)

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.

6 — The Steane code

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\).

The Steane code drawn on a triangleSeven qubits on a triangle: qubits 1, 2 and 4 at the corners, 3, 5 and 6 at the midpoints of the sides (3 between 1 and 2, 5 between 1 and 4, 6 between 2 and 4), and 7 at the centre. The triangle is divided into three four-sided plaquettes, one per row of the Hamming parity-check matrix: {4,5,6,7} (h1), {2,3,6,7} (h2) and {1,3,5,7} (h3). Each plaquette carries one X-type and one Z-type stabilizer on its four qubits. A single error on qubit j is detected by exactly the plaquettes that contain j, and reading them as a binary number gives j.1243567h1 = 0001111qubits {4,5,6,7}h2 = 0110011qubits {2,3,6,7}h3 = 1010101qubits {1,3,5,7}each plaquette: X on its 4 qubitsand Z on its 4 qubitsan error on qubit j lightsthe plaquettes holding j
The Steane code on a triangle: each of the three plaquettes is one row of the Hamming matrix and carries an \(X\)-type and a \(Z\)-type stabilizer on its four qubits. Qubit \(j\) belongs to the plaquettes given by the binary digits of \(j\): qubit 5 = 101 belongs to \(h_1\) and \(h_3\).

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 .

\(Y\) acts like \(X\) and \(Z\) together (up to phase), so both halves of the syndrome see it, and each half spells the qubit number in binary.
\(6=110\): qubit 6 lies in plaquettes \(h_1\) and \(h_2\). Both the \(Z\)-checks and the \(X\)-checks read 110, and the decoder applies \(Y_6\).
error\(Z\)-checks \((h_1h_2h_3)\)\(X\)-checks \((h_1h_2h_3)\)decoder
none000000nothing
\(X_j\)\(j\) in binary000apply \(X_j\)
\(Z_j\)000\(j\) in binaryapply \(Z_j\)
\(Y_j\)\(j\) in binary\(j\) in binaryapply \(Y_j\)

Steane syndrome decoder

Pick an error type, then click qubits to place errors (click again to remove). Each plaquette shows its \(Z\)-check (left half, sees \(X\) and \(Y\)) and \(X\)-check (right half, sees \(Z\) and \(Y\)). The decoder applies the Hamming correction to each half separately. Find two errors that make the decoder create a logical error.

7 — Why stabilizer circuits are easy to simulate

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.

The theorem needs every ingredient to map Paulis to Paulis: Clifford gates, Pauli measurements and Pauli noise.
The first two use only Clifford gates, Pauli measurements and Pauli noise, which is exactly what Stim handles. A supply of \(T\) gates is what lifts a circuit beyond classical simulation.

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.

8 — Try it in code

tool — Pauli strings in Stim (stim 1.16)
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).

tool — measuring stabilizers a million times
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.

9 — Summary and exercises

summary

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.

exercises 22
  1. Do \(XYZ\) and \(ZZZ\) commute? Do \(XXY\) and \(YZX\)?
  2. For the \([[4,2,2]]\) code, find the syndromes of \(Z_3\) and \(Y_2\). Which errors of weight 2 have a trivial syndrome but are not in \(S\)?
  3. Shor's code has 8 generators on 9 qubits. Confirm \(k=1\). Its distance is 3; give a logical operator of weight 3.
  4. Using the table of Section 8, decode the \([[5,1,3]]\) syndrome 1100.
  5. In the Steane code, a \(Z\) error occurs on qubit 5. Which checks fire? What if a \(Z\) occurs on qubit 5 and an \(X\) on qubit 2?
  6. Two different sets of generators can define the same stabilizer group. Show that \(\langle Z_1Z_2,Z_2Z_3\rangle=\langle Z_1Z_2,Z_1Z_3\rangle\).
  1. \(XYZ\) vs \(ZZZ\): positions 1 (\(X\)/\(Z\)) and 2 (\(Y\)/\(Z\)) anticommute, position 3 does not: two, so they commute. \(XXY\) vs \(YZX\): all three positions anticommute: odd, so they anticommute.
  2. \(Z_3\) anticommutes with \(XXXX\) only: syndrome \((1,0)\) in the order \((XXXX,ZZZZ)\). \(Y_2\) anticommutes with both: \((1,1)\). Any weight-2 operator of the same type, such as \(X_iX_j\) or \(Z_iZ_j\) (and also e.g. \(Y_iY_j\)), has trivial syndrome and is a logical operator.
  3. \(k=9-8=1\). \(Z_1Z_4Z_7\) (one \(Z\) in each block) changes the sign inside every block, \(|000\rangle\pm|111\rangle\mapsto|000\rangle\mp|111\rangle\), so it swaps \(|0_L\rangle\) and \(|1_L\rangle\): a logical \(\bar X\) of weight 3. \(X_1X_2X_3\) fixes a "+" block and negates a "−" block, so it acts as \(\bar Z\), also of weight 3.
  4. 1100 is the syndrome of \(X_3\): apply \(X_3\).
  5. \(Z_5\): \(X\)-checks of \(h_1\) and \(h_3\) (101 = 5); no \(Z\)-check fires. Adding \(X_2\): \(Z\)-checks of \(h_2\) only (010 = 2). The two halves are decoded independently, so both errors are corrected: CSS codes correct one \(X\) and one \(Z\) error at the same time, even on different qubits.
  6. \(Z_1Z_3=(Z_1Z_2)(Z_2Z_3)\), so the second group is inside the first; and \(Z_2Z_3=(Z_1Z_2)(Z_1Z_3)\), so the first is inside the second.

Reading and sources

all numbers and figures in these notes are computed in python (verify_L22.py) and follow the page theme