Repository: Qiskit/qiskit

Issue:
Add from_heavy_hex() and from_heavy_square() generator methods to CouplingMap
<!-- ⚠️ If you do not respect this template, your issue will be closed -->
<!-- ⚠️ Make sure to browse the opened and closed issues to confirm this idea does not exist. -->

### What is the expected enhancement?


In  [retworkx 0.10.0](https://qiskit.org/documentation/retworkx/release_notes.html#relnotes-0-10-0) new generator functions were added for quickly generating heavy hex and heavy square graphs: https://qiskit.org/documentation/retworkx/stubs/retworkx.generators.directed_heavy_hex_graph.html#retworkx.generators.directed_heavy_hex_graph  and https://qiskit.org/documentation/retworkx/stubs/retworkx.generators.directed_heavy_square_graph.html#retworkx.generators.directed_heavy_square_graph. We should leverage these fun and add 2 new constructor methods to the `CouplingMap` to easily create coupling maps using a heavy hex and heavy square (similar to the existing `from_grid` and `from_ring` methods) 


Discussion:

Resolving patch:
diff --git a/qiskit/transpiler/coupling.py b/qiskit/transpiler/coupling.py
--- a/qiskit/transpiler/coupling.py
+++ b/qiskit/transpiler/coupling.py
@@ -320,6 +320,56 @@ def from_grid(cls, num_rows, num_columns, bidirectional=True):
         )
         return cmap
 
+    @classmethod
+    def from_heavy_hex(cls, distance, bidirectional=True):
+        """Return a heavy hexagon graph coupling map
+
+        A heavy hexagon graph is described in:
+
+        https://journals.aps.org/prx/abstract/10.1103/PhysRevX.10.011022
+
+        Args:
+            distance (int): The code distance for the generated heavy hex
+                graph. The value for distance can be any odd positive integer.
+                The distance relates to the number of qubits by:
+                :math:`n = \\frac{5d^2 - 2d - 1}{2}` where :math:`n` is the
+                number of qubits and :math:`d` is the ``distance`` parameter.
+            bidirectional (bool): Whether the edges in the output coupling
+                graph are bidirectional or not. By default this is set to
+                ``True``
+        Returns:
+            CouplingMap: A heavy hex coupling graph
+        """
+        cmap = cls(description="heavy-hex")
+        cmap.graph = rx.generators.directed_heavy_hex_graph(distance, bidirectional=bidirectional)
+        return cmap
+
+    @classmethod
+    def from_heavy_square(cls, distance, bidirectional=True):
+        """Return a heavy square graph coupling map.
+
+        A heavy square graph is described in:
+
+        https://journals.aps.org/prx/abstract/10.1103/PhysRevX.10.011022
+
+        Args:
+            distance (int): The code distance for the generated heavy square
+                graph. The value for distance can be any odd positive integer.
+                The distance relates to the number of qubits by:
+                :math:`n = 3d^2 - 2d` where :math:`n` is the
+                number of qubits and :math:`d` is the ``distance`` parameter.
+            bidirectional (bool): Whether the edges in the output coupling
+                graph are bidirectional or not. By default this is set to
+                ``True``
+        Returns:
+            CouplingMap: A heavy square coupling graph
+        """
+        cmap = cls(description="heavy-square")
+        cmap.graph = rx.generators.directed_heavy_square_graph(
+            distance, bidirectional=bidirectional
+        )
+        return cmap
+
     def largest_connected_component(self):
         """Return a set of qubits in the largest connected component."""
         return max(rx.weakly_connected_components(self.graph), key=len)


Test patch:
diff --git a/test/python/transpiler/test_coupling.py b/test/python/transpiler/test_coupling.py
--- a/test/python/transpiler/test_coupling.py
+++ b/test/python/transpiler/test_coupling.py
@@ -196,6 +196,166 @@ def test_grid_factory_unidirectional(self):
         expected = [(0, 3), (0, 1), (3, 4), (1, 4), (1, 2), (4, 5), (2, 5)]
         self.assertEqual(set(edges), set(expected))
 
+    def test_heavy_hex_factory(self):
+        coupling = CouplingMap.from_heavy_hex(3, bidirectional=False)
+        edges = coupling.get_edges()
+        expected = [
+            (0, 9),
+            (0, 13),
+            (1, 13),
+            (1, 14),
+            (2, 14),
+            (3, 9),
+            (3, 15),
+            (4, 15),
+            (4, 16),
+            (5, 12),
+            (5, 16),
+            (6, 17),
+            (7, 17),
+            (7, 18),
+            (8, 12),
+            (8, 18),
+            (10, 14),
+            (10, 16),
+            (11, 15),
+            (11, 17),
+        ]
+        self.assertEqual(set(edges), set(expected))
+
+    def test_heavy_hex_factory_bidirectional(self):
+        coupling = CouplingMap.from_heavy_hex(3, bidirectional=True)
+        edges = coupling.get_edges()
+        expected = [
+            (0, 9),
+            (0, 13),
+            (1, 13),
+            (1, 14),
+            (2, 14),
+            (3, 9),
+            (3, 15),
+            (4, 15),
+            (4, 16),
+            (5, 12),
+            (5, 16),
+            (6, 17),
+            (7, 17),
+            (7, 18),
+            (8, 12),
+            (8, 18),
+            (9, 0),
+            (9, 3),
+            (10, 14),
+            (10, 16),
+            (11, 15),
+            (11, 17),
+            (12, 5),
+            (12, 8),
+            (13, 0),
+            (13, 1),
+            (14, 1),
+            (14, 2),
+            (14, 10),
+            (15, 3),
+            (15, 4),
+            (15, 11),
+            (16, 4),
+            (16, 5),
+            (16, 10),
+            (17, 6),
+            (17, 7),
+            (17, 11),
+            (18, 7),
+            (18, 8),
+        ]
+        self.assertEqual(set(edges), set(expected))
+
+    def test_heavy_square_factory(self):
+        coupling = CouplingMap.from_heavy_square(3, bidirectional=False)
+        edges = coupling.get_edges()
+        expected = [
+            (0, 15),
+            (1, 16),
+            (2, 11),
+            (3, 12),
+            (3, 17),
+            (4, 18),
+            (5, 11),
+            (6, 12),
+            (6, 19),
+            (7, 20),
+            (9, 15),
+            (9, 17),
+            (10, 16),
+            (10, 18),
+            (13, 17),
+            (13, 19),
+            (14, 18),
+            (14, 20),
+            (15, 1),
+            (16, 2),
+            (17, 4),
+            (18, 5),
+            (19, 7),
+            (20, 8),
+        ]
+        self.assertEqual(set(edges), set(expected))
+
+    def test_heavy_square_factory_bidirectional(self):
+        coupling = CouplingMap.from_heavy_square(3, bidirectional=True)
+        edges = coupling.get_edges()
+        expected = [
+            (0, 15),
+            (1, 15),
+            (1, 16),
+            (2, 11),
+            (2, 16),
+            (3, 12),
+            (3, 17),
+            (4, 17),
+            (4, 18),
+            (5, 11),
+            (5, 18),
+            (6, 12),
+            (6, 19),
+            (7, 19),
+            (7, 20),
+            (8, 20),
+            (9, 15),
+            (9, 17),
+            (10, 16),
+            (10, 18),
+            (11, 2),
+            (11, 5),
+            (12, 3),
+            (12, 6),
+            (13, 17),
+            (13, 19),
+            (14, 18),
+            (14, 20),
+            (15, 0),
+            (15, 1),
+            (15, 9),
+            (16, 1),
+            (16, 2),
+            (16, 10),
+            (17, 3),
+            (17, 4),
+            (17, 9),
+            (17, 13),
+            (18, 4),
+            (18, 5),
+            (18, 10),
+            (18, 14),
+            (19, 6),
+            (19, 7),
+            (19, 13),
+            (20, 7),
+            (20, 8),
+            (20, 14),
+        ]
+        self.assertEqual(set(edges), set(expected))
+
     def test_subgraph(self):
         coupling = CouplingMap.from_line(6, bidirectional=False)
         with self.assertWarns(DeprecationWarning):