Retooling the bit-flip quantum repetition code to handle phase errors#
From bit flips to phase flips#
So far, you have learned about the bit-flip quantum repetition code. In complete analogy with the classical setting, the bit-flip quantum repetition code protects information by redundantly encoding the state of a single qubit into multiple qubits while respecting the no-cloning theorem.
You have also learned how to prepare the logical states \(\vert0\rangle_L = \vert000\ldots\rangle\) and \(\vert1\rangle_L = \vert111\ldots\rangle\). The logical superposition states are then
\(\vert+\rangle_L = \frac{1}{\sqrt{2}}\left(\vert0\rangle_L + \vert1\rangle_L\right) = \frac{1}{\sqrt{2}}\left(\vert000\ldots\rangle + \vert111\ldots\rangle\right)\)
and
\(\vert-\rangle_L = \frac{1}{\sqrt{2}}\left(\vert0\rangle_L - \vert1\rangle_L\right) = \frac{1}{\sqrt{2}}\left(\vert000\ldots\rangle - \vert111\ldots\rangle\right)\).
The bit-flip repetition code identifies \(X\) errors on the logical states \(\vert0\rangle_L\) and \(\vert1\rangle_L\) by checking the parity of the qubits, two at a time, through measurement of the observables \(Z_1Z_2\), \(Z_2Z_3\), \(Z_3Z_4\) and so on. The effect of an \(X\) error on \(\vert0\rangle_L\) and \(\vert1\rangle_L\) is to simply flip one of the bits, and the pairwise parity checks pick up this error.
Recall that \(Z\) errors have no effect on the logical \(\vert0\rangle_L\) and \(\vert1\rangle_L\) states, since they simply produce an overall phase. These overall phases are not picked up by the parity checks.
The effect of \(Z\) errors on the logical superposition states is different. To see this, recall that \(Z_k\vert+\rangle_L = \vert-\rangle_L\) and \(Z_k\vert-\rangle_L = \vert+\rangle_L\) for any individual qubit \(k\). However, we are unable to detect this phase-flip using our standard \(Z_iZ_{i+1}\) parity checks since the pairwise parities remain unaffected. We will need to modify our syndrome detection procedure to protect superposition states against phase flips.
In this chapter, we will make changes to the bit-flip repetition code so that we can protect against phase flips. In the process, we will create the so-called phase-flip repetition code.
Detecting phase flips#
Recall that the effect of a \(Z\) gate (phase flip) is to transform between \(\vert+\rangle\) and \(\vert-\rangle\). More explicitly, a \(Z\) error is a phase-flip error because \(Z\vert+\rangle = \vert-\rangle\) and \(Z\vert-\rangle = \vert+\rangle\). This phase flip is not detectable through the bit-wise parity checks directly.
There are two ways to get around this challenge. The first solution is to sandwich phase-flip errors between Hadamard gates (recall the circuit identity \(HZH = X\)). This transforms phase flips into bit flips, and we can use our existing toolkit for bit-flip repetition codes. Alternatively, we can directly measure \(X_iX_{i+1}\) parity checks (see Dan Browne’s lecture notes, section 1.3.9 for the circuit that achieves this parity check). Since the two methods are equivalent, we will use the first method. However, note that phase-flip repetition codes are typically associated with \(X_iX_{i+1}\) parity checks in the literature.
Deciding whether a logical error has occurred#
In addition to updating our procedure for detecting phase flips, we will also need to update our procedure for deciding whether the information contained in these syndrome measurements constitutes a logical error.
In the bit-flip repetition code scenario, we were protecting the logical states \(\vert0\rangle_L\) and \(\vert1\rangle_L\). We determined whether a logical error has happened by seeing whether we can use the decoded syndromes to flip the measured data qubits. Importantly, we flipped the post-measurement outcomes of the data qubits using the decoded syndromes and compared it with the initially prepared logical state. If the two matched, then we determined that a logical error didn’t happen.
Whenever a logical error did happen, we only detected that it happened using our syndromes – in principle, we could have corrected the error by flipping the relevant data qubits within the circuit, as opposed to our current procedure where we flip the outcomes of measuring the data qubits. In fact, our process for measuring the logical error probability (by flipping the measured data qubits) assumes that correcting within the circuit before the measurements and after the measurements produces identical results. However, to correct within the circuit, we would have had to first measure the syndromes, then decode these syndromes to identify error locations, and then apply corrective flips (or keep track of them in software and update future steps). In real quantum systems, we typically measure and decode the syndromes to track which errors occurred, and update future steps in the computation based on the identified errors – for this reason, the decoder and the control system around the quantum computer need to operate in concert to avoid a backlog of such syndromes from occurring.
In the phase-flip setting, we can no longer assume that correcting within the circuit before the measurements and after the measurements produces identical results. This is because we can no longer measure the data qubits (which hold superposition states \(\vert+\rangle_L = \frac{1}{\sqrt{2}}\left(\vert000\ldots\rangle + \vert111\ldots\rangle\right)\) and recover the full information about them in one shot. This procedure worked for bit-flip repetition codes, where we were protecting \(\vert0\rangle_L = \vert000\ldots\rangle\) or \(\vert1\rangle_L=\vert111\ldots\rangle\) from errors, since the data qubits are also measured in the \(Z\) basis, which leaves the logical states (flipped or otherwise) unchanged.
Therefore, we will need to update how we determine whether a logical error has occurred.
Examples demonstrating the challenge presented by superposition states
Consider the following two scenarios in a 3-qubit repetition code, one in the bit-flip setting, and another in the phase-flip setting, where an error has occurred on qubits 1 and 2. Note that 2 errors in a 3-qubit repetition code is indeed a logical error, and we expect the decoder to fail to detect these errors successfully.
Deciding whether a logical error occurred in the bit-flip repetition code
Assume that we started with the logical state \(\vert0\rangle_L = \vert000\rangle\)
Bit flips occur on qubits 1 and 2, qubit 0 unaffected. State is now \(\vert011\rangle\)
Measured data qubits: [0, 1, 1]
Syndrome measurements: [1 0]
Decoded error locations: [0]
Using the decoded error locations to flip the measured data qubits: [1, 1, 1]
Compare with known initial state: [1, 1, 1] does not equal [0, 0, 0]
We decide correctly that a logical failure has happened.
Deciding whether a logical error occurred in the phase-flip repetition code
Assume that we started with the logical state \(\vert+\rangle_L = \frac{1}{\sqrt{2}}\left(\vert000\rangle + \vert111\rangle\right)\)
Phase flips occur on qubits 1 and 2, qubit 0 unaffected. Since we are converting phase flips into bit flips, state is now \(\frac{1}{\sqrt{2}}\left(\vert011\rangle + \vert100\rangle\right)\)
Measured data qubits: [0, 1, 1]
Syndrome measurements: [1 0]
Decoded error locations: [0]
Using the decoded error locations to flip the measured data qubits: [1, 1, 1]
Compare with known initial state: Initial state could have collapsed to \(\vert000\rangle\) or \(\vert111\rangle\). Looks like a match to one of these.
We decide that a logical failure did not happen, and don’t account for it!
Note that the misidentification in the phase-flip setting would have occurred even if we measured the data qubits in the Hadamard basis.
In both scenarios above, a logical error has indeed happened because 2 qubits (more than half) have flipped in a 3-qubit repetition code. The logical error misleads the decoder toward identifying the wrong qubits as error locations. We decide whether a logical error has occurred by flipping the post-measurement information from the data qubits using the misled decoder and comparing whether this produces the known initial state. In the bit-flip case, we can spot the mismatch and detect the logical error. However, in the phase-flip case, we can be misled into thinking that a logical error did not happen when working with superposition states. This is because we are operating with only partial information about the superposition after measurements of the data qubits.
Updating our toolkit#
As discussed above, we can no longer assume that correcting within the circuit before the measurements and after the measurements produces identical results. An error-corrected quantum computer will need to resolve these errors by acting on the syndrome information in real time.
In order to create a plot similar to the one we produced for bit-flip repetition codes comparing the physical and logical error probabilities, we will need to simulate the scenario where the decoder is perfectly keeping up with the information generated by the syndromes, and interpreting the errors correctly when fewer than half the qubits have experienced errors. Then, a logical error occurs when the decoder is unable to interpret the correct error locations because there are too many errors.
Since we cannot infer the correct number of errors that have occurred using the post-measurement information from the data qubits, we will need to find another way to record the ground truth to compare against our decoder. In the code below, we will directly record the errors that have been applied for later comparison with the decoded syndromes. On real quantum processors, this is a commonly used technique to know precisely what errors occurred.
To speed up the simulations, we will collect all the circuits and run them in one batch, rather than one by one.
def get_logical_error_probability_for_rep_code(n_qubits, error_probability,
logical_state = '0', error_gate = cirq.X,
n_shots = 100,
simulator = cirq.Simulator()
):
# step 1: build the repetition code circuit without errors
base_circuit = create_full_repetition_code_circuit(n_qubits, error_probability, logical_state = logical_state,
error_gate = error_gate)
# step 2: generate all errors
# first, create independent errors in a n_shots x n_qubits matrix
# then, for each shot, the errors can be taken sliced out of this matrix and applied to the data qubits
actual_errors_all_shots = []
error_mask = np.random.random((n_shots, n_qubits)) < error_probability
for shot in range(n_shots):
actual_errors_all_shots.append(np.where(error_mask[shot])[0].tolist())
# step 3: insert all errors into copies of the base_circuit
circuits = []
if logical_state == '+':
insert_index = (1 + # initial H gate
(n_qubits - 1) + # CNOT gates to create logical +
+ 1) # H gates to turn phase flips into bit flips
elif logical_state == '-':
insert_index = (1 + # initial H gate
(n_qubits - 1) + # CNOT gates to create logical +
1 + # Z gate to turn logical + into logical -
1) # H gates to turn phase flips into bit flips
data_qubits = cirq.LineQubit.range(n_qubits)
for shot_errors in actual_errors_all_shots:
circuit = base_circuit.copy()
error_moment = []
for i in range(n_qubits):
if i in shot_errors:
error_moment.append(error_gate(data_qubits[i]))
if error_moment:
circuit.insert(insert_index, cirq.Moment(error_moment)) # insert a moment with all errors
circuits.append(circuit)
# step 4: run all noise instances (circuits) in one batch
results = simulator.run_batch(circuits, repetitions=1)
# step 5: decode the syndrome information
syndromes = [results[i][0].measurements['syndrome'].tolist()[0] for i in range(n_shots)]
decoder = MWPMDecoder1D(num_qubits=n_qubits)
decoded_syndromes = [decoder.decode(syndrome) for syndrome in syndromes]
# step 6: count logical errors
logical_errors = 0
# phase-flip rep code, detecting phase flips by turning them into bit flips
for actual_error_locations, decoded_error_locations, in zip(actual_errors_all_shots, decoded_syndromes):
# compare decoder with knowledge of actual error locations
if not np.array_equal(actual_error_locations, decoded_error_locations):
logical_errors += 1
return logical_errors * 1. / n_shots
Putting it all together#
Minor changes in the code#
In the function create_full_repetition_code_circuit, we remove the errors for later insertion by get_logical_error_probability_for_rep_code as described above. We also insert the Hadamard gates that convert phase flips into bit flips.
No changes in the code#
The functions get_logical_error_probability_simulated, get_syndrome_measurement, create_repetition_code_encoder, get_logical_error_probability_analytical and plot_logical_error_probabilities all remain the same as in the bit-flip case.
Running a logical error probability simulation for phase-flip repetition codes#
Now, we put the above functions together to run a full simulation of the phase-flip repetition codes protecting a logical \(\vert+\rangle_L\) state from \(Z\) errors.
distances = [3, 5, 7, 9]
physical_errors = np.logspace(-3, 0, 10)
logical_state = '+'
error_gate = cirq.Z
n_shots = 50000
all_logical_errors = get_logical_error_probability_simulated(distances, physical_errors, n_shots = n_shots,
logical_state = logical_state, error_gate = error_gate,
simulator = cirq.CliffordSimulator())
all_analytical_errors = get_logical_error_probability_analytical(distances, physical_errors)
Simulating distance-3 repetition code circuits
100%|████████████████████████████████████████████████████████████████████████████████| 10/10 [04:32<00:00, 27.30s/it]
Simulating distance-5 repetition code circuits
100%|████████████████████████████████████████████████████████████████████████████████| 10/10 [07:39<00:00, 45.90s/it]
Simulating distance-7 repetition code circuits
100%|████████████████████████████████████████████████████████████████████████████████| 10/10 [11:16<00:00, 67.68s/it]
Simulating distance-9 repetition code circuits
100%|████████████████████████████████████████████████████████████████████████████████| 10/10 [15:18<00:00, 91.87s/it]
Note that the simulations are significantly slower than in the bit-flip case, since we are running 1 shot of many (n_shots) circuits, each with randomly applied \(Z\) errors, as opposed to n_shots runs of 1 circuit using Cirq’s gate.with_probability() functionality. In the next chapter, we will learn how to run faster simulations.
plot_logical_error_probabilities(distances, physical_errors, all_logical_errors, all_analytical_errors)
Summary#
In this chapter, you learned how to construct and test phase-flip repetition codes. While the previously discussed bit-flip repetition codes were completely analogous with the classical setting, phase-flip repetition codes required additional considerations to account for the effect of measurements on superpositions.
These two kinds of repetition codes protect from bit-flip or phase-flip errors, but not at the same time. In a real quantum computer, protecting from both kinds of errors will be necessary. In the next chapters, you will learn how to run faster simulations, and then discover the Shor code which combines bit-flip and phase-flip repetition codes intuitively. Following that, you will learn how to implement the surface code – a 2D extension of the 1D repetition codes that protects from both kinds of errors and is particularly well-suited for implementation on real quantum processors.
Version History#
v0: Sep 12, 2025, github/@aasfaw
v1: Sep 13, 2025, github/@aasfaw readability improvements and typo fixes
v2: Sep 16, 2025, github/@aasfaw edits capturing feedback from Earl Campbell