-
Notifications
You must be signed in to change notification settings - Fork 0
/
ex1.py
592 lines (431 loc) · 15.6 KB
/
ex1.py
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
#!/usr/bin/env python
# coding: utf-8
# # Prologue - Your first day at the lab
#
# *You just started your graduate studies with Dr. Ryoko as your advisor.<br/>
# On your first day, you arrive at the lab only to find no one there.*
#
#
# *You decide to look around while waiting for your advisor to arrive. <br/>
# Suddenly, one of the monitors nearby lights up and startles you.*<br/>
#
# *There in the monitor, looking straight at you was Dr. Ryoko,* <br/>
# *wearing her signature traditional Japanese outfit.*
#
#
# *Dr. Ryoko greets you and tells you that she is visiting another lab.* <br/>
# *She tells you that she is in the middle of an important experiment,*<br/>
# *and won't be back for another few weeks.*<br/>
#
# *She asks you to work on these exercises until she returns...*
#
# [<< Click here to communicate with Dr. Ryoko through the web cam >>](https://youtu.be/eLw7fWb2xv4)
# # Week1-A: Adder in Quantum Circuits
#
# Hi! Welcome to our lab. This week, we will start by learning how to perform simple additions using quantum circuits.<br/>
#
# Just like in classical computation, where you can combine different logical gates (e.g., AND, OR, XOR, etc.) to create binary adders, you can make adders with quantum circuits as well.
# Before starting the exercises, run the first cell below by clicking on it and then pressing 'shift' + 'enter'. This is the general way to execute a code cell in a Jupyter notebook environment that you are using now. While it is running, you will see `In [*]`: in the top left of that cell. Once it finishes running, you will see a number instead of the star, which indicates how many cells you've run. You can find more information about Jupyter notebooks here: https://qiskit.org/textbook/ch-prerequisites/python-and-jupyter-notebooks.html.
# In[1]:
# Importing standard Qiskit libraries and configuring account
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import IBMQ, Aer, execute
from qiskit.visualization import plot_bloch_multivector
# If you run this code outside IBM Quantum Experience,
# run the following commands to store your API token locally.
# Please refer https://qiskit.org/documentation/install.html#access-ibm-quantum-systems
# IBMQ.save_account('MY_API_TOKEN')
# Loading your IBM Q account(s)
IBMQ.load_account()
# ### What are quantum circuits?
# Quantum circuits are models for quantum computation in which a computation is a sequence of quantum gates. Let's take a look at some of the popular quantum gates.
# ### X Gate
# An X gate equates to a rotation around the X-axis of the Bloch sphere by $\pi$ radians.
# It maps $|0\rangle$ to $|1\rangle$ and $|1\rangle$ to $|0\rangle$. It is the quantum equivalent of the NOT gate for classical computers and is sometimes called a bit-flip. If you are not familiar with linear algebra, you can learn it here: https://qiskit.org/textbook/ch-appendix/linear_algebra.html.
#
# $X = \begin{pmatrix}
# 0 & 1 \\
# 1 & 0 \\
# \end{pmatrix}$
# In[2]:
# Let's do an X-gate on a |0> qubit
q = QuantumRegister(1)
qc = QuantumCircuit(q)
qc.x(q[0])
qc.draw(output='mpl')
# Note: There is a new syntax that omits `Quantum Register` , but in this challenge, we will use the above syntax because it is easier to understand the algorithms of complex quantum circuits. (You can see the new notation [here](https://qiskit.org/documentation/stubs/qiskit.circuit.QuantumCircuit.html?highlight=quantumcircuit#qiskit.circuit.QuantumCircuit).)
# In[3]:
# Let's see the result
backend = Aer.get_backend('statevector_simulator')
result = execute(qc, backend).result().get_statevector(qc, decimals=3)
plot_bloch_multivector(result)
# ### H Gate
# A Hadamard gate represents a rotation of $\pi$ about the axis that is in the middle of the $X$-axis and $Z$-axis.
# It maps the basis state $|0\rangle$ to $\frac{|0\rangle + |1\rangle}{\sqrt{2}}$, which means that a measurement will have equal probabilities of being `1` or `0`, creating a 'superposition' of states. This state is also written as $|+\rangle$.
#
# $H = \frac{1}{\sqrt{2}}\begin{pmatrix}
# 1 & 1 \\
# 1 & -1 \\
# \end{pmatrix}$
# In[4]:
# Let's do an H-gate on a |0> qubit
q = QuantumRegister(1)
qc = QuantumCircuit(q)
qc.h(q[0])
qc.draw(output='mpl')
# In[5]:
# Let's see the result
backend = Aer.get_backend('statevector_simulator')
result = execute(qc, backend).result().get_statevector(qc, decimals=3)
plot_bloch_multivector(result)
# ### Z Gate
# The Z gate represents a rotation around the Z-axis of the Bloch sphere by $\pi$ radians. It is sometimes called a 'phase shift gate'.
#
# $Z = \begin{pmatrix}
# 1 & 0 \\
# 0 & -1 \\
# \end{pmatrix}$
# In[6]:
# Let's do an Z-gate on |+>
q = QuantumRegister(1)
qc = QuantumCircuit(q)
qc.h(q[0])
qc.z(q[0])
qc.draw(output='mpl')
# In[8]:
# Let's see the result
backend = Aer.get_backend('statevector_simulator')
result = execute(qc, backend).result().get_statevector(qc, decimals=3)
plot_bloch_multivector(result)
# ### CX Gate (CNOT Gate)
# The controlled NOT (or CNOT or CX) gate acts on two qubits. It performs the NOT operation (equivalent to applying an X gate) on the second qubit only when the first qubit is $|1\rangle$ and otherwise leaves it unchanged. Note: Qiskit numbers the bits in a string from right to left.
#
# $CX = \begin{pmatrix}
# 1 & 0 & 0 & 0\\
# 0 & 1 & 0 & 0\\
# 0 & 0 & 0 & 1\\
# 0 & 0 & 1 & 0\\
# \end{pmatrix}$
# In[9]:
# Let's do an CX-gate on |00>
q = QuantumRegister(2)
qc = QuantumCircuit(q)
qc.cx(q[0],q[1])
qc.draw(output='mpl')
# ### CZ Gate
# The CZ gate acts on two qubits, called a 'control bit' and a 'target bit'. It flips the sign (equivalent to applying the phase shift Z gate) of the target qubit if and only if the control qubit is $|1\rangle$.
#
# $CZ = \begin{pmatrix}
# 1 & 0 & 0 & 0\\
# 0 & 1 & 0 & 0\\
# 0 & 0 & 1 & 0\\
# 0 & 0 & 0 & -1\\
# \end{pmatrix}$
# In[10]:
# Let's do an CZ-gate on |00>
q = QuantumRegister(2)
qc = QuantumCircuit(q)
qc.cz(q[0],q[1])
qc.draw(output='mpl')
# Note: A CZ gate can also be constructed from a CX gate and an H gate.
# In[10]:
# Let's make CZ-gate with CX-gate and H-gate
q = QuantumRegister(2)
qc = QuantumCircuit(q)
qc.h(q[1])
qc.cx(q[0],q[1])
qc.h(q[1])
qc.draw(output='mpl')
# ### CCX Gate
# The CCX gate is also called a Toffoli gate.
# The CCX gate is a three-bit gate, with two controls and one target as their input and output. If the first two bits are in the state $|1\rangle$, it applies a Pauli-X (or NOT) on the third bit. Otherwise, it does nothing. Note: Qiskit numbers the bits in a string from right to left.
#
# $CCX = \begin{pmatrix}
# 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\
# 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0\\
# 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0\\
# 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0\\
# 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0\\
# 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0\\
# 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1\\
# 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0\\
# \end{pmatrix}$
# In[12]:
# Let's do an CCX-gate on |000>
q = QuantumRegister(3)
qc = QuantumCircuit(q)
qc.ccx(q[0],q[1],q[2])
qc.draw(output='mpl')
# To learn about other quantum gates, refer to [Single Qubit Gates](https://qiskit.org/textbook/ch-states/single-qubit-gates.html) in the Qiskit Textbook.
# ## Creating logical gates with quantum gates
# Now let's start creating a classic logic gate using quantum gates.
# Each gate and their truth tables will be shown. Here we denote quantum registers as 'q' and classical registers as 'c' where we encode the output of the measurement.
# ### NOT Gate
# As was mentioned before, an X gate can be considered a NOT gate. The truth table for a NOT gate looks like this:
#
#
# |input|output|
# |--|--|
# |0|1|
# |1|0|
# In[12]:
# Create a Quantum Circuit with 1 quantum register and 1 classical register
q = QuantumRegister(1)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)
qc.x(q[0])
qc.measure(q[0], c[0]) # Map the quantum measurement to the classical bits
qc.draw(output='mpl')
# ### AND Gate
# The truth table for an AND Gate looks like this:
#
# |A (input)|B (input)|output|
# |--|--|--|
# |0|0|0|
# |0|1|0|
# |1|0|0|
# |1|1|1|
#
# With a CCX gate, the result of an AND gate for two control bits will be output to its target bit.
# In[13]:
q = QuantumRegister(3)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)
qc.ccx(q[0], q[1], q[2])
qc.measure(q[2], c[0])
qc.draw(output='mpl')
# ### NAND Gate
# A NAND gate can be made by applying a NOT gate after applying an AND gate.
#
# |A(input)|B(input)|output|
# |--|--|--|
# |0|0|1|
# |0|1|1|
# |1|0|1|
# |1|1|0|
# In[13]:
q = QuantumRegister(3)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)
qc.ccx(q[0], q[1], q[2])
qc.x(q[2])
qc.measure(q[2], c[0])
qc.draw(output='mpl')
# ### OR Gate
#
# |A(input)|B(input)|output|
# |--|--|--|
# |0|0|0|
# |0|1|1|
# |1|0|1|
# |1|1|1|
# In[15]:
q = QuantumRegister(3)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)
qc.cx(q[1], q[2])
qc.cx(q[0], q[2])
qc.ccx(q[0], q[1], q[2])
qc.measure(q[2], c[0])
qc.draw(output='mpl')
# ### XOR Gate
# |A(input)|B(input)|output|
# |--|--|--|
# |0|0|0|
# |0|1|1|
# |1|0|1|
# |1|1|0|
# In[16]:
q = QuantumRegister(3)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)
qc.cx(q[1], q[2])
qc.cx(q[0], q[2])
qc.measure(q[2], c[0])
qc.draw(output='mpl')
# ### NOR Gate
#
# |A(input)|B(input)|output|
# |--|--|--|
# |0|0|1|
# |0|1|0|
# |1|0|0|
# |1|1|0|
# In[17]:
q = QuantumRegister(3)
c = ClassicalRegister(1)
qc = QuantumCircuit(q,c)
qc.cx(q[1], q[2])
qc.cx(q[0], q[2])
qc.ccx(q[0], q[1], q[2])
qc.x(q[2])
qc.measure(q[2], c[0])
qc.draw(output='mpl')
# # Adder
# An adder is a digital logic circuit that performs addition of numbers.
#
# In this example, we are going to take a look at the simplest adders, namely half adder and full adder.
# ## Half Adder
# The half adder is used to add together the two least significant digits in a binary sum.
# It has two single binary inputs, called A and B, and two outputs C (carry out) and S (sum).
# The output C will be used as an input to the Full Adder, which will be explained later, for obtaining the value in the higher digit.
#
# Half adders can be described with the truth table shown below.
#
# |A (input)|B (input)|S (sum)|C (carry out)|
# |-----------|------------|------------|------------|
# |0|0|0|0|
# |0|1|1|0|
# |1|0|1|0|
# |1|1|0|1|
#
# From the truth table, you should notice that the carry output, C, is a result of operating an AND gate against A and B, where the output S is a result of operating an XOR against A and B.
# As we have already created the AND and XOR gates, we can combine these gates and create a half adder as follows.
#
# We denote our quantum register as 'q', classical registers as 'c', assign inputs A and B to q[0] and q[1], the sum output S and carry output C to q[2] and q[3].
# In[14]:
#Define registers and a quantum circuit
q = QuantumRegister(4)
c = ClassicalRegister(2)
qc = QuantumCircuit(q,c)
#XOR
qc.cx(q[1], q[2])
qc.cx(q[0], q[2])
qc.barrier()
#AND
qc.ccx(q[0], q[1], q[3])
qc.barrier()
#Sum
qc.measure(q[2], c[0])
#Carry out
qc.measure(q[3], c[1])
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=1000)
result = job.result()
count =result.get_counts()
print(count)
qc.draw(output='mpl')
# ## <span style="color: red; ">IMPORTANT: How to calculate Quantum Costs using an Unroller</span>
# There are several ways to evaluate an efficiency of a program (quantum circuit). Such as:
#
# 1. Number of quantum bits
# 2. Depth
# 3. Program execution speed (Runtime)
# 4. Number of instructions
#
# These are all important factors that impact the results and throughput of quantum computation. In this particular challenge, we will use the number of instructions to evaluate the efficiency of our program. We will call the number of instructions "cost" throughout this challenge and will use the following formula to evaluate the cost of a circuit.
#
# Cost $=$ Single-qubit gates $+$ CX gates $\times 10$
#
# Any given quantum circuit can be decomposed into single-qubit gates (an instruction given to a single qubit) and two-qubit gates. With the current Noisy Intermediate-Scale Quantum (NISQ) devices, CX gate error rates are generally 10x higher than a single qubit gate. Therefore, we will weigh CX gates 10 times more than a single-qubit gate for cost evaluation.
#
# You can evaluate gate costs by yourself by using a program called "Unroller."
# To elaborate on this, let's take a look at the example below.
# In[19]:
import numpy as np
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister
from qiskit import BasicAer, execute
from qiskit.quantum_info import Pauli, state_fidelity, process_fidelity
q = QuantumRegister(4, 'q0')
c = ClassicalRegister(1, 'c0')
qc = QuantumCircuit(q, c)
qc.ccx(q[0], q[1], q[2])
qc.cx(q[3], q[1])
qc.h(q[3])
qc.ccx(q[3], q[2], q[1])
qc.measure(q[3],c[0])
qc.draw(output='mpl')
# In[15]:
qc.count_ops()
# As you can see, this quantum circuit contains a Hadamard gate, a CX gate and CCX gates. By using qiskit.transpiler and importing PassManager, we can decompose this circuit into gates specified by the Unroller as shown below. In this case, into U3 gates and CX gates.
# In[21]:
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import Unroller
pass_ = Unroller(['u3', 'cx'])
pm = PassManager(pass_)
new_circuit = pm.run(qc)
new_circuit.draw(output='mpl')
# In[22]:
new_circuit.count_ops()
# Thus, the cost of this circuit is $19+13\times10=149$.
#
# You can easily check how any arbitrary gate can be decomposed by using the Unroller. So, if you are interested in how a particular two-qubit gate or three-qubit gate can be decomposed, we encourage you to try it yourself. In the example below, we used the Unroller to decompose a CCX gate into U3 gates and CX gates.
# In[17]:
q = QuantumRegister(3, 'q0')
c = ClassicalRegister(1, 'c0')
qc = QuantumCircuit(q, c)
qc.ccx(q[0], q[1], q[2])
qc.draw(output='mpl')
# In[18]:
pass_ = Unroller(['u3', 'cx'])
pm = PassManager(pass_)
new_circuit = pm.run(qc)
new_circuit.draw(output='mpl')
# In[25]:
new_circuit.count_ops()
# So, the total cost of a CCX gate can be calculated as $9+6\times10=69$.
# # Learning Exercise I-A
# The full adder takes two binary numbers plus an overflow bit, which we will call X, as its input.
# Create a full adder with input data:
#
# $A=1$, $B=0$, $X=1$
# .
#
# The truth table for the full adder is given below.
#
# |A(input)|B(input)|X(carry input)|S(sum)|C(carry out)|
# |--|--|--|--|--|
# |0|0|0|0|0|
# |0|0|1|1|0|
# |0|1|0|1|0|
# |0|1|1|0|1|
# |1|0|0|1|0|
# |1|0|1|0|1|
# |1|1|0|0|1|
# |1|1|1|1|1|
#
# Call your quantum register 'q' and classical register 'c'. Assign inputs A, B and X to q[0], q[1] and q[2] respectively, the sum output S to c[0] and carry output C to c[1].
# In[27]:
from qiskit import QuantumRegister, ClassicalRegister, QuantumCircuit
from qiskit import IBMQ, Aer, execute
##### build your quantum circuit here
q = QuantumRegister(5,'q')
c = ClassicalRegister(2,'c')
qc = QuantumCircuit(q,c)
#INPUT
qc.x(q[0])
qc.x(q[2])
#SUM
qc.cx(q[2],q[3])
qc.cx(q[1],q[3])
qc.cx(q[0],q[3])
qc.barrier()
#CARRY
qc.ccx(q[1],q[2],q[4])
qc.cx(q[0],q[4])
qc.barrier()
#MEASURE SUM
qc.measure(q[3],c[0])
#MEASURE CARRY
qc.measure(q[4],c[1])
pass_ = Unroller(['u3', 'cx'])
pm = PassManager(pass_)
new_circuit = pm.run(qc)
new_circuit.draw(output='mpl')
new_circuit.count_ops()
# execute the circuit by qasm_simulator
backend = Aer.get_backend('qasm_simulator')
job = execute(qc, backend, shots=1000)
result = job.result()
count =result.get_counts()
print(count)
qc.draw(output='mpl')
# In[28]:
# Check your answer using following code
from qc_grader import grade_ex1a
grade_ex1a(qc)
# In[29]:
# Submit your answer. You can re-submit at any time.
from qc_grader import submit_ex1a
submit_ex1a(qc)
# In[ ]: