Quantum Error Correction

The challenges with Quantum Computing — Part 5

Arsalan Pardesi
5 min readDec 28, 2023
Binary heart
Photo by Alexander Sinn on Unsplash

The majority of the algorithms that we have discussed so far assume that qubits are perfect and can be prepared in any state and manipulated with precision. Qubits that obey these assumptions are called logical qubits. However, with the current state of technology, the imperfections cannot be removed entirely. We refer to these imperfect qubits as physical qubits.

The quantum computers use these physical qubits despite the imperfections by using error mitigation effects. Error correction in quantum computing is crucial because quantum systems are extremely sensitive to external disturbances (noise), a phenomenon known as quantum decoherence. This sensitivity can lead to errors in quantum computations, which are a major challenge in building reliable quantum computers.

Two ways of mitigating error are repetition code and zero-noise extrapolation. We will explore repetition code by implementing a bit-flip repetition code shortly. A zero-noise extrapolation technique runs a quantum algorithm at different noise levels and then extrapolates the results to estimate the outcome in the absence of noise.

Error Correction

Whenever you send a message, even one as simple as a bit, there is always a possibility for small errors to occur and for the bit to flip from 0 to 1 or vice versa. In classical communication, it is easy to correct this error by using some repetition, so instead of sending 1, you can send 3 bits of data as 111. The receiver can now correct the error, if one bit is flipped. The actual error correction techniques in classical communication might be more complex and sophisticated, but the idea is to build some sort of redundancy.

In quantum computing, this is not easy as (i) we cannot copy qubits due to the no-cloning theorem, and (ii) measuring a qubit will let its state collapse. We will approach this problem by utilizing additional ancilla qubits, which we will use as stabilizers. The idea is that these ancillas are not itself entangled with the qubits which store the state but still provide us hints about possible errors when being measured.

Let’s see how we can implement a bit-flip repetition code in Qiskit. We will use the lab from IBM Quantum Spring Challenge 2023 to implement this.

Implementing a Bit-flip Repetition Code

Setting up circuit and initializing qubit:

# Setup a base quantum circuit for our experiments
encoding = QuantumRegister(3)
stabilizer = QuantumRegister(2)

encoding_q0, encoding_q1, encoding_q2 = encoding
stabilizer_q0, stabilizer_q1 = stabilizer

# Results of the encoding
results = ClassicalRegister(3)

result_b0, result_b1, result_b2 = results

# For measuring the stabilizer
syndrome = ClassicalRegister(2)

syndrome_b0, syndrome_b1 = syndrome

# The actual qubit which is encoded
state = encoding[0]

# The ancillas used for encoding the state
ancillas = encoding[1:]


# Initializing
def initialize_circuit() -> QuantumCircuit:
return QuantumCircuit(encoding, stabilizer, results, syndrome)

# Initializing the qubit
initial_state = initialize_circuit()

initial_state.x(encoding[0])
initial_state.draw(output="mpl")

We will now encode the qubit. Similar to classical case, we will use repetition code in order to store the initial qubit, i.e. mapping our state |ψ⟩ = a|0⟩ + b|1⟩ to the state |ψ⟩ = a|000⟩ + b|111⟩. This is an entangled state, therefore when 1 qubit is measured the outcome of the other 2 qubits is also known:

# Encoding using bit flip code
def encode_bit_flip(qc, state, ancillas):
qc.barrier(state, *ancillas)
for ancilla in ancillas:
qc.cx(state, ancilla)
return qc


# The circuit encoding our qubit
encoding_circuit = encode_bit_flip(initialize_circuit(), state, ancillas)

# The circuit including all parts so far
complete_circuit = initial_state.compose(encoding_circuit)
complete_circuit.draw(output="mpl")

We will now prepare a decoding circuit that will allow us to decode the state:

# Decoding (doing the reverse)
def decode_bit_flip(qc, state, ancillas):
qc.barrier(state, *ancillas)
for ancilla in ancillas:
qc.cx(state, ancilla)
return qc


decoding_circuit = decode_bit_flip(initialize_circuit(), state, ancillas)

decoding_circuit.draw(output="mpl")

We will now compute the syndrome bits (using our ancilla qubits), such that they can be measured to detect single bit-flip errors:

# Add functions such that the classical bits can be used to see which qubit is flipped in the case a single qubit is flipped.
# Use 2 classical bits for it.
# 0 = 00 = no qubit flipped
# 1 = 01 = first qubit (qubit 0) flipped
# 2 = 10 second qubit (qubit 1) flipped
# 3 = 11 = third qubit (qubit 2) flipped
def measure_syndrome_bit(qc, encoding, stabilizer):
qc.barrier()
encoding_q0, encoding_q1, encoding_q2 = encoding
stabilizer_q0, stabilizer_q1 = stabilizer

# For measuring the stabilizer

qc.cx(encoding_q2, stabilizer_q0)
qc.cx(encoding_q0, stabilizer_q0)
qc.cx(encoding_q1, stabilizer_q1)
qc.cx(encoding_q2, stabilizer_q1)

qc.barrier()
qc.measure(stabilizer, syndrome)
with qc.if_test((syndrome_b0, 1)):
qc.x(stabilizer_q0)
with qc.if_test((syndrome_b1, 1)):
qc.x(stabilizer_q1)

return qc


syndrome_circuit = measure_syndrome_bit(initialize_circuit(), encoding, stabilizer)

complete_circuit = initial_state.compose(encoding_circuit).compose(syndrome_circuit)
complete_circuit.draw("mpl")

We will now use our dynamic circuit to use our syndrome measurement in order to correct potential errors:

# Correcting the errors
def apply_correction_bit(qc, encoding, syndrome):
qc.barrier()
encoding_q0, encoding_q1, encoding_q2 = encoding

with qc.if_test((syndrome_b0, 0)):
with qc.if_test((syndrome_b1,1)):
qc.x(encoding_q1)


with qc.if_test((syndrome_b0, 1)):
with qc.if_test((syndrome_b1,0)):
qc.x(encoding_q0)

with qc.if_test((syndrome_b0, 1)):
with qc.if_test((syndrome_b1,1)):
qc.x(encoding_q2)

return qc


correction_circuit = apply_correction_bit(initialize_circuit(), encoding, syndrome)
complete_circuit = (
initial_state.compose(encoding_circuit)
.compose(syndrome_circuit)
.compose(correction_circuit)
)
complete_circuit.draw(output="mpl")

Let us now add the decoder to our circuit to measure our initial qubit:

def apply_final_readout(qc, encoding, results):
qc.barrier(encoding)
qc.measure(encoding, results)
return qc


measuring_circuit = apply_final_readout(initialize_circuit(), encoding, results)
complete_circuit = (
initial_state.compose(encoding_circuit)
.compose(syndrome_circuit)
.compose(correction_circuit)
.compose(decoding_circuit)
.compose(measuring_circuit)
)

complete_circuit.draw(output="mpl")

This is what the final circuit looks like:

bit-flip correction circuit
Bit-flip correction circuit

Let’s test our circuit on a simulator:

backend = Aer.get_backend("qasm_simulator")
# We run the simulation and get the counts
counts = backend.run(complete_circuit, shots=1000).result().get_counts()
# And now we plot a histogram to see the possible outcomes and corresponding probabilities
plot_histogram(counts)
historgram
Error correction circuit output

We can see that we get the correct results (00 001), since we initialized our initial qubit in the state 1.

Conclusion

Quantum computing provides an exciting preview of what is to come. While the technology is at the stage currently where we cannot yet invent many useful applications, ongoing research and developments are expected to address current challenges. Continued progress in hardware, algorithms and error correction techniques is essential for unlocking the full potential of quantum computing. They are not expected to replace classical computers but to complement them, solving specific problems more efficiently.

If you like the article, please share and follow me on Medium and LinkedIn to stay connected.

--

--

Arsalan Pardesi

Strategy & Transactions professional — Writes about SaaS, diligence, value creation, growth strategies, data analytics and ML.