# Lab 8 Quantum Error Correction

Prerequisite

Other relevant materials

```
from qiskit import *
from qiskit.visualization import plot_histogram
from qiskit.visualization import plot_state_qsphere, plot_bloch_vector
from qiskit.quantum_info import Statevector
import numpy as np
```

```
sim = Aer.get_backend('aer_simulator')
```

## Part 1: 3-qubit code

**Goal**

Create circuits for 3-qubit code that encodes a one qubit state into a three qubit code state and utilize partiy check to detect and localise either bit-flip ( X ) or phase-flip ( Z ) errors on a single qubit in the codes.

### 1. Detect and locate a single bit-flip ( X ) error utilizing 3-qubit code and parity check circuit.

#### 📓Step A. Create a circuit to encode the state $|\psi\rangle = \frac{1}{\sqrt{2}}(|0\rangle + i|1\rangle)$ into 3-qubit code state, $\frac{1}{\sqrt{2}}(|000\rangle + i|111\rangle)$ to be protected from one bit-flip ( X ) error.

```
qc_3qx = QuantumCircuit(3)
### your code goes here. ###
#######
qc_3qx.draw('mpl')
```

#### 📓Step B. Plot the 3-qubit code state produced by the above circuit, `qc_3qx`

on the qsphere to validate the encoding.

The state displayed on the qshpere should match the 3-qubit code state for $|\psi\rangle$ upto a global phase.

```
### your code goes here ###
```

#### Step C. Implement the parity check gates to detect and localize a single bit flip error on the code qubits.

Following operation will apply a single bit flip error on the code. Execute the cells below.

```
def apply_err(n, err):
qc = QuantumCircuit(int(n), name='Error')
which_qubit = np.random.randint(n)
if err=='bit':
qc.x(which_qubit)
elif err=='phase':
qc.z(which_qubit)
else:
pass
err = qc.to_gate()
return err, which_qubit
```

```
err, which_qubit = apply_err(3, 'bit')
qc_3qx.append(err,range(3))
qc_3qx.draw('mpl')
```

📓 Apply the parity check gates on the circuit `qc`

with the extra registers for the appropriate number of auxiliary qubits and syndrome bits to locate a bit-flip ( X ) error on a single qubit in the code. ( For 3 qubit code, the number of syndrome bit states is equal to the number of possible single bit-flip error, including no error.)

```
# Execute this cell to add the extra registers
k = int(input('number of auxiliary qubits ( / syndrome bits): '))
qc_3qx.add_register(QuantumRegister(k, 'auxiliary'))
qc_3qx.add_register(ClassicalRegister(k, 'syndrome'))
```

```
# Apply the parity check gates and measure the parities on the syndrome bits to localize a single bit-flip ( X ) error on the code.
### your code goes here. ###
######
qc_3qx.draw('mpl')
```

#### 📓Step D. Complete the dictionary in the following cell to make the syndrome look-up table for all single bit-flip errors on the three qubit code.

The key of the dictionary is a two-bit string for the syndrome and the values reprent the corresponding a single qubit error gates with the appropriate qubit indicies on the code circuit.

```
#### complete the dictionary ###
table_syndrome = {'00': 'I[0]I[1]I[2]', '01':' your answer ',
'10':' your answer ', '11':' I[0]X[1]I[2] '}
######
print(table_syndrome)
```

Run the following cell to execute the circuit `qc_xerr`

on `aer_simulator`

.

```
qc_3qx_trans = transpile(qc_3qx, sim)
syndrome = sim.run(qc_3qx_trans, shots=1, memory=True).result().get_memory()
print(syndrome)
```

#### 📓Step E. Find the single bit-error and two bit-error based on the measured syndrome bits and check your answer with the value of the variable, `which_qubit`

.

Run the following cell.

```
your_answer = input('Enter the index of the code qubit that underwent bit-flip error: ')
print('\n')
print(which_qubit == int(your_answer))
```

#### Step F. Identify the logical $X$ ( $X_{L}$ ) and logical $Z$ ( $Z_{L}$ ) operators for the three qubit bit flip code.

📓 Find the logical $X$, $X_{L}$, for three qubit bit flip code where $X_{L}|000\rangle = |111\rangle$. Construct a circuit to perform $X_{L}|0\rangle_{L}$ and simulate it to check if the state produced by the circuit is $|1\rangle_{L}$, where the logical code word basis states, $|0\rangle_{L}$ and $|1\rangle_{L}$, are encoded as $|0\rangle_{L} = |000\rangle, ~ |1\rangle_{L}=|111\rangle$ .

```
### your code goes here ###
qc_XL = QuantumCircuit(3)
qc_XL.x(range(3))
qc_XL.measure_all()
bits_out = sim.run(qc_XL, shots=1, memory=True).result().get_memory()
print(bits_out)
```

📓 Find the minimal weight encoded logical $Z$ ( there are four equivalent $Z_{L}$s. ) where $Z_{L}|+\rangle_{L} = |-\rangle_{L}$ and $|+\rangle_{L} = |0\rangle_{L} + |1\rangle_{L}, ~~ |-\rangle_{L} = |0\rangle_{L} - |1\rangle_{L}$ . What is the distance of the three qubit bit flip code? Can it detect or/and correct a single phase-flip ( Z ) error?

- weight: the weight of an operator is the number of qubits that it acts non-trivially on. ( E.g. $Z\otimes Z\otimes I$ has weight 2. )
- distance of a code: the minimum number of errors that will change one logical code word basis state to another or the maximum number of errors that can be detected.

**Your answer :**

### 2. Construct a circuit that encodes one qubit state into 3-qubit code states and can locate a single phase-flip ( Z ) error through parity check.

#### 📓Step A. Create a circuit to encode the state $|\psi\rangle = \frac{1}{\sqrt{2}}(|0\rangle + i|1\rangle)$ into a 3-qubit code to protect it from one phase-flip ( Z ) error.

Here, the logical code word basis states, $|0\rangle_{L}$ and $|1\rangle_{L}$, are encoded as, $|0\rangle_{L} = |+++\rangle$ and $|1\rangle_{L} = |---\rangle$.

```
qc_3qz = QuantumCircuit(3)
### your code goes here. ###
########
qc_3qz.draw('mpl')
```

#### 📓Step B. Implement the parity check gates to detect and localize a single phase flip error on the code qubits.

Extra registers need to be added on the circuit for the auxiliary qubits and syndrome bits.

```
err, which_qubit = apply_err(3, 'phase')
qc_3qz.append(err, range(3))
### your code goes here ###
##########
qc_3qz.draw('mpl')
```

Excute the cell below to run the circuit `qc_zerr`

on `aer_simulator`

.

```
qc_3qz_trans = transpile(qc_3qz, sim)
syndrome = sim.run(qc_3qz_trans, shots=1, memory=True).result().get_memory()
print(syndrome)
```

#### 📓Step C. Find the single phase-error based on the measured syndrome bits and check your answer with the value of the variable, `which_qubit`

.

```
your_answer = input('Enter the index of the code qubit that underwent phase-flip error: ')
print('\n')
print(which_qubit == int(your_answer))
```

## Part 2: Repetition code as a stabilizer code

**Goal**

Stabilizer formalism defines Quantum Error Correcting Codes ( QECC ) by the set of operators that stabilize the logical code word states, instead of its state vectors. Interpreting quantum error correcting codes in terms of its stabilizers is beneficial since the decription of QECC is more concise and encoded logical operators can be obtained systematically. In the part 2 of this lab, we re-construct the repetition code as a stabilizer code to understand this new method.

### 📓 1. Find the two independent stablizer operators for the 3 qubit code states, $|000\rangle,~ |111\rangle$.

- The stabilizer operator $S$ of a n-qubit state $|\psi\rangle$ is the set of n-qubit Pauli operators that satisfy $S|\psi\rangle = |\psi\rangle$.

**Your answer :**

### 2. Construct a Quantum Phase Estimation ( QPE ) circuit to produce the eigenvalues for one of the stablizer, $I\otimes Z\otimes Z$.

#### 📓Step A. What are the eigenvalues of the operator $I\otimes Z \otimes Z$? Determine the minimum number of counting qubits needed in QPE circuit to estimate the eigenvalue accurately.

```
## Your answer goes here
t = int(input('The number of counting qubit: '))
```

#### 📓Step B. Complete the following code to build the QPE circuit.

Note: The QPE circuit essentially performs the projective measurement of the operator $I\otimes Z\otimes Z$ on the $|\psi\rangle = |0\rangle \otimes |0\rangle \otimes|+\rangle$.

```
qc0 = QuantumCircuit(t+3, 1)
qc0.h(-1)
qc0.barrier()
## your code goes here ##
######
qc0.measure(0, 0)
qc0.draw('mpl')
```

```
counts_qc0 = sim.run(qc0, shots=8192).result().get_counts()
plot_histogram(counts_qc0)
```

#### 📓Step C. Using the circuit identities, convert QPE part of the above circuit `qc0`

to the one with only CNOT gates.

```
qc1 = QuantumCircuit(t+3, 1)
qc1.h(-1)
qc1.barrier()
## your code goes here ##
######
qc1.measure(0, 0)
qc1.draw('mpl')
```

```
counts_qc1 = sim.run(qc1, shots=8192).result().get_counts()
plot_histogram(counts_qc1)
```

#### Summary

An error correcting code can be defined as an independent set of stabilizers and the errors are dectected by the measurement outcomes of this set of stabilizers.

Here, in part2 of this lab, we checked that there are two indepndent stabilizers for the three qubit repetition code, and measuring a stabilizer is equivalent to checking a parity. Therefore, to obtain set of syndromes for a single bit error detection with the three qubit repetition code, the circuit for encoding an arbitrary single qubit state and measuring two stabilizers has the folllowing structure.