Skip to content

Commit 49d7c2f

Browse files
authored
Merge pull request #14 from tqsd/controlled_two_qubit_gate
Adding three qubit custom controlled gate
2 parents 22318d6 + 7be07ad commit 49d7c2f

15 files changed

Lines changed: 308 additions & 147 deletions

eqsn/gates.py

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import multiprocessing
22
import logging
33
import numpy as np
4-
from eqsn.qubit_thread import SINGLE_GATE, MERGE_SEND, MERGE_ACCEPT, MEASURE,\
5-
MEASURE_NON_DESTRUCTIVE, GIVE_STATEVECTOR, DOUBLE_GATE, \
6-
CONTROLLED_GATE, NEW_QUBIT, ADD_MERGED_QUBITS_TO_DICT
4+
from eqsn.qubit_thread import SINGLE_GATE, MERGE_SEND, MERGE_ACCEPT, MEASURE, \
5+
MEASURE_NON_DESTRUCTIVE, GIVE_STATEVECTOR, DOUBLE_GATE, \
6+
CONTROLLED_GATE, NEW_QUBIT, ADD_MERGED_QUBITS_TO_DICT, CONTROLLED_TWO_GATE
77
from eqsn.shared_dict import SharedDict
88
from eqsn.worker_process import WorkerProcess
99
from eqsn.process_picker import ProcessPicker
@@ -139,7 +139,7 @@ def K_gate(self, q_id):
139139
Args:
140140
q_id(String): ID of the Qubit to apply the gate to.
141141
"""
142-
x = 0.5 * np.array([[1+1j, 1-1j], [-1+1j, -1-1j]], dtype=np.csingle)
142+
x = 0.5 * np.array([[1 + 1j, 1 - 1j], [-1 + 1j, -1 - 1j]], dtype=np.csingle)
143143
q = self.shared_dict.get_queues_for_ids([q_id])[0]
144144
q.put([SINGLE_GATE, x, q_id])
145145

@@ -205,15 +205,15 @@ def merge_qubits(self, q_id1, q_id2):
205205
q_id1 (String): Id of the Qubit merged into q_id2.
206206
q_id2 (String): Id of the Qubit merged with q_id1.
207207
"""
208-
l = self.shared_dict.get_queues_for_ids([q_id1, q_id2])
209-
if len(l) == 1:
208+
queues = self.shared_dict.get_queues_for_ids([q_id1, q_id2])
209+
if len(queues) == 1:
210210
return # Already merged
211211
else:
212212
# Block the dictionary, that noone can send commands to the qubits,
213213
logging.debug("Merge Qubits %s and %s.", q_id1, q_id2)
214214
self.shared_dict.block_shared_dict()
215-
q1 = l[0]
216-
q2 = l[1]
215+
q1 = queues[0]
216+
q2 = queues[1]
217217
merge_q = self.manager.Queue()
218218
qubits_q = self.manager.Queue()
219219
q1.put([MERGE_SEND, q_id1, merge_q, qubits_q])
@@ -283,6 +283,22 @@ def custom_two_qubit_gate(self, q_id1, q_id2, gate):
283283
q = self.shared_dict.get_queues_for_ids([q_id1])[0]
284284
q.put([DOUBLE_GATE, gate, q_id1, q_id2])
285285

286+
def custom_two_qubit_control_gate(self, q_id1, q_id2, q_id3, gate):
287+
"""
288+
Applies a two Qubit gate to two Qubits.
289+
290+
Args:
291+
q_id1 (String): ID of the first Qubit of the gate.
292+
q_id2 (String): ID of the second Qubit of the gate.
293+
q_id3 (String): ID of the third Qubit of the gate.
294+
gate(np.ndarray): 4x4 unitary matrix gate.
295+
"""
296+
self.merge_qubits(q_id1, q_id2)
297+
self.merge_qubits(q_id1, q_id3)
298+
299+
q = self.shared_dict.get_queues_for_ids([q_id1])[0]
300+
q.put([CONTROLLED_TWO_GATE, gate, q_id1, q_id2, q_id3])
301+
286302
def custom_controlled_gate(self, applied_to_id, controlled_by_id, gate):
287303
"""
288304
Applies a custom controlled gate to a Qubit.

eqsn/process_picker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def get_instance(amount_processes, process_queue_list):
1111
a new object o the class is created.
1212
1313
Args:
14-
cpu_count(int): The amount of CPU cores.
14+
amount_processes (int): The amount of processes.
1515
process_queue_list(List): A List of all Processes.
1616
"""
1717
if ProcessPicker.__instance is None:

eqsn/qubit_thread.py

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import logging
33
from copy import deepcopy as dp
44
import random
5-
import sys
65

76
NONE = 0
87
SINGLE_GATE = 1
@@ -15,6 +14,7 @@
1514
ADD_MERGED_QUBITS_TO_DICT = 9
1615
GIVE_STATEVECTOR = 10
1716
DOUBLE_GATE = 11
17+
CONTROLLED_TWO_GATE = 12
1818

1919

2020
class QubitThread(object):
@@ -71,30 +71,36 @@ def give_statevector(self, channel):
7171
Sends the Qubit IDs and their state vectors over a channel.
7272
7373
Args:
74-
channel(Queue): Channel to return the requested data to.
74+
channel (Queue): Channel to return the requested data to.
7575
"""
7676
channel.put((dp(self.qubits), dp(self.qubit)))
7777

7878
def apply_controlled_gate(self, mat, q_id1, q_id2):
7979
"""
8080
Applies a controlled gate to q_id1
81+
82+
Args:
83+
mat (np.ndarray): The matrix to apply
84+
q_id1 (str): The target qubit id
85+
q_id2 (str): The control qubit id
8186
"""
8287
first_mat = 1
8388
second_mat = 1
8489
nr1 = self.qubits.index(q_id1)
8590
nr2 = self.qubits.index(q_id2)
91+
8692
min_nr = min(nr1, nr2)
8793
max_nr = max(nr1, nr2)
88-
if min_nr == nr1:
89-
# first_mat = mat
90-
pass
94+
9195
total_amount = len(self.qubits)
9296
before = min_nr
9397
after = total_amount - max_nr - 1
9498
mid = total_amount - before - after - 2
99+
95100
if before > 0:
96101
first_mat = np.eye(2 ** before)
97102
second_mat = np.eye(2 ** before)
103+
98104
# Apply first part of Matrix
99105
if min_nr == nr1:
100106
first_mat = np.kron(first_mat, np.eye(2))
@@ -106,6 +112,7 @@ def apply_controlled_gate(self, mat, q_id1, q_id2):
106112
if mid > 0:
107113
first_mat = np.kron(first_mat, np.eye(2 ** mid))
108114
second_mat = np.kron(second_mat, np.eye(2 ** mid))
115+
109116
# Apply second part of Matrix
110117
if min_nr == nr1:
111118
first_mat = np.kron(first_mat, np.array([[1, 0], [0, 0]]))
@@ -117,6 +124,7 @@ def apply_controlled_gate(self, mat, q_id1, q_id2):
117124
if after > 0:
118125
first_mat = np.kron(first_mat, np.eye(2 ** after))
119126
second_mat = np.kron(second_mat, np.eye(2 ** after))
127+
120128
apply_mat = first_mat + second_mat
121129
self.qubit = np.dot(apply_mat, self.qubit)
122130

@@ -134,7 +142,7 @@ def merge_accept(self, channel):
134142
self.qubit = np.kron(self.qubit, vector)
135143
logging.debug("Qubit Thread merged, new qubits are %r", self.qubits)
136144

137-
def merge_send(self, channel, chanel2):
145+
def merge_send(self, channel, channel2):
138146
"""
139147
Send own process data to another process and suicide.
140148
@@ -145,20 +153,21 @@ def merge_send(self, channel, chanel2):
145153
"""
146154
channel.put(dp(self.qubits))
147155
channel.put(dp(self.qubit))
148-
chanel2.put(dp(self.qubits))
156+
channel2.put(dp(self.qubits))
149157
return
150158

151159
def swap_qubits(self, q_id1, q_id2):
152160
"""
153161
Swaps the position of qubit q_id1 with q_id2
154-
in the statevector.
162+
in the state vector.
155163
156164
q_id1(String): Qubit id of one of the qubits to swap.
157165
q_id2(String): Qubit id of the other qubit to swap.
158166
"""
159-
def cnot(q_id1, q_id2):
167+
168+
def cnot(_q_id1, _q_id2):
160169
mat = np.asarray([[0, 1], [1, 0]])
161-
self.apply_controlled_gate(mat, q_id1, q_id2)
170+
self.apply_controlled_gate(mat, _q_id1, _q_id2)
162171

163172
# Check if they are the same ids
164173
if q_id1 == q_id2:
@@ -173,9 +182,28 @@ def cnot(q_id1, q_id2):
173182
i2 = self.qubits.index(q_id2)
174183
self.qubits[i1], self.qubits[i2] = self.qubits[i2], self.qubits[i1]
175184

185+
def apply_controlled_two_qubit_gate(self, mat, q_id1, q_id2, q_id3):
186+
"""
187+
Applies a 3 qubit controlled gate
188+
Args:
189+
mat (np.ndarray): The 4x4 unitary gate to apply
190+
q_id1 (str): The control qubit
191+
q_id2 (str): A target qubit
192+
q_id3 (str): A target qubit
193+
"""
194+
# Move the qubits to the correct position
195+
self.swap_qubits(q_id1, self.qubits[0])
196+
self.swap_qubits(q_id2, self.qubits[1])
197+
self.swap_qubits(q_id3, self.qubits[2])
198+
199+
first_mat = np.block([[np.eye(4), np.zeros((4, 4))], [np.zeros((4, 4)), mat]])
200+
total_mat = np.kron(first_mat, np.eye(2 ** (len(self.qubits) - 3)))
201+
202+
self.qubit = np.dot(total_mat, self.qubit)
203+
176204
def apply_two_qubit_gate(self, gate, q_id1, q_id2):
177205
"""
178-
Applies a two qubit gate to the statevector.
206+
Applies a two qubit gate to the state vector.
179207
180208
Args:
181209
gate(np.ndarray): 4x4 unitary matrix
@@ -185,11 +213,12 @@ def apply_two_qubit_gate(self, gate, q_id1, q_id2):
185213
# Bring the qubits in the right order
186214
i2 = self.qubits.index(q_id2)
187215
if i2 > 0:
188-
new_i1 = i2-1
216+
new_i1 = i2 - 1
189217
self.swap_qubits(q_id1, self.qubits[new_i1])
190218
else:
191219
self.swap_qubits(q_id1, self.qubits[0])
192220
self.swap_qubits(q_id2, self.qubits[1])
221+
193222
apply_mat = gate
194223
nr1 = self.qubits.index(q_id1)
195224
total_amount = len(self.qubits)
@@ -309,6 +338,8 @@ def run(self):
309338
self.apply_single_gate(item[1], item[2])
310339
elif item[0] == CONTROLLED_GATE:
311340
self.apply_controlled_gate(item[1], item[2], item[3])
341+
elif item[0] == CONTROLLED_TWO_GATE:
342+
self.apply_controlled_two_qubit_gate(item[1], item[2], item[3], item[4])
312343
elif item[0] == MEASURE:
313344
self.measure(item[1], item[2])
314345
# no qubit left, terminate

eqsn/shared_dict.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,6 @@ def stop_all_threads(self):
296296

297297
def stop_shared_dict(self):
298298
"""
299-
Releases the dicionary object.
299+
Releases the dictionary object.
300300
"""
301301
SharedDict.__instance = None

eqsn/worker_process.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import logging
22
import threading
3-
import os
43
from queue import Queue
4+
5+
from eqsn.qubit_thread import SINGLE_GATE, MERGE_SEND, MERGE_ACCEPT, MEASURE, \
6+
MEASURE_NON_DESTRUCTIVE, GIVE_STATEVECTOR, \
7+
CONTROLLED_GATE, NEW_QUBIT, ADD_MERGED_QUBITS_TO_DICT, CONTROLLED_TWO_GATE, \
8+
DOUBLE_GATE, QubitThread
59
from eqsn.shared_dict import SharedDict
6-
from eqsn.qubit_thread import SINGLE_GATE, MERGE_SEND, MERGE_ACCEPT, MEASURE,\
7-
MEASURE_NON_DESTRUCTIVE, GIVE_STATEVECTOR, \
8-
CONTROLLED_GATE, NEW_QUBIT, ADD_MERGED_QUBITS_TO_DICT, \
9-
DOUBLE_GATE, QubitThread
1010

1111

1212
class WorkerProcess(object):
@@ -21,6 +21,7 @@ def __init__(self, queue):
2121
queue (Queue): Queue for receiving commands from main Process.
2222
"""
2323
self.queue = queue
24+
self.shared_dict = None
2425

2526
def run(self):
2627
"""
@@ -42,6 +43,8 @@ def run(self):
4243
amount_single_gate += 1
4344
elif item[0] == CONTROLLED_GATE:
4445
self.apply_controlled_gate(item[1], item[2], item[3])
46+
elif item[0] == CONTROLLED_TWO_GATE:
47+
self.apply_two_qubit_controlled_gate(item[1], item[2], item[3], item[4])
4548
elif item[0] == MEASURE:
4649
self.measure(item[1], item[2])
4750
elif item[0] == MERGE_ACCEPT:
@@ -57,14 +60,14 @@ def run(self):
5760
elif item[0] == DOUBLE_GATE:
5861
self.apply_two_qubit_gate(item[1], item[2], item[3])
5962
else:
60-
raise ValueError("Command does not exist!")
63+
raise ValueError(f"Command does not exist! {item[0]}")
6164

6265
def new_qubit(self, q_id):
6366
"""
6467
Creates a new qubit with an id.
6568
6669
Args:
67-
id (String): Id of the new qubit.
70+
q_id (String): Id of the new qubit.
6871
"""
6972
q = Queue()
7073
thread = QubitThread(q_id, q)
@@ -115,6 +118,11 @@ def stop_all(self):
115118
self.shared_dict.stop_all_threads()
116119
self.shared_dict.stop_shared_dict()
117120

121+
def apply_two_qubit_controlled_gate(self, gate, q_id1, q_id2, q_id3):
122+
# Qubits are already mergered, they have the same queue
123+
q = self.shared_dict.get_queues_for_ids([q_id1])[0]
124+
q.put([CONTROLLED_TWO_GATE, gate, q_id1, q_id2, q_id3])
125+
118126
def apply_two_qubit_gate(self, gate, q_id1, q_id2):
119127
"""
120128
Applies a two qubit gate to a thread.
@@ -179,8 +187,8 @@ def merge_accept(self, q_id, queue):
179187
Handle a merge accept.
180188
181189
Args:
182-
q_id(String): ID of the qubit which should accept the merge.
183-
channel(Queue): channel to receive qubit ids and statevectors from.
190+
q_id (String): ID of the qubit which should accept the merge.
191+
queue (Queue): channel to receive qubit ids and statevectors from.
184192
"""
185193
q = self.shared_dict.get_queues_for_ids([q_id])[0]
186194
q.put([MERGE_ACCEPT, queue])

run_tests.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ def __exit__(self, etype, value, traceback):
2020

2121
if __name__ == "__main__":
2222
files = [f for f in glob.glob("./tests/**/*.py")] + [f for f in glob.glob("./tests/*.py")]
23-
print(files)
2423
for f in files:
25-
print("Start with " + str(f))
2624
if f in ignore:
2725
continue
2826
path, filename = os.path.split(f)
2927
with cd(path):
28+
print('Testing %s' % filename)
3029
exitcode = os.system("python %s" % filename)
3130
if exitcode != 0:
32-
assert False, "Testfile %s was not successful!" % f
31+
assert False, "Testing %s was not successful!" % f
32+
else:
33+
print("Testing %s was successful!" % filename)
34+
35+
print('Done tests')
36+
exit(0)

tests/test_calling_gates_from_threads.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,15 @@
33

44

55
def test_call_single_qubit_gate_from_threads():
6-
q_sim = EQSN()
6+
eqsn = EQSN()
77

8-
def call_X_gate_n_times(id, n):
9-
for c in range(n):
10-
# print("Apply %d time." % c)
11-
q_sim.X_gate(id)
8+
def call_X_gate_n_times(_id, n):
9+
for _ in range(n):
10+
eqsn.X_gate(_id)
1211

1312
id1 = str(1)
14-
q_sim.new_qubit(id1)
15-
n = 999
13+
eqsn.new_qubit(id1)
14+
n = 99
1615
nr_threads = 5
1716
thread_list = []
1817
for _ in range(nr_threads):
@@ -21,12 +20,11 @@ def call_X_gate_n_times(id, n):
2120
thread_list.append(t)
2221
for t in thread_list:
2322
t.join()
24-
m = q_sim.measure(id1)
25-
print("Measured %d." % m)
23+
m = eqsn.measure(id1)
2624
assert m == 1
27-
print("Test was successfull!")
28-
q_sim.stop_all()
25+
eqsn.stop_all()
2926

3027

3128
if __name__ == "__main__":
3229
test_call_single_qubit_gate_from_threads()
30+
exit(0)

0 commit comments

Comments
 (0)