-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathquantum.py
300 lines (247 loc) · 9.71 KB
/
quantum.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
import numpy as np
import random
from math import log
from cmath import sqrt
from functools import reduce
def kron(*matrices):
return reduce(np.kron, matrices[1:], matrices[0])
def row_swap(matrix_to_swap, i, j):
matrix = np.matrix(np.copy(matrix_to_swap))
tmp = np.copy(matrix[i])
matrix[i] = np.copy(matrix[j])
matrix[j] = tmp
return matrix
def to_row(matrix):
"""
Converts a numpy column vector to a row (list). The array must have no more
than one column.
"""
rows, cols = matrix.shape
assert cols == 1, "to_row can only flatten a matrix with one column width"
return matrix.transpose().tolist()[0]
zero = np.matrix('1; 0')
one = np.matrix('0; 1')
I = np.eye(2, dtype=int)
X = np.matrix('0 1; 1 0')
Z = np.matrix('1 0; 0 -1')
H = 1/sqrt(2) * (X + Z)
entangled_pair = 1/sqrt(2) * (kron(zero, zero) + kron(one, one))
cnot_10 = np.matrix('1 0 0 0; 0 1 0 0; 0 0 0 1; 0 0 1 0')
cnot_01 = np.matrix('1 0 0 0; 0 0 0 1; 0 0 1 0; 0 1 0 0')
def get_entangled_pair():
return Q(entangled_pair)
class Q(object):
def __init__(self, state):
sq_mag = sum(abs(a)**2 for a in to_row(state))
assert abs(1-sq_mag) < 0.00001, "Squared magnitudes must sum to 1"
self.state = state
def __add__(self, other):
new_state = self.state + other.state
norm_factor = sum(p**2 for p in to_row(new_state))
norm_new_state = 1/norm_factor * new_state
return Q(norm_new_state)
def apply_gate(self, gate, qubit_idx):
# construct a unitary transformation matrix of order `num_qubits`
# `qubits` is a reversed range since qubits index from right to left
qubits = reversed(range(self.num_qubits))
gate_seq = (gate if i == qubit_idx else I for i in qubits)
unitary_transform = kron(*gate_seq)
return self.apply_unitary(unitary_transform)
def apply_unitary(self, unitary):
# multiply the state with this unitary transform
new_state = unitary * self.state
# renormalize the state
norm_factor = sum(p**2 for p in to_row(new_state))
norm_new_state = 1/norm_factor * new_state
return Q(norm_new_state)
def apply_cnot(self, control, target):
control_mask = 2 ** control
target_mask = 2 ** target
new_state = self.state
swapped = set([])
for qubit in range(2 ** self.num_qubits):
# only consider qubits (states) where the control bit is > 0
if qubit & control_mask == 0: continue
# compute the target bit index
target_idx = qubit ^ target_mask
# this method calls for (i, j) and (j, i) to be swapped
# to prevent double-swapping, check if (i, j) have not already
# been swapped
if (target_idx, qubit) in swapped: continue
# perform the row swap
new_state = row_swap(new_state, qubit, target_idx)
# add both (i, j) and (j, i) to the swapped set
swapped.add((target_idx, qubit))
swapped.add((qubit, target_idx))
return Q(new_state)
def apply_func(self, func):
"""
Apply `func` to the state. U|x>|y> = |x>|y ^ func(x)>
For an `n`-qubit state:
|x> (input) is assumed to be qubits 1-n
|y> (output) is assumed to be ONLY qubit 0
`func` must return either 0 or 1 for any input x
"""
qubits = range(2 ** self.num_qubits)
state_matrix = self.state
swapped = set([])
for current_state in qubits:
x = current_state >> 1 # take bits 1-n
y = current_state & 1 # take just bit 0
fx = func(x)
assert fx == 0 or fx == 1, "f(x) must map to either {0, 1}"
yfunc = y ^ fx # yfunc = y xor f(x)
# new state is the original `x` bits with the `y ^ f(x)` bit at end
new_state = (x << 1) + yfunc
if (current_state, new_state) not in swapped:
state_matrix = row_swap(state_matrix, current_state, new_state)
swapped.add((current_state, new_state))
swapped.add((new_state, current_state))
# resulting state is the one with swapped rows
return Q(state_matrix)
@property
def num_qubits(self):
rows, cols = self.state.shape
qubits = log(rows, 2)
assert qubits % 1.0 == 0, "Got irregular number of qubits"
return int(qubits)
def measure(self):
amplitudes = to_row(self.state)
probabilities = ((a**2).real for a in amplitudes)
cumul = 0
rand = random.random()
for idx, pdensity in enumerate(probabilities):
cumul += pdensity
if rand <= cumul:
return idx
raise AssertionError("probabilities did not sum to 1")
def __repr__(self):
return str(self.state)
def __eq__(self, other):
# the matrix equality comparator doesn't allow rounding error.
# see docs.scipy.org/doc/numpy/reference/generated/numpy.allclose.html
return np.allclose(self.state, other.state)
import unittest
class TestQ(unittest.TestCase):
def test_num_qubits_1(self):
state = Q(zero)
self.assertEqual(state.num_qubits, 1)
def test_num_qubits_2(self):
state = Q(kron(zero, zero))
self.assertEqual(state.num_qubits, 2)
def test_num_qubits_3(self):
QUBITS = 10
state = Q(kron(*(zero for x in range(QUBITS))))
self.assertEqual(state.num_qubits, QUBITS)
def test_cnot_1(self):
state = Q(kron(one, zero))
state = state.apply_cnot(1, 0)
exp_state = Q(kron(one, one))
self.assertEqual(state, exp_state)
def test_cnot_2(self):
state = Q(kron(one, zero))
state = state.apply_cnot(0, 1)
exp_state = Q(kron(one, zero))
self.assertEqual(state, exp_state)
def test_cnot_3(self):
state = Q(kron(one, one))
state = state.apply_cnot(0, 1)
exp_state = Q(kron(zero, one))
self.assertEqual(state, exp_state)
def test_cnot_4(self):
state = Q(kron(one, one, one))
state = state.apply_cnot(2, 0)
exp_state = Q(kron(one, one, zero))
self.assertEqual(state, exp_state)
def test_cnot_5(self):
state = Q(kron(one, one, zero))
state = state.apply_cnot(2, 0)
exp_state = Q(kron(one, one, one))
self.assertEqual(state, exp_state)
def test_cnot_6(self):
state = Q(kron(one, one, one))
state = state.apply_cnot(0, 2)
exp_state = Q(kron(zero, one, one))
self.assertEqual(state, exp_state)
def test_cnot_7(self):
state = Q(kron(zero, one, one, zero, one))
state = state.apply_cnot(2, 4)
exp_state = Q(kron(one, one, one, zero, one))
self.assertEqual(state, exp_state)
def test_cnot_8(self):
state = Q(kron(one, zero, one, one, one, zero, one))
state = state.apply_cnot(1, 6)
exp_state = Q(kron(one, zero, one, one, one, zero, one))
self.assertEqual(state, exp_state)
def test_cnot_9(self):
state = Q(kron(one, zero, one, one, one, zero, one))
state = state.apply_cnot(2, 6)
exp_state = Q(kron(zero, zero, one, one, one, zero, one))
self.assertEqual(state, exp_state)
def test_apply_gate_1(self):
state = Q(zero)
state = state.apply_gate(X, 0)
exp_state = Q(one)
self.assertEqual(state, exp_state)
def test_apply_gate_2(self):
state = Q(kron(zero, zero))
state = state.apply_gate(X, 0)
exp_state = Q(kron(zero, one))
self.assertEqual(state, exp_state)
def test_apply_gate_3(self):
state = Q(kron(one, zero))
state = state.apply_gate(X, 1)
exp_state = Q(kron(zero, zero))
self.assertEqual(state, exp_state)
def test_apply_gate_entangled_pair(self):
state = Q(kron(zero, zero))
state = state.apply_gate(H, 1)
state = state.apply_cnot(1, 0)
exp_state = Q(entangled_pair)
self.assertEqual(state, exp_state)
def test_apply_func_1(self):
# f(x) = 0 for all x
func = lambda x: 0
state = Q(kron(zero, zero))
# apply func with input = 1, output = 0
state = state.apply_func(func)
# |0> |0 ^ f(0)> == |0>|0>
exp_state = Q(kron(zero, zero))
self.assertEqual(state, exp_state)
def test_apply_func_2(self):
func = lambda x: 1
state = Q(kron(one, one))
state = state.apply_func(func)
# |1>|1 ^ f(1)> == |1>|1 ^ 1> == |1>|0>
exp_state = Q(kron(one, zero))
self.assertEqual(state, exp_state)
def test_apply_func_3(self):
func = lambda x: 1
state = Q(kron(one, one, one, one))
state = state.apply_func(func)
# |111>|1 ^ f(111)> == |111>|1^1> == |1110>
exp_state = Q(kron(one, one, one, zero))
self.assertEqual(state, exp_state)
def test_apply_func_4(self):
func = lambda x: x % 2
state = Q(kron(one, one, zero))
state = state.apply_func(func)
# |11>|0 ^ f(10)> == |11>|0 ^ 1> == |111>
exp_state = Q(kron(one, one, one))
self.assertEqual(state, exp_state)
def test_apply_func_5(self):
func = lambda x: x % 2
state = Q(kron(one, one, one, zero, zero))
state = state.apply_func(func)
# |1110>|0 ^ f(1110)>
# = |1110>|0 ^ 0>
# = |11100>
exp_state = Q(kron(one, one, one, zero, zero))
self.assertEqual(state, exp_state)
def test_apply_invalid_func(self):
func = lambda x: 2
state = Q(kron(zero))
with self.assertRaises(AssertionError):
state.apply_func(func)
if __name__ == '__main__':
unittest.main()