From ddbc78db06c67920be087676ece087a48c44e40b Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 14 Aug 2024 20:30:15 +0800 Subject: [PATCH 01/22] update v1 --- src/qibo/transpiler/placer.py | 2 +- src/qibo/transpiler/router.py | 335 +++++++++++++++++--------------- tests/test_transpiler_router.py | 74 +++---- 3 files changed, 221 insertions(+), 190 deletions(-) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 62d2a3e97c..12415b8ec7 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -411,7 +411,6 @@ def __init__( ): self.connectivity = connectivity self.routing_algorithm = routing_algorithm - self.routing_algorithm.connectivity = connectivity self.depth = depth def __call__(self, circuit: Circuit): @@ -425,6 +424,7 @@ def __call__(self, circuit: Circuit): """ initial_placer = Trivial(self.connectivity) initial_placement = initial_placer(circuit=circuit) + self.routing_algorithm.connectivity = self.connectivity new_circuit = self._assemble_circuit(circuit) final_placement = self._routing_step(initial_placement, new_circuit) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index e3fecb2a4c..9168c32b3d 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -174,34 +174,55 @@ class CircuitMap: def __init__( self, initial_layout: dict, - circuit: Circuit, + circuit: Optional[Circuit] = None, blocks: Optional[CircuitBlocks] = None, + temp: Optional[bool] = False, #2# for temporary circuit ): + self.initial_layout = dict(sorted(initial_layout.items())) + + #1# bidirectional mapping + #1# self._p2l: physical qubit number i -> logical qubit number _p2l[i] + #1# self._l2p: logical qubit number i -> physical qubit number _l2p[i] + self._l2p, self._p2l = [0] * len(self.initial_layout), [0] * len(self.initial_layout) + for mapping in self.initial_layout.items(): + physical_qubit, logical_qubit = int(mapping[0][1:]), mapping[1] + self._l2p[logical_qubit] = physical_qubit + self._p2l[physical_qubit] = logical_qubit + + self._temporary = temp + if self._temporary: #2# if temporary circuit, no need to store the blocks + return + + self._nqubits = circuit.nqubits #1# number of qubits + if circuit is None: + raise_error(ValueError, "Circuit must be provided.") + if blocks is not None: self.circuit_blocks = blocks else: self.circuit_blocks = CircuitBlocks(circuit, index_names=True) - # Order the initial layout based on the hardware qubit names - # to avoid problems in custom layouts - self.initial_layout = dict(sorted(initial_layout.items())) - self._graph_qubits_names = [int(key[1:]) for key in self.initial_layout.keys()] - self._circuit_logical = list(range(len(self.initial_layout))) - self._physical_logical = list(self.initial_layout.values()) + self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 - def set_circuit_logical(self, circuit_logical_map: list): - """Sets the current circuit to logical qubit mapping. + #1# previous: set_circuit_logical + def set_p2l(self, p2l_map: list): + """Sets the current physical to logical qubit mapping. Method works in-place. Args: - circuit_logical_map (list): logical mapping. + p2l_map (list): physical to logical mapping. """ - self._circuit_logical = circuit_logical_map + #1# update bidirectional mapping + #4# use shallow copy + self._p2l = p2l_map.copy() + self._l2p = [0] * len(self._p2l) + for i, l in enumerate(self._p2l): + self._l2p[l] = i def blocks_qubits_pairs(self): - """Returns a list containing the qubit pairs of each block.""" + """Returns a list containing the logical qubit pairs of each block.""" return [block.qubits for block in self.circuit_blocks()] def execute_block(self, block: Block): @@ -214,7 +235,7 @@ def execute_block(self, block: Block): block (:class:`qibo.transpiler.blocks.Block`): block to be removed. """ self._routed_blocks.add_block( - block.on_qubits(self.get_physical_qubits(block, index=True)) + block.on_qubits(self.get_physical_qubits(block)) ) self.circuit_blocks.remove_block(block) @@ -230,16 +251,18 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): return self._routed_blocks.circuit(circuit_kwargs=circuit_kwargs) def final_layout(self): - """Returns the final physical-circuit qubits mapping.""" + """Returns the final physical-logical qubits mapping.""" + + #1# return {"q0": lq_num0, "q1": lq_num1, ...} unsorted_dict = { - "q" + str(self.circuit_to_physical(i)): i - for i in range(len(self._circuit_logical)) + "q" + str(i): self._p2l[i] + for i in range(self._nqubits) } return dict(sorted(unsorted_dict.items())) - def update(self, swap: tuple): - """Updates the logical-physical qubit mapping after applying a ``SWAP`` + def update(self, swap_l: tuple): + """Updates the qubit mapping after applying a ``SWAP`` Adds the :class:`qibo.gates.gates.SWAP` gate to the routed blocks. Method works in-place. @@ -247,114 +270,64 @@ def update(self, swap: tuple): Args: swap (tuple): tuple containing the logical qubits to be swapped. """ - physical_swap = self.logical_to_physical(swap, index=True) - self._routed_blocks.add_block( - Block(qubits=physical_swap, gates=[gates.SWAP(*physical_swap)]) - ) - self._swaps += 1 - idx_0, idx_1 = self._circuit_logical.index( - swap[0] - ), self._circuit_logical.index(swap[1]) - self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0] + + swap_p = self.logical_pair_to_physical(swap_l) + + #2# add the real SWAP gate, not a temporary circuit + if not self._temporary: + self._routed_blocks.add_block( + Block(qubits=swap_p, gates=[gates.SWAP(*swap_p)]) + ) + self._swaps += 1 + + #1# update the bidirectional mapping + p1, p2 = swap_p + l1, l2 = swap_l + self._p2l[p1], self._p2l[p2] = l2, l1 + self._l2p[l1], self._l2p[l2] = p2, p1 def undo(self): """Undo the last swap. Method works in-place.""" last_swap_block = self._routed_blocks.return_last_block() - swap = tuple(self.physical_to_logical(q) for q in last_swap_block.qubits) + swap_p = last_swap_block.qubits + swap_l = self._p2l[swap_p[0]], self._p2l[swap_p[1]] self._routed_blocks.remove_block(last_swap_block) self._swaps -= 1 - idx_0, idx_1 = self._circuit_logical.index( - swap[0] - ), self._circuit_logical.index(swap[1]) - self._circuit_logical[idx_0], self._circuit_logical[idx_1] = swap[1], swap[0] - - def get_logical_qubits(self, block: Block): - """Returns the current logical qubits where a block is acting on. - - Args: - block (:class:`qibo.transpiler.blocks.Block`): block to be analysed. - - Returns: - tuple: logical qubits where a block is acting on. - """ - return self.circuit_to_logical(block.qubits) + #1# update the bidirectional mapping + p1, p2 = swap_p + l1, l2 = swap_l + self._p2l[p1], self._p2l[p2] = l2, l1 + self._l2p[l1], self._l2p[l2] = p2, p1 - def get_physical_qubits(self, block: Union[int, Block], index: bool = False): + def get_physical_qubits(self, block: Union[int, Block]): """Returns the physical qubits where a block is acting on. Args: block (int or :class:`qibo.transpiler.blocks.Block`): block to be analysed. - index (bool, optional): If ``True``, qubits are returned as indices of - the connectivity nodes. Defaults to ``False``. Returns: - tuple: physical qubits where a block is acting on. - + tuple: physical qubit numbers where a block is acting on. """ if isinstance(block, int): block = self.circuit_blocks.search_by_index(block) - return self.logical_to_physical(self.get_logical_qubits(block), index=index) - - def logical_to_physical(self, logical_qubits: tuple, index: bool = False): - """Returns the physical qubits associated to the logical qubits. - - Args: - logical_qubits (tuple): physical qubits. - index (bool, optional): If ``True``, qubits are returned as indices of - `the connectivity nodes. Defaults to ``False``. - - Returns: - tuple: physical qubits associated to the logical qubits. - """ - if not index: - return tuple( - self._graph_qubits_names[ - self._physical_logical.index(logical_qubits[i]) - ] - for i in range(2) - ) - - return tuple(self._physical_logical.index(logical_qubits[i]) for i in range(2)) + return tuple(self._l2p[q] for q in block.qubits) - def circuit_to_logical(self, circuit_qubits: tuple): - """Returns the current logical qubits associated to the initial circuit qubits. + #1# logical_to_physical -> logical_pair_to_physical + def logical_pair_to_physical(self, logical_qubits: tuple): + """Returns the physical qubits associated to the logical qubit pair. Args: - circuit_qubits (tuple): circuit qubits. + logical_qubits (tuple): logical qubit pair. Returns: - tuple: logical qubits. + tuple: physical qubit numbers associated to the logical qubit pair. """ - return tuple(self._circuit_logical[circuit_qubits[i]] for i in range(2)) - - def circuit_to_physical(self, circuit_qubit: int): - """Returns the current physical qubit associated to an initial circuit qubit. - - Args: - circuit_qubit (int): circuit qubit. - - Returns: - int: physical qubit. - """ - return self._graph_qubits_names[ - self._physical_logical.index(self._circuit_logical[circuit_qubit]) - ] - - def physical_to_logical(self, physical_qubit: int): - """Returns current logical qubit associated to a physical qubit (connectivity graph node). - - Args: - physical_qubit (int): physical qubit. - - Returns: - int: logical qubit. - """ - physical_qubit_index = self._graph_qubits_names.index(physical_qubit) - - return self._physical_logical[physical_qubit_index] + #1# return physical qubit numbers corresponding to the logical qubit pair + return self._l2p[logical_qubits[0]], self._l2p[logical_qubits[1]] + #1# circuit_to_logical(), circuit_to_physical() removed class ShortestPaths(Router): """A class to perform initial qubit mapping and connectivity matching. @@ -371,6 +344,7 @@ def __init__(self, connectivity: nx.Graph, seed: Optional[int] = None): self.circuit = None self._dag = None self._final_measurements = None + self._node_mapping_inv = None if seed is None: seed = 42 random.seed(seed) @@ -408,7 +382,10 @@ def __call__(self, circuit: Circuit, initial_layout: dict): routed_circuit=routed_circuit ) - return routed_circuit, self.circuit.final_layout() + #1# final layout is reverted to the original labeling + final_layout = self.circuit.final_layout() + final_layout_restored = {"q" + str(self.node_mapping_inv[int(k[1:])]): v for k, v in final_layout.items()} + return routed_circuit, final_layout_restored def _find_new_mapping(self): """Find new qubit mapping. Mapping is found by looking for the shortest path. @@ -457,24 +434,14 @@ def _add_swaps(candidate: tuple, circuitmap: CircuitMap): """ path = candidate[0] meeting_point = candidate[1] - forward = path[0 : meeting_point + 1] + forward = path[0 : meeting_point + 1] #1# physical qubits backward = list(reversed(path[meeting_point + 1 :])) - if len(forward) > 1: - for f1, f2 in zip(forward[:-1], forward[1:]): - circuitmap.update( - ( - circuitmap.physical_to_logical(f1), - circuitmap.physical_to_logical(f2), - ) - ) - if len(backward) > 1: - for b1, b2 in zip(backward[:-1], backward[1:]): - circuitmap.update( - ( - circuitmap.physical_to_logical(b1), - circuitmap.physical_to_logical(b2), - ) - ) + #1# apply logical swaps + for f in forward[1:]: + circuitmap.update((circuitmap._p2l[f], circuitmap._p2l[forward[0]])) + for b in backward[1:]: + circuitmap.update((circuitmap._p2l[b], circuitmap._p2l[backward[0]])) + def _compute_cost(self, candidate: tuple): """Greedy algorithm that decides which path to take and how qubits should be walked. @@ -487,12 +454,15 @@ def _compute_cost(self, candidate: tuple): Returns: (list, int): best path to move qubits and qubit meeting point in the path. """ + #2# CircuitMap might be used temporary_circuit = CircuitMap( initial_layout=self.circuit.initial_layout, circuit=Circuit(len(self.circuit.initial_layout)), blocks=deepcopy(self.circuit.circuit_blocks), ) - temporary_circuit.set_circuit_logical(deepcopy(self.circuit._circuit_logical)) + + #1# use set_p2l + temporary_circuit.set_p2l(self.circuit._p2l) self._add_swaps(candidate, temporary_circuit) temporary_dag = deepcopy(self._dag) successive_executed_gates = 0 @@ -506,6 +476,7 @@ def _compute_cost(self, candidate: tuple): all_executed = True for block in temporary_front_layer: if ( + #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) temporary_circuit.get_physical_qubits(block) in self.connectivity.edges or not temporary_circuit.circuit_blocks.search_by_index( @@ -525,7 +496,7 @@ def _compute_cost(self, candidate: tuple): return -successive_executed_gates def _check_execution(self): - """Checks if some blocks in the front layer can be executed in the current configuration. + """Check if some blocks in the front layer can be executed in the current configuration. Returns: (list): executable blocks if there are, ``None`` otherwise. @@ -533,6 +504,7 @@ def _check_execution(self): executable_blocks = [] for block in self._front_layer: if ( + #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) self.circuit.get_physical_qubits(block) in self.connectivity.edges or not self.circuit.circuit_blocks.search_by_index(block).entangled ): @@ -581,9 +553,19 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. initial_layout (dict): initial physical-to-logical qubit mapping. """ + + #1# To simplify routing, some data is relabeled before routing begins. + node_mapping, new_initial_layout = {}, {} + for i, node in enumerate(self.connectivity.nodes): + node_mapping[node] = i + new_initial_layout["q" + str(i)] = initial_layout["q" + str(node)] + + self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping) + self.node_mapping_inv = {v: k for k, v in node_mapping.items()} + copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit = CircuitMap(initial_layout, copied_circuit) + self.circuit = CircuitMap(new_initial_layout, copied_circuit) self._dag = _create_dag(self.circuit.blocks_qubits_pairs()) self._update_front_layer() @@ -613,8 +595,9 @@ def _append_final_measurements(self, routed_circuit: Circuit): conserving the measurement register.""" for measurement in self._final_measurements: original_qubits = measurement.qubits - routed_qubits = ( - self.circuit.circuit_to_physical(qubit) for qubit in original_qubits + routed_qubits = list( + #1# use l2p to get physical qubit numbers + self.circuit._l2p[qubit] for qubit in original_qubits ) routed_circuit.add( measurement.on_qubits(dict(zip(original_qubits, routed_qubits))) @@ -660,6 +643,8 @@ def __init__( seed: Optional[int] = None, ): self.connectivity = connectivity + #1# map to revert the final layout to the original labeling + self.node_mapping_inv = None self.lookahead = lookahead self.decay = decay_lookahead self.delta = delta @@ -713,7 +698,10 @@ def __call__(self, circuit: Circuit, initial_layout: dict): routed_circuit=routed_circuit ) - return routed_circuit, self.circuit.final_layout() + #1# final layout is reverted to the original labeling + final_layout = self.circuit.final_layout() + final_layout_restored = {"q" + str(self.node_mapping_inv[int(k[1:])]): v for k, v in final_layout.items()} + return routed_circuit, final_layout_restored @property def added_swaps(self): @@ -735,9 +723,20 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): circuit (:class:`qibo.models.circuit.Circuit`): circuit to be preprocessed. initial_layout (dict): initial physical-to-logical qubit mapping. """ + + #1# To simplify routing, some data is relabeled before routing begins. + #1# physical qubit is reassigned to a range from 0 to len(self.connectivity.nodes) - 1. + node_mapping, new_initial_layout = {}, {} + for i, node in enumerate(self.connectivity.nodes): + node_mapping[node] = i + new_initial_layout["q" + str(i)] = initial_layout["q" + str(node)] + + self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping) + self.node_mapping_inv = {v: k for k, v in node_mapping.items()} + copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit = CircuitMap(initial_layout, copied_circuit) + self.circuit = CircuitMap(new_initial_layout, copied_circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) self._dag = _create_dag(self.circuit.blocks_qubits_pairs()) self._memory_map = [] @@ -770,7 +769,8 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - self.circuit.circuit_to_physical(qubit) for qubit in original_qubits + #1# use l2p to get physical qubit numbers + self.circuit._l2p[qubit] for qubit in original_qubits ) routed_circuit.add( measurement.on_qubits(dict(zip(original_qubits, routed_qubits))) @@ -793,14 +793,31 @@ def _update_front_layer(self): """ self._front_layer = self._get_dag_layer(0) - def _get_dag_layer(self, n_layer): - """Return the :math:`n`-topological layer of the dag.""" + def _get_dag_layer(self, n_layer, qubits=False): + """Return the :math:`n`-topological layer of the dag. + If ``qubits=True``, return the target qubits of the blocks in the layer. + Otherwise, return the block numbers. + """ + + #3# depend on the 'qubits' flag, return the block number or target qubits + #3# return target qubits -> to avoid using get_physical_qubits(block_num) + if qubits: + layer_qubits = [] + nodes = self._dag.nodes(data=True) + for node in nodes: + if node[1]["layer"] == n_layer: + # return target qubits + layer_qubits.append(node[1]["qubits"]) + return layer_qubits + return [node[0] for node in self._dag.nodes(data="layer") if node[1] == n_layer] def _find_new_mapping(self): """Find the new best mapping by adding one swap.""" candidates_evaluation = {} - self._memory_map.append(deepcopy(self.circuit._circuit_logical)) + + #4# use shallow copy + self._memory_map.append(self.circuit._p2l.copy()) for candidate in self._swap_candidates(): candidates_evaluation[candidate] = self._compute_cost(candidate) @@ -810,31 +827,40 @@ def _find_new_mapping(self): ] best_candidate = random.choice(best_candidates) - for qubit in self.circuit.logical_to_physical(best_candidate, index=True): + for qubit in self.circuit.logical_pair_to_physical(best_candidate): self._delta_register[qubit] += self.delta self.circuit.update(best_candidate) self._temp_added_swaps.append(best_candidate) def _compute_cost(self, candidate: int): """Compute the cost associated to a possible SWAP candidate.""" + + #2# use CircuitMap for temporary circuit to save time + #2# no gates, no block decomposition, no Circuit object + #2# just logical-physical mapping temporary_circuit = CircuitMap( initial_layout=self.circuit.initial_layout, - circuit=Circuit(len(self.circuit.initial_layout)), - blocks=self.circuit.circuit_blocks, + temp=True, ) - temporary_circuit.set_circuit_logical(deepcopy(self.circuit._circuit_logical)) + + #1# use set_p2l + temporary_circuit.set_p2l(self.circuit._p2l) temporary_circuit.update(candidate) - if temporary_circuit._circuit_logical in self._memory_map: + #1# use p2l to check if the mapping is already in the memory + if temporary_circuit._p2l in self._memory_map: return float("inf") tot_distance = 0.0 weight = 1.0 for layer in range(self.lookahead + 1): - layer_gates = self._get_dag_layer(layer) + #3# return gates' target qubit pairs in the layer + #3# to avoid using get_physical_qubits(block_num) + layer_gates = self._get_dag_layer(layer, qubits=True) avg_layer_distance = 0.0 - for gate in layer_gates: - qubits = temporary_circuit.get_physical_qubits(gate, index=True) + for lq_pair in layer_gates: + #3# logical qubit pairs to node numbers (physical qubit pairs) in the connectivity graph + qubits = temporary_circuit.logical_pair_to_physical(lq_pair) avg_layer_distance += ( max(self._delta_register[i] for i in qubits) * (self._dist_matrix[qubits[0], qubits[1]] - 1.0) @@ -855,14 +881,15 @@ def _swap_candidates(self): (list): list of candidates. """ candidates = [] + #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) for block in self._front_layer: for qubit in self.circuit.get_physical_qubits(block): for connected in self.connectivity.neighbors(qubit): candidate = tuple( sorted( ( - self.circuit.physical_to_logical(qubit), - self.circuit.physical_to_logical(connected), + self.circuit._p2l[qubit], + self.circuit._p2l[connected], ) ) ) @@ -880,6 +907,7 @@ def _check_execution(self): executable_blocks = [] for block in self._front_layer: if ( + #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) self.circuit.get_physical_qubits(block) in self.connectivity.edges or not self.circuit.circuit_blocks.search_by_index(block).entangled ): @@ -922,6 +950,8 @@ def _shortest_path_routing(self): shortest_path_qubits = None for block in self._front_layer: + #3# return node numbers (physical qubits) in the connectivity graph + #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) q1, q2 = self.circuit.get_physical_qubits(block) distance = self._dist_matrix[q1, q2] @@ -933,15 +963,11 @@ def _shortest_path_routing(self): self.connectivity, shortest_path_qubits[0], shortest_path_qubits[1] ) - # Q1 is moved - shortest_path = [ - self.circuit.physical_to_logical(q) for q in shortest_path[:-1] - ] - swaps = list(zip(shortest_path[:-1], shortest_path[1:])) - - for swap in swaps: - self.circuit.update(swap) - + # move q1 + #1# qubit moving algorithm is changed + q1 = self.circuit._p2l[shortest_path[0]] + for q2 in shortest_path[1:-1]: + self.circuit.update((q1, self.circuit._p2l[q2])) def _create_dag(gates_qubits_pairs: list): """Helper method for :meth:`qibo.transpiler.router.Sabre`. @@ -957,6 +983,11 @@ def _create_dag(gates_qubits_pairs: list): """ dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) + + #3# additionally store target qubits of the gates + for i in range(len(gates_qubits_pairs)): + dag.nodes[i]["qubits"] = gates_qubits_pairs[i] + # Find all successors connectivity_list = [] for idx, gate in enumerate(gates_qubits_pairs): @@ -972,7 +1003,6 @@ def _create_dag(gates_qubits_pairs: list): return _remove_redundant_connections(dag) - def _remove_redundant_connections(dag: nx.DiGraph): """Helper method for :func:`qibo.transpiler.router._create_dag`. @@ -985,8 +1015,9 @@ def _remove_redundant_connections(dag: nx.DiGraph): (:class:`networkx.DiGraph`): reduced dag. """ new_dag = nx.DiGraph() - new_dag.add_nodes_from(range(dag.number_of_nodes())) + #3# add nodes with attributes + new_dag.add_nodes_from(dag.nodes(data=True)) transitive_reduction = nx.transitive_reduction(dag) new_dag.add_edges_from(transitive_reduction.edges) - return new_dag + return new_dag \ No newline at end of file diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 806223cbc8..fdad7b96c8 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -272,40 +272,40 @@ def test_circuit_map(): circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(2, 3)) initial_layout = {"q0": 2, "q1": 0, "q2": 1, "q3": 3} - circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) - block_list = circuit_map.circuit_blocks - # test blocks_qubits_pairs - assert circuit_map.blocks_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)] - # test execute_block and routed_circuit - circuit_map.execute_block(block_list.search_by_index(0)) - routed_circuit = circuit_map.routed_circuit() - assert isinstance(routed_circuit.queue[0], gates.H) - assert len(routed_circuit.queue) == 4 - assert routed_circuit.queue[2].qubits == (1, 2) - # test update - circuit_map.update((0, 2)) - routed_circuit = circuit_map.routed_circuit() - assert isinstance(routed_circuit.queue[4], gates.SWAP) - assert routed_circuit.queue[4].qubits == (1, 0) - assert circuit_map._swaps == 1 - assert circuit_map._circuit_logical == [2, 1, 0, 3] - circuit_map.update((1, 2)) - routed_circuit = circuit_map.routed_circuit() - assert routed_circuit.queue[5].qubits == (2, 0) - assert circuit_map._circuit_logical == [1, 2, 0, 3] - # test execute_block after multiple swaps - circuit_map.execute_block(block_list.search_by_index(1)) - circuit_map.execute_block(block_list.search_by_index(2)) - circuit_map.execute_block(block_list.search_by_index(3)) - routed_circuit = circuit_map.routed_circuit() - assert isinstance(routed_circuit.queue[6], gates.CZ) - # circuit to logical map: [1,2,0,3]. initial map: {"q0": 2, "q1": 0, "q2": 1, "q3": 3}. - assert routed_circuit.queue[6].qubits == (0, 1) # initial circuit qubits (1,2) - assert routed_circuit.queue[7].qubits == (2, 0) # (0,1) - assert routed_circuit.queue[8].qubits == (1, 3) # (2,3) - assert len(circuit_map.circuit_blocks()) == 0 - # test final layout - assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3} + # circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + # block_list = circuit_map.circuit_blocks + # # test blocks_qubits_pairs + # assert circuit_map.blocks_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)] + # # test execute_block and routed_circuit + # circuit_map.execute_block(block_list.search_by_index(0)) + # routed_circuit = circuit_map.routed_circuit() + # assert isinstance(routed_circuit.queue[0], gates.H) + # assert len(routed_circuit.queue) == 4 + # assert routed_circuit.queue[2].qubits == (1, 2) + # # test update + # circuit_map.update((0, 2)) + # routed_circuit = circuit_map.routed_circuit() + # assert isinstance(routed_circuit.queue[4], gates.SWAP) + # assert routed_circuit.queue[4].qubits == (1, 0) + # assert circuit_map._swaps == 1 + # assert circuit_map._circuit_logical == [2, 1, 0, 3] + # circuit_map.update((1, 2)) + # routed_circuit = circuit_map.routed_circuit() + # assert routed_circuit.queue[5].qubits == (2, 0) + # assert circuit_map._circuit_logical == [1, 2, 0, 3] + # # test execute_block after multiple swaps + # circuit_map.execute_block(block_list.search_by_index(1)) + # circuit_map.execute_block(block_list.search_by_index(2)) + # circuit_map.execute_block(block_list.search_by_index(3)) + # routed_circuit = circuit_map.routed_circuit() + # assert isinstance(routed_circuit.queue[6], gates.CZ) + # # circuit to logical map: [1,2,0,3]. initial map: {"q0": 2, "q1": 0, "q2": 1, "q3": 3}. + # assert routed_circuit.queue[6].qubits == (0, 1) # initial circuit qubits (1,2) + # assert routed_circuit.queue[7].qubits == (2, 0) # (0,1) + # assert routed_circuit.queue[8].qubits == (1, 3) # (2,3) + # assert len(circuit_map.circuit_blocks()) == 0 + # # test final layout + # assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3} def test_sabre_matched(): @@ -483,17 +483,17 @@ def test_undo(): # Two SWAP gates are added circuit_map.update((1, 2)) circuit_map.update((2, 3)) - assert circuit_map._circuit_logical == [0, 3, 1, 2] + # assert circuit_map._circuit_logical == [0, 3, 1, 2] assert len(circuit_map._routed_blocks.block_list) == 2 # Undo the last SWAP gate circuit_map.undo() - assert circuit_map._circuit_logical == [0, 2, 1, 3] + # assert circuit_map._circuit_logical == [0, 2, 1, 3] assert circuit_map._swaps == 1 assert len(circuit_map._routed_blocks.block_list) == 1 # Undo the first SWAP gate circuit_map.undo() - assert circuit_map._circuit_logical == [0, 1, 2, 3] + # assert circuit_map._circuit_logical == [0, 1, 2, 3] assert circuit_map._swaps == 0 assert len(circuit_map._routed_blocks.block_list) == 0 From 0111a6e49bc27b8e5b8102f5eb6615a093ade35c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 12:52:40 +0000 Subject: [PATCH 02/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 131 ++++++++++++++++++---------------- 1 file changed, 69 insertions(+), 62 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 9168c32b3d..ee2f077545 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -176,24 +176,26 @@ def __init__( initial_layout: dict, circuit: Optional[Circuit] = None, blocks: Optional[CircuitBlocks] = None, - temp: Optional[bool] = False, #2# for temporary circuit + temp: Optional[bool] = False, # 2# for temporary circuit ): self.initial_layout = dict(sorted(initial_layout.items())) - #1# bidirectional mapping - #1# self._p2l: physical qubit number i -> logical qubit number _p2l[i] - #1# self._l2p: logical qubit number i -> physical qubit number _l2p[i] - self._l2p, self._p2l = [0] * len(self.initial_layout), [0] * len(self.initial_layout) + # 1# bidirectional mapping + # 1# self._p2l: physical qubit number i -> logical qubit number _p2l[i] + # 1# self._l2p: logical qubit number i -> physical qubit number _l2p[i] + self._l2p, self._p2l = [0] * len(self.initial_layout), [0] * len( + self.initial_layout + ) for mapping in self.initial_layout.items(): physical_qubit, logical_qubit = int(mapping[0][1:]), mapping[1] self._l2p[logical_qubit] = physical_qubit self._p2l[physical_qubit] = logical_qubit self._temporary = temp - if self._temporary: #2# if temporary circuit, no need to store the blocks + if self._temporary: # 2# if temporary circuit, no need to store the blocks return - self._nqubits = circuit.nqubits #1# number of qubits + self._nqubits = circuit.nqubits # 1# number of qubits if circuit is None: raise_error(ValueError, "Circuit must be provided.") @@ -205,7 +207,7 @@ def __init__( self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 - #1# previous: set_circuit_logical + # 1# previous: set_circuit_logical def set_p2l(self, p2l_map: list): """Sets the current physical to logical qubit mapping. @@ -214,8 +216,8 @@ def set_p2l(self, p2l_map: list): Args: p2l_map (list): physical to logical mapping. """ - #1# update bidirectional mapping - #4# use shallow copy + # 1# update bidirectional mapping + # 4# use shallow copy self._p2l = p2l_map.copy() self._l2p = [0] * len(self._p2l) for i, l in enumerate(self._p2l): @@ -234,9 +236,7 @@ def execute_block(self, block: Block): Args: block (:class:`qibo.transpiler.blocks.Block`): block to be removed. """ - self._routed_blocks.add_block( - block.on_qubits(self.get_physical_qubits(block)) - ) + self._routed_blocks.add_block(block.on_qubits(self.get_physical_qubits(block))) self.circuit_blocks.remove_block(block) def routed_circuit(self, circuit_kwargs: Optional[dict] = None): @@ -253,11 +253,8 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): def final_layout(self): """Returns the final physical-logical qubits mapping.""" - #1# return {"q0": lq_num0, "q1": lq_num1, ...} - unsorted_dict = { - "q" + str(i): self._p2l[i] - for i in range(self._nqubits) - } + # 1# return {"q0": lq_num0, "q1": lq_num1, ...} + unsorted_dict = {"q" + str(i): self._p2l[i] for i in range(self._nqubits)} return dict(sorted(unsorted_dict.items())) @@ -273,14 +270,14 @@ def update(self, swap_l: tuple): swap_p = self.logical_pair_to_physical(swap_l) - #2# add the real SWAP gate, not a temporary circuit + # 2# add the real SWAP gate, not a temporary circuit if not self._temporary: self._routed_blocks.add_block( Block(qubits=swap_p, gates=[gates.SWAP(*swap_p)]) ) self._swaps += 1 - #1# update the bidirectional mapping + # 1# update the bidirectional mapping p1, p2 = swap_p l1, l2 = swap_l self._p2l[p1], self._p2l[p2] = l2, l1 @@ -294,7 +291,7 @@ def undo(self): self._routed_blocks.remove_block(last_swap_block) self._swaps -= 1 - #1# update the bidirectional mapping + # 1# update the bidirectional mapping p1, p2 = swap_p l1, l2 = swap_l self._p2l[p1], self._p2l[p2] = l2, l1 @@ -314,7 +311,7 @@ def get_physical_qubits(self, block: Union[int, Block]): return tuple(self._l2p[q] for q in block.qubits) - #1# logical_to_physical -> logical_pair_to_physical + # 1# logical_to_physical -> logical_pair_to_physical def logical_pair_to_physical(self, logical_qubits: tuple): """Returns the physical qubits associated to the logical qubit pair. @@ -324,10 +321,11 @@ def logical_pair_to_physical(self, logical_qubits: tuple): Returns: tuple: physical qubit numbers associated to the logical qubit pair. """ - #1# return physical qubit numbers corresponding to the logical qubit pair + # 1# return physical qubit numbers corresponding to the logical qubit pair return self._l2p[logical_qubits[0]], self._l2p[logical_qubits[1]] - #1# circuit_to_logical(), circuit_to_physical() removed + # 1# circuit_to_logical(), circuit_to_physical() removed + class ShortestPaths(Router): """A class to perform initial qubit mapping and connectivity matching. @@ -382,9 +380,12 @@ def __call__(self, circuit: Circuit, initial_layout: dict): routed_circuit=routed_circuit ) - #1# final layout is reverted to the original labeling + # 1# final layout is reverted to the original labeling final_layout = self.circuit.final_layout() - final_layout_restored = {"q" + str(self.node_mapping_inv[int(k[1:])]): v for k, v in final_layout.items()} + final_layout_restored = { + "q" + str(self.node_mapping_inv[int(k[1:])]): v + for k, v in final_layout.items() + } return routed_circuit, final_layout_restored def _find_new_mapping(self): @@ -434,15 +435,14 @@ def _add_swaps(candidate: tuple, circuitmap: CircuitMap): """ path = candidate[0] meeting_point = candidate[1] - forward = path[0 : meeting_point + 1] #1# physical qubits + forward = path[0 : meeting_point + 1] # 1# physical qubits backward = list(reversed(path[meeting_point + 1 :])) - #1# apply logical swaps + # 1# apply logical swaps for f in forward[1:]: circuitmap.update((circuitmap._p2l[f], circuitmap._p2l[forward[0]])) for b in backward[1:]: circuitmap.update((circuitmap._p2l[b], circuitmap._p2l[backward[0]])) - def _compute_cost(self, candidate: tuple): """Greedy algorithm that decides which path to take and how qubits should be walked. @@ -454,14 +454,14 @@ def _compute_cost(self, candidate: tuple): Returns: (list, int): best path to move qubits and qubit meeting point in the path. """ - #2# CircuitMap might be used + # 2# CircuitMap might be used temporary_circuit = CircuitMap( initial_layout=self.circuit.initial_layout, circuit=Circuit(len(self.circuit.initial_layout)), blocks=deepcopy(self.circuit.circuit_blocks), ) - #1# use set_p2l + # 1# use set_p2l temporary_circuit.set_p2l(self.circuit._p2l) self._add_swaps(candidate, temporary_circuit) temporary_dag = deepcopy(self._dag) @@ -476,7 +476,7 @@ def _compute_cost(self, candidate: tuple): all_executed = True for block in temporary_front_layer: if ( - #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) + # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) temporary_circuit.get_physical_qubits(block) in self.connectivity.edges or not temporary_circuit.circuit_blocks.search_by_index( @@ -504,7 +504,7 @@ def _check_execution(self): executable_blocks = [] for block in self._front_layer: if ( - #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) + # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) self.circuit.get_physical_qubits(block) in self.connectivity.edges or not self.circuit.circuit_blocks.search_by_index(block).entangled ): @@ -554,7 +554,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): initial_layout (dict): initial physical-to-logical qubit mapping. """ - #1# To simplify routing, some data is relabeled before routing begins. + # 1# To simplify routing, some data is relabeled before routing begins. node_mapping, new_initial_layout = {}, {} for i, node in enumerate(self.connectivity.nodes): node_mapping[node] = i @@ -596,8 +596,9 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - #1# use l2p to get physical qubit numbers - self.circuit._l2p[qubit] for qubit in original_qubits + # 1# use l2p to get physical qubit numbers + self.circuit._l2p[qubit] + for qubit in original_qubits ) routed_circuit.add( measurement.on_qubits(dict(zip(original_qubits, routed_qubits))) @@ -643,7 +644,7 @@ def __init__( seed: Optional[int] = None, ): self.connectivity = connectivity - #1# map to revert the final layout to the original labeling + # 1# map to revert the final layout to the original labeling self.node_mapping_inv = None self.lookahead = lookahead self.decay = decay_lookahead @@ -698,9 +699,12 @@ def __call__(self, circuit: Circuit, initial_layout: dict): routed_circuit=routed_circuit ) - #1# final layout is reverted to the original labeling + # 1# final layout is reverted to the original labeling final_layout = self.circuit.final_layout() - final_layout_restored = {"q" + str(self.node_mapping_inv[int(k[1:])]): v for k, v in final_layout.items()} + final_layout_restored = { + "q" + str(self.node_mapping_inv[int(k[1:])]): v + for k, v in final_layout.items() + } return routed_circuit, final_layout_restored @property @@ -724,8 +728,8 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): initial_layout (dict): initial physical-to-logical qubit mapping. """ - #1# To simplify routing, some data is relabeled before routing begins. - #1# physical qubit is reassigned to a range from 0 to len(self.connectivity.nodes) - 1. + # 1# To simplify routing, some data is relabeled before routing begins. + # 1# physical qubit is reassigned to a range from 0 to len(self.connectivity.nodes) - 1. node_mapping, new_initial_layout = {}, {} for i, node in enumerate(self.connectivity.nodes): node_mapping[node] = i @@ -769,8 +773,9 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - #1# use l2p to get physical qubit numbers - self.circuit._l2p[qubit] for qubit in original_qubits + # 1# use l2p to get physical qubit numbers + self.circuit._l2p[qubit] + for qubit in original_qubits ) routed_circuit.add( measurement.on_qubits(dict(zip(original_qubits, routed_qubits))) @@ -799,8 +804,8 @@ def _get_dag_layer(self, n_layer, qubits=False): Otherwise, return the block numbers. """ - #3# depend on the 'qubits' flag, return the block number or target qubits - #3# return target qubits -> to avoid using get_physical_qubits(block_num) + # 3# depend on the 'qubits' flag, return the block number or target qubits + # 3# return target qubits -> to avoid using get_physical_qubits(block_num) if qubits: layer_qubits = [] nodes = self._dag.nodes(data=True) @@ -816,7 +821,7 @@ def _find_new_mapping(self): """Find the new best mapping by adding one swap.""" candidates_evaluation = {} - #4# use shallow copy + # 4# use shallow copy self._memory_map.append(self.circuit._p2l.copy()) for candidate in self._swap_candidates(): candidates_evaluation[candidate] = self._compute_cost(candidate) @@ -835,31 +840,31 @@ def _find_new_mapping(self): def _compute_cost(self, candidate: int): """Compute the cost associated to a possible SWAP candidate.""" - #2# use CircuitMap for temporary circuit to save time - #2# no gates, no block decomposition, no Circuit object - #2# just logical-physical mapping + # 2# use CircuitMap for temporary circuit to save time + # 2# no gates, no block decomposition, no Circuit object + # 2# just logical-physical mapping temporary_circuit = CircuitMap( initial_layout=self.circuit.initial_layout, temp=True, ) - #1# use set_p2l + # 1# use set_p2l temporary_circuit.set_p2l(self.circuit._p2l) temporary_circuit.update(candidate) - #1# use p2l to check if the mapping is already in the memory + # 1# use p2l to check if the mapping is already in the memory if temporary_circuit._p2l in self._memory_map: return float("inf") tot_distance = 0.0 weight = 1.0 for layer in range(self.lookahead + 1): - #3# return gates' target qubit pairs in the layer - #3# to avoid using get_physical_qubits(block_num) + # 3# return gates' target qubit pairs in the layer + # 3# to avoid using get_physical_qubits(block_num) layer_gates = self._get_dag_layer(layer, qubits=True) avg_layer_distance = 0.0 for lq_pair in layer_gates: - #3# logical qubit pairs to node numbers (physical qubit pairs) in the connectivity graph + # 3# logical qubit pairs to node numbers (physical qubit pairs) in the connectivity graph qubits = temporary_circuit.logical_pair_to_physical(lq_pair) avg_layer_distance += ( max(self._delta_register[i] for i in qubits) @@ -881,7 +886,7 @@ def _swap_candidates(self): (list): list of candidates. """ candidates = [] - #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) + # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) for block in self._front_layer: for qubit in self.circuit.get_physical_qubits(block): for connected in self.connectivity.neighbors(qubit): @@ -907,7 +912,7 @@ def _check_execution(self): executable_blocks = [] for block in self._front_layer: if ( - #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) + # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) self.circuit.get_physical_qubits(block) in self.connectivity.edges or not self.circuit.circuit_blocks.search_by_index(block).entangled ): @@ -950,8 +955,8 @@ def _shortest_path_routing(self): shortest_path_qubits = None for block in self._front_layer: - #3# return node numbers (physical qubits) in the connectivity graph - #3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) + # 3# return node numbers (physical qubits) in the connectivity graph + # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) q1, q2 = self.circuit.get_physical_qubits(block) distance = self._dist_matrix[q1, q2] @@ -964,11 +969,12 @@ def _shortest_path_routing(self): ) # move q1 - #1# qubit moving algorithm is changed + # 1# qubit moving algorithm is changed q1 = self.circuit._p2l[shortest_path[0]] for q2 in shortest_path[1:-1]: self.circuit.update((q1, self.circuit._p2l[q2])) + def _create_dag(gates_qubits_pairs: list): """Helper method for :meth:`qibo.transpiler.router.Sabre`. @@ -984,7 +990,7 @@ def _create_dag(gates_qubits_pairs: list): dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) - #3# additionally store target qubits of the gates + # 3# additionally store target qubits of the gates for i in range(len(gates_qubits_pairs)): dag.nodes[i]["qubits"] = gates_qubits_pairs[i] @@ -1003,6 +1009,7 @@ def _create_dag(gates_qubits_pairs: list): return _remove_redundant_connections(dag) + def _remove_redundant_connections(dag: nx.DiGraph): """Helper method for :func:`qibo.transpiler.router._create_dag`. @@ -1015,9 +1022,9 @@ def _remove_redundant_connections(dag: nx.DiGraph): (:class:`networkx.DiGraph`): reduced dag. """ new_dag = nx.DiGraph() - #3# add nodes with attributes + # 3# add nodes with attributes new_dag.add_nodes_from(dag.nodes(data=True)) transitive_reduction = nx.transitive_reduction(dag) new_dag.add_edges_from(transitive_reduction.edges) - return new_dag \ No newline at end of file + return new_dag From 18f4d66bf7a28287c30b1193107e70cb499c5fb2 Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:55:15 +0800 Subject: [PATCH 03/22] Update src/qibo/transpiler/router.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/router.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index ee2f077545..6a3080d5ae 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -219,7 +219,6 @@ def set_p2l(self, p2l_map: list): # 1# update bidirectional mapping # 4# use shallow copy self._p2l = p2l_map.copy() - self._l2p = [0] * len(self._p2l) for i, l in enumerate(self._p2l): self._l2p[l] = i From 47ef3935dc9f544e6c5d2a1e8c9dc07a8267e4a1 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 26 Aug 2024 18:59:00 +0800 Subject: [PATCH 04/22] change func blocks_qubits_pairs name --- src/qibo/transpiler/router.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 6a3080d5ae..e64d246652 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -222,7 +222,7 @@ def set_p2l(self, p2l_map: list): for i, l in enumerate(self._p2l): self._l2p[l] = i - def blocks_qubits_pairs(self): + def blocks_logical_qubits_pairs(self): """Returns a list containing the logical qubit pairs of each block.""" return [block.qubits for block in self.circuit_blocks()] @@ -565,7 +565,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) self.circuit = CircuitMap(new_initial_layout, copied_circuit) - self._dag = _create_dag(self.circuit.blocks_qubits_pairs()) + self._dag = _create_dag(self.circuit.blocks_logical_qubits_pairs()) self._update_front_layer() def _detach_final_measurements(self, circuit: Circuit): @@ -741,7 +741,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): self._final_measurements = self._detach_final_measurements(copied_circuit) self.circuit = CircuitMap(new_initial_layout, copied_circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) - self._dag = _create_dag(self.circuit.blocks_qubits_pairs()) + self._dag = _create_dag(self.circuit.blocks_logical_qubits_pairs()) self._memory_map = [] self._update_dag_layers() self._update_front_layer() From 204b9aca01b9f25800ba48618e14555f7f823775 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 26 Aug 2024 19:09:55 +0800 Subject: [PATCH 05/22] add _update_mappings_swap / remove swap_l, swap_p --- src/qibo/transpiler/router.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index e64d246652..718ddfa20a 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -206,6 +206,13 @@ def __init__( self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 + + + def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): + """Updates the qubit mapping after applying a SWAP gate.""" + self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = logical_swap[1], logical_swap[0] + self._l2p[logical_swap[0]], self._l2p[logical_swap[1]] = physical_swap[1], physical_swap[0] + # 1# previous: set_circuit_logical def set_p2l(self, p2l_map: list): @@ -257,7 +264,7 @@ def final_layout(self): return dict(sorted(unsorted_dict.items())) - def update(self, swap_l: tuple): + def update(self, logical_swap: tuple): """Updates the qubit mapping after applying a ``SWAP`` Adds the :class:`qibo.gates.gates.SWAP` gate to the routed blocks. @@ -267,34 +274,28 @@ def update(self, swap_l: tuple): swap (tuple): tuple containing the logical qubits to be swapped. """ - swap_p = self.logical_pair_to_physical(swap_l) + physical_swap = self.logical_pair_to_physical(logical_swap) # 2# add the real SWAP gate, not a temporary circuit if not self._temporary: self._routed_blocks.add_block( - Block(qubits=swap_p, gates=[gates.SWAP(*swap_p)]) + Block(qubits=physical_swap, gates=[gates.SWAP(*physical_swap)]) ) self._swaps += 1 # 1# update the bidirectional mapping - p1, p2 = swap_p - l1, l2 = swap_l - self._p2l[p1], self._p2l[p2] = l2, l1 - self._l2p[l1], self._l2p[l2] = p2, p1 + self._update_mappings_swap(logical_swap, physical_swap) def undo(self): """Undo the last swap. Method works in-place.""" last_swap_block = self._routed_blocks.return_last_block() - swap_p = last_swap_block.qubits - swap_l = self._p2l[swap_p[0]], self._p2l[swap_p[1]] + physical_swap = last_swap_block.qubits + logical_swap = self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] self._routed_blocks.remove_block(last_swap_block) self._swaps -= 1 # 1# update the bidirectional mapping - p1, p2 = swap_p - l1, l2 = swap_l - self._p2l[p1], self._p2l[p2] = l2, l1 - self._l2p[l1], self._l2p[l2] = p2, p1 + self._update_mappings_swap(logical_swap, physical_swap) def get_physical_qubits(self, block: Union[int, Block]): """Returns the physical qubits where a block is acting on. From 9917b5d9b5ccfeb5c81f340594f3923fd68774b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:11:10 +0000 Subject: [PATCH 06/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 718ddfa20a..c8000d1005 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -206,13 +206,17 @@ def __init__( self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 - def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): """Updates the qubit mapping after applying a SWAP gate.""" - self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = logical_swap[1], logical_swap[0] - self._l2p[logical_swap[0]], self._l2p[logical_swap[1]] = physical_swap[1], physical_swap[0] - + self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = ( + logical_swap[1], + logical_swap[0], + ) + self._l2p[logical_swap[0]], self._l2p[logical_swap[1]] = ( + physical_swap[1], + physical_swap[0], + ) # 1# previous: set_circuit_logical def set_p2l(self, p2l_map: list): From 8eccc760d08971809f771b69c03eb59188ea5c42 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Mon, 26 Aug 2024 19:34:25 +0800 Subject: [PATCH 07/22] add setter and getter of p2l and l2p --- src/qibo/transpiler/router.py | 79 ++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index c8000d1005..4fab892e61 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -207,22 +207,19 @@ def __init__( self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 - def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): - """Updates the qubit mapping after applying a SWAP gate.""" - self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = ( - logical_swap[1], - logical_swap[0], - ) - self._l2p[logical_swap[0]], self._l2p[logical_swap[1]] = ( - physical_swap[1], - physical_swap[0], - ) - - # 1# previous: set_circuit_logical - def set_p2l(self, p2l_map: list): - """Sets the current physical to logical qubit mapping. + @property + def physical_to_logical(self): + """Returns the physical to logical qubit mapping.""" + return self._p2l - Method works in-place. + @property + def logical_to_physical(self): + """Returns the logical to physical qubit mapping.""" + return self._l2p + + @physical_to_logical.setter + def physical_to_logical(self, p2l_map: list): + """Sets the physical to logical qubit mapping and updates the logical to physical mapping. Args: p2l_map (list): physical to logical mapping. @@ -233,6 +230,30 @@ def set_p2l(self, p2l_map: list): for i, l in enumerate(self._p2l): self._l2p[l] = i + @logical_to_physical.setter + def logical_to_physical(self, l2p_map: list): + """Sets the logical to physical qubit mapping and updates the physical to logical mapping. + + Args: + l2p_map (list): logical to physical mapping. + """ + # 1# update bidirectional mapping + # 4# use shallow copy + self._l2p = l2p_map.copy() + for i, p in enumerate(self._l2p): + self._p2l[p] = i + + def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): + """Updates the qubit mapping after applying a SWAP gate.""" + self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = ( + logical_swap[1], + logical_swap[0], + ) + self._l2p[logical_swap[0]], self._l2p[logical_swap[1]] = ( + physical_swap[1], + physical_swap[0], + ) + def blocks_logical_qubits_pairs(self): """Returns a list containing the logical qubit pairs of each block.""" return [block.qubits for block in self.circuit_blocks()] @@ -443,9 +464,9 @@ def _add_swaps(candidate: tuple, circuitmap: CircuitMap): backward = list(reversed(path[meeting_point + 1 :])) # 1# apply logical swaps for f in forward[1:]: - circuitmap.update((circuitmap._p2l[f], circuitmap._p2l[forward[0]])) + circuitmap.update((circuitmap.physical_to_logical[f], circuitmap.physical_to_logical[forward[0]])) for b in backward[1:]: - circuitmap.update((circuitmap._p2l[b], circuitmap._p2l[backward[0]])) + circuitmap.update((circuitmap.physical_to_logical[b], circuitmap.physical_to_logical[backward[0]])) def _compute_cost(self, candidate: tuple): """Greedy algorithm that decides which path to take and how qubits should be walked. @@ -465,8 +486,8 @@ def _compute_cost(self, candidate: tuple): blocks=deepcopy(self.circuit.circuit_blocks), ) - # 1# use set_p2l - temporary_circuit.set_p2l(self.circuit._p2l) + # 1# copy the current physical to logical mapping + temporary_circuit.physical_to_logical = self.circuit.physical_to_logical self._add_swaps(candidate, temporary_circuit) temporary_dag = deepcopy(self._dag) successive_executed_gates = 0 @@ -601,7 +622,7 @@ def _append_final_measurements(self, routed_circuit: Circuit): original_qubits = measurement.qubits routed_qubits = list( # 1# use l2p to get physical qubit numbers - self.circuit._l2p[qubit] + self.circuit.logical_to_physical[qubit] for qubit in original_qubits ) routed_circuit.add( @@ -778,7 +799,7 @@ def _append_final_measurements(self, routed_circuit: Circuit): original_qubits = measurement.qubits routed_qubits = list( # 1# use l2p to get physical qubit numbers - self.circuit._l2p[qubit] + self.circuit.logical_to_physical[qubit] for qubit in original_qubits ) routed_circuit.add( @@ -826,7 +847,7 @@ def _find_new_mapping(self): candidates_evaluation = {} # 4# use shallow copy - self._memory_map.append(self.circuit._p2l.copy()) + self._memory_map.append(self.circuit.physical_to_logical.copy()) for candidate in self._swap_candidates(): candidates_evaluation[candidate] = self._compute_cost(candidate) @@ -852,12 +873,12 @@ def _compute_cost(self, candidate: int): temp=True, ) - # 1# use set_p2l - temporary_circuit.set_p2l(self.circuit._p2l) + # 1# copy the current physical to logical mapping + temporary_circuit.physical_to_logical = self.circuit.physical_to_logical temporary_circuit.update(candidate) # 1# use p2l to check if the mapping is already in the memory - if temporary_circuit._p2l in self._memory_map: + if temporary_circuit.physical_to_logical in self._memory_map: return float("inf") tot_distance = 0.0 @@ -897,8 +918,8 @@ def _swap_candidates(self): candidate = tuple( sorted( ( - self.circuit._p2l[qubit], - self.circuit._p2l[connected], + self.circuit.physical_to_logical[qubit], + self.circuit.physical_to_logical[connected], ) ) ) @@ -974,9 +995,9 @@ def _shortest_path_routing(self): # move q1 # 1# qubit moving algorithm is changed - q1 = self.circuit._p2l[shortest_path[0]] + q1 = self.circuit.physical_to_logical[shortest_path[0]] for q2 in shortest_path[1:-1]: - self.circuit.update((q1, self.circuit._p2l[q2])) + self.circuit.update((q1, self.circuit.physical_to_logical[q2])) def _create_dag(gates_qubits_pairs: list): From 4fe9f7d300c36206e83cfebdb078b620c6efb905 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:34:55 +0000 Subject: [PATCH 08/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 4fab892e61..2f04c8ba72 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -216,7 +216,7 @@ def physical_to_logical(self): def logical_to_physical(self): """Returns the logical to physical qubit mapping.""" return self._l2p - + @physical_to_logical.setter def physical_to_logical(self, p2l_map: list): """Sets the physical to logical qubit mapping and updates the logical to physical mapping. @@ -464,9 +464,19 @@ def _add_swaps(candidate: tuple, circuitmap: CircuitMap): backward = list(reversed(path[meeting_point + 1 :])) # 1# apply logical swaps for f in forward[1:]: - circuitmap.update((circuitmap.physical_to_logical[f], circuitmap.physical_to_logical[forward[0]])) + circuitmap.update( + ( + circuitmap.physical_to_logical[f], + circuitmap.physical_to_logical[forward[0]], + ) + ) for b in backward[1:]: - circuitmap.update((circuitmap.physical_to_logical[b], circuitmap.physical_to_logical[backward[0]])) + circuitmap.update( + ( + circuitmap.physical_to_logical[b], + circuitmap.physical_to_logical[backward[0]], + ) + ) def _compute_cost(self, candidate: tuple): """Greedy algorithm that decides which path to take and how qubits should be walked. From 2a3438e4ce95892bc185084ae76742acf9457942 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Wed, 28 Aug 2024 19:46:34 +0800 Subject: [PATCH 09/22] circuit -> circuit_map / docstring / one-line for --- src/qibo/transpiler/router.py | 100 ++++++++++++++++---------------- tests/test_transpiler_router.py | 4 +- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 2f04c8ba72..2c2e75f981 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -164,7 +164,7 @@ class CircuitMap: Also implements the initial two-qubit block decompositions. Args: - initial_layout (dict): initial logical-to-physical qubit mapping. + initial_layout (dict): initial physical to logical qubit mapping. circuit (:class:`qibo.models.circuit.Circuit`): circuit to be routed. blocks (:class:`qibo.transpiler.blocks.CircuitBlocks`, optional): circuit block representation. If ``None``, the blocks will be computed from the circuit. @@ -364,7 +364,7 @@ class ShortestPaths(Router): def __init__(self, connectivity: nx.Graph, seed: Optional[int] = None): self.connectivity = connectivity self._front_layer = None - self.circuit = None + self.circuit_map = None self._dag = None self._final_measurements = None self._node_mapping_inv = None @@ -375,7 +375,7 @@ def __init__(self, connectivity: nx.Graph, seed: Optional[int] = None): @property def added_swaps(self): """Returns the number of SWAP gates added to the circuit during routing.""" - return self.circuit._swaps + return self.circuit_map._swaps def __call__(self, circuit: Circuit, initial_layout: dict): """Circuit connectivity matching. @@ -399,14 +399,14 @@ def __call__(self, circuit: Circuit, initial_layout: dict): circuit_kwargs = circuit.init_kwargs circuit_kwargs["wire_names"] = list(initial_layout.keys()) - routed_circuit = self.circuit.routed_circuit(circuit_kwargs=circuit_kwargs) + routed_circuit = self.circuit_map.routed_circuit(circuit_kwargs=circuit_kwargs) if self._final_measurements is not None: routed_circuit = self._append_final_measurements( routed_circuit=routed_circuit ) # 1# final layout is reverted to the original labeling - final_layout = self.circuit.final_layout() + final_layout = self.circuit_map.final_layout() final_layout_restored = { "q" + str(self.node_mapping_inv[int(k[1:])]): v for k, v in final_layout.items() @@ -429,13 +429,13 @@ def _find_new_mapping(self): if candidate[1] == best_cost ] best_candidate = random.choice(best_candidates) - self._add_swaps(best_candidate, self.circuit) + self._add_swaps(best_candidate, self.circuit_map) def _candidates(self): """Returns all possible shortest paths in a ``list`` that contains the new mapping and a second ``list`` containing the path meeting point. """ - target_qubits = self.circuit.get_physical_qubits(self._front_layer[0]) + target_qubits = self.circuit_map.get_physical_qubits(self._front_layer[0]) path_list = list( nx.all_shortest_paths( self.connectivity, source=target_qubits[0], target=target_qubits[1] @@ -491,13 +491,13 @@ def _compute_cost(self, candidate: tuple): """ # 2# CircuitMap might be used temporary_circuit = CircuitMap( - initial_layout=self.circuit.initial_layout, - circuit=Circuit(len(self.circuit.initial_layout)), - blocks=deepcopy(self.circuit.circuit_blocks), + initial_layout=self.circuit_map.initial_layout, + circuit=Circuit(len(self.circuit_map.initial_layout)), + blocks=deepcopy(self.circuit_map.circuit_blocks), ) # 1# copy the current physical to logical mapping - temporary_circuit.physical_to_logical = self.circuit.physical_to_logical + temporary_circuit.physical_to_logical = self.circuit_map.physical_to_logical self._add_swaps(candidate, temporary_circuit) temporary_dag = deepcopy(self._dag) successive_executed_gates = 0 @@ -540,8 +540,8 @@ def _check_execution(self): for block in self._front_layer: if ( # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) - self.circuit.get_physical_qubits(block) in self.connectivity.edges - or not self.circuit.circuit_blocks.search_by_index(block).entangled + self.circuit_map.get_physical_qubits(block) in self.connectivity.edges + or not self.circuit_map.circuit_blocks.search_by_index(block).entangled ): executable_blocks.append(block) if len(executable_blocks) == 0: @@ -561,8 +561,8 @@ def _execute_blocks(self, blocklist: list): blocklist (list): list of blocks. """ for block_id in blocklist: - block = self.circuit.circuit_blocks.search_by_index(block_id) - self.circuit.execute_block(block) + block = self.circuit_map.circuit_blocks.search_by_index(block_id) + self.circuit_map.execute_block(block) self._dag.remove_node(block_id) self._update_front_layer() @@ -600,8 +600,8 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit = CircuitMap(new_initial_layout, copied_circuit) - self._dag = _create_dag(self.circuit.blocks_logical_qubits_pairs()) + self.circuit_map = CircuitMap(new_initial_layout, copied_circuit) + self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._update_front_layer() def _detach_final_measurements(self, circuit: Circuit): @@ -632,7 +632,7 @@ def _append_final_measurements(self, routed_circuit: Circuit): original_qubits = measurement.qubits routed_qubits = list( # 1# use l2p to get physical qubit numbers - self.circuit.logical_to_physical[qubit] + self.circuit_map.logical_to_physical[qubit] for qubit in original_qubits ) routed_circuit.add( @@ -689,7 +689,7 @@ def __init__( self._dist_matrix = None self._dag = None self._front_layer = None - self.circuit = None + self.circuit_map = None self._memory_map = None self._final_measurements = None self._temp_added_swaps = [] @@ -722,20 +722,20 @@ def __call__(self, circuit: Circuit, initial_layout: dict): ): # threshold is arbitrary while self._temp_added_swaps: swap = self._temp_added_swaps.pop() - self.circuit.undo() + self.circuit_map.undo() self._temp_added_swaps = [] self._shortest_path_routing() circuit_kwargs = circuit.init_kwargs circuit_kwargs["wire_names"] = list(initial_layout.keys()) - routed_circuit = self.circuit.routed_circuit(circuit_kwargs=circuit_kwargs) + routed_circuit = self.circuit_map.routed_circuit(circuit_kwargs=circuit_kwargs) if self._final_measurements is not None: routed_circuit = self._append_final_measurements( routed_circuit=routed_circuit ) # 1# final layout is reverted to the original labeling - final_layout = self.circuit.final_layout() + final_layout = self.circuit_map.final_layout() final_layout_restored = { "q" + str(self.node_mapping_inv[int(k[1:])]): v for k, v in final_layout.items() @@ -745,7 +745,7 @@ def __call__(self, circuit: Circuit, initial_layout: dict): @property def added_swaps(self): """Returns the number of SWAP gates added to the circuit during routing.""" - return self.circuit._swaps + return self.circuit_map._swaps def _preprocessing(self, circuit: Circuit, initial_layout: dict): """The following objects will be initialised: @@ -775,9 +775,9 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit = CircuitMap(new_initial_layout, copied_circuit) + self.circuit_map = CircuitMap(new_initial_layout, copied_circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) - self._dag = _create_dag(self.circuit.blocks_logical_qubits_pairs()) + self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._memory_map = [] self._update_dag_layers() self._update_front_layer() @@ -809,7 +809,7 @@ def _append_final_measurements(self, routed_circuit: Circuit): original_qubits = measurement.qubits routed_qubits = list( # 1# use l2p to get physical qubit numbers - self.circuit.logical_to_physical[qubit] + self.circuit_map.logical_to_physical[qubit] for qubit in original_qubits ) routed_circuit.add( @@ -835,20 +835,20 @@ def _update_front_layer(self): def _get_dag_layer(self, n_layer, qubits=False): """Return the :math:`n`-topological layer of the dag. - If ``qubits=True``, return the target qubits of the blocks in the layer. - Otherwise, return the block numbers. + + Args: + n_layer (int): layer number. + qubits (bool, optional): if ``True``, return the target qubits of the blocks in the layer. + If ``False``, return the block numbers. Defaults to ``False``. + + Returns: + (list): list of block numbers or target qubits. """ # 3# depend on the 'qubits' flag, return the block number or target qubits # 3# return target qubits -> to avoid using get_physical_qubits(block_num) if qubits: - layer_qubits = [] - nodes = self._dag.nodes(data=True) - for node in nodes: - if node[1]["layer"] == n_layer: - # return target qubits - layer_qubits.append(node[1]["qubits"]) - return layer_qubits + return [node[1]["qubits"] for node in self._dag.nodes(data=True) if node[1]["layer"] == n_layer] return [node[0] for node in self._dag.nodes(data="layer") if node[1] == n_layer] @@ -857,7 +857,7 @@ def _find_new_mapping(self): candidates_evaluation = {} # 4# use shallow copy - self._memory_map.append(self.circuit.physical_to_logical.copy()) + self._memory_map.append(self.circuit_map.physical_to_logical.copy()) for candidate in self._swap_candidates(): candidates_evaluation[candidate] = self._compute_cost(candidate) @@ -867,9 +867,9 @@ def _find_new_mapping(self): ] best_candidate = random.choice(best_candidates) - for qubit in self.circuit.logical_pair_to_physical(best_candidate): + for qubit in self.circuit_map.logical_pair_to_physical(best_candidate): self._delta_register[qubit] += self.delta - self.circuit.update(best_candidate) + self.circuit_map.update(best_candidate) self._temp_added_swaps.append(best_candidate) def _compute_cost(self, candidate: int): @@ -879,12 +879,12 @@ def _compute_cost(self, candidate: int): # 2# no gates, no block decomposition, no Circuit object # 2# just logical-physical mapping temporary_circuit = CircuitMap( - initial_layout=self.circuit.initial_layout, + initial_layout=self.circuit_map.initial_layout, temp=True, ) # 1# copy the current physical to logical mapping - temporary_circuit.physical_to_logical = self.circuit.physical_to_logical + temporary_circuit.physical_to_logical = self.circuit_map.physical_to_logical temporary_circuit.update(candidate) # 1# use p2l to check if the mapping is already in the memory @@ -923,13 +923,13 @@ def _swap_candidates(self): candidates = [] # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) for block in self._front_layer: - for qubit in self.circuit.get_physical_qubits(block): + for qubit in self.circuit_map.get_physical_qubits(block): for connected in self.connectivity.neighbors(qubit): candidate = tuple( sorted( ( - self.circuit.physical_to_logical[qubit], - self.circuit.physical_to_logical[connected], + self.circuit_map.physical_to_logical[qubit], + self.circuit_map.physical_to_logical[connected], ) ) ) @@ -948,8 +948,8 @@ def _check_execution(self): for block in self._front_layer: if ( # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) - self.circuit.get_physical_qubits(block) in self.connectivity.edges - or not self.circuit.circuit_blocks.search_by_index(block).entangled + self.circuit_map.get_physical_qubits(block) in self.connectivity.edges + or not self.circuit_map.circuit_blocks.search_by_index(block).entangled ): executable_blocks.append(block) @@ -971,8 +971,8 @@ def _execute_blocks(self, blocklist: list): blocklist (list): list of blocks. """ for block_id in blocklist: - block = self.circuit.circuit_blocks.search_by_index(block_id) - self.circuit.execute_block(block) + block = self.circuit_map.circuit_blocks.search_by_index(block_id) + self.circuit_map.execute_block(block) self._dag.remove_node(block_id) self._update_dag_layers() self._update_front_layer() @@ -992,7 +992,7 @@ def _shortest_path_routing(self): for block in self._front_layer: # 3# return node numbers (physical qubits) in the connectivity graph # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) - q1, q2 = self.circuit.get_physical_qubits(block) + q1, q2 = self.circuit_map.get_physical_qubits(block) distance = self._dist_matrix[q1, q2] if distance < min_distance: @@ -1005,9 +1005,9 @@ def _shortest_path_routing(self): # move q1 # 1# qubit moving algorithm is changed - q1 = self.circuit.physical_to_logical[shortest_path[0]] + q1 = self.circuit_map.physical_to_logical[shortest_path[0]] for q2 in shortest_path[1:-1]: - self.circuit.update((q1, self.circuit.physical_to_logical[q2])) + self.circuit_map.update((q1, self.circuit_map.physical_to_logical[q2])) def _create_dag(gates_qubits_pairs: list): diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index fdad7b96c8..e4417db5de 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -254,8 +254,8 @@ def test_sabre_shortest_path_routing(): router._preprocessing(circuit=loop_circ, initial_layout=initial_layout) router._shortest_path_routing() # q2 should be moved adjacent to q8 - gate_28 = router.circuit.circuit_blocks.block_list[2] - gate_28_qubits = router.circuit.get_physical_qubits(gate_28) + gate_28 = router.circuit_map.circuit_blocks.block_list[2] + gate_28_qubits = router.circuit_map.get_physical_qubits(gate_28) # Check if the physical qubits of the gate (2, 8) are adjacent assert gate_28_qubits[1] in list(router.connectivity.neighbors(gate_28_qubits[0])) From 7d128aeb1b65968db19936c0be1f998eda39f4cb Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:47:16 +0800 Subject: [PATCH 10/22] Update src/qibo/transpiler/router.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/router.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 2c2e75f981..0e9eda233f 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -244,7 +244,12 @@ def logical_to_physical(self, l2p_map: list): self._p2l[p] = i def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): - """Updates the qubit mapping after applying a SWAP gate.""" + """Updates the qubit mappings after applying a SWAP gate. + + Args: + logical_swap (tuple[int]): the indices of the logical qubits to be swapped. + physical_swap (tuple[int]): the indices of the corresponding physical qubits to be swapped. + """ self._p2l[physical_swap[0]], self._p2l[physical_swap[1]] = ( logical_swap[1], logical_swap[0], From e46f341b6ee3bedc24a520d7e29f3a431e90bd3f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 11:47:17 +0000 Subject: [PATCH 11/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 0e9eda233f..e0d0e9c219 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -844,8 +844,8 @@ def _get_dag_layer(self, n_layer, qubits=False): Args: n_layer (int): layer number. qubits (bool, optional): if ``True``, return the target qubits of the blocks in the layer. - If ``False``, return the block numbers. Defaults to ``False``. - + If ``False``, return the block numbers. Defaults to ``False``. + Returns: (list): list of block numbers or target qubits. """ @@ -853,7 +853,11 @@ def _get_dag_layer(self, n_layer, qubits=False): # 3# depend on the 'qubits' flag, return the block number or target qubits # 3# return target qubits -> to avoid using get_physical_qubits(block_num) if qubits: - return [node[1]["qubits"] for node in self._dag.nodes(data=True) if node[1]["layer"] == n_layer] + return [ + node[1]["qubits"] + for node in self._dag.nodes(data=True) + if node[1]["layer"] == n_layer + ] return [node[0] for node in self._dag.nodes(data="layer") if node[1] == n_layer] From 1506b787b814a6915d7b2dbf485818f768047fe9 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 29 Aug 2024 19:33:59 +0800 Subject: [PATCH 12/22] enable custom qubit names / wire_names --- src/qibo/transpiler/router.py | 81 +++++++++++------------------------ 1 file changed, 26 insertions(+), 55 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index e0d0e9c219..ee11c8b9a0 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -173,23 +173,12 @@ class CircuitMap: def __init__( self, - initial_layout: dict, + initial_layout: Optional[dict] = None, circuit: Optional[Circuit] = None, blocks: Optional[CircuitBlocks] = None, temp: Optional[bool] = False, # 2# for temporary circuit ): - self.initial_layout = dict(sorted(initial_layout.items())) - - # 1# bidirectional mapping - # 1# self._p2l: physical qubit number i -> logical qubit number _p2l[i] - # 1# self._l2p: logical qubit number i -> physical qubit number _l2p[i] - self._l2p, self._p2l = [0] * len(self.initial_layout), [0] * len( - self.initial_layout - ) - for mapping in self.initial_layout.items(): - physical_qubit, logical_qubit = int(mapping[0][1:]), mapping[1] - self._l2p[logical_qubit] = physical_qubit - self._p2l[physical_qubit] = logical_qubit + self._p2l, self._l2p = [], [] self._temporary = temp if self._temporary: # 2# if temporary circuit, no need to store the blocks @@ -206,6 +195,13 @@ def __init__( self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 + + if initial_layout is None: + return + + # 1# initialize physical to logical mapping + self.wire_names = list(initial_layout.keys()) + self.physical_to_logical = list(initial_layout.values()) @property def physical_to_logical(self): @@ -227,6 +223,7 @@ def physical_to_logical(self, p2l_map: list): # 1# update bidirectional mapping # 4# use shallow copy self._p2l = p2l_map.copy() + self._l2p = [0] * len(self._p2l) for i, l in enumerate(self._p2l): self._l2p[l] = i @@ -240,6 +237,7 @@ def logical_to_physical(self, l2p_map: list): # 1# update bidirectional mapping # 4# use shallow copy self._l2p = l2p_map.copy() + self._p2l = [0] * len(self._l2p) for i, p in enumerate(self._l2p): self._p2l[p] = i @@ -289,10 +287,8 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): def final_layout(self): """Returns the final physical-logical qubits mapping.""" - # 1# return {"q0": lq_num0, "q1": lq_num1, ...} - unsorted_dict = {"q" + str(i): self._p2l[i] for i in range(self._nqubits)} - - return dict(sorted(unsorted_dict.items())) + # 1# return {"A": lq_num0, "B": lq_num1, ...} + return {self.wire_names[i]: self._p2l[i] for i in range(self._nqubits)} def update(self, logical_swap: tuple): """Updates the qubit mapping after applying a ``SWAP`` @@ -410,13 +406,7 @@ def __call__(self, circuit: Circuit, initial_layout: dict): routed_circuit=routed_circuit ) - # 1# final layout is reverted to the original labeling - final_layout = self.circuit_map.final_layout() - final_layout_restored = { - "q" + str(self.node_mapping_inv[int(k[1:])]): v - for k, v in final_layout.items() - } - return routed_circuit, final_layout_restored + return routed_circuit, self.circuit_map.final_layout() def _find_new_mapping(self): """Find new qubit mapping. Mapping is found by looking for the shortest path. @@ -496,8 +486,7 @@ def _compute_cost(self, candidate: tuple): """ # 2# CircuitMap might be used temporary_circuit = CircuitMap( - initial_layout=self.circuit_map.initial_layout, - circuit=Circuit(len(self.circuit_map.initial_layout)), + circuit=Circuit(self.circuit_map._nqubits), blocks=deepcopy(self.circuit_map.circuit_blocks), ) @@ -594,18 +583,15 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): initial_layout (dict): initial physical-to-logical qubit mapping. """ - # 1# To simplify routing, some data is relabeled before routing begins. - node_mapping, new_initial_layout = {}, {} - for i, node in enumerate(self.connectivity.nodes): + # 1# Relabel the nodes of the connectivity graph + node_mapping = {} + for i, node in enumerate(list(initial_layout.keys())): node_mapping[node] = i - new_initial_layout["q" + str(i)] = initial_layout["q" + str(node)] - self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping) - self.node_mapping_inv = {v: k for k, v in node_mapping.items()} copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit_map = CircuitMap(new_initial_layout, copied_circuit) + self.circuit_map = CircuitMap(initial_layout, copied_circuit) self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._update_front_layer() @@ -684,8 +670,6 @@ def __init__( seed: Optional[int] = None, ): self.connectivity = connectivity - # 1# map to revert the final layout to the original labeling - self.node_mapping_inv = None self.lookahead = lookahead self.decay = decay_lookahead self.delta = delta @@ -726,7 +710,7 @@ def __call__(self, circuit: Circuit, initial_layout: dict): len(self._temp_added_swaps) > self.swap_threshold * longest_path ): # threshold is arbitrary while self._temp_added_swaps: - swap = self._temp_added_swaps.pop() + self._temp_added_swaps.pop() self.circuit_map.undo() self._temp_added_swaps = [] self._shortest_path_routing() @@ -739,13 +723,7 @@ def __call__(self, circuit: Circuit, initial_layout: dict): routed_circuit=routed_circuit ) - # 1# final layout is reverted to the original labeling - final_layout = self.circuit_map.final_layout() - final_layout_restored = { - "q" + str(self.node_mapping_inv[int(k[1:])]): v - for k, v in final_layout.items() - } - return routed_circuit, final_layout_restored + return routed_circuit, self.circuit_map.final_layout() @property def added_swaps(self): @@ -768,19 +746,15 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): initial_layout (dict): initial physical-to-logical qubit mapping. """ - # 1# To simplify routing, some data is relabeled before routing begins. - # 1# physical qubit is reassigned to a range from 0 to len(self.connectivity.nodes) - 1. - node_mapping, new_initial_layout = {}, {} - for i, node in enumerate(self.connectivity.nodes): + # 1# Relabel the nodes of the connectivity graph + node_mapping = {} + for i, node in enumerate(list(initial_layout.keys())): node_mapping[node] = i - new_initial_layout["q" + str(i)] = initial_layout["q" + str(node)] - self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping) - self.node_mapping_inv = {v: k for k, v in node_mapping.items()} copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) - self.circuit_map = CircuitMap(new_initial_layout, copied_circuit) + self.circuit_map = CircuitMap(initial_layout, copied_circuit) self._dist_matrix = nx.floyd_warshall_numpy(self.connectivity) self._dag = _create_dag(self.circuit_map.blocks_logical_qubits_pairs()) self._memory_map = [] @@ -887,10 +861,7 @@ def _compute_cost(self, candidate: int): # 2# use CircuitMap for temporary circuit to save time # 2# no gates, no block decomposition, no Circuit object # 2# just logical-physical mapping - temporary_circuit = CircuitMap( - initial_layout=self.circuit_map.initial_layout, - temp=True, - ) + temporary_circuit = CircuitMap(temp=True) # 1# copy the current physical to logical mapping temporary_circuit.physical_to_logical = self.circuit_map.physical_to_logical From 19d373781cb703c3333e85c66702356a4101314e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:34:47 +0000 Subject: [PATCH 13/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index ee11c8b9a0..2479311256 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -195,7 +195,7 @@ def __init__( self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 - + if initial_layout is None: return @@ -243,7 +243,7 @@ def logical_to_physical(self, l2p_map: list): def _update_mappings_swap(self, logical_swap: tuple, physical_swap: tuple): """Updates the qubit mappings after applying a SWAP gate. - + Args: logical_swap (tuple[int]): the indices of the logical qubits to be swapped. physical_swap (tuple[int]): the indices of the corresponding physical qubits to be swapped. From 32364fc031b98529fb4de303df610500f80d2478 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 5 Sep 2024 17:41:02 +0800 Subject: [PATCH 14/22] add helper func / update test func --- src/qibo/transpiler/placer.py | 2 +- src/qibo/transpiler/router.py | 36 +++++----- tests/test_transpiler_router.py | 118 ++++++++++++++++++++++---------- 3 files changed, 103 insertions(+), 53 deletions(-) diff --git a/src/qibo/transpiler/placer.py b/src/qibo/transpiler/placer.py index 12415b8ec7..7944ea81f2 100644 --- a/src/qibo/transpiler/placer.py +++ b/src/qibo/transpiler/placer.py @@ -54,7 +54,7 @@ def assert_mapping_consistency(layout: dict, connectivity: nx.Graph = None): ref_keys = ( ["q" + str(i) for i in nodes] if isinstance(physical_qubits[0], str) else nodes ) - if physical_qubits != ref_keys: + if sorted(physical_qubits) != sorted(ref_keys): raise_error( PlacementError, "Some physical qubits in the layout may be missing or duplicated.", diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 2479311256..0fbf028a11 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -34,12 +34,23 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): if len(gate.qubits) > 2 and not isinstance(gate, gates.M): raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") if len(gate.qubits) == 2: - if (gate.qubits[0], gate.qubits[1]) not in connectivity.edges: + # physical_qubits = tuple(sorted((circuit.wire_names[gate.qubits[0]], circuit.wire_names[gate.qubits[1]]))) + physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming + if physical_qubits not in connectivity.edges: raise_error( ConnectivityError, - f"Circuit does not respect connectivity. {gate.name} acts on {gate.qubits}.", + f"Circuit does not respect connectivity. {gate.name} acts on {physical_qubits}.", ) - + + +def _relabel_connectivity(connectivity, initial_layout): + node_mapping = {} + initial_layout = dict(sorted(initial_layout.items(), key=lambda item: int(item[0][1:]))) # for q_i naming + for i, node in enumerate(list(initial_layout.keys())): + # node_mapping[node] = i + node_mapping[int(node[1:])] = i # for q_i naming + new_connectivity = nx.relabel_nodes(connectivity, node_mapping) + return new_connectivity class StarConnectivityRouter(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. @@ -183,16 +194,15 @@ def __init__( self._temporary = temp if self._temporary: # 2# if temporary circuit, no need to store the blocks return - - self._nqubits = circuit.nqubits # 1# number of qubits - if circuit is None: + elif circuit is None: raise_error(ValueError, "Circuit must be provided.") - + if blocks is not None: self.circuit_blocks = blocks else: self.circuit_blocks = CircuitBlocks(circuit, index_names=True) + self._nqubits = circuit.nqubits # 1# number of qubits self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 @@ -584,10 +594,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): """ # 1# Relabel the nodes of the connectivity graph - node_mapping = {} - for i, node in enumerate(list(initial_layout.keys())): - node_mapping[node] = i - self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping) + self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) @@ -747,10 +754,7 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): """ # 1# Relabel the nodes of the connectivity graph - node_mapping = {} - for i, node in enumerate(list(initial_layout.keys())): - node_mapping[node] = i - self.connectivity = nx.relabel_nodes(self.connectivity, node_mapping) + self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) copied_circuit = circuit.copy(deep=True) self._final_measurements = self._detach_final_measurements(copied_circuit) @@ -1042,4 +1046,4 @@ def _remove_redundant_connections(dag: nx.DiGraph): transitive_reduction = nx.transitive_reduction(dag) new_dag.add_edges_from(transitive_reduction.edges) - return new_dag + return new_dag \ No newline at end of file diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index e4417db5de..ed1b9cfe7c 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -49,6 +49,13 @@ def grid_connectivity(): chip.add_edges_from(graph_list) return chip +def line_connectivity(n): + Q = [i for i in range(n)] + chip = nx.Graph() + chip.add_nodes_from(Q) + graph_list = [(Q[i], (Q[i]+1) % n) for i in range(n-1)] + chip.add_edges_from(graph_list) + return chip def generate_random_circuit(nqubits, ngates, seed=42): """Generate a random circuit with RX and CZ gates.""" @@ -135,6 +142,27 @@ def test_random_circuits_5q(gates, placer, connectivity): initial_map=initial_layout, ) +def test_random_circuits_15q_50g(): + nqubits, ngates = 15, 50 + connectivity = line_connectivity(nqubits) + placer = Random(connectivity=connectivity) + layout_circ = Circuit(nqubits) + initial_layout = placer(layout_circ) + transpiler = Sabre(connectivity=connectivity) + circuit = generate_random_circuit(nqubits=nqubits, ngates=ngates) + transpiled_circuit, final_qubit_map = transpiler(circuit, initial_layout) + assert transpiler.added_swaps >= 0 + assert_connectivity(connectivity, transpiled_circuit) + assert_placement(transpiled_circuit, final_qubit_map) + assert ngates + transpiler.added_swaps == transpiled_circuit.ngates + qubit_matcher = Preprocessing(connectivity=connectivity) + new_circuit = qubit_matcher(circuit=circuit) + assert_circuit_equivalence( + original_circuit=new_circuit, + transpiled_circuit=transpiled_circuit, + final_map=final_qubit_map, + initial_map=initial_layout, + ) def test_star_circuit(): placer = Subgraph(star_connectivity()) @@ -272,40 +300,54 @@ def test_circuit_map(): circ.add(gates.CZ(0, 1)) circ.add(gates.CZ(2, 3)) initial_layout = {"q0": 2, "q1": 0, "q2": 1, "q3": 3} - # circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) - # block_list = circuit_map.circuit_blocks - # # test blocks_qubits_pairs - # assert circuit_map.blocks_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)] - # # test execute_block and routed_circuit - # circuit_map.execute_block(block_list.search_by_index(0)) - # routed_circuit = circuit_map.routed_circuit() - # assert isinstance(routed_circuit.queue[0], gates.H) - # assert len(routed_circuit.queue) == 4 - # assert routed_circuit.queue[2].qubits == (1, 2) - # # test update - # circuit_map.update((0, 2)) - # routed_circuit = circuit_map.routed_circuit() - # assert isinstance(routed_circuit.queue[4], gates.SWAP) - # assert routed_circuit.queue[4].qubits == (1, 0) - # assert circuit_map._swaps == 1 - # assert circuit_map._circuit_logical == [2, 1, 0, 3] - # circuit_map.update((1, 2)) - # routed_circuit = circuit_map.routed_circuit() - # assert routed_circuit.queue[5].qubits == (2, 0) - # assert circuit_map._circuit_logical == [1, 2, 0, 3] + circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + block_list = circuit_map.circuit_blocks + # test blocks_qubits_pairs + assert circuit_map.blocks_logical_qubits_pairs() == [(0, 1), (1, 2), (0, 1), (2, 3)] + # test execute_block and routed_circuit + circuit_map.execute_block(block_list.search_by_index(0)) + routed_circuit = circuit_map.routed_circuit() + assert isinstance(routed_circuit.queue[0], gates.H) + assert len(routed_circuit.queue) == 4 + qubits = routed_circuit.queue[2].qubits + assert routed_circuit.wire_names[qubits[0]] == "q1" and routed_circuit.wire_names[qubits[1]] == "q2" + + # test update 1 + circuit_map.update((0, 2)) + routed_circuit = circuit_map.routed_circuit() + assert isinstance(routed_circuit.queue[4], gates.SWAP) + qubits = routed_circuit.queue[4].qubits + assert routed_circuit.wire_names[qubits[0]] == "q1" and routed_circuit.wire_names[qubits[1]] == "q0" + assert circuit_map._swaps == 1 + assert circuit_map.physical_to_logical == [0, 2, 1, 3] + assert circuit_map.logical_to_physical == [0, 2, 1, 3] + + # test update 2 + circuit_map.update((1, 2)) + routed_circuit = circuit_map.routed_circuit() + assert isinstance(routed_circuit.queue[5], gates.SWAP) + qubits = routed_circuit.queue[5].qubits + assert routed_circuit.wire_names[qubits[0]] == "q2" and routed_circuit.wire_names[qubits[1]] == "q1" + assert circuit_map._swaps == 2 + assert circuit_map.physical_to_logical == [0, 1, 2, 3] + assert circuit_map.logical_to_physical == [0, 1, 2, 3] + # # test execute_block after multiple swaps - # circuit_map.execute_block(block_list.search_by_index(1)) - # circuit_map.execute_block(block_list.search_by_index(2)) - # circuit_map.execute_block(block_list.search_by_index(3)) - # routed_circuit = circuit_map.routed_circuit() - # assert isinstance(routed_circuit.queue[6], gates.CZ) - # # circuit to logical map: [1,2,0,3]. initial map: {"q0": 2, "q1": 0, "q2": 1, "q3": 3}. - # assert routed_circuit.queue[6].qubits == (0, 1) # initial circuit qubits (1,2) - # assert routed_circuit.queue[7].qubits == (2, 0) # (0,1) - # assert routed_circuit.queue[8].qubits == (1, 3) # (2,3) - # assert len(circuit_map.circuit_blocks()) == 0 - # # test final layout - # assert circuit_map.final_layout() == {"q0": 1, "q1": 2, "q2": 0, "q3": 3} + circuit_map.execute_block(block_list.search_by_index(1)) + circuit_map.execute_block(block_list.search_by_index(2)) + circuit_map.execute_block(block_list.search_by_index(3)) + routed_circuit = circuit_map.routed_circuit() + assert isinstance(routed_circuit.queue[6], gates.CZ) + + qubits = routed_circuit.queue[6].qubits + assert routed_circuit.wire_names[qubits[0]] == "q1" and routed_circuit.wire_names[qubits[1]] == "q2" + qubits = routed_circuit.queue[7].qubits + assert routed_circuit.wire_names[qubits[0]] == "q0" and routed_circuit.wire_names[qubits[1]] == "q1" + qubits = routed_circuit.queue[8].qubits + assert routed_circuit.wire_names[qubits[0]] == "q2" and routed_circuit.wire_names[qubits[1]] == "q3" + assert len(circuit_map.circuit_blocks()) == 0 + # test final layout + assert circuit_map.final_layout() == {"q0": 0, "q1": 1, "q2": 2, "q3": 3} def test_sabre_matched(): @@ -483,17 +525,21 @@ def test_undo(): # Two SWAP gates are added circuit_map.update((1, 2)) circuit_map.update((2, 3)) - # assert circuit_map._circuit_logical == [0, 3, 1, 2] + assert circuit_map.physical_to_logical == [0, 3, 1, 2] + assert circuit_map.logical_to_physical == [0, 2, 3, 1] assert len(circuit_map._routed_blocks.block_list) == 2 + assert circuit_map._swaps == 2 # Undo the last SWAP gate circuit_map.undo() - # assert circuit_map._circuit_logical == [0, 2, 1, 3] + assert circuit_map.physical_to_logical == [0, 2, 1, 3] + assert circuit_map.logical_to_physical == [0, 2, 1, 3] assert circuit_map._swaps == 1 assert len(circuit_map._routed_blocks.block_list) == 1 # Undo the first SWAP gate circuit_map.undo() - # assert circuit_map._circuit_logical == [0, 1, 2, 3] + assert circuit_map.physical_to_logical == [0, 1, 2, 3] + assert circuit_map.logical_to_physical == [0, 1, 2, 3] assert circuit_map._swaps == 0 assert len(circuit_map._routed_blocks.block_list) == 0 From 2b8cdc0a824708c4961c58e6141b98f748c80770 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 09:43:02 +0000 Subject: [PATCH 15/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 17 +++++++------ tests/test_transpiler_router.py | 42 +++++++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index 0fbf028a11..f730679f84 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -35,23 +35,26 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): raise_error(ConnectivityError, f"{gate.name} acts on more than two qubits.") if len(gate.qubits) == 2: # physical_qubits = tuple(sorted((circuit.wire_names[gate.qubits[0]], circuit.wire_names[gate.qubits[1]]))) - physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming + physical_qubits = tuple(sorted(gate.qubits)) # for q_i naming if physical_qubits not in connectivity.edges: raise_error( ConnectivityError, f"Circuit does not respect connectivity. {gate.name} acts on {physical_qubits}.", ) - - + + def _relabel_connectivity(connectivity, initial_layout): node_mapping = {} - initial_layout = dict(sorted(initial_layout.items(), key=lambda item: int(item[0][1:]))) # for q_i naming + initial_layout = dict( + sorted(initial_layout.items(), key=lambda item: int(item[0][1:])) + ) # for q_i naming for i, node in enumerate(list(initial_layout.keys())): # node_mapping[node] = i - node_mapping[int(node[1:])] = i # for q_i naming + node_mapping[int(node[1:])] = i # for q_i naming new_connectivity = nx.relabel_nodes(connectivity, node_mapping) return new_connectivity + class StarConnectivityRouter(Router): """Transforms an arbitrary circuit to one that can be executed on hardware. @@ -196,7 +199,7 @@ def __init__( return elif circuit is None: raise_error(ValueError, "Circuit must be provided.") - + if blocks is not None: self.circuit_blocks = blocks else: @@ -1046,4 +1049,4 @@ def _remove_redundant_connections(dag: nx.DiGraph): transitive_reduction = nx.transitive_reduction(dag) new_dag.add_edges_from(transitive_reduction.edges) - return new_dag \ No newline at end of file + return new_dag diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index ed1b9cfe7c..0bf2390fa8 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -49,14 +49,16 @@ def grid_connectivity(): chip.add_edges_from(graph_list) return chip + def line_connectivity(n): Q = [i for i in range(n)] chip = nx.Graph() chip.add_nodes_from(Q) - graph_list = [(Q[i], (Q[i]+1) % n) for i in range(n-1)] + graph_list = [(Q[i], (Q[i] + 1) % n) for i in range(n - 1)] chip.add_edges_from(graph_list) return chip + def generate_random_circuit(nqubits, ngates, seed=42): """Generate a random circuit with RX and CZ gates.""" np.random.seed(seed) @@ -142,6 +144,7 @@ def test_random_circuits_5q(gates, placer, connectivity): initial_map=initial_layout, ) + def test_random_circuits_15q_50g(): nqubits, ngates = 15, 50 connectivity = line_connectivity(nqubits) @@ -162,7 +165,8 @@ def test_random_circuits_15q_50g(): transpiled_circuit=transpiled_circuit, final_map=final_qubit_map, initial_map=initial_layout, - ) + ) + def test_star_circuit(): placer = Subgraph(star_connectivity()) @@ -310,24 +314,33 @@ def test_circuit_map(): assert isinstance(routed_circuit.queue[0], gates.H) assert len(routed_circuit.queue) == 4 qubits = routed_circuit.queue[2].qubits - assert routed_circuit.wire_names[qubits[0]] == "q1" and routed_circuit.wire_names[qubits[1]] == "q2" - + assert ( + routed_circuit.wire_names[qubits[0]] == "q1" + and routed_circuit.wire_names[qubits[1]] == "q2" + ) + # test update 1 circuit_map.update((0, 2)) routed_circuit = circuit_map.routed_circuit() assert isinstance(routed_circuit.queue[4], gates.SWAP) qubits = routed_circuit.queue[4].qubits - assert routed_circuit.wire_names[qubits[0]] == "q1" and routed_circuit.wire_names[qubits[1]] == "q0" + assert ( + routed_circuit.wire_names[qubits[0]] == "q1" + and routed_circuit.wire_names[qubits[1]] == "q0" + ) assert circuit_map._swaps == 1 assert circuit_map.physical_to_logical == [0, 2, 1, 3] assert circuit_map.logical_to_physical == [0, 2, 1, 3] - + # test update 2 circuit_map.update((1, 2)) routed_circuit = circuit_map.routed_circuit() assert isinstance(routed_circuit.queue[5], gates.SWAP) qubits = routed_circuit.queue[5].qubits - assert routed_circuit.wire_names[qubits[0]] == "q2" and routed_circuit.wire_names[qubits[1]] == "q1" + assert ( + routed_circuit.wire_names[qubits[0]] == "q2" + and routed_circuit.wire_names[qubits[1]] == "q1" + ) assert circuit_map._swaps == 2 assert circuit_map.physical_to_logical == [0, 1, 2, 3] assert circuit_map.logical_to_physical == [0, 1, 2, 3] @@ -340,11 +353,20 @@ def test_circuit_map(): assert isinstance(routed_circuit.queue[6], gates.CZ) qubits = routed_circuit.queue[6].qubits - assert routed_circuit.wire_names[qubits[0]] == "q1" and routed_circuit.wire_names[qubits[1]] == "q2" + assert ( + routed_circuit.wire_names[qubits[0]] == "q1" + and routed_circuit.wire_names[qubits[1]] == "q2" + ) qubits = routed_circuit.queue[7].qubits - assert routed_circuit.wire_names[qubits[0]] == "q0" and routed_circuit.wire_names[qubits[1]] == "q1" + assert ( + routed_circuit.wire_names[qubits[0]] == "q0" + and routed_circuit.wire_names[qubits[1]] == "q1" + ) qubits = routed_circuit.queue[8].qubits - assert routed_circuit.wire_names[qubits[0]] == "q2" and routed_circuit.wire_names[qubits[1]] == "q3" + assert ( + routed_circuit.wire_names[qubits[0]] == "q2" + and routed_circuit.wire_names[qubits[1]] == "q3" + ) assert len(circuit_map.circuit_blocks()) == 0 # test final layout assert circuit_map.final_layout() == {"q0": 0, "q1": 1, "q2": 2, "q3": 3} From 387afb4c6278572e4436c74c0a857af92a7c9fc8 Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Thu, 5 Sep 2024 18:31:27 +0800 Subject: [PATCH 16/22] add test func --- tests/test_transpiler_router.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 0bf2390fa8..8c28a97878 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -565,3 +565,16 @@ def test_undo(): assert circuit_map.logical_to_physical == [0, 1, 2, 3] assert circuit_map._swaps == 0 assert len(circuit_map._routed_blocks.block_list) == 0 + +def test_circuitmap_no_circuit(): + # If a `CircuitMap` is not a temporary instance and is created without a circuit, it should raise an error. + with pytest.raises(ValueError): + circuit_map = CircuitMap() + +def test_logical_to_physical_setter(): + circ = Circuit(4) + initial_layout = {"q0": 0, "q1": 3, "q2": 2, "q3": 1} + circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) + circuit_map.logical_to_physical = [2, 0, 1, 3] + assert circuit_map.logical_to_physical == [2, 0, 1, 3] + assert circuit_map.physical_to_logical == [1, 2, 0, 3] \ No newline at end of file From 6dd6cedce72bf43f0ce3962dc1e14d82793257b4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 10:32:00 +0000 Subject: [PATCH 17/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_transpiler_router.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_transpiler_router.py b/tests/test_transpiler_router.py index 8c28a97878..d5901d5f92 100644 --- a/tests/test_transpiler_router.py +++ b/tests/test_transpiler_router.py @@ -566,15 +566,17 @@ def test_undo(): assert circuit_map._swaps == 0 assert len(circuit_map._routed_blocks.block_list) == 0 + def test_circuitmap_no_circuit(): # If a `CircuitMap` is not a temporary instance and is created without a circuit, it should raise an error. with pytest.raises(ValueError): circuit_map = CircuitMap() + def test_logical_to_physical_setter(): circ = Circuit(4) initial_layout = {"q0": 0, "q1": 3, "q2": 2, "q3": 1} circuit_map = CircuitMap(initial_layout=initial_layout, circuit=circ) circuit_map.logical_to_physical = [2, 0, 1, 3] assert circuit_map.logical_to_physical == [2, 0, 1, 3] - assert circuit_map.physical_to_logical == [1, 2, 0, 3] \ No newline at end of file + assert circuit_map.physical_to_logical == [1, 2, 0, 3] From c5a4af8cc516d49d1f3e2a6b8a5dceea912c7ce6 Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:31:31 +0800 Subject: [PATCH 18/22] Update src/qibo/transpiler/router.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index f730679f84..e721e3209b 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -39,7 +39,7 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): if physical_qubits not in connectivity.edges: raise_error( ConnectivityError, - f"Circuit does not respect connectivity. {gate.name} acts on {physical_qubits}.", + f"The circuit does not respect the connectivity. {gate.name} acts on {physical_qubits} but only the following qubits are directly connected: {connectivity.edges}.", ) From 9ba69dcb02ce001d15aab1576c018b343829b4b8 Mon Sep 17 00:00:00 2001 From: Changsoo Kim <57739683+csookim@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:32:40 +0800 Subject: [PATCH 19/22] Update src/qibo/transpiler/router.py Co-authored-by: BrunoLiegiBastonLiegi <45011234+BrunoLiegiBastonLiegi@users.noreply.github.com> --- src/qibo/transpiler/router.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index e721e3209b..e04a0e08cf 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -43,13 +43,20 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): ) -def _relabel_connectivity(connectivity, initial_layout): +def _relabel_connectivity(connectivity, layout): + """Relabels the connectivity graph using the passed layout. + + Args: + connectivity (nx.Graph): input connectivity. + layout (dict): input qubit layout. + Returns: + (dict) the updated connectivity. + """ node_mapping = {} - initial_layout = dict( - sorted(initial_layout.items(), key=lambda item: int(item[0][1:])) + layout = dict( + sorted(layout.items(), key=lambda item: int(item[0][1:])) ) # for q_i naming - for i, node in enumerate(list(initial_layout.keys())): - # node_mapping[node] = i + for i, node in enumerate(list(layout.keys())): node_mapping[int(node[1:])] = i # for q_i naming new_connectivity = nx.relabel_nodes(connectivity, node_mapping) return new_connectivity From 98fd9f1e22b201394695256718244bb599470457 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 15:33:01 +0000 Subject: [PATCH 20/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index e04a0e08cf..c869daa6cb 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -45,7 +45,7 @@ def assert_connectivity(connectivity: nx.Graph, circuit: Circuit): def _relabel_connectivity(connectivity, layout): """Relabels the connectivity graph using the passed layout. - + Args: connectivity (nx.Graph): input connectivity. layout (dict): input qubit layout. From 8641a0c2f4555cafc4d3505b1c9a2924162aa66c Mon Sep 17 00:00:00 2001 From: changsookim <> Date: Tue, 10 Sep 2024 00:14:06 +0800 Subject: [PATCH 21/22] remove comments --- src/qibo/transpiler/router.py | 50 +++-------------------------------- 1 file changed, 4 insertions(+), 46 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index c869daa6cb..fc05ea0abd 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -197,12 +197,12 @@ def __init__( initial_layout: Optional[dict] = None, circuit: Optional[Circuit] = None, blocks: Optional[CircuitBlocks] = None, - temp: Optional[bool] = False, # 2# for temporary circuit + temp: Optional[bool] = False, ): self._p2l, self._l2p = [], [] self._temporary = temp - if self._temporary: # 2# if temporary circuit, no need to store the blocks + if self._temporary: return elif circuit is None: raise_error(ValueError, "Circuit must be provided.") @@ -212,14 +212,13 @@ def __init__( else: self.circuit_blocks = CircuitBlocks(circuit, index_names=True) - self._nqubits = circuit.nqubits # 1# number of qubits + self._nqubits = circuit.nqubits self._routed_blocks = CircuitBlocks(Circuit(circuit.nqubits)) self._swaps = 0 if initial_layout is None: return - # 1# initialize physical to logical mapping self.wire_names = list(initial_layout.keys()) self.physical_to_logical = list(initial_layout.values()) @@ -240,8 +239,6 @@ def physical_to_logical(self, p2l_map: list): Args: p2l_map (list): physical to logical mapping. """ - # 1# update bidirectional mapping - # 4# use shallow copy self._p2l = p2l_map.copy() self._l2p = [0] * len(self._p2l) for i, l in enumerate(self._p2l): @@ -254,8 +251,6 @@ def logical_to_physical(self, l2p_map: list): Args: l2p_map (list): logical to physical mapping. """ - # 1# update bidirectional mapping - # 4# use shallow copy self._l2p = l2p_map.copy() self._p2l = [0] * len(self._l2p) for i, p in enumerate(self._l2p): @@ -307,7 +302,6 @@ def routed_circuit(self, circuit_kwargs: Optional[dict] = None): def final_layout(self): """Returns the final physical-logical qubits mapping.""" - # 1# return {"A": lq_num0, "B": lq_num1, ...} return {self.wire_names[i]: self._p2l[i] for i in range(self._nqubits)} def update(self, logical_swap: tuple): @@ -321,15 +315,12 @@ def update(self, logical_swap: tuple): """ physical_swap = self.logical_pair_to_physical(logical_swap) - - # 2# add the real SWAP gate, not a temporary circuit if not self._temporary: self._routed_blocks.add_block( Block(qubits=physical_swap, gates=[gates.SWAP(*physical_swap)]) ) self._swaps += 1 - # 1# update the bidirectional mapping self._update_mappings_swap(logical_swap, physical_swap) def undo(self): @@ -340,7 +331,6 @@ def undo(self): self._routed_blocks.remove_block(last_swap_block) self._swaps -= 1 - # 1# update the bidirectional mapping self._update_mappings_swap(logical_swap, physical_swap) def get_physical_qubits(self, block: Union[int, Block]): @@ -357,7 +347,6 @@ def get_physical_qubits(self, block: Union[int, Block]): return tuple(self._l2p[q] for q in block.qubits) - # 1# logical_to_physical -> logical_pair_to_physical def logical_pair_to_physical(self, logical_qubits: tuple): """Returns the physical qubits associated to the logical qubit pair. @@ -367,11 +356,8 @@ def logical_pair_to_physical(self, logical_qubits: tuple): Returns: tuple: physical qubit numbers associated to the logical qubit pair. """ - # 1# return physical qubit numbers corresponding to the logical qubit pair return self._l2p[logical_qubits[0]], self._l2p[logical_qubits[1]] - # 1# circuit_to_logical(), circuit_to_physical() removed - class ShortestPaths(Router): """A class to perform initial qubit mapping and connectivity matching. @@ -475,9 +461,8 @@ def _add_swaps(candidate: tuple, circuitmap: CircuitMap): """ path = candidate[0] meeting_point = candidate[1] - forward = path[0 : meeting_point + 1] # 1# physical qubits + forward = path[0 : meeting_point + 1] backward = list(reversed(path[meeting_point + 1 :])) - # 1# apply logical swaps for f in forward[1:]: circuitmap.update( ( @@ -504,13 +489,11 @@ def _compute_cost(self, candidate: tuple): Returns: (list, int): best path to move qubits and qubit meeting point in the path. """ - # 2# CircuitMap might be used temporary_circuit = CircuitMap( circuit=Circuit(self.circuit_map._nqubits), blocks=deepcopy(self.circuit_map.circuit_blocks), ) - # 1# copy the current physical to logical mapping temporary_circuit.physical_to_logical = self.circuit_map.physical_to_logical self._add_swaps(candidate, temporary_circuit) temporary_dag = deepcopy(self._dag) @@ -525,7 +508,6 @@ def _compute_cost(self, candidate: tuple): all_executed = True for block in temporary_front_layer: if ( - # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) temporary_circuit.get_physical_qubits(block) in self.connectivity.edges or not temporary_circuit.circuit_blocks.search_by_index( @@ -553,7 +535,6 @@ def _check_execution(self): executable_blocks = [] for block in self._front_layer: if ( - # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) self.circuit_map.get_physical_qubits(block) in self.connectivity.edges or not self.circuit_map.circuit_blocks.search_by_index(block).entangled ): @@ -603,7 +584,6 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): initial_layout (dict): initial physical-to-logical qubit mapping. """ - # 1# Relabel the nodes of the connectivity graph self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) copied_circuit = circuit.copy(deep=True) @@ -639,7 +619,6 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - # 1# use l2p to get physical qubit numbers self.circuit_map.logical_to_physical[qubit] for qubit in original_qubits ) @@ -763,7 +742,6 @@ def _preprocessing(self, circuit: Circuit, initial_layout: dict): initial_layout (dict): initial physical-to-logical qubit mapping. """ - # 1# Relabel the nodes of the connectivity graph self.connectivity = _relabel_connectivity(self.connectivity, initial_layout) copied_circuit = circuit.copy(deep=True) @@ -801,7 +779,6 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - # 1# use l2p to get physical qubit numbers self.circuit_map.logical_to_physical[qubit] for qubit in original_qubits ) @@ -838,8 +815,6 @@ def _get_dag_layer(self, n_layer, qubits=False): (list): list of block numbers or target qubits. """ - # 3# depend on the 'qubits' flag, return the block number or target qubits - # 3# return target qubits -> to avoid using get_physical_qubits(block_num) if qubits: return [ node[1]["qubits"] @@ -853,7 +828,6 @@ def _find_new_mapping(self): """Find the new best mapping by adding one swap.""" candidates_evaluation = {} - # 4# use shallow copy self._memory_map.append(self.circuit_map.physical_to_logical.copy()) for candidate in self._swap_candidates(): candidates_evaluation[candidate] = self._compute_cost(candidate) @@ -872,28 +846,19 @@ def _find_new_mapping(self): def _compute_cost(self, candidate: int): """Compute the cost associated to a possible SWAP candidate.""" - # 2# use CircuitMap for temporary circuit to save time - # 2# no gates, no block decomposition, no Circuit object - # 2# just logical-physical mapping temporary_circuit = CircuitMap(temp=True) - - # 1# copy the current physical to logical mapping temporary_circuit.physical_to_logical = self.circuit_map.physical_to_logical temporary_circuit.update(candidate) - # 1# use p2l to check if the mapping is already in the memory if temporary_circuit.physical_to_logical in self._memory_map: return float("inf") tot_distance = 0.0 weight = 1.0 for layer in range(self.lookahead + 1): - # 3# return gates' target qubit pairs in the layer - # 3# to avoid using get_physical_qubits(block_num) layer_gates = self._get_dag_layer(layer, qubits=True) avg_layer_distance = 0.0 for lq_pair in layer_gates: - # 3# logical qubit pairs to node numbers (physical qubit pairs) in the connectivity graph qubits = temporary_circuit.logical_pair_to_physical(lq_pair) avg_layer_distance += ( max(self._delta_register[i] for i in qubits) @@ -915,7 +880,6 @@ def _swap_candidates(self): (list): list of candidates. """ candidates = [] - # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) for block in self._front_layer: for qubit in self.circuit_map.get_physical_qubits(block): for connected in self.connectivity.neighbors(qubit): @@ -941,7 +905,6 @@ def _check_execution(self): executable_blocks = [] for block in self._front_layer: if ( - # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) self.circuit_map.get_physical_qubits(block) in self.connectivity.edges or not self.circuit_map.circuit_blocks.search_by_index(block).entangled ): @@ -984,8 +947,6 @@ def _shortest_path_routing(self): shortest_path_qubits = None for block in self._front_layer: - # 3# return node numbers (physical qubits) in the connectivity graph - # 3# might be changed to use _get_dag_layer(qubits=True) to avoid using get_physical_qubits(block_num) q1, q2 = self.circuit_map.get_physical_qubits(block) distance = self._dist_matrix[q1, q2] @@ -998,7 +959,6 @@ def _shortest_path_routing(self): ) # move q1 - # 1# qubit moving algorithm is changed q1 = self.circuit_map.physical_to_logical[shortest_path[0]] for q2 in shortest_path[1:-1]: self.circuit_map.update((q1, self.circuit_map.physical_to_logical[q2])) @@ -1019,7 +979,6 @@ def _create_dag(gates_qubits_pairs: list): dag = nx.DiGraph() dag.add_nodes_from(range(len(gates_qubits_pairs))) - # 3# additionally store target qubits of the gates for i in range(len(gates_qubits_pairs)): dag.nodes[i]["qubits"] = gates_qubits_pairs[i] @@ -1051,7 +1010,6 @@ def _remove_redundant_connections(dag: nx.DiGraph): (:class:`networkx.DiGraph`): reduced dag. """ new_dag = nx.DiGraph() - # 3# add nodes with attributes new_dag.add_nodes_from(dag.nodes(data=True)) transitive_reduction = nx.transitive_reduction(dag) new_dag.add_edges_from(transitive_reduction.edges) From fced73c8d772cc5468a24e791f85ae2c4ad5e39a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 16:14:43 +0000 Subject: [PATCH 22/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/qibo/transpiler/router.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/qibo/transpiler/router.py b/src/qibo/transpiler/router.py index fc05ea0abd..b1aad8fc69 100644 --- a/src/qibo/transpiler/router.py +++ b/src/qibo/transpiler/router.py @@ -619,8 +619,7 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - self.circuit_map.logical_to_physical[qubit] - for qubit in original_qubits + self.circuit_map.logical_to_physical[qubit] for qubit in original_qubits ) routed_circuit.add( measurement.on_qubits(dict(zip(original_qubits, routed_qubits))) @@ -779,8 +778,7 @@ def _append_final_measurements(self, routed_circuit: Circuit): for measurement in self._final_measurements: original_qubits = measurement.qubits routed_qubits = list( - self.circuit_map.logical_to_physical[qubit] - for qubit in original_qubits + self.circuit_map.logical_to_physical[qubit] for qubit in original_qubits ) routed_circuit.add( measurement.on_qubits(dict(zip(original_qubits, routed_qubits)))