# Quantum error correction (My Lab)

Cahyati Supriyati Sangaji(My Note)

You can do actual insightful science with IBMQ devices and the knowledge you have about quantum error correction. All you need are a few tools from Qiskit.

!pip install -U qiskit==0.19

!pip install -U qiskit-ibmq-provider==0.7from qiskit import *

from IPython.display import clear_output

clear_output()

# Using a noise model

In this lab we are going to deal with noisy quantum systems, or at least simulations of them. To deal with this in Qiskit, we need to import some things.

`from qiskit.providers.aer.noise import NoiseModel`

from qiskit.providers.aer.noise.errors import pauli_error, depolarizing_error

from qiskit.providers.aer.noise import thermal_relaxation_error

The following function is designed to create a noise model which will be good for what we are doing here. It has two types of noise:

- Errors on
`cx`

gates in which an`x`

,`y`

or`z`

is randomly applied to each qubit. - Errors in measurement which simulated a thermal process happening over time.

`def make_noise(p_cx=0,T1T2Tm=(1,1,0)):`

'''

Returns a noise model specified by the inputs

- p_cx: probability of depolarizing noise on each

qubit during a cx

- T1T2Tm: tuple with (T1,T2,Tm), the T1 and T2 times

and the measurement time

'''

noise_model = NoiseModel()

# depolarizing error for cx

error_cx = depolarizing_error(p_cx, 1)

error_cx = error_cx.tensor(error_cx)

noise_model.add_all_qubit_quantum_error(error_cx, ["cx"])

# thermal error for measurement

(T1,T2,Tm) = T1T2Tm

error_meas = thermal_relaxation_error(T1, T2, Tm)

noise_model.add_all_qubit_quantum_error(error_meas, "measure")

return noise_model

Let’s check it out on a simple four qubit circuit. One qubit has an `x`

applied. Two others has a `cx`

. One has nothing. Then all are measured.

qc = QuantumCircuit(4)

qc.x(0)

qc.cx(1,2)

qc.measure_all()qc.draw(output='mpl')

This is a simple circuit with a simple output, as we’ll see when we run it.

`execute( qc, Aer.get_backend('qasm_simulator'), shots=8192).result().get_counts()`

Out:

`{'0001': 8192}`

Now let’s run it with noise on the `cx`

gates only.

noise_model = make_noise(p_cx=0.1)execute( qc, Aer.get_backend('qasm_simulator'), noise_model=noise_model, shots=8192).result().get_counts()

Out:

`{'0001': 7426, '0101': 372, '0111': 27, '0011': 367}`

for Tm in (0.01,0.1,1,10):noise_model = make_noise(p_cx=0, T1T2Tm=(1,1,Tm))counts = execute( qc, Aer.get_backend('qasm_simulator'), noise_model=noise_model, shots=8192).result().get_counts()

print('Tm =',Tm,', counts =',counts)

Out:

`Tm = 0.01 , counts = {'0000': 91, '0001': 8101}`

Tm = 0.1 , counts = {'0000': 790, '0001': 7402}

Tm = 1 , counts = {'0000': 5122, '0001': 3070}

Tm = 10 , counts = {'0000': 8191, '0001': 1}

he most notable effect of this noise is that it causes `1`

values to relax down to `0`

.

# Running repetition codes

Qiskit has tools to make it easy to set up, run and analyze repetition codes.

`from qiskit.ignis.verification.topological_codes import RepetitionCode`

from qiskit.ignis.verification.topological_codes import GraphDecoder

from qiskit.ignis.verification.topological_codes import lookuptable_decoding, postselection_decoding

Here’s one with four repetitions and a single measurement round.

`d = 4`

T = 1

code = RepetitionCode(d,T)

The repetition code object contains a couple of circuits: for encoded logical values of `0`

and `1`

.

`code.circuit`

Out:

`{'0': <qiskit.circuit.quantumcircuit.QuantumCircuit at 0x2105c48fb48>,`

'1': <qiskit.circuit.quantumcircuit.QuantumCircuit at 0x2105c48fd88>}

Here’s the one for `0`

.

`code.circuit['0'].draw(output='text')`

And for `1`

.

`code.circuit['1'].draw(output='text')`

We can run both circuits at once by first converting them into a list.

circuits = code.get_circuit_list()job = execute( circuits, Aer.get_backend('qasm_simulator'), noise_model=noise_model, shots=8192)

Once they’ve run, we can extract the results and convert them into a form that allows us to more easily look at syndrome changes.

raw_results = {}

for log in ['0','1']:

raw_results[log] = job.result().get_counts(log)results = code.process_results( raw_results )

It’s easiest to just package this up into a function.

def get_results(code, noise_model, shots=8192):

circuits = code.get_circuit_list() job = execute( circuits, Aer.get_backend('qasm_simulator'),

noise_model=noise_model, shots=shots) raw_results = {}

for log in ['0','1']:

raw_results[log] = job.result().get_counts(log) results = code.process_results( raw_results )

return results

First let’s look at an example without any noise, to keep things simple.

noise_model = make_noise() # noise model with no noiseresults = get_results(code, noise_model)results

Out:

`{'0': {'0 0 000 000': 8192}, '1': {'1 1 000 000': 8192}}`

Here’s an example with some `cx`

noise.

noise_model = make_noise(p_cx=0.01)results = get_results(code, noise_model)for log in results:

print('\nMost common results for a stored',log)

for output in results[log]:

if results[log][output]>100:

print(output,'ocurred for',results[log]

[output],'samples.')

Out:

`Most common results for a stored 0`

0 0 000 000 ocurred for 7754 samples.

Most common results for a stored 1

1 1 000 000 ocurred for 7720 samples.

The main thing we need to know is the probability of a logical error. By setting up and using a decoder, we can find out!

decoder = GraphDecoder(code)decoder.get_logical_prob(results)

Out:

`{'0': 0.000244140625, '1': 0.0001220703125}`

By calculating these value for different sizes of code and noise models, we can learn more about how the noise will affect large circuits. This is important for error correction, but also for the applications that we’ll try to run before error correction is possible.

Even more importantly, running these codes on real devices allows us to see the effects of real noise. Small-scale quantum error correction experiments like these will allow us to study the devices we have access to, understand what they do and why they do it, and test their abilities.

This is the most important exercise that you can try: doing real and insightful experiments on cutting-edge quantum hardware. It’s the kind of thing that professional researchers do and write papers about. I know this because I’m one of those researchers.

See the following examples:

- “A repetition code of 15 qubits”, James R. Wootton and Daniel Loss, Phys. Rev. A 97, 052313 (2018)
- “Benchmarking near-term devices with quantum error correction”, James R. Wootton, Quantum Science and Technology (2020)

As well as the relevant chapter of the Qiskit textbook: 5.1 Introduction to Quantum Error Correction using Repetition Codes.

By running repetition codes on the IBM quantum devices available to you, looking at the results and figuring out why they look like they do, you could soon know things about them that no-one else does!

# Transpiling for real devices

The first step toward using a real quantum device is to load your IBMQ account and set up the provider.

IBMQ.save_account('<Your Token>')

IBMQ.load_account()provider = IBMQ.get_provider(hub='ibm-q')

Now you can set up a backend object for your device of choice. We’ll go for the biggest device on offer: Melbourne.

`backend = provider.get_backend('ibmq_16_melbourne')`

Using the Jupyter tools, we can take a closer look.

import qiskit.tools.jupyter

%matplotlib inlinebackend

Out:

VBox(children=(HTML(value="<h1 style='color:#ffffff;background-color:#000000;padding-top: 1%;padding-bottom: 1…<IBMQBackend('ibmq_16_melbourne') from IBMQ(hub='ibm-q', group='open', project='main')>

This has enough qubits to run a 𝑑=8 repetition code. Let’s set this up and get the circuits to run.

d = 8

code = RepetitionCode(8,1)raw_circuits = code.get_circuit_list()

Rather than show such a big circuit, let’s just look at how many of each type of gate there are. For example, repetition codes should have 2(𝑑−1)`cx`

gates in, which means 14 in this case.

`raw_circuits[1].count_ops()`

Out:

`OrderedDict([('measure', 15), ('cx', 14), ('x', 8)])`

Before running on a real device we need to transpile. This is the process of turing the circuits into ones that the device can actually run. It is usually done automatically before running, but we can also do it ourself using the code below.

`circuits = []`

for qc in raw_circuits:

circuits.append( transpile(qc, backend=backend) )

Let’s check what this process did to the gates in the circuit.

`circuits[1].count_ops()`

Out:

`OrderedDict([('cx', 68), ('measure', 15), ('u3', 8), ('barrier', 1)])`

Note that this has `u3`

gates (which the circuit previously didn't) and the `x`

gates have disappeared. The solution to this is simple. The `x`

gates have just been described as specific forms of `u3`

gates, which is the way that the hardware understands single qubit operations.

More concerning is what has happened to the `cx`

gates. There are now 74!.

This is due to connectivity. If you ask for a combination of `cx`

gates that cannot be directly implemented, the transpiler will do some fancy tricks to make a circuit which is effectively the same as the one you want. This comes at the cost of inserting `cx`

gates. For more information, see 2.4 More Circuit-Identities.

However, here our circuit *is* something that can be directly implemented. The transpiler just didn’t realize (and figuring it out is a hard problem). We can solve the problem by telling the transpiler exactly which qubits on the device should be used as the qubits in our code.

This is done by setting up an `initial_layout`

as follows.

def get_initial_layout(code,line):

initial_layout = {}

for j in range(code.d):

initial_layout[code.code_qubit[j]] = line[2*j]

for j in range(code.d-1):

initial_layout[code.link_qubit[j]] = line[2*j+1]

return initial_layout

line = [6,5,4,3,2,1,0,14,13,12,11,10,9,8,7]

initial_layout = get_initial_layout(code,line)initial_layout

Out:

`{Qubit(QuantumRegister(8, 'code_qubit'), 0): 6,`

Qubit(QuantumRegister(8, 'code_qubit'), 1): 4,

Qubit(QuantumRegister(8, 'code_qubit'), 2): 2,

Qubit(QuantumRegister(8, 'code_qubit'), 3): 0,

Qubit(QuantumRegister(8, 'code_qubit'), 4): 13,

Qubit(QuantumRegister(8, 'code_qubit'), 5): 11,

Qubit(QuantumRegister(8, 'code_qubit'), 6): 9,

Qubit(QuantumRegister(8, 'code_qubit'), 7): 7,

Qubit(QuantumRegister(7, 'link_qubit'), 0): 5,

Qubit(QuantumRegister(7, 'link_qubit'), 1): 3,

Qubit(QuantumRegister(7, 'link_qubit'), 2): 1,

Qubit(QuantumRegister(7, 'link_qubit'), 3): 14,

Qubit(QuantumRegister(7, 'link_qubit'), 4): 12,

Qubit(QuantumRegister(7, 'link_qubit'), 5): 10,

Qubit(QuantumRegister(7, 'link_qubit'), 6): 8}

With this, let’s try transpilation again.

`circuits = []`

for qc in raw_circuits:

circuits.append( transpile(qc, backend=backend, initial_layout=initial_layout) )

circuits[1].count_ops()

Out:

`OrderedDict([('measure', 15), ('cx', 14), ('u3', 8)])`

Perfect!

Now try for yourself on one of the devices that we’ve now retired: Tokyo.

from qiskit.test.mock import FakeTokyobackend = FakeTokyo()backend

The largest repetition code this can handle is one with 𝑑=10.

d = 10

code = RepetitionCode(d,1)

raw_circuits = code.get_circuit_list()raw_circuits[1].count_ops()

Out:

`OrderedDict([('measure', 19), ('cx', 18), ('x', 10)])`

For this we need to find a line of 19 qubits across the coupling map.

line = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]

initial_layout = get_initial_layout(code,line)circuits = []

for qc in raw_circuits:

circuits.append(transpile(qc, backend=backend, initial_layout=initial_layout) )

circuits[1].count_ops()

Out:

`OrderedDict([('cx', 90),`

('measure', 19),

('u3', 10),

('u2', 4),

('barrier', 1)])

Clearly, the line chosen in the cell above was not a good example. Find a line such that the transpiled circuit `circuits[1]`

has exactly 18 `cx`

gates.

line = None

# define line variable so the transpiled circuit has exactly 18 #CNOTs.

### WRITE YOUR CODE BETWEEN THESE LINES - START

def get_initial_layout(code,line):

initial_layout = {}

for j in range(code.d):

initial_layout[code.code_qubit[j]] = line[2*j]

for j in range(code.d-1):

initial_layout[code.link_qubit[j]] = line[2*j+1]

return initial_layoutline = [18,14,19,13,12,11,17,16,15,10,6,5,0,1,7,8,4,9,3,2]### WRITE YOUR CODE BETWEEN THESE LINES - ENDinitial_layout = get_initial_layout(code,line)circuits = []

for qc in raw_circuits:

circuits.append(transpile(qc, backend=backend, initial_layout=initial_layout) )

circuits[1].count_ops()

Out:

`OrderedDict([('measure', 19), ('cx', 18), ('u3', 10)])`

*References:*

Qiskit (Global Summer School), Introduction to Quantum Computing and Quantum Hardware — Lab 5.