2 Simplified SABRE improvements focusing on key quality enhancements 3 without breaking existing functionality. 7 from collections
import deque, defaultdict
9 from squander.gates.gates_Wrapper
import CNOT
21 initial_layout="smart",
23 stochastic_selection=False,
27 Parameters match original with addition of: 28 initial_layout: 'smart', 'random', or 'trivial' 35 self.
rng = np.random.RandomState(random_seed)
48 if initial_layout ==
"smart":
50 elif initial_layout ==
"random":
52 self.
rng.shuffle(self.
pi)
63 Smart initial layout based on circuit connectivity. 64 This is the MOST IMPORTANT improvement for reducing SWAPs. 67 interaction_count = defaultdict(int)
68 gates = circuit.get_Gates()
71 if gate.get_Control_Qbit() != -1:
72 q1 = gate.get_Target_Qbit()
73 q2 = gate.get_Control_Qbit()
75 key = (min(q1, q2), max(q1, q2))
76 interaction_count[key] += 1
78 if not interaction_count:
83 most_connected = max(interaction_count.items(), key=
lambda x: x[1])
84 q1, q2 = most_connected[0]
100 placed_logical = {q1, q2}
101 placed_physical = {p1, p2}
104 remaining_logical = [
109 def interaction_score(q):
111 for placed_q
in placed_logical:
112 key = (min(q, placed_q), max(q, placed_q))
113 score += interaction_count.get(key, 0)
116 remaining_logical.sort(key=interaction_score, reverse=
True)
119 for logical_q
in remaining_logical:
122 best_score = float(
"inf")
124 for physical_q
in range(self.
qbit_num):
125 if physical_q
not in placed_physical:
129 for other_q
in placed_logical:
130 key = (min(logical_q, other_q), max(logical_q, other_q))
131 weight = interaction_count.get(key, 0)
133 other_physical = pi[other_q]
134 total_dist += self.
D[physical_q][other_physical] * weight
138 avg_dist = total_dist / count
142 if avg_dist < best_score:
143 best_score = avg_dist
144 best_physical = physical_q
146 if best_physical
is not None:
147 pi[logical_q] = best_physical
148 placed_logical.add(logical_q)
149 placed_physical.add(best_physical)
154 """BFS distance computation - faster than Floyd-Warshall.""" 158 adj = defaultdict(list)
166 queue = deque([(start, 0)])
170 node, dist = queue.popleft()
171 for neighbor
in adj[node]:
172 if neighbor
not in visited:
173 visited.add(neighbor)
174 D[start][neighbor] = dist + 1
175 queue.append((neighbor, dist + 1))
180 """Generate possible connections - using sets for O(1) lookup.""" 181 possible_connections_control = [set()
for _
in range(self.
qbit_num)]
182 neighbours = [set()
for _
in range(self.
qbit_num)]
185 possible_connections_control[qbit2].add(qbit1)
186 neighbours[qbit1].add(qbit2)
187 neighbours[qbit2].add(qbit1)
189 return possible_connections_control, neighbours
192 """Check if gate is possible (unchanged from original).""" 196 Q_control = pi[q_control]
197 Q_target = pi[q_target]
198 if Q_target
in self.possible_connections_control[Q_control]:
200 elif Q_control
in self.possible_connections_control[Q_target]:
205 """Basic cost function (unchanged).""" 208 return self.
D[pi[q1]][pi[q2]]
211 """Update mapping after swap.""" 213 pi[q1], pi[q2] = pi[q2], pi[q1]
217 """Get inverse mapping - optimized.""" 218 inverse_pi = np.empty(len(pi), dtype=int)
219 inverse_pi[pi] = np.arange(len(pi))
223 """Get candidate SWAPs - optimized with sets.""" 226 involved_qbits = set()
229 gate = DAG[gate_idx][0]
230 q_control = gate.get_Control_Qbit()
231 q_target = gate.get_Target_Qbit()
234 involved_qbits.add(q_control)
235 involved_qbits.add(q_target)
237 for qbit
in involved_qbits:
240 q_cand = inverse_pi[Q_cand]
241 swap = (min(qbit,
int(q_cand)), max(qbit,
int(q_cand)))
247 """Generate extended set - simplified version.""" 256 DAG[front_gate][0].get_Target_Qbit(),
257 DAG[front_gate][0].get_Control_Qbit(),
260 children = list(DAG[front_gate][1])
263 for gate_idx
in children[:]:
264 gate = DAG[gate_idx][0]
266 if gate.get_Control_Qbit() == -1:
267 children.extend(DAG[gate_idx][1])
270 gate_idx, IDAG, resolved_gates
275 gate.get_Target_Qbit()
in involved_qbits
276 or gate.get_Control_Qbit()
in involved_qbits
278 and gate_idx
not in E_set
285 children.extend(DAG[gate_idx][1])
287 children.remove(gate_idx)
292 """Calculate gate depth - iterative to avoid stack issues.""" 295 stack = list(IDAG[gate_idx][1])
298 parent_idx = stack.pop()
299 if parent_idx
in visited:
301 visited.add(parent_idx)
303 if not resolved_gates[parent_idx]:
304 parent_gate = IDAG[parent_idx][0]
305 if parent_gate.get_Control_Qbit() != -1:
307 stack.extend(IDAG[parent_idx][1])
312 """Check dependencies - iterative.""" 314 stack = list(IDAG[gate_idx][1])
317 parent_idx = stack.pop()
318 if parent_idx
in visited:
320 visited.add(parent_idx)
322 if not resolved_gates[parent_idx]:
324 stack.extend(IDAG[parent_idx][1])
330 Improved scoring that rewards immediately executable gates. 340 gate = DAG[gate_idx][0]
341 q_target, q_control = gate.get_Target_Qbit(), gate.get_Control_Qbit()
344 dist = self.
H_basic(pi_temp, q_target, q_control)
358 gate = DAG[gate_idx][0]
359 q_target, q_control = gate.get_Target_Qbit(), gate.get_Control_Qbit()
361 score_temp += self.
H_basic(pi_temp, q_target, q_control) * (
364 score_temp *= self.
W / len(E)
368 if gates_resolved == 0:
374 """Main heuristic search - with improved scoring.""" 376 resolved_gates = [
False] * len(DAG)
377 flags = [0] * len(DAG)
380 E = self.
generate_E(F, DAG, IDAG, resolved_gates)
384 execute_gate_list = []
388 gate = DAG[gate_idx][0]
390 pi, gate.get_Target_Qbit(), gate.get_Control_Qbit()
393 execute_gate_list.append(gate_idx)
394 resolved_gates[gate_idx] =
True 395 flags[gate_idx] = flag
397 if len(execute_gate_list) != 0:
399 for gate_idx
in execute_gate_list:
401 gate_order.append(gate_idx)
402 successors = DAG[gate_idx][1]
403 for new_gate_idx
in successors:
405 new_gate_idx, IDAG, resolved_gates
407 if resolved
and new_gate_idx
not in F:
408 F.append(new_gate_idx)
410 E = self.
generate_E(F, DAG, IDAG, resolved_gates)
419 if SWAP == swap_prev:
420 scores.append(np.inf)
425 SWAP, F, E, pi, DAG, IDAG, resolved_gates
431 min_idx = np.argmin(scores)
432 min_swap = swaps[min_idx]
434 gate_order.append(min_swap)
438 print(
"ERROR: circuit cannot be mapped please check topology")
441 return gate_order, flags, swap_count
445 gates = circuit.get_Gates()
447 for idx
in range(len(gates)):
448 gate_idx = len(gates) - 1 - idx
449 gate = gates[gate_idx]
450 reverse_circuit.add_Gate(gate)
451 return reverse_circuit
455 gates = circuit.get_Gates()
457 DAG.append([gate, circuit.get_Children(gate)])
462 gates = circuit.get_Gates()
464 inverse_DAG.append([gate, circuit.get_Parents(gate)])
469 gates = circuit.get_Gates()
470 for gate_idx
in range(len(gates)):
471 gate = gates[gate_idx]
472 if len(circuit.get_Parents(gate)) == 0:
473 initial_layer.append(gate_idx)
477 circuit_mapped = Circuit(self.
qbit_num)
478 gates = circuit.get_Gates()
479 parameters_new = np.array([])
481 for gate_idx
in gate_order:
482 if isinstance(gate_idx, int):
483 gate = gates[gate_idx]
484 if gate.get_Parameter_Num() != 0:
485 start_idx = gate.get_Parameter_Start_Index()
487 start_idx : start_idx + gate.get_Parameter_Num()
489 parameters_new = np.append(parameters_new, params)
491 q_target, q_control = gate.get_Target_Qbit(), gate.get_Control_Qbit()
492 Q_target = init_pi[q_target]
493 Q_control = init_pi[q_control]
if q_control != -1
else -1
495 gate.set_Target_Qbit(Q_target)
497 gate.set_Control_Qbit(Q_control)
499 if flags[gate_idx] == 1:
500 gate.set_Target_Qbit(Q_control)
501 gate.set_Control_Qbit(Q_target)
502 if isinstance(gate, CNOT):
503 circuit_mapped.add_H(Q_target)
504 circuit_mapped.add_H(Q_control)
506 circuit_mapped.add_Gate(gate)
508 if flags[gate_idx] == 1
and isinstance(gate, CNOT):
509 circuit_mapped.add_H(Q_target)
510 circuit_mapped.add_H(Q_control)
512 q1, q2 = gate_idx[0], gate_idx[1]
513 Q1, Q2 = init_pi[q1], init_pi[q2]
514 circuit_mapped.add_CNOT(Q1, Q2)
515 circuit_mapped.add_CNOT(Q2, Q1)
516 circuit_mapped.add_CNOT(Q1, Q2)
519 return circuit_mapped, parameters_new
522 """Stochastic swap selection.""" 523 scores_array = np.array(scores)
524 valid_mask = ~np.isinf(scores_array)
526 if not np.any(valid_mask):
529 valid_scores = scores_array[valid_mask]
530 min_score = np.min(valid_scores)
534 close_indices = np.where((scores_array <= threshold) & valid_mask)[0]
536 if len(close_indices) > 1:
537 selected_idx = self.
rng.choice(close_indices)
538 return swaps[selected_idx]
540 return swaps[close_indices[0]]
542 min_idx = np.argmin(scores_array)
543 return swaps[min_idx]
546 """Main entry point - unchanged interface.""" 547 init_pi = self.
pi.copy()
557 for idx
in range(iter_num):
560 first_layer_reverse.copy(), init_pi, DAG_reverse, IDAG_reverse
563 final_pi = init_pi.copy()
565 first_layer.copy(), final_pi, DAG, IDAG
568 circuit, init_pi.copy(), gate_order, flags, parameters
570 return circuit_mapped, parameters_new, init_pi, final_pi, swap_count
def get_mapped_circuit(self, circuit, init_pi, gate_order, flags, parameters)
def map_circuit(self, parameters=np.array([]), iter_num=1)
def obtain_SWAPS(self, F, pi, DAG)
def generate_DAG(self, circuit)
def calculate_gate_depth(self, gate_idx, IDAG, resolved_gates)
def __init__(self, quantum_circuit, topology, max_lookahead=4, max_E_size=20, W=0.5, alpha=0.9, initial_layout="smart", score_tolerance=0.01, stochastic_selection=False, random_seed=None)
def Heuristic_search(self, F, pi, DAG, IDAG)
def is_gate_possible(self, pi, q_target, q_control)
def compute_distances_bfs(self)
def select_swap_stochastic(self, swaps, scores)
def check_dependencies(self, gate_idx, IDAG, resolved_gates)
def generate_possible_connections(self)
def H_basic(self, pi, q1, q2)
def score_swap_improved(self, swap, F, E, pi, DAG, IDAG, resolved_gates)
def get_reverse_circuit(self, circuit)
def _compute_smart_initial_layout(self, circuit)
def generate_E(self, F, DAG, IDAG, resolved_gates)
def get_initial_layer(self, circuit)
def update_pi(self, pi, SWAP)
def generate_inverse_DAG(self, circuit)