5 Created on Tue Jun 30 15:44:26 2020 6 Copyright 2020 Peter Rakyta, Ph.D. 8 Licensed under the Apache License, Version 2.0 (the "License"); 9 you may not use this file except in compliance with the License. 10 You may obtain a copy of the License at 12 http://www.apache.org/licenses/LICENSE-2.0 14 Unless required by applicable law or agreed to in writing, software 15 distributed under the License is distributed on an "AS IS" BASIS, 16 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 See the License for the specific language governing permissions and 18 limitations under the License. 20 @author: Peter Rakyta, Ph.D. 25 from squander.gates.gates_Wrapper
import CNOT
38 def __init__(self,quantum_circuit,topology, max_lookahead=4, max_E_size=20,W=0.5,alpha=0.9):
39 self.circuit_qbit_num = quantum_circuit.get_Qbit_Num()
40 self.qbit_num = max(max(topology))+1
41 self.initial_circuit = quantum_circuit
42 self.pi = np.arange(self.qbit_num)
43 np.random.shuffle(self.pi)
44 self.topology = topology
45 self.D = self.Floyd_Warshall()
46 self.max_E_size = max_E_size
49 self.possible_connections_control,self.neighbours = self.geneterate_possible_connections()
50 self.max_lookahead = max_lookahead
55 D = np.ones((self.qbit_num,self.qbit_num))*np.inf
56 for vertex
in self.topology:
60 for idx
in range(self.qbit_num):
62 for k
in range(self.qbit_num):
63 for i
in range(self.qbit_num):
64 for j
in range(self.qbit_num):
65 if D[i][j] > D[i][k] + D[k][j]:
66 D[i][j] = D[i][k] + D[k][j]
71 possible_connections_control = [ []
for _
in range(self.qbit_num) ]
72 neighbours = [ []
for _
in range(self.qbit_num) ]
73 for connection
in self.topology:
74 qbit1,qbit2 = connection
75 possible_connections_control[qbit2].append(qbit1)
76 if qbit2
not in neighbours[qbit1]:
77 neighbours[qbit1].append(qbit2)
78 if qbit1
not in neighbours[qbit2]:
79 neighbours[qbit2].append(qbit1)
80 return possible_connections_control,neighbours
85 gates = circuit.get_Gates()
86 reverse_circuit = Circuit(self.circuit_qbit_num)
87 for idx
in range(len(gates)):
88 gate_idx = len(gates)-1-idx
89 gate = gates[gate_idx]
90 reverse_circuit.add_Gate(gate)
91 return reverse_circuit
97 gates = circuit.get_Gates()
99 DAG.append([gate,circuit.get_Children(gate)])
106 gates = circuit.get_Gates()
108 inverse_DAG.append([gate,circuit.get_Parents(gate)])
115 gates = circuit.get_Gates()
116 for gate_idx
in range(len(gates)):
117 gate = gates[gate_idx]
118 if len(circuit.get_Parents(gate))==0:
119 initial_layer.append(gate_idx)
130 Q_control = pi[q_control]
131 Q_target = pi[q_target]
132 if Q_target
in self.possible_connections_control[Q_control]:
134 elif Q_control
in self.possible_connections_control[Q_target]:
147 return self.D[pi[q1]][pi[q2]]
162 inverse_pi = list(range(len(pi)))
163 for i
in range(len(pi)):
175 inverse_pi = self.get_inverse_pi(pi)
178 gate = DAG[gate_idx][0]
179 q_control = gate.get_Control_Qbit()
180 q_target = gate.get_Target_Qbit()
183 if q_control
not in involved_qbits:
184 involved_qbits.append(q_control)
185 if q_target
not in involved_qbits:
186 involved_qbits.append(q_target)
187 for qbit
in involved_qbits:
188 candidates = self.neighbours[pi[qbit]]
189 for Q_cand
in candidates:
190 swap = [min(qbit,inverse_pi[Q_cand]),max(qbit,inverse_pi[Q_cand])]
191 if swap
not in swaps:
202 if len(E) < self.max_E_size:
205 children = list(DAG[front_gate][1]).copy()
206 while (lookahead_count<self.max_lookahead
and len(children)>0):
207 for gate_idx
in children.copy():
208 gate = DAG[gate_idx][0]
209 if gate.get_Control_Qbit() == -1:
210 children.extend(DAG[gate_idx][1])
212 gate_depth = self.calculate_gate_depth(gate_idx,IDAG,resolved_gates)
213 if len(E)< self.max_E_size
and (gate.get_Target_Qbit()
in involved_qbits
or gate.get_Control_Qbit()
in involved_qbits)
and gate_idx
not in E
and gate_depth<6:
216 if lookahead_count<self.max_lookahead:
217 children.extend(DAG[gate_idx][1])
218 children.remove(gate_idx)
230 gate = IDAG[gate_idx][0]
231 parents = list(IDAG[gate_idx][1])
232 while (len(parents)>0):
233 for parent_idx
in parents.copy():
234 parent_gate = IDAG[parent_idx][0]
235 if resolved_gates[parent_idx]==
False:
236 if parent_gate.get_Control_Qbit() != -1:
238 parents.extend(IDAG[parent_idx][1])
239 parents.remove(parent_idx)
248 gate = IDAG[gate_idx][0]
249 parents = list(IDAG[gate_idx][1])
250 while (len(parents)>0
and resolved!=
False):
251 for parent_idx
in parents.copy():
252 parent_gate = IDAG[parent_idx][0]
253 resolved *= resolved_gates[parent_idx]
254 parents.extend(IDAG[parent_idx][1])
255 parents.remove(parent_idx)
268 resolved_gates = [
False]*len(DAG)
272 E = self.generate_E(F,DAG,IDAG,resolved_gates)
275 execute_gate_list = []
277 gate = DAG[gate_idx][0]
278 possible, flag = self.is_gate_possible(pi,gate.get_Target_Qbit(),gate.get_Control_Qbit())
280 execute_gate_list.append(gate_idx)
281 resolved_gates[gate_idx] =
True 282 flags[gate_idx] = flag
283 if len(execute_gate_list ) != 0:
284 for gate_idx
in execute_gate_list:
286 gate_order.append(gate_idx)
287 successors = DAG[gate_idx][1]
288 for new_gate_idx
in successors:
289 resolved = self.check_dependencies(new_gate_idx,IDAG,resolved_gates)
290 if resolved
and new_gate_idx
not in F:
291 F.append(new_gate_idx)
292 E = self.generate_E(F,DAG,IDAG,resolved_gates)
297 swaps = self.obtain_SWAPS(F,pi,DAG)
299 for SWAP_idx
in range(len(swaps)):
300 SWAP = swaps[SWAP_idx]
301 if SWAP == swap_prev:
302 scores.append(np.inf)
305 self.update_pi(pi_temp,SWAP)
308 gate = DAG[gate_idx][0]
309 q_target, q_control = gate.get_Target_Qbit(),gate.get_Control_Qbit()
310 score += self.H_basic(pi_temp,q_target, q_control)
315 gate = DAG[gate_idx][0]
316 q_target, q_control = gate.get_Target_Qbit(),gate.get_Control_Qbit()
317 score_temp += self.H_basic(pi_temp,q_target, q_control)*(self.alpha**self.calculate_gate_depth(gate_idx,IDAG,resolved_gates))
318 score_temp *= self.W/len(E)
321 min_idx = np.argmin(np.array(scores))
322 min_swap = swaps[min_idx]
324 gate_order.append(min_swap)
325 self.update_pi(pi,min_swap)
328 print(
"ERORR: circuit cannot be mapped please check topology")
330 return gate_order,flags,swap_count
340 circuit_mapped = Circuit(self.qbit_num)
341 gates = circuit.get_Gates()
342 parameters_new = np.array([])
343 for gate_idx
in gate_order:
344 if isinstance(gate_idx,int):
346 if gate.get_Parameter_Num() != 0:
347 start_idx = gate.get_Parameter_Start_Index()
348 params = parameters[start_idx:start_idx + gate.get_Parameter_Num()]
349 parameters_new = np.append(parameters_new,params)
350 q_target,q_control = gate.get_Target_Qbit(),gate.get_Control_Qbit()
351 Q_target,Q_control = init_pi[q_target],init_pi[q_control]
352 gate.set_Target_Qbit(Q_target)
353 gate.set_Control_Qbit(Q_control)
354 if flags[gate_idx] == 1:
355 gate.set_Target_Qbit(Q_control)
356 gate.set_Control_Qbit(Q_target)
357 if isinstance(gate,CNOT):
358 circuit_mapped.add_H(Q_target)
359 circuit_mapped.add_H(Q_control)
360 circuit_mapped.add_Gate(gate)
361 if flags[gate_idx] == 1
and isinstance(gate,CNOT):
362 circuit_mapped.add_H(Q_target)
363 circuit_mapped.add_H(Q_control)
365 q1,q2 = gate_idx[0],gate_idx[1]
366 Q1,Q2 = init_pi[q1],init_pi[q2]
367 circuit_mapped.add_CNOT(Q1,Q2)
368 circuit_mapped.add_CNOT(Q2,Q1)
369 circuit_mapped.add_CNOT(Q1,Q2)
370 self.update_pi(init_pi,[q1,q2])
372 return circuit_mapped,parameters_new
379 init_pi = self.pi.copy()
380 circuit = self.initial_circuit
381 reverse_circuit = self.get_reverse_circuit(circuit)
382 DAG = self.generate_DAG(circuit)
383 IDAG = self.generate_inverse_DAG(circuit)
384 first_layer = self.get_initial_layer(circuit)
385 DAG_reverse = self.generate_DAG(reverse_circuit)
386 IDAG_reverse = self.generate_inverse_DAG(reverse_circuit)
387 first_layer_reverse = self.get_initial_layer(reverse_circuit)
388 for idx
in range(iter_num):
389 self.Heuristic_search(first_layer.copy(),init_pi,DAG,IDAG)
390 self.Heuristic_search(first_layer_reverse.copy(),init_pi,DAG_reverse,IDAG_reverse)
391 final_pi = init_pi.copy()
392 gate_order,flags,swap_count = self.Heuristic_search(first_layer.copy(),final_pi,DAG,IDAG)
393 circuit_mapped,parameters_new = self.get_mapped_circuit(circuit,init_pi.copy(),gate_order,flags,parameters)
394 return circuit_mapped,parameters_new,init_pi,final_pi,swap_count
def is_gate_possible(self, pi, q_target, q_control)
checks if connection between two virtual qubits q_target and q_control is possible, if second return is 1 it means gate might need to be inverted (with H gates in the case of CNOT)
def get_initial_layer(self, circuit)
generates initial layer of input circuit for more see at: https://arxiv.org/pdf/1809.02573
def update_pi(self, pi, SWAP)
basic cost function that returns distance of two virtual qubits q1 and q2
def obtain_SWAPS(self, F, pi, DAG)
Get all possible swaps between all involved qubits in F F front layer containing unresolved gates...
def calculate_gate_depth(self, gate_idx, IDAG, resolved_gates)
Calculates the number of predecessing unresolved gates for a gate.
def map_circuit(self, parameters=np.array([]), iter_num=1)
Returns mapped circuit on physical qubits Q with swaps included.
def H_basic(self, pi, q1, q2)
basic cost function that returns distance of two virtual qubits q1 and q2
def Floyd_Warshall(self)
Floyd_warshall algorithm to calculate distance matrix.
def generate_E(self, F, DAG, IDAG, resolved_gates)
Generates lookahead extended layer containing all upcoming two qubit gates.
def geneterate_possible_connections(self)
generate list of possible connections based on inputted topology
def Heuristic_search(self, F, pi, DAG, IDAG)
Heuristic search to map circuit to topology based on initial mapping pi this is an implementation of ...
def __init__(self, quantum_circuit, topology, max_lookahead=4, max_E_size=20, W=0.5, alpha=0.9)
Constructor of the class.
def generate_inverse_DAG(self, circuit)
generates inverse DAG of input circuit
def get_reverse_circuit(self, circuit)
generates reverse circuit
def get_mapped_circuit(self, circuit, init_pi, gate_order, flags, parameters)
Returns mapped circuit on physical qubits Q with swaps included.
def check_dependencies(self, gate_idx, IDAG, resolved_gates)
Checks if gate dependencies have been resolved.
def get_Control_Qbit(self)
def generate_DAG(self, circuit)
generates DAG of input circuit
def get_inverse_pi(self, pi)
returns inverse pi mapping physical qubits Q to virtual qubits q
def get_Target_Qbit(self)