Ejemplo n.º 1
0
    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)])
Ejemplo n.º 4
0
    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.")
Ejemplo n.º 6
0
    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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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