def run(self, dag): """run the layout method""" qubits = dag.qubits qubit_indices = {qubit: index for index, qubit in enumerate(qubits)} interactions = [] for node in dag.op_nodes(include_directives=False): len_args = len(node.qargs) if len_args == 2: interactions.append((qubit_indices[node.qargs[0]], qubit_indices[node.qargs[1]])) if len_args >= 3: raise TranspilerError( "VF2Layout only can handle 2-qubit gates or less. Node " f"{node.name} ({node}) is {len_args}-qubit") if self.strict_direction: cm_graph = self.coupling_map.graph im_graph = PyDiGraph(multigraph=False) else: cm_graph = self.coupling_map.graph.to_undirected() im_graph = PyGraph(multigraph=False) cm_nodes = list(cm_graph.node_indexes()) if self.seed != -1: random.Random(self.seed).shuffle(cm_nodes) shuffled_cm_graph = type(cm_graph)() shuffled_cm_graph.add_nodes_from(cm_nodes) new_edges = [(cm_nodes[edge[0]], cm_nodes[edge[1]]) for edge in cm_graph.edge_list()] shuffled_cm_graph.add_edges_from_no_data(new_edges) cm_nodes = [ k for k, v in sorted(enumerate(cm_nodes), key=lambda item: item[1]) ] cm_graph = shuffled_cm_graph im_graph.add_nodes_from(range(len(qubits))) im_graph.add_edges_from_no_data(interactions) mappings = vf2_mapping(cm_graph, im_graph, subgraph=True, id_order=False, induced=False) try: mapping = next(mappings) stop_reason = "solution found" layout = Layout({ qubits[im_i]: cm_nodes[cm_i] for cm_i, im_i in mapping.items() }) self.property_set["layout"] = layout for reg in dag.qregs.values(): self.property_set["layout"].add_register(reg) except StopIteration: stop_reason = "nonexistent solution" self.property_set["VF2Layout_stop_reason"] = stop_reason
def _swap(self, node1: int, node2: int, tokens: MutableMapping[int, int], digraph: rx.PyDiGraph, sub_digraph: rx.PyDiGraph, todo_nodes: MutableSet[int]) -> None: """Swap two nodes, maintaining the data structures.""" assert self.graph.has_edge( node1, node2), "The swap is being performed on a non-existent edge." # Swap the tokens on the nodes, taking into account no-token nodes. token1 = tokens.pop(node1, None) token2 = tokens.pop(node2, None) if token2 is not None: tokens[node1] = token2 if token1 is not None: tokens[node2] = token1 # Recompute the edges incident to node 1 and 2 for node in [node1, node2]: digraph.remove_edges_from([ (node, successor) for successor in digraph.successor_indices(node) ]) sub_digraph.remove_edges_from([ (node, successor) for successor in sub_digraph.successor_indices(node) ]) self._add_token_edges(node, tokens, digraph, sub_digraph) if node in tokens and tokens[node] != node: todo_nodes.add(node) elif node in todo_nodes: todo_nodes.remove(node)
def _add_token_edges(self, node: int, tokens: Mapping[int, int], digraph: rx.PyDiGraph, sub_digraph: rx.PyDiGraph) -> None: """Add diedges to the graph wherever a token can be moved closer to its destination.""" if node not in tokens: return if tokens[node] == node: digraph.extend_from_edge_list([(node, node)]) return for neighbor in self.graph.neighbors(node): if self.distance(neighbor, tokens[node]) < self.distance( node, tokens[node]): digraph.extend_from_edge_list([(node, neighbor)]) sub_digraph.extend_from_edge_list([(node, neighbor)])
def run(self, dag): """run the layout method""" if self.target is None and (self.coupling_map is None or self.properties is None): raise TranspilerError( "A target must be specified or a coupling map and properties must be provided" ) if not self.strict_direction and self.avg_error_map is None: self.avg_error_map = vf2_utils.build_average_error_map( self.target, self.properties, self.coupling_map ) result = vf2_utils.build_interaction_graph(dag, self.strict_direction) if result is None: self.property_set["VF2PostLayout_stop_reason"] = VF2PostLayoutStopReason.MORE_THAN_2Q return im_graph, im_graph_node_map, reverse_im_graph_node_map = result if self.target is not None: if self.strict_direction: cm_graph = PyDiGraph(multigraph=False) else: cm_graph = PyGraph(multigraph=False) cm_graph.add_nodes_from( [self.target.operation_names_for_qargs((i,)) for i in range(self.target.num_qubits)] ) for qargs in self.target.qargs: len_args = len(qargs) # If qargs == 1 we already populated it and if qargs > 2 there are no instructions # using those in the circuit because we'd have already returned by this point if len_args == 2: cm_graph.add_edge( qargs[0], qargs[1], self.target.operation_names_for_qargs(qargs) ) cm_nodes = list(cm_graph.node_indexes()) else: cm_graph, cm_nodes = vf2_utils.shuffle_coupling_graph( self.coupling_map, self.seed, self.strict_direction ) logger.debug("Running VF2 to find post transpile mappings") if self.target and self.strict_direction: mappings = vf2_mapping( cm_graph, im_graph, node_matcher=_target_match, edge_matcher=_target_match, subgraph=True, id_order=False, induced=False, call_limit=self.call_limit, ) else: mappings = vf2_mapping( cm_graph, im_graph, subgraph=True, id_order=False, induced=False, call_limit=self.call_limit, ) chosen_layout = None initial_layout = Layout(dict(enumerate(dag.qubits))) try: if self.strict_direction: chosen_layout_score = self._score_layout( initial_layout, im_graph_node_map, reverse_im_graph_node_map, im_graph ) else: chosen_layout_score = vf2_utils.score_layout( self.avg_error_map, initial_layout, im_graph_node_map, reverse_im_graph_node_map, im_graph, self.strict_direction, ) # Circuit not in basis so we have nothing to compare against return here except KeyError: self.property_set[ "VF2PostLayout_stop_reason" ] = VF2PostLayoutStopReason.NO_SOLUTION_FOUND return logger.debug("Initial layout has score %s", chosen_layout_score) start_time = time.time() trials = 0 for mapping in mappings: trials += 1 logger.debug("Running trial: %s", trials) stop_reason = VF2PostLayoutStopReason.SOLUTION_FOUND layout = Layout( {reverse_im_graph_node_map[im_i]: cm_nodes[cm_i] for cm_i, im_i in mapping.items()} ) if self.strict_direction: layout_score = self._score_layout( layout, im_graph_node_map, reverse_im_graph_node_map, im_graph ) else: layout_score = vf2_utils.score_layout( self.avg_error_map, layout, im_graph_node_map, reverse_im_graph_node_map, im_graph, self.strict_direction, ) logger.debug("Trial %s has score %s", trials, layout_score) if layout_score < chosen_layout_score: logger.debug( "Found layout %s has a lower score (%s) than previous best %s (%s)", layout, layout_score, chosen_layout, chosen_layout_score, ) chosen_layout = layout chosen_layout_score = layout_score elapsed_time = time.time() - start_time if self.time_limit is not None and elapsed_time >= self.time_limit: logger.debug( "VFPostLayout has taken %s which exceeds configured max time: %s", elapsed_time, self.time_limit, ) break if chosen_layout is None: stop_reason = VF2PostLayoutStopReason.NO_SOLUTION_FOUND else: existing_layout = self.property_set["layout"] # If any ancillas in initial layout map them back to the final layout output if existing_layout is not None and len(existing_layout) > len(chosen_layout): virtual_bits = chosen_layout.get_virtual_bits() used_bits = set(virtual_bits.values()) num_qubits = len(cm_graph) for bit in dag.qubits: if len(chosen_layout) == len(existing_layout): break if bit not in virtual_bits: for i in range(num_qubits): if i not in used_bits: used_bits.add(i) chosen_layout.add(bit, i) break self.property_set["post_layout"] = chosen_layout self.property_set["VF2PostLayout_stop_reason"] = stop_reason
def _trial_map(self, digraph: rx.PyDiGraph, sub_digraph: rx.PyDiGraph, todo_nodes: MutableSet[int], tokens: MutableMapping[int, int]) -> Iterator[Swap[int]]: """Try to map the tokens to their destinations and minimize the number of swaps.""" def swap(node0: int, node1: int) -> None: """Swap two nodes, maintaining data structures. Args: node0: The first node node1: The second node """ self._swap(node0, node1, tokens, digraph, sub_digraph, todo_nodes) # Can't just iterate over todo_nodes, since it may change during iteration. steps = 0 while todo_nodes and steps <= 4 * len(self.graph)**2: todo_node_id = self.seed.integers(0, len(todo_nodes)) todo_node = tuple(todo_nodes)[todo_node_id] # Try to find a happy swap chain first by searching for a cycle, # excluding self-loops. # Note that if there are only unhappy swaps involving this todo_node, # then an unhappy swap must be performed at some point. # So it is not useful to globally search for all happy swap chains first. cycle = rx.digraph_find_cycle(sub_digraph, source=todo_node) if len(cycle) > 0: assert len(cycle) > 1, "The cycle was not happy." # We iterate over the cycle in reversed order, starting at the last edge. # The first edge is excluded. for edge in list(cycle)[-1:0:-1]: yield edge swap(edge[0], edge[1]) steps += len(cycle) - 1 else: # Try to find a node without a token to swap with. try: edge = next(edge for edge in rx.digraph_dfs_edges( sub_digraph, todo_node) if edge[1] not in tokens) # Swap predecessor and successor, because successor does not have a token yield edge swap(edge[0], edge[1]) steps += 1 except StopIteration: # Unhappy swap case cycle = rx.digraph_find_cycle(digraph, source=todo_node) assert len(cycle) == 1, "The cycle was not unhappy." unhappy_node = cycle[0][0] # Find a node that wants to swap with this node. try: predecessor = next( predecessor for predecessor in digraph.predecessor_indices( unhappy_node) if predecessor != unhappy_node) except StopIteration: logger.error( "Unexpected StopIteration raised when getting predecessors" "in unhappy swap case.") return yield unhappy_node, predecessor swap(unhappy_node, predecessor) steps += 1 if todo_nodes: raise RuntimeError( "Too many iterations while approximating the Token Swaps.")
def run(self, dag): """run the layout method""" if self.coupling_map is None: raise TranspilerError("coupling_map or target must be specified.") qubits = dag.qubits qubit_indices = {qubit: index for index, qubit in enumerate(qubits)} interactions = [] for node in dag.op_nodes(include_directives=False): len_args = len(node.qargs) if len_args == 2: interactions.append((qubit_indices[node.qargs[0]], qubit_indices[node.qargs[1]])) if len_args >= 3: self.property_set[ "VF2Layout_stop_reason"] = VF2LayoutStopReason.MORE_THAN_2Q return if self.strict_direction: cm_graph = self.coupling_map.graph im_graph = PyDiGraph(multigraph=False) else: cm_graph = self.coupling_map.graph.to_undirected() im_graph = PyGraph(multigraph=False) cm_nodes = list(cm_graph.node_indexes()) if self.seed != -1: random.Random(self.seed).shuffle(cm_nodes) shuffled_cm_graph = type(cm_graph)() shuffled_cm_graph.add_nodes_from(cm_nodes) new_edges = [(cm_nodes[edge[0]], cm_nodes[edge[1]]) for edge in cm_graph.edge_list()] shuffled_cm_graph.add_edges_from_no_data(new_edges) cm_nodes = [ k for k, v in sorted(enumerate(cm_nodes), key=lambda item: item[1]) ] cm_graph = shuffled_cm_graph im_graph.add_nodes_from(range(len(qubits))) im_graph.add_edges_from_no_data(interactions) # To avoid trying to over optimize the result by default limit the number # of trials based on the size of the graphs. For circuits with simple layouts # like an all 1q circuit we don't want to sit forever trying every possible # mapping in the search space if self.max_trials is None: im_graph_edge_count = len(im_graph.edge_list()) cm_graph_edge_count = len(cm_graph.edge_list()) self.max_trials = max(im_graph_edge_count, cm_graph_edge_count) + 15 logger.debug("Running VF2 to find mappings") mappings = vf2_mapping( cm_graph, im_graph, subgraph=True, id_order=False, induced=False, call_limit=self.call_limit, ) chosen_layout = None chosen_layout_score = None start_time = time.time() trials = 0 for mapping in mappings: trials += 1 logger.debug("Running trial: %s", trials) stop_reason = VF2LayoutStopReason.SOLUTION_FOUND layout = Layout({ qubits[im_i]: cm_nodes[cm_i] for cm_i, im_i in mapping.items() }) # If the graphs have the same number of nodes we don't need to score or do multiple # trials as the score heuristic currently doesn't weigh nodes based on gates on a # qubit so the scores will always all be the same if len(cm_graph) == len(im_graph): chosen_layout = layout break layout_score = self._score_layout(layout) logger.debug("Trial %s has score %s", trials, layout_score) if chosen_layout is None: chosen_layout = layout chosen_layout_score = layout_score elif layout_score < chosen_layout_score: logger.debug( "Found layout %s has a lower score (%s) than previous best %s (%s)", layout, layout_score, chosen_layout, chosen_layout_score, ) chosen_layout = layout chosen_layout_score = layout_score if self.max_trials > 0 and trials >= self.max_trials: logger.debug("Trial %s is >= configured max trials %s", trials, self.max_trials) break elapsed_time = time.time() - start_time if self.time_limit is not None and elapsed_time >= self.time_limit: logger.debug( "VF2Layout has taken %s which exceeds configured max time: %s", elapsed_time, self.time_limit, ) break if chosen_layout is None: stop_reason = VF2LayoutStopReason.NO_SOLUTION_FOUND else: self.property_set["layout"] = chosen_layout for reg in dag.qregs.values(): self.property_set["layout"].add_register(reg) self.property_set["VF2Layout_stop_reason"] = stop_reason
def parse_puzzle(p: str) -> PyDiGraph: g = PyDiGraph() vals = [[int(c) for c in row] for row in p.split("\n")] pos_idx = {} for y, row in enumerate(vals): for x, v in enumerate(row): pos_idx[(x, y)] = g.add_node(v) for y, row in enumerate(vals): for x, v in enumerate(row): n = pos_idx[(x, y)] if x > 0: g.add_edge(n, pos_idx[(x - 1, y)], vals[y][x - 1]) if y > 0: g.add_edge(n, pos_idx[(x, y - 1)], vals[y - 1][x]) if x < len(row) - 1: g.add_edge(n, pos_idx[(x + 1, y)], vals[y][x + 1]) if y < len(vals) - 1: g.add_edge(n, pos_idx[(x, y + 1)], vals[y + 1][x]) return g
def build_interaction_graph(dag, strict_direction=True): """Build an interaction graph from a dag.""" if strict_direction: im_graph = PyDiGraph(multigraph=False) else: im_graph = PyGraph(multigraph=False) im_graph_node_map = {} reverse_im_graph_node_map = {} for node in dag.op_nodes(include_directives=False): len_args = len(node.qargs) if len_args == 1: if node.qargs[0] not in im_graph_node_map: weight = defaultdict(int) weight[node.name] += 1 im_graph_node_map[node.qargs[0]] = im_graph.add_node(weight) reverse_im_graph_node_map[im_graph_node_map[ node.qargs[0]]] = node.qargs[0] else: im_graph[im_graph_node_map[node.qargs[0]]][node.op.name] += 1 if len_args == 2: if node.qargs[0] not in im_graph_node_map: im_graph_node_map[node.qargs[0]] = im_graph.add_node( defaultdict(int)) reverse_im_graph_node_map[im_graph_node_map[ node.qargs[0]]] = node.qargs[0] if node.qargs[1] not in im_graph_node_map: im_graph_node_map[node.qargs[1]] = im_graph.add_node( defaultdict(int)) reverse_im_graph_node_map[im_graph_node_map[ node.qargs[1]]] = node.qargs[1] edge = (im_graph_node_map[node.qargs[0]], im_graph_node_map[node.qargs[1]]) if im_graph.has_edge(*edge): im_graph.get_edge_data(*edge)[node.name] += 1 else: weight = defaultdict(int) weight[node.name] += 1 im_graph.add_edge(*edge, weight) if len_args > 2: return None return im_graph, im_graph_node_map, reverse_im_graph_node_map