Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add from_heavy_hex() and from_heavy_square() generator methods to CouplingMap #6959

Merged
merged 18 commits into from
Sep 21, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions qiskit/transpiler/coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Comment on lines +332 to +336
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering how one might make a coupling map like ibmq_mumbai using this, since it is a 27-qubit graph. Maybe if there was a way to remove some qubits from a coupling map, a user could start with a base and manually work towards that.
image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think the only way right now would be to leverage the reduce() method after calling the heavy hex generator. Something like:

from retworkx.visualization import mpl_draw
from qiskit.transpiler import CouplingMap

cmap = CouplingMap.from_heavy_hex(5)
mumbai_cmap = cmap.reduce([4, 40, 3, 39, 27, 2, 9, 44, 38, 1, 8, 26, 43, 7, 42, 29, 6, 13, 47, 41, 5, 12, 28, 46, 11, 45, 10])
mpl_draw(mumbai_cmap.graph, with_labels=True).show()

Figure_1

It's not the prettiest interface for that though, mostly because the node order retworkx creates is by type (data, syndrome, and flag qubits are created up front based on the number given the distance: https://github.com/Qiskit/retworkx/blob/0.10.2/src/generators.rs#L1692-L1702

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With bidirectional=False:

undirected

bidirectional (bool): Whether the edges in the output coupling
graph are bidirectional or not. By default this is set to
``True``
Comment on lines +337 to +339
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If bidirectional=False, what direction is chosen? I think this should be according to the paper too. See fig 10 in https://arxiv.org/pdf/1907.09528.pdf.

The black dots are control, and other colors are target. The edge direction in the graph should always go from control to target. This is for both heavy-hex and heavy-square.

image

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does follow the diagram from the paper. This was something that I brought up in review on the retworkx PRs: Qiskit/rustworkx#293 (review) and Qiskit/rustworkx#313 (review)

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 hex
graph. The value for distance can be any odd positive integer.
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
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 hex coupling graph
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
"""
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
features:
- |
Added two new constructor methods,
:meth:`~qiskit.transpiler.CouplingMap.from_heavy_hex` and
:meth:`~qiskit.transpiler.CouplingMap.from_heavy_square`, to the
:class:`~qiskit.transpiler.CouplingMap` class. These constructor methods
are used to create a :class:`~qiskit.transpiler.CouplingMap` that are
a heavy hex or heavy square graph as described in:
https://journals.aps.org/prx/abstract/10.1103/PhysRevX.10.011022

For example:

.. jupyter-execute::

from qiskit.transpiler import CouplingMap

cmap = CouplingMap.from_heavy_hex(5)
cmap.draw()


.. jupyter-execute::

from qiskit.transpiler import CouplingMap

cmap = CouplingMap.from_heavy_square(5)
cmap.draw()
160 changes: 160 additions & 0 deletions test/python/transpiler/test_coupling.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down