IBM Quantum Challenge: Spring 2023 — Lab 5

Exploring the World of Quantum Computing: IBM’s Quantum Challenge Spring 2023 — Lab 5: Creating a GHZ State with Error Correction

Monit Sharma
19 min readJun 1, 2023

Introduction:

Welcome back to our final instalment of the blog series on the recently concluded IBM Quantum Challenge Spring 2023! Throughout this series, we have delved into the captivating realm of quantum computing, exploring various lab exercises that participants tackled during the challenge. In this fifth and bonus article, we will dive into the exciting lab exercise where participants utilized their knowledge of dynamic circuits and error correction to create a GHZ state and leverage the power of a 127-qubit processor. Let’s explore the intricacies of this bonus lab and unravel the exciting possibilities it unveils.

Recap: Dynamic Circuits and Error Correction

Before diving into the lab exercise, let’s recap our knowledge of dynamic circuits and error correction. Dynamic circuits enable the incorporation of mid-circuit measurements and classical feedforward, providing flexibility in adjusting quantum gates based on measurement outcomes. Error correction, on the other hand, is a vital technique in quantum computing aimed at mitigating errors and preserving the integrity of quantum states. This lab exercise combines the power of both dynamic circuits and error correction to accomplish a specific task — creating a GHZ state.

Understanding the GHZ State

The GHZ (Greenberger-Horne-Zeilinger) state is an entangled state of multiple qubits, where all qubits are either in the state |0⟩ or |1⟩, but their values are correlated. This state exhibits strong quantum correlations and plays a crucial role in various quantum algorithms and protocols. Creating a GHZ state requires precise control and manipulation of multiple qubits to achieve the desired entanglement pattern.

Generation of the 3-qubit GHZ state using quantum logic gates.

Advantages and Applications of GHZ States

GHZ states have significant applications in quantum communication, quantum teleportation, and quantum cryptography. The creation and manipulation of GHZ states pave the way for advanced quantum protocols, entanglement-based quantum algorithms, and distributed quantum computing. By successfully completing this lab exercise, participants gained valuable insights into the creation and utilization of GHZ states and the potential applications of such states in quantum information processing.

The Challenge:

Creating a GHZ State with Error Correction In this bonus lab exercise, participants were tasked with using their knowledge of dynamic circuits and error correction to create a GHZ state using a 127-qubit processor. Participants had to design a circuit that implemented the necessary gates, error correction codes, and measurements to prepare and verify the GHZ state. The exercise challenged participants to apply their understanding of dynamic circuits and error correction techniques in a practical scenario.

Using 127 Qubits

Now, with all the knowledge of dynamic circuits, we gained from the previous exercises, as a reward for making it to this lab, now we get a chance to run the circuit on the 127 qubit device.

Although working with physical devices ( real quantum hardware) has its own challenges, and this worsens if we have a big number of qubits, as the path becomes long, and we have to make sure that it doesn’t introduce too many errors.

In the final challenge, we are asked to prepare a fully entangled 127-qubit state, which is just the GHZ state in a clever way.

Then building up on our previous knowledge of error correction could be applied in order to use such the 127-qubit GHZ state in order to create a good 54-qubit GHZ state.

The device we will be using is ibm_sherbrooke and use the even-numbered qubits for the 54 qubit GHZ state, which leaves the odd-numbered qubits to be used as stabilizers.

Note: Here we do use the same register for the odd and even qubits, even though they will be used differently. We do this since it would make it quite a bit more complicated to create the initial 127 qubit GHZ state, especially when you want to optimize the depth manually.

Let’s start with the code:

# Importing all the parts we need
from typing import List, Optional

from qiskit import transpile, QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.result import marginal_counts

import warnings

warnings.filterwarnings("ignore")

import math

pi = math.pi

# Preparing registers
quantum_register = QuantumRegister(127)
classical_register = ClassicalRegister(127)

# For simplicity we map the physical qubits to the logical qubits directly using the same number.
initial_layout = [
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
109,
110,
111,
112,
113,
114,
115,
116,
117,
118,
119,
120,
121,
122,
123,
124,
125,
126,
]
# The "even"" qubits will be used for the 54 qubit GHZ-state
ghz_qubits = [
0,
2,
4,
6,
8,
10,
12,
18,
20,
22,
24,
26,
28,
30,
32,
37,
39,
41,
43,
45,
47,
49,
51,
56,
58,
60,
62,
64,
66,
68,
70,
75,
77,
79,
81,
83,
85,
87,
89,
94,
96,
98,
100,
102,
104,
106,
108,
114,
116,
118,
120,
122,
124,
126,
]
# The "odd" qubits will be used as the stabilizers
stabilizer_qubits = [
1,
3,
5,
7,
9,
11,
14,
15,
16,
17,
19,
21,
23,
25,
27,
29,
31,
33,
34,
35,
36,
38,
40,
42,
44,
46,
48,
50,
52,
53,
54,
55,
57,
59,
61,
63,
65,
67,
69,
71,
72,
73,
74,
76,
78,
80,
82,
84,
86,
88,
90,
91,
92,
93,
95,
97,
99,
101,
103,
105,
107,
109,
110,
111,
112,
115,
117,
119,
121,
123,
125,
]

Step 1: Creating a BIG GHZ-state

A lot of quantum algorithms have something to do with entangling their qubits. One often used state is the generalized GHZ state.

The GHZ state is fully entangled and it can be generalized to any number of qubits, so let’s try the extreme, let’s make a GHZ state on a 127-qubit system.

The challenge is not to entangle the qubits in theory but to entangle them on an actual device. This means you should take the layout of the device into account (so not using CX-gates between qubits which are not directly connected with each other). and additionally try to get the circuit depth as low as possible in order to reduce noise.

Exercise 1

For the first exercise, make a 127-qubit GHZ state for the 127-qubit device ibm_sherbrook using only Hadamard and CNOT gates. You can try to reduce the circuit depth as well.

As a hint, It can help to think first about how you would do it (with minimum depth) for an ideal device where all qubits are connected with each other.

def generate_ghz127():
qc = QuantumCircuit(quantum_register, classical_register)

# Add your code here
#
#

# Hadamard gate
# qc.h(64)
# qc.rz(pi/2, 63) not needed since qubit is in state 0
qc.h(64)
# qc.sx(64)
# qc.rz(pi/2, 64)

qc.cx(64, 63)

qc.cx(63, 62)
qc.cx(64, 65)

qc.cx(62, 61)
qc.cx(64, 54)
qc.cx(65, 66)

qc.cx(61, 60)
qc.cx(62, 72)
qc.cx(54, 45)
qc.cx(66, 73)

qc.cx(60, 53)
qc.cx(72, 81)
qc.cx(45, 46)
qc.cx(66, 67)
qc.cx(73, 85)

qc.cx(53, 41)
qc.cx(60, 59)
qc.cx(81, 80)
qc.cx(46, 47)
qc.cx(45, 44)
qc.cx(67, 68)
qc.cx(85, 86)

qc.cx(41, 40)
qc.cx(59, 58)
qc.cx(44, 43)
qc.cx(47, 35)
qc.cx(68, 69)
qc.cx(80, 79)
qc.cx(81, 82)
qc.cx(85, 84)
qc.cx(86, 87)

qc.cx(43, 34)
qc.cx(40, 39)
qc.cx(41, 42)
qc.cx(58, 71)
qc.cx(79, 91)
qc.cx(82, 83)
qc.cx(35, 28)
qc.cx(47, 48)
qc.cx(68, 55)
qc.cx(69, 70)
qc.cx(87, 93)

qc.cx(39, 33)
qc.cx(34, 24)
qc.cx(28, 29)
qc.cx(48, 49)
qc.cx(58, 57)
qc.cx(71, 77)
qc.cx(79, 78)
qc.cx(91, 98)
qc.cx(83, 92)
qc.cx(93, 106)
qc.cx(87, 88)
qc.cx(70, 74)

qc.cx(33, 20)
qc.cx(24, 23)
qc.cx(28, 27)
qc.cx(29, 30)
qc.cx(39, 38)
qc.cx(49, 50)
qc.cx(57, 56)
qc.cx(77, 76)
qc.cx(88, 89)
qc.cx(98, 97)
qc.cx(92, 102)
qc.cx(106, 107)

qc.cx(20, 19)
qc.cx(23, 22)
qc.cx(27, 26)
qc.cx(30, 17)
qc.cx(38, 37)
qc.cx(50, 51)
qc.cx(56, 52)
qc.cx(76, 75)
qc.cx(97, 96)
qc.cx(102, 101)
qc.cx(106, 105)
qc.cx(107, 108)

qc.cx(17, 12)
qc.cx(19, 18)
qc.cx(20, 21)
qc.cx(24, 25)
qc.cx(22, 15)
qc.cx(26, 16)
qc.cx(30, 31)
qc.cx(51, 36)
qc.cx(75, 90)
qc.cx(96, 109)
qc.cx(98, 99)
qc.cx(101, 100)
qc.cx(102, 103)
qc.cx(105, 104)
qc.cx(108, 112)

qc.cx(12, 11)
qc.cx(15, 4)
qc.cx(16, 8)
qc.cx(18, 14)
qc.cx(31, 32)
qc.cx(90, 94)
qc.cx(96, 95)
qc.cx(100, 110)
qc.cx(104, 111)
qc.cx(109, 114)
qc.cx(112, 126)

qc.cx(4, 3)
qc.cx(8, 7)
qc.cx(11, 10)
qc.cx(12, 13)
qc.cx(14, 0)
qc.cx(110, 118)
qc.cx(111, 122)
qc.cx(114, 115)
qc.cx(126, 125)

qc.cx(0, 1)
qc.cx(3, 2)
qc.cx(4, 5)
qc.cx(7, 6)
qc.cx(10, 9)
qc.cx(114, 113)
qc.cx(115, 116)
qc.cx(118, 119)
qc.cx(122, 121)
qc.cx(125, 124)

qc.cx(116, 117)
qc.cx(119, 120)
qc.cx(122, 123)

# qc.barrier()

# We can check if number of cx is equal to 124 and a single H gate and each one is targeted once only and only from a
# Qubit already in superposition

# Add your code here

return qc


ghz_circuit = generate_ghz127()
print(ghz_circuit.depth())

The circuit depth is 17

Submit it to the grader:

# Submit your circuit

from qc_grader.challenges.spring_2023 import grade_ex5a

grade_ex5a(ghz_circuit)

Step 2: Reducing the Size by half

The previous circuit is very big, hence more error-prone, we want to use this circuit to now make a reduced GHZ state but only use the even number of qubits this time. How can we do that?

We have to unentangle the odd qubits so that they can be used for measurement without the GHZ state collapsing, and since we are unentangling the qubits, we do it in such a way that it generates stabilizers ( remember the previous lab exercise). Similar to the error correction part.

They must be 0 if both qubits that they are connected with have the same value, and have the value 1, if their value is different, and later these stabilizers are used to apply the error correction.

Exercise 2

Unentangle the odd qubits of the created GHZ state in order to create stabilizers. Again, you can challenge yourself to make the circuit depth as small as possible.

Hint: Take into account how you created your GHZ state above. Your way of unentangling depends on how it was created.

def deentangle_qubits():
qc = QuantumCircuit(quantum_register, classical_register)

# Add your code here
#
#

qc.cx(62, 63)

qc.cx(66, 65)

qc.cx(60, 61)
qc.cx(45, 54)

qc.cx(81, 72)
qc.cx(85, 73)

qc.cx(41, 53)
qc.cx(47, 46)
qc.cx(68, 67)

qc.cx(58, 59)
qc.cx(43, 44)
qc.cx(43, 42)
qc.cx(79, 80)
qc.cx(87, 86)

qc.cx(39, 40)
qc.cx(83, 82)
qc.cx(83, 84) # other side
qc.cx(28, 35)
qc.cx(70, 69)

qc.cx(24, 34)
qc.cx(49, 48)
qc.cx(49, 55)
qc.cx(77, 71)
qc.cx(77, 78) # other side
qc.cx(98, 91)
qc.cx(106, 93)

qc.cx(20, 33)
qc.cx(30, 29)
qc.cx(56, 57)
qc.cx(89, 88)
qc.cx(89, 74) # other side
qc.cx(102, 92)

qc.cx(22, 23)
qc.cx(22, 21)
qc.cx(26, 27)
qc.cx(37, 38)
qc.cx(37, 52)
qc.cx(51, 50)
qc.cx(75, 76)
qc.cx(96, 97)
qc.cx(108, 107)

qc.cx(12, 17)
qc.cx(18, 19)
qc.cx(26, 25) # other side
qc.cx(100, 101)
qc.cx(100, 99)
qc.cx(104, 105)
qc.cx(104, 103)

qc.cx(4, 15)
qc.cx(8, 16)
qc.cx(32, 31)
qc.cx(32, 36)
qc.cx(94, 90)
qc.cx(94, 95)
qc.cx(114, 109)
qc.cx(126, 112)

qc.cx(10, 11)
qc.cx(0, 14)
qc.cx(118, 110)
qc.cx(122, 111)

qc.cx(2, 3)
qc.cx(2, 1) # other side
qc.cx(6, 7)
qc.cx(6, 5)
qc.cx(8, 9)
qc.cx(116, 115)
qc.cx(124, 125)

qc.cx(118, 117)
qc.cx(120, 119)
qc.cx(120, 121)
qc.cx(124, 123)

qc.cx(114, 113)
qc.cx(12, 13)

return qc


unentangle_circuit = deentangle_qubits()
print(unentangle_circuit.depth())

complete_circuit = ghz_circuit.compose(unentangle_circuit)

The images aren’t being uploaded due to their size. So, you can’t visualize much in this, you just have to believe my words.😂

# Submit your circuit

from qc_grader.challenges.spring_2023 import grade_ex5b

grade_ex5b(complete_circuit)

Now since all odd qubits are unentangled from the even qubits, we can measure them without collapsing the GHZ state.

# Measuring stabilizers this can also be used in post processing to see what went wrong.


def measure_stabilizers():
qc = QuantumCircuit(quantum_register, classical_register)
qc.measure(stabilizer_qubits, stabilizer_qubits)
# qc.barrier()
return qc


stabilizer_circuit = measure_stabilizers()

These measurement results could be then used to improve the 54 qubit GHZ-state, but for now, let’s skip this.

After having measured the odd qubits, they could be reset and you could use the 54 GHZ state in your algorithm, using the odd qubits as potential ancillas.

However, before thinking about using the GHZ state, we should first test how good your GHZ state is.

Therefore, we need to measure the 54 qubit GHZ state.

# Measuring the GHZ qubits


def measure_ghz():
qc = QuantumCircuit(quantum_register, classical_register)
qc.measure(ghz_qubits, ghz_qubits)
return qc


measure_circuit = measure_ghz()

Now let's put everything together, such that we can test it.

# Everything together

simple_ghz = (
ghz_circuit.compose(unentangle_circuit)
.compose(stabilizer_circuit)
.compose(measure_circuit)
)

Step 3: Preparing and Running on the Device

We are now ready to run our GHZ state on a real device, and for that, we can use an actual 127-qubit device!

There are not that many people in the world who worked with 127 qubits, and you will be soon one of them!

First, we prepare everything we need and make sure we are ready.

# Importing provider and getting access to the system
from qiskit_ibm_provider import IBMProvider
from qiskit import transpile


provider = IBMProvider()


hub = "system-request"
group = "3-9-23-access"
project = "main"

backend_name = "ibm_sherbrooke"
backend = provider.get_backend(backend_name, instance=f"{hub}/{group}/{project}")

number_of_shots: int = 1024
from qiskit_ibm_provider import IBMProvider
from qiskit import transpile


provider = IBMProvider()


hub = "ibm-q-internal"
group = "deployed"
project = "default"

backend_name = "ibm_brisbane"
backend = provider.get_backend(backend_name, instance=f"{hub}/{group}/{project}")

number_of_shots: int = 2024

We first transpile it for the actual device, this should not cause too many changes, since we already had the physical device in mind when making the circuit in the first place, and only used connections which exist.

# First we transpile the GHZ-state for the actual device
qc_transpiled = transpile(simple_ghz, backend, initial_layout=initial_layout)

Now we can let the job run on the 127-qubit device! We add some tags here to make it easier to find them in the future.

Running the job will take some time, depending on how many other people are in the queue trying to run their jobs.

# Now we can run the job
# We add memory=true to be easier able to analyse how good the result were and the tags to make it easier to find it later.
job = backend.run(
qc_transpiled,
shots=number_of_shots,
memory=True,
job_tags=["ghz_state", "spring_challenge"],
)

# Getting the data of the job for testing
data = job.result().get_memory()

In case you are coming back later, and want to retrieve a job, you can find it in the IBM Quantum Computing Homepage (with the tags used above), copy the name of the job (its id) and replace the job_id below with it. A job id should look something like this: ch36cf1pleju56fajrqg

# Change job id to the id of your previous submitted job something like "ch36cf1pleju56fajrqg"
# You only need to run this if you come back at a later time
job = provider.backend.retrieve_job("job_id")

# Getting the data of the job for testing
data = job.result().get_memory()

Step 4: Testing the GHZ-state

After you got the results back from running your GHZ state, you might want to test how good it is. We have not yet added any error mitigation or error correction, so we will expect results which still can be improved.

There are different ways to test how good the results are, of course, you can just try to look at the results, but since the qubits which are used for the GHZ-State are not next to each other, this might be a bit annoying.

So having a function telling us about the quality, or about the errors in the GHZ-State might be useful.

Exercise 3

Create a function to test the GHZ state.

We want you to have some freedom here in how you want to test it and find a way which makes sense for you. Important is here that the better the GHZ state is the lower the output of your function.

Hint: This function should be made to test the data we have created, so only testing the qubits which are part of the GHZ-state

# A function to test the quality of a GHZ-state. The lower the better
def test_ghz(data):
ghz_qubits = [
0,
2,
4,
6,
8,
10,
12,
18,
20,
22,
24,
26,
28,
30,
32,
37,
39,
41,
43,
45,
47,
49,
51,
56,
58,
60,
62,
64,
66,
68,
70,
75,
77,
79,
81,
83,
85,
81,
89,
94,
96,
98,
100,
102,
104,
106,
108,
114,
116,
118,
120,
122,
124,
126,
]
quality_list = []
average = 0
best = len(data)
worst = 0

for result in data:
count_zeroes = 0
count_ones = 0
bit_count = -1
for bit in result:
bit_count = bit_count + 1
if bit_count not in ghz_qubits:
continue
if bit == "0":
count_zeroes = count_zeroes + 1
else:
count_ones = count_ones + 1
if count_ones < count_zeroes:
count_zeroes = count_ones

quality_list.append(count_zeroes)
average = average + count_zeroes

if count_zeroes > worst:
worst = count_zeroes
if count_zeroes < best:
best = count_zeroes

average = average / len(data)
print(average)
print(worst)
print(best)
return average


test_ghz(data)

You now got a number rating your results. Before we go on, think about your method. What would the number look like for really good states? How for a really bad state? (And what would such a bad state look like)?

Now let’s test your testing function and let's see if it does what it should. (Remember it should give low results for good states, which have phew errors and high results for states with lots of errors).

# Submit your circuit

from qc_grader.challenges.spring_2023 import grade_ex5c

# Since we test here a function, we do not need brackets after test_ghz, since the input is the function
grade_ex5c(test_ghz)

So now after we got the results for the GHZ state, the question is how can we improve on it?

One of the first steps would be to try to decrease the depth of the circuit further. Let's look at the depth of our transpiled circuit:

qc_transpiled.depth()

We can see that the depth got bigger during the transpilation process, by more than a factor 2.

Let’s take a look at what it looks like now, to see where this additional depth comes from:

qc_transpiled.draw()

Wasn’t able to save the images an outputs for the later part of the notebook. So can’t really show you want it looks like. I’ll follow the offical qiskit’s documentation from here.

What we can actually see here is that the Hadamard gate and also the CX gate got transpiled into other Gates. This is not so surprising for the Hadamard gate, since none of the IBM Quantum devices have it directly, but it is always constructed by 3 rotations.

On the other hand, the ECR-gate which you can see is new and only used in some devices. It is also an entangling gate, similar to the CX-gate, but it works a bit differently. However, a CX-gate can be made using only a single ECR-gate and local rotations.

Learn more about ECR-gates.

Now that you know that the ECR-gate is used instead, could you make a circuit with a better depth?

This is not an exercise, but something you can try, if you want to, since reducing depth is often the best way to minimize the error rate.

If you are interested in other ways one could use to make the GHZ state better, feel free to read on. The following part is just a bonus and not at all mandatory. It is meant as a short outlook to show how error correction could be used, but also show why error correction can be hard.

Bonus: The Way to Correcting Errors

Error correction is still an active and important research topic. So correcting errors on a real device, even in a simple case like our GHZ-state example, is not that straightforward forward and we want to give you the opportunity to come up with your own ideas while giving you some guidance and hints on what potential ways could be.

Step 1: How good is the actual State?

Before we can correct any errors, we first need to know how good the state was, and even this is not as straightforward as one might think.

The simplest way for testing how good your GHZ state was, is to test how many of the even-numbered qubits have different results from the majority. This is what you have most likely used above.

However, this does not necessarily mean that this also correlated with the number of errors which occurred. And one could say that the number of errors which occurred is more important, for telling how good the GHZ state is.

Example A to illustrate this thought: Let’s say we have 10 qubits connected in a line and form a GHZ-state with them:

0–1–2–3–4–5–6–7–8–9

If we now say that the error occurred not in the readout, but when applying the entangling gate between qubit 4 and qubit 5 the output could look like this:

0000011111

This would be the worst possible result for the measurement “how many qubits have the same result”.

For this reason, a run in which more errors occurred, could actually lead to a better measure for “how many qubits have the same result” although most likely that state would be a lot less useful.

If the above state would have been created and you had (working) stabilizer measurements between the qubits, you would only have the one between qubits 4 and 5 be 1. And to correct that error you could potentially just flip qubits 5–9.

To get a first idea of how the errors look like for your case, it might be a good idea, to look at the raw data, the results which you got from running it on the actual device, and take into account how you built the GHZ-state.

For Example A we could maybe find an error correction which would look like this:

# Simple idea for Example A find where the error happened and flip accordingly
def correct_ghz():
qc = QuantumCircuit(quantum_register, classical_register)

with qc.if_test((classical_register[9], 1)):
qc.x(quantum_register[9])

with qc.if_test((classical_register[8], 1)):
qc.x(quantum_register[8])

with qc.if_test((classical_register[8], 1)):
qc.x(quantum_register[9])

with qc.if_test((classical_register[7], 1)):
qc.x(quantum_register[9])

with qc.if_test((classical_register[7], 1)):
qc.x(quantum_register[8])

with qc.if_test((classical_register[7], 1)):
qc.x(quantum_register[7])

# ...

# qc.barrier()
return qc


correcting_circuit = correct_ghz()

And if we would now adapt the above code to our 54 GHZ case, we could build everything together:

# Everything together corrected

error_corrected_ghz = (
ghz_circuit.compose(unentangle_circuit)
.compose(stabilizer_circuit)
.compose(correcting_circuit)
.compose(measure_circuit)
)

Step 2: Why considering single stabilizers might not be enough.

In the error correction exercise, we have seen that you could either use single bits, or the whole bitstring from the measurement of the stabilizer qubits in order to condition operations on them.

Since our stabilizer measurements are 54 bits long, using the whole bit strings to condition on them, is not feasible, since this would need 2⁵⁴ different bitstrings which need to be considered.

On the other hand, if we just consider single bit it might not be possible/overly complicated.

Example B: We have again 10 qubits connected in a line and form a GHZ-state with them:

0–1–2–3–4–5–6–7–8–9

Now we assume that we get the measurement result 0000010000

If we have stabilizer measurements in this case the one on the right of the 1 and the one on the left of the 1 would both have value 1.

This means in this case just looking at a single stabilizer would not be enough to know that you only need to flip qubit number 5 (and no others), unless we would be ok with flipping some qubits several times, which is not ideal.

So in this case it would be ideal if we could apply functions to the measurement results of the stabilizers, and use their results for as conditions in the dynamic circuits.

This is possible, however, this needs to be done in Open QASM3 code, instead of in Qiskit, which will not be covered in this exercise.

Learn more about it from here

from qiskit import qasm3, QuantumCircuit, transpile

# Creating a bell circuit
qc_bell = QuantumCircuit(2, 2)
qc_bell.h(0)
qc_bell.cx(0, 1)
qc_bell.measure(0, 0)
qc_bell.measure(0, 1)

# Transpiling it for our device (as above it does not have the H- and CX- Gates)
qc_bell = transpile(qc_bell, backend)

# Generate qasm3 code before we can print it
exporter = qasm3.Exporter(
includes=[], disable_constants=True, basis_gates=backend.configuration().basis_gates
)
print(qasm3_bell := exporter.dumps(qc_bell))

# Draw a circuit as comparison
qc_bell.draw(output="mpl", idle_wires=False)

Step 3: How not to introduce more Errors

There are different reasons which can lead to errors. Decoherence over time and errors introduced in entangling gates like the CX-Gate are two of them. Both of them can also apply to the stabilizers and we have to make sure to not introduce more noise in our attempts of correcting it since that would beat the whole purpose. This might be less of a problem in the future, when the general error rate will go further down, but is still something which needs to be considered.

What does this mean for us?

Well, one can think of when we want to create and measure the stabilizers. Do we unentangle them directly after they are no longer used for entangling the next qubit? Do we wait until the whole circuit is entangled? (This of course depends on which kind of errors we might want to correct). We can also think about resetting the stabilizers and creating new ones later (using the same qubits) and use 2 phases in which we try to correct errors.

And we can think of what the probability is that an error occurred from entangling a qubit (through the stabilizer) and compare it to the probability that an error is introduced in the stabilizer by unentangling it. So maybe having the stabilizers just makes it worse?

When is this worth it?

You can play around with the code below to get some impressions and ideas, again this is something which can change in the future, when error rates will go further down.

# All the probabilities here only consider errors introduced by the CX gate and assumes they are bit flip errors.

# Probability for a single CX gate
p1 = 0.01
# Probability that there is an error after 2 CX gates (going through stabilizer)
p2 = p1 * (1 - p1) + (1 - p1) * p1
# Probability that the stabilizer shows something wrong even though it is correct
p3 = p1 * p1 + (1 - p1) * (1 - p1) * p1

print("Probability of a single cx having an error: {}".format(p1))
print("Probability of having an error after 2 cx: {:.4f}".format(p2))
print("Probability of the stabilizer showing a non existent error: {:.4f}".format(p3))

Step 4: What can one do?

There are a lot of possibilities you can try:

  • You can try to find some good logical functions using several stabilizers and use QASM3
  • You can think about resetting the stabilizers and reusing them.
  • You can try if the simple approach as seen in example A could be made to work (by changing the timing of when the measurements are made.)
  • You can find your own idea! be creative!

What is important for all of them is that you not just blindly start trying on the quantum computer, but instead try to verify your ideas first using the data of letting the initial GHZ state run on the device.

You have created testing data above: You have the output of the GHZ state and also the output of the stabilizers.

If your approach does not work as postprocessing, then running it on the actual device, where all the operations have a further error rate, will also not work.

When you have an algorithm which works in theory with the data you have (and generated a better GHZ-state according to your test you can use the code below to try to let your whole circuit, including the error correction, run on the actual device.

Hint: Make sure you use the error_correction function above and also generate the error_corrected_ghz above.

# First we transpile the GHZ-state for the actual device
qc_corrected_transpiled = transpile(
error_corrected_ghz, backend, initial_layout=initial_layout
)

# Now we can run the job
job_corrected = backend.run(
qc_corrected_transpiled,
dynamic=True,
shots=number_of_shots,
memory=True,
job_tags=["dynamic", "spring_challenge"],
)

job_id = job_corrected.job_id()
print(job_id)
job_corrected = provider.retrieve_job(job_id)
job_corrected.status()
# And get the results back
counts_corrected = job_corrected.result().get_counts()

Congratulations!

You finished the IBM Quantum Spring Challenge 2023. Now you have the knowledge of running the 127-qubit quantum hardware.

Lab 5 of the IBM Quantum Challenge Spring 2023 provided participants with an exciting opportunity to put their knowledge of dynamic circuits and error correction to the test. By successfully creating a GHZ state using a 127-qubit processor, participants demonstrated their ability to design and execute complex quantum circuits. The lab exercise highlighted the practical applications of GHZ states and the significance of error correction techniques in preserving the integrity of quantum information.

As we conclude our series on the IBM Quantum Challenge Spring 2023, we hope that it has served as an enlightening journey into the world of quantum computing. Challenges like these provide valuable opportunities for learning, experimentation, and collaboration in this rapidly evolving field. Make sure to check all the previous challenges and summer schools organised by Qiskit that I’ve attended and solved, here.

Happy quantum exploring!

--

--