def _mpl(graph: PyGraph, self_loop: bool, **kwargs): """ Auxiliary function for drawing the lattice using matplotlib. Args: graph : graph to be drawn. self_loop : Draw self-loops, which are edges connecting a node to itself. **kwargs : Kwargs for drawing the lattice. Raises: MissingOptionalLibraryError: Requires matplotlib. """ # pylint: disable=unused-import from matplotlib import pyplot as plt if not self_loop: self_loops = [(i, i) for i in range(graph.num_nodes()) if graph.has_edge(i, i)] graph.remove_edges_from(self_loops) mpl_draw( graph=graph, **kwargs, ) plt.draw()
def _mpl(graph: PyGraph, self_loop: bool, **kwargs): """ Auxiliary function for drawing the lattice using matplotlib. Args: graph : graph to be drawn. self_loop : Draw self-loops, which are edges connecting a node to itself. **kwargs : Kwargs for drawing the lattice. Raises: MissingOptionalLibraryError: Requires matplotlib. """ if not HAS_MATPLOTLIB: raise MissingOptionalLibraryError( libname="Matplotlib", name="_mpl", pip_install="pip install matplotlib" ) from matplotlib import pyplot as plt if not self_loop: self_loops = [(i, i) for i in range(graph.num_nodes()) if graph.has_edge(i, i)] graph.remove_edges_from(self_loops) mpl_draw( graph=graph, **kwargs, ) plt.draw()
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 __init__(self, graph: PyGraph) -> None: """ Args: graph: Input graph for Lattice. `graph.multigraph` must be False. Raises: ValueError: If `graph.multigraph` is True for a given graph, it is invalid. """ if graph.multigraph: raise ValueError( f"Invalid `graph.multigraph` {graph.multigraph} is given. " "`graph.multigraph` must be `False`." ) if graph.edges() == [None] * graph.num_edges(): weighted_edges = [edge + (1.0,) for edge in graph.edge_list()] for start, end, weight in weighted_edges: graph.update_edge(start, end, weight) self._graph = graph self.pos: Optional[dict] = None
def test_from_nodes_and_edges(self): """Test from_nodes_edges.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(6)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (0, 2, -1.0), (2, 3, 2.0), (4, 2, -1.0), (4, 4, 3.0), (2, 5, -1.0), ] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) target_num_nodes = 6 target_weighted_edge_list = [ (2, 5, -1.0), (4, 4, 3), (4, 2, -1.0), (2, 3, 2.0), (0, 2, -1.0), (0, 1, 1.0 + 1.0j), ] target_lattice = Lattice.from_nodes_and_edges( target_num_nodes, target_weighted_edge_list) self.assertTrue( is_isomorphic(lattice.graph, target_lattice.graph, edge_matcher=lambda x, y: x == y))
def test_from_parameters(self): """Test from_parameters.""" coupling_matrix = np.array([[1.0, 1.0 + 1.0j, 2.0 - 2.0j], [1.0 - 1.0j, 0.0, 0.0], [2.0 + 2.0j, 0.0, 1.0]]) ism = cast(IsingModel, IsingModel.from_parameters(coupling_matrix)) with self.subTest("Check the graph."): target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(3)) target_weight = [(0, 0, 1.0), (0, 1, 1.0 + 1.0j), (0, 2, 2.0 - 2.0j), (2, 2, 1.0)] target_graph.add_edges_from(target_weight) self.assertTrue( is_isomorphic(ism.lattice.graph, target_graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the coupling matrix."): assert_array_equal(ism.coupling_matrix(), coupling_matrix) with self.subTest("Check the second q op representation."): coupling = [ ("Z_0 Z_1", 1.0 + 1.0j), ("Z_0 Z_2", 2.0 - 2.0j), ("X_0", 1.0), ("X_2", 1.0), ] ham = coupling self.assertSetEqual(set(ham), set(ism.second_q_ops().to_list()))
def test_nonnumeric_weight_raises(self): """Test the initialization with a graph with non-numeric edge weights raises.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(3)) graph.add_edges_from([(0, 1, 1), (1, 2, "banana")]) with self.assertRaises(ValueError): _ = Lattice(graph)
def test_init(self): """Test init.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(3)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (0, 2, -1.0), (1, 1, 2.0), ] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) fhm = FermiHubbardModel(lattice, onsite_interaction=10.0) with self.subTest("Check the graph."): self.assertTrue( is_isomorphic(fhm.lattice.graph, lattice.graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the hopping matrix"): hopping_matrix = fhm.hopping_matrix() target_matrix = np.array([[0.0, 1.0 + 1.0j, -1.0], [1.0 - 1.0j, 2.0, 0.0], [-1.0, 0.0, 0.0]]) assert_array_equal(hopping_matrix, target_matrix) with self.subTest("Check the second q op representation."): hopping = [ ("+_0 -_2", 1.0 + 1.0j), ("-_0 +_2", -(1.0 - 1.0j)), ("+_0 -_4", -1.0), ("-_0 +_4", 1.0), ("+_1 -_3", 1.0 + 1.0j), ("-_1 +_3", -(1.0 - 1.0j)), ("+_1 -_5", -1.0), ("-_1 +_5", 1.0), ("+_2 -_2", 2.0), ("+_3 -_3", 2.0), ] interaction = [ ("+_0 -_0 +_1 -_1", 10.0), ("+_2 -_2 +_3 -_3", 10.0), ("+_4 -_4 +_5 -_5", 10.0), ] ham = hopping + interaction self.assertSetEqual( set(ham), set(fhm.second_q_ops(display_format="sparse").to_list()))
def test_from_parameters(self): """Test from_parameters.""" hopping_matrix = np.array([[1.0, 1.0 + 1.0j, 2.0 + 2.0j], [1.0 - 1.0j, 0.0, 0.0], [2.0 - 2.0j, 0.0, 1.0]]) onsite_interaction = 10.0 fhm = FermiHubbardModel.from_parameters(hopping_matrix, onsite_interaction) with self.subTest("Check the graph."): target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(3)) target_weight = [(0, 0, 1.0), (0, 1, 1.0 + 1.0j), (0, 2, 2.0 + 2.0j), (2, 2, 1.0)] target_graph.add_edges_from(target_weight) self.assertTrue( is_isomorphic(fhm.lattice.graph, target_graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the hopping matrix."): assert_array_equal(fhm.hopping_matrix(), hopping_matrix) with self.subTest("Check the second q op representation."): hopping = [ ("+_0 -_2", 1.0 + 1.0j), ("-_0 +_2", -(1.0 - 1.0j)), ("+_0 -_4", 2.0 + 2.0j), ("-_0 +_4", -(2.0 - 2.0j)), ("+_1 -_3", 1.0 + 1.0j), ("-_1 +_3", -(1.0 - 1.0j)), ("+_1 -_5", 2.0 + 2.0j), ("-_1 +_5", -(2.0 - 2.0j)), ("+_0 -_0", 1.0), ("+_1 -_1", 1.0), ("+_4 -_4", 1.0), ("+_5 -_5", 1.0), ] interaction = [ ("+_0 -_0 +_1 -_1", onsite_interaction), ("+_2 -_2 +_3 -_3", onsite_interaction), ("+_4 -_4 +_5 -_5", onsite_interaction), ] ham = hopping + interaction self.assertSetEqual( set(ham), set(fhm.second_q_ops(display_format="sparse").to_list()))
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
def test_to_adjacency_matrix(self): """Test to_adjacency_matrix.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(3)) weighted_edge_list = [(0, 1, 1.0 + 1.0j), (0, 2, -1.0), (2, 2, 3)] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) target_matrix = np.array([[0, 1 + 1j, -1.0], [1 - 1j, 0, 0], [-1.0, 0, 3.0]]) assert_array_equal(lattice.to_adjacency_matrix(weighted=True), target_matrix) target_matrix = np.array([[0, 1, 1], [1, 0, 0], [1, 0, 1]]) assert_array_equal(lattice.to_adjacency_matrix(), target_matrix)
def from_nodes_and_edges( cls, num_nodes: int, weighted_edges: List[Tuple[int, int, complex]] ) -> "Lattice": """Return an instance of Lattice from the number of nodes and the list of edges. Args: num_nodes: The number of nodes. weighted_edges: A list of tuples consisting of two nodes and the weight between them. Returns: Lattice generated from lists of nodes and edges. """ graph = PyGraph(multigraph=False) graph.add_nodes_from(range(num_nodes)) graph.add_edges_from(weighted_edges) return cls(graph)
def test_from_networkx(self): """Test initialization from a networkx graph.""" graph = nx.Graph() graph.add_nodes_from(range(5)) graph.add_edges_from([(i, i + 1) for i in range(4)]) lattice = Lattice(graph) target_graph = PyGraph() target_graph.add_nodes_from(range(5)) target_graph.add_edges_from([(i, i + 1, 1) for i in range(4)]) self.assertTrue( is_isomorphic(lattice.graph, target_graph, edge_matcher=lambda x, y: x == y))
def _generate_lattice_from_parameters(interaction_matrix: np.ndarray): # make a graph from the interaction matrix. # This should be replaced by from_adjacency_matrix of retworkx. shape = interaction_matrix.shape if len(shape) != 2 or shape[0] != shape[1]: raise ValueError( f"Invalid shape of `interaction_matrix`, {shape}, is given." "It must be a square matrix.") graph = PyGraph(multigraph=False) graph.add_nodes_from(range(shape[0])) for source_index in range(shape[0]): for target_index in range(source_index, shape[0]): weight = interaction_matrix[source_index, target_index] if not weight == 0.0: graph.add_edge(source_index, target_index, weight) return Lattice(graph)
def test_copy(self): """Test test_copy.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(6)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (0, 2, -1.0), (2, 3, 2.0), (2, 4, -1.0), (4, 4, 3.0), (2, 5, -1.0), ] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) lattice_copy = lattice.copy() self.assertTrue( is_isomorphic(lattice_copy.graph, graph, edge_matcher=lambda x, y: x == y))
def test_init(self): """Test init.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(3)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (0, 2, -1.0), (1, 1, 2.0), ] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) ism = IsingModel(lattice) with self.subTest("Check the graph."): self.assertTrue( is_isomorphic(ism.lattice.graph, lattice.graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the coupling matrix"): coupling_matrix = ism.coupling_matrix() target_matrix = np.array([[0.0, 1.0 + 1.0j, -1.0], [1.0 - 1.0j, 2.0, 0.0], [-1.0, 0.0, 0.0]]) assert_array_equal(coupling_matrix, target_matrix) with self.subTest("Check the second q op representation."): coupling = [ ("Z_0 Z_1", 1.0 + 1.0j), ("Z_0 Z_2", -1.0), ("X_1", 2.0), ] ham = coupling self.assertSetEqual(set(ham), set(ism.second_q_ops().to_list()))
def from_json(cls, s: str) -> 'Topology': """Initializes from a JSON string.""" # load dict from JSON string data = orjson.loads(s) # validate type _type = data.pop("type") if _type != cls.__name__: raise TypeError(f"cannot deserialize from type `{_type}`") # initialize an empty graph graph = PyGraph() # process atoms for atom in data["atoms"]: graph.add_node(Atom.from_json(orjson.dumps(atom))) # process bonds for bond in data["bonds"]: bond = Bond.from_json(orjson.dumps(bond)) graph.add_edge(bond.indices[0], bond.indices[1], edge=bond) # return instance return cls(graph)
def test_init(self): """Test init.""" rows = 3 cols = 2 edge_parameter = (1.0 + 1.0j, 2.0 + 2.0j) onsite_parameter = 1.0 boundary_condition = (BoundaryCondition.PERIODIC, BoundaryCondition.OPEN) square = SquareLattice(rows, cols, edge_parameter, onsite_parameter, boundary_condition) with self.subTest("Check the graph."): target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(6)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (1, 2, 1.0 + 1.0j), (0, 2, 1.0 - 1.0j), (3, 4, 1.0 + 1.0j), (4, 5, 1.0 + 1.0j), (3, 5, 1.0 - 1.0j), (0, 3, 2.0 + 2.0j), (1, 4, 2.0 + 2.0j), (2, 5, 2.0 + 2.0j), (0, 0, 1.0), (1, 1, 1.0), (2, 2, 1.0), (3, 3, 1.0), (4, 4, 1.0), (5, 5, 1.0), ] target_graph.add_edges_from(weighted_edge_list) self.assertTrue( is_isomorphic(square.graph, target_graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the number of nodes."): self.assertEqual(square.num_nodes, 6) with self.subTest("Check the set of nodes."): self.assertSetEqual(set(square.node_indexes), set(range(6))) with self.subTest("Check the set of weights."): target_set = { (0, 1, 1.0 + 1.0j), (1, 2, 1.0 + 1.0j), (0, 2, 1.0 - 1.0j), (3, 4, 1.0 + 1.0j), (4, 5, 1.0 + 1.0j), (3, 5, 1.0 - 1.0j), (0, 3, 2.0 + 2.0j), (1, 4, 2.0 + 2.0j), (2, 5, 2.0 + 2.0j), (0, 0, 1.0), (1, 1, 1.0), (2, 2, 1.0), (3, 3, 1.0), (4, 4, 1.0), (5, 5, 1.0), } self.assertSetEqual(set(square.weighted_edge_list), target_set) with self.subTest("Check the adjacency matrix."): target_matrix = np.array([ [1.0, 1.0 + 1.0j, 1.0 - 1.0j, 2.0 + 2.0j, 0.0, 0.0], [1.0 - 1.0j, 1.0, 1.0 + 1.0j, 0.0, 2.0 + 2.0j, 0.0], [1.0 + 1.0j, 1.0 - 1.0j, 1.0, 0.0, 0.0, 2.0 + 2.0j], [2.0 - 2.0j, 0.0, 0.0, 1.0, 1.0 + 1.0j, 1.0 - 1.0j], [0.0, 2.0 - 2.0j, 0.0, 1.0 - 1.0j, 1.0, 1.0 + 1.0j], [0.0, 0.0, 2.0 - 2.0j, 1.0 + 1.0j, 1.0 - 1.0j, 1.0], ]) assert_array_equal(square.to_adjacency_matrix(weighted=True), target_matrix)
def test_uniform_parameters(self): """Test uniform_parameters.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(3)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (0, 2, -1.0), (1, 1, 2.0), ] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) uniform_ism = cast( IsingModel, IsingModel.uniform_parameters( lattice, uniform_interaction=1.0 + 1.0j, uniform_onsite_potential=0.0, ), ) with self.subTest("Check the graph."): target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(3)) target_weight = [ (0, 1, 1.0 + 1.0j), (0, 2, 1.0 + 1.0j), (0, 0, 0.0), (1, 1, 0.0), (2, 2, 0.0), ] target_graph.add_edges_from(target_weight) self.assertTrue( is_isomorphic(uniform_ism.lattice.graph, target_graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the coupling matrix."): coupling_matrix = uniform_ism.coupling_matrix() target_matrix = np.array([[0.0, 1.0 + 1.0j, 1.0 + 1.0j], [1.0 - 1.0j, 0.0, 0.0], [1.0 - 1.0j, 0.0, 0.0]]) assert_array_equal(coupling_matrix, target_matrix) with self.subTest("Check the second q op representation."): coupling = [ ("Z_0 Z_1", 1.0 + 1.0j), ("Z_0 Z_2", 1.0 + 1.0j), ("X_0", 0.0), ("X_1", 0.0), ("X_2", 0.0), ] ham = coupling self.assertSetEqual(set(ham), set(uniform_ism.second_q_ops().to_list()))
def __init__( self, size: Tuple[int, ...], edge_parameter: Union[complex, Tuple[complex, ...]] = 1.0, onsite_parameter: complex = 0.0, boundary_condition: Union[ BoundaryCondition, Tuple[BoundaryCondition, ...] ] = BoundaryCondition.OPEN, ) -> None: """ Args: size: Lengths of each dimension. edge_parameter: Weights on the edges in each direction. When it is a single value, it is interpreted as a tuple of the same length as `size` consisting of the same values. Defaults to 1.0. onsite_parameter: Weight on the self-loops, which are edges connecting a node to itself. This is uniform over the lattice points. Defaults to 0.0. boundary_condition: Boundary condition for each dimension. The available boundary conditions are: BoundaryCondition.OPEN, BoundaryCondition.PERIODIC. When it is a single value, it is interpreted as a tuple of the same length as `size` consisting of the same values. Defaults to BoundaryCondition.OPEN. Raises: ValueError: When edge parameter or boundary condition is a tuple, the length of that is not the same as that of size. """ self._dim = len(size) self._size = size # edge parameter if isinstance(edge_parameter, (int, float, complex)): edge_parameter = (edge_parameter,) * self._dim elif isinstance(edge_parameter, tuple): if len(edge_parameter) != self._dim: raise ValueError( "size mismatch, " f"`edge_parameter`: {len(edge_parameter)}, `size`: {self._dim}." "The length of `edge_parameter` must be the same as that of size." ) self._edge_parameter = edge_parameter self._onsite_parameter = onsite_parameter # boundary condition if isinstance(boundary_condition, BoundaryCondition): boundary_condition = (boundary_condition,) * self._dim elif isinstance(boundary_condition, tuple): if len(boundary_condition) != self._dim: raise ValueError( "size mismatch, " f"`boundary_condition`: {len(boundary_condition)}, `size`: {self._dim}." "The length of `boundary_condition` must be the same as that of size." ) self._boundary_condition = boundary_condition graph = PyGraph(multigraph=False) graph.add_nodes_from(range(np.prod(size))) # add edges excluding the boundary edges bulk_edge_list = self._bulk_edges() graph.add_edges_from(bulk_edge_list) # add self-loops. self_loop_list = self._self_loops() graph.add_edges_from(self_loop_list) # add edges that cross the boundaries boundary_edge_list = self._create_boundary_edges() graph.add_edges_from(boundary_edge_list) # a list of edges that depend on the boundary condition self._boundary_edges = [(edge[0], edge[1]) for edge in boundary_edge_list] super().__init__(graph) # default position for one and two-dimensional cases. self.pos = self._default_position()
def __init__( self, rows: int, cols: int, edge_parameter: Union[complex, Tuple[complex, complex, complex]] = 1.0, onsite_parameter: complex = 0.0, boundary_condition: BoundaryCondition = BoundaryCondition.OPEN, ) -> None: """ Args: rows: Length of the x direction. cols: Length of the y direction. edge_parameter: Weights on the edges in x, y and diagonal directions. This is specified as a tuple of length 3 or a single value. When it is a single value, it is interpreted as a tuple of length 3 consisting of the same values. Defaults to 1.0, onsite_parameter: Weight on the self-loops, which are edges connecting a node to itself. Defaults to 0.0. boundary_condition: Boundary condition for the lattice. The available boundary conditions are: BoundaryCondition.OPEN, BoundaryCondition.PERIODIC. Defaults to BoundaryCondition.OPEN. Raises: ValueError: Given size, edge parameter or boundary condition are invalid values. """ self.rows = rows self.cols = cols self.size = (rows, cols) self.dim = 2 self.boundary_condition = boundary_condition if rows < 2 or cols < 2 or (rows, cols) == (2, 2): # If it's True, triangular lattice can't be well defined. raise ValueError("Both of `rows` and `cols` must not be (2, 2)" "and must be greater than or equal to 2.") if isinstance(edge_parameter, (int, float, complex)): edge_parameter = (edge_parameter, edge_parameter, edge_parameter) elif isinstance(edge_parameter, tuple): if len(edge_parameter) != 3: raise ValueError( f"The length of `edge_parameter` must be 3, not {len(edge_parameter)}." ) self.edge_parameter = edge_parameter self.onsite_parameter = onsite_parameter graph = PyGraph(multigraph=False) graph.add_nodes_from(range(np.prod(self.size))) # add edges excluding the boundary edges bulk_edges = self._bulk_edges() graph.add_edges_from(bulk_edges) # add self-loops self_loop_list = self._self_loops() graph.add_edges_from(self_loop_list) # add edges that cross the boundaries boundary_edge_list = self._boundary_edges() graph.add_edges_from(boundary_edge_list) # a list of edges that depend on the boundary condition self.boundary_edges = [(edge[0], edge[1]) for edge in boundary_edge_list] super().__init__(graph) # default position self.pos = self._default_position()
def test_init(self): """Test init.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(6)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (0, 2, -1.0), (2, 3, 2.0), (2, 4, -1.0), (4, 4, 3.0), (2, 5, -1.0), ] graph.add_edges_from(weighted_edge_list) lattice = Lattice(graph) with self.subTest("Check the type of lattice."): self.assertIsInstance(lattice, Lattice) with self.subTest("Check graph."): target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(6)) target_weighted_edge_list = [ (4, 4, 3.0), (0, 1, 1 + 1j), (2, 3, 2.0), (2, 4, -1.0), (2, 5, -1.0), (0, 2, -1), ] target_graph.add_edges_from(target_weighted_edge_list) self.assertTrue( is_isomorphic(lattice.graph, target_graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the number of nodes."): self.assertEqual(lattice.num_nodes, 6) with self.subTest("Check the set of nodes."): self.assertSetEqual(set(lattice.node_indexes), set(range(6))) with self.subTest("Check the set of weights."): target_set = { (0, 1, 1 + 1j), (4, 4, 3), (2, 5, -1.0), (0, 2, -1.0), (2, 3, 2.0), (2, 4, -1.0), } self.assertEqual(set(lattice.weighted_edge_list), target_set)
def test_edges_removed(self): """Test the initialization with a graph where edges have been removed.""" graph = PyGraph(multigraph=False) graph.add_nodes_from(range(3)) graph.add_edges_from([(0, 1, 1), (1, 2, 1)]) graph.remove_edge_from_index(0) lattice = Lattice(graph) target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(3)) target_graph.add_edges_from([(1, 2, 1)]) self.assertTrue( is_isomorphic(lattice.graph, target_graph, edge_matcher=lambda x, y: x == y))
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 __init__(self, graph: Optional[PyGraph] = None) -> None: if graph is None: graph = PyGraph() self._graph = graph
def test_init(self): """Test init.""" size = (2, 2, 2) edge_parameter = (1.0 + 1.0j, 0.0, -2.0 - 2.0j) onsite_parameter = 5.0 boundary_condition = ( BoundaryCondition.OPEN, BoundaryCondition.PERIODIC, BoundaryCondition.OPEN, ) hyper_cubic = HyperCubicLattice(size, edge_parameter, onsite_parameter, boundary_condition) with self.subTest("Check the graph."): target_graph = PyGraph(multigraph=False) target_graph.add_nodes_from(range(8)) weighted_edge_list = [ (0, 1, 1.0 + 1.0j), (2, 3, 1.0 + 1.0j), (4, 5, 1.0 + 1.0j), (6, 7, 1.0 + 1.0j), (0, 2, 0.0), (1, 3, 0.0), (4, 6, 0.0), (5, 7, 0.0), (0, 4, -2.0 - 2.0j), (1, 5, -2.0 - 2.0j), (2, 6, -2.0 - 2.0j), (3, 7, -2.0 - 2.0j), (0, 0, 5.0), (1, 1, 5.0), (2, 2, 5.0), (3, 3, 5.0), (4, 4, 5.0), (5, 5, 5.0), (6, 6, 5.0), (7, 7, 5.0), ] target_graph.add_edges_from(weighted_edge_list) self.assertTrue( is_isomorphic(hyper_cubic.graph, target_graph, edge_matcher=lambda x, y: x == y)) with self.subTest("Check the number of nodes."): self.assertEqual(hyper_cubic.num_nodes, 8) with self.subTest("Check the set of nodes."): self.assertSetEqual(set(hyper_cubic.node_indexes), set(range(8))) with self.subTest("Check the set of weights."): target_set = { (0, 1, 1.0 + 1.0j), (2, 3, 1.0 + 1.0j), (4, 5, 1.0 + 1.0j), (6, 7, 1.0 + 1.0j), (0, 2, 0.0), (1, 3, 0.0), (4, 6, 0.0), (5, 7, 0.0), (0, 4, -2.0 - 2.0j), (1, 5, -2.0 - 2.0j), (2, 6, -2.0 - 2.0j), (3, 7, -2.0 - 2.0j), (0, 0, 5.0), (1, 1, 5.0), (2, 2, 5.0), (3, 3, 5.0), (4, 4, 5.0), (5, 5, 5.0), (6, 6, 5.0), (7, 7, 5.0), } self.assertSetEqual(set(hyper_cubic.weighted_edge_list), target_set) with self.subTest("Check the adjacency matrix."): target_matrix = np.array([ [5.0, 1.0 + 1.0j, 0.0, 0.0, -2.0 - 2.0j, 0.0, 0.0, 0.0], [1.0 - 1.0j, 5.0, 0.0, 0.0, 0.0, -2.0 - 2.0j, 0.0, 0.0], [0.0, 0.0, 5.0, 1.0 + 1.0j, 0.0, 0.0, -2.0 - 2.0j, 0.0], [0.0, 0.0, 1.0 - 1.0j, 5.0, 0.0, 0.0, 0.0, -2.0 - 2.0j], [-2.0 + 2.0j, 0.0, 0.0, 0.0, 5.0, 1.0 + 1.0j, 0.0, 0.0], [0.0, -2.0 + 2.0j, 0.0, 0.0, 1.0 - 1.0j, 5.0, 0.0, 0.0], [0.0, 0.0, -2.0 + 2.0j, 0.0, 0.0, 0.0, 5.0, 1.0 + 1.0j], [0.0, 0.0, 0.0, -2.0 + 2.0j, 0.0, 0.0, 1.0 - 1.0j, 5.0], ]) assert_array_equal(hyper_cubic.to_adjacency_matrix(weighted=True), target_matrix)
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